From 077c911cca31dc5a8d8ca3a317e6d43d88ea46ee Mon Sep 17 00:00:00 2001 From: kekee000 Date: Mon, 9 Mar 2015 22:08:14 +0800 Subject: [PATCH] add find contour --- demo/js/canvasImage.js | 2 +- src/graphics/image/findContours.js | 168 +++++------------------------ test/add.bmp | Bin 0 -> 214874 bytes test/circle.bmp | Bin 0 -> 82510 bytes test/contour.png | Bin 0 -> 18879 bytes 5 files changed, 28 insertions(+), 142 deletions(-) create mode 100644 test/add.bmp create mode 100644 test/circle.bmp create mode 100644 test/contour.png diff --git a/demo/js/canvasImage.js b/demo/js/canvasImage.js index 8a4078c..46ff232 100644 --- a/demo/js/canvasImage.js +++ b/demo/js/canvasImage.js @@ -21,7 +21,7 @@ define( var cvsW = context.width; var img = new Image(); - img.src = "../test/add.gif"; + img.src = "../test/contour.png"; var width; var height; img.onload = function (){ diff --git a/src/graphics/image/findContours.js b/src/graphics/image/findContours.js index 386b0d6..cc9a818 100644 --- a/src/graphics/image/findContours.js +++ b/src/graphics/image/findContours.js @@ -22,7 +22,7 @@ define( * @param {boolean} isClockwise 是否顺时针 * @return {Array} 找到的轮廓点集 */ - function findContour(imageData, startPoint, isInContour, isClockwise) { + function findContour(imageData, startPoint, isInContour) { var data = imageData.data; var width = imageData.width; var height = imageData.height; @@ -37,66 +37,15 @@ define( var contour = [startPoint]; var finded; do { - startIndex = index + 8 + (isClockwise ? 3 : 7); + startIndex = index + 11; finded = false; for (var i = 0; i < 7; i++) { - index = (startIndex + (isClockwise ? - i : i )) % 8; - dx = x + DIRECTION[index][0]; - dy = y + DIRECTION[index][1]; - // 找到轮廓点 - if (!!data[dx + dy * width] === isInContour) { - x = dx; - y = dy; - finded = true; - contour.push({ - x: dx, - y: dy - }); - break; - } - } - - if (!finded) { - return null; - } - } - while (x !== startX || y !== startY); - - return contour; - } - - /** - * 逆时针查找轮廓边缘点 - * - * @param {Object} imageData 图像数据 - * @param {Object} startPoint 起始点 - * @param {boolean} isInContour 是否在轮廓内 - * - * @return {Array} 找到的轮廓点集 - */ - function findContourAntiClockWise(imageData, startPoint, isInContour) { - var data = imageData.data; - var width = imageData.width; - var height = imageData.height; - var index = 0; // 当前方向 - var startIndex; // 当前起始方向 - var startX = startPoint.x; - var startY = startPoint.y; - var x = startX; - var y = startY; - var dx; - var dy; - var contour = [startPoint]; - var finded; - do { - startIndex = index + 7 + 8; - finded = false; - for (var i = 0; i < 7; i++) { - index = (startIndex + i) % 8; + index = (startIndex - i) % 8; dx = x + DIRECTION[index][0]; dy = y + DIRECTION[index][1]; // 找到轮廓点 if (!!data[dx + dy * width] === isInContour) { + data[dx + dy * width] = isInContour ? 2 : 3; x = dx; y = dy; finded = true; @@ -133,14 +82,8 @@ define( var line; var prevLine; var isInContour = false; - var startPointArray = []; // 记录外轮廓起始节点 - var startInCurvePointArray = []; // 记录内轮廓起始节点 - var currentLineArray = []; // 记录当前点集合 - var prevLineEmpty = true; // 上一行是否空行 - var startPoint; - var endPoint; - var point; - + var contours = []; + var contour; // 查找外轮廓坐标 for (y = 0; y < height; y ++) { @@ -148,93 +91,36 @@ define( isInContour = false; currentLineArray = []; for (x = 0; x < width; x++) { - // 外轮廓起始点 - if (!isInContour && data[line + x]) { + // 查找外轮廓 + if (!isInContour && data[line + x] === 1) { isInContour = true; - currentLineArray.push({ - x: x, - y: y, - isInContour: true - }); + if (!data[line + x - 1]) { + contour = findContour(imageData, { + x: x, + y: y + }, true); + contours.push(contour); + } + } + // 查找内轮廓 else if (isInContour && !data[line + x]) { isInContour = false; - currentLineArray.push({ - x: x, - y: y, - isInContour: false - }); - } - } - - if (!currentLineArray.length) { - prevLineEmpty = true; - } - else { - - // 轮廓顶部 - if (prevLineEmpty) { - currentLineArray.forEach(function (p) { - if (p.isInContour) { - startPointArray.push(p); - } - }) - } - else { - prevLine = (y - 1) * width; - for (var i = 0, l = currentLineArray.length - 1; i < l; i++) { - // 在曲线内的线段,判断上一条线全为白点,则为新contour的起始点 - if (currentLineArray[i].isInContour) { - var isAllWhite = true; - for (var j = currentLineArray[i].x, je = currentLineArray[i + 1].x - 1; j < je; j++) { - if (data[prevLine + j]) { - isAllWhite = false; - break; - } - } - if (isAllWhite) { - startPointArray.push(currentLineArray[i]); - } - } - else if (!currentLineArray[i].isInContour) { - var isAllBlack = true; - for (var j = currentLineArray[i].x, je = currentLineArray[i + 1].x - 1; j < je; j++) { - if (!data[prevLine + j]) { - isAllBlack = false; - break; - } - } - if (isAllBlack) { - startInCurvePointArray.push(currentLineArray[i]); - } - } + if (data[line + x - 1] === 1) { + contour = findContour(imageData, { + x: x - 1, + y: y + }, true); + contours.push(contour.reverse()); } } - - prevLineEmpty = false; } } - var contours = []; - if (!startPointArray) { - return contours; - } - else { - startPointArray.forEach(function (p) { - var contour = findContour(imageData, p, true, true); - if (contour) { - contours.push(contour); - } - }); - startInCurvePointArray.forEach(function (p) { - var contour = findContour(imageData, p, false, false); - if (contour) { - contours.push(contour); - } - }); - } - - return contours; + // 这里需要过滤一些极小的轮廓 + return contours.filter(function (contour) { + return contour.length > 10; + }); } return findContours; diff --git a/test/add.bmp b/test/add.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0d154cd5ecbda8dd5d085eebd9e85cce67cd35e3 GIT binary patch literal 214874 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 R5P$##AOHafK;VMF{{fBYHc|ip literal 0 HcmV?d00001 diff --git a/test/circle.bmp b/test/circle.bmp new file mode 100644 index 0000000000000000000000000000000000000000..17d4381d4b608da393267c44b814d3eae96de756 GIT binary patch literal 82510 zcmeI5D|8c06o7A%wh4XEHciu4TADsX(zH$6ytA`EoP$90KL>$8AP@)y0)aqa5C{YU zfj}S-2m}IwKp+qZ1OkIVAP@)y0)asAC*)5yZ<5U>_ukFTIb~-yyW#FPcV_O~xifqA z_}{Y*D0~t_0HORbp3e^F{~X}?bADbqz&i$i{_-c`pZwY1zkkE8U%%kz&!6z)#}D}a z{X2a7_6@#%{R&^ce1XrOKf|X_pWx%ikMQBc2YCPfJ-mDO4&J_f3vb@Mf!D8J!>d=X z;N{Dg@Z!Y_c>er3JbU&Go<4mFPo6x1$B!SwqeqY6;lqdU;K2j9fB!z*yLS)n-n|QV z?%aXfw{OF(Teslm&6{xJ#tpcB{W@H`b`7pxy$V;ZT!G7%FTXi*Vt>1voxF zhV$po!?|k$kmD03aE|ZGyM>dxx zC1AB7Tj#`bB%W1z(~rs=hL^^i4R0RMldhT5ajhw^$}d#zXxk^6m)8@=#8#q(mQ_M4 zrM08Yi2>I;Z?ru?MrSnVqv>hOIk%nZAvcKLuv(zTF{Qo+)S!gL#)pyZC6>{TRm2` zVj<+V+_tK$eyeSdrn;K&+6UawdS8^S$WooM3%IfUrrObyFZS}AI@sW|Xwgu#ea1;2 ze;<=G*#7fccBKk(*Ei3uWiUGVA+^KX!8G2J(P%pa4>*IH?wk2hk21@)cX0UA^^eoP zGpQM-joe}%t+qzRp)GI(=UD2y{ZR`~n_~!W6JXmI-KE zp`>ztR^a?Zu<5$u1kQ_Sx7D@>Sw6nZN%Wqy5?<;Ald3w3Hn^(9Wu)Uwe84e7WE!bm z4iR&%2n|@3QfxA)sVkKN6G^K8d2rKNBCM*!&?bn3bM;}Cw2DZZnb-(vaL#C(&(ZhG zPLt-g)OQ~3{)cLjFt{;7#&KFYBw=`-4cd{e~yQ4yNUIy6lDhoV| zSy4}y#bB3M$K-a%4p&)V)Q$mlW0f)jQ;7tkzPU{9W^d|2Exsv>$Zg!>XX)q9Da6IR zv=@5(r4$fHT5O^P8zDrDk!I;5FEK=1JWg&0f)bNxgk2rS#v`iXE6=pDz=A+<9hKUnX>_jZPeRYzl!L$ zWISE3>$~Q3#frNaE-^B6GGqk-bHBlAUA;l9m_mFX9{sJ$YGGC65npZWsStx*5;Jg+ z9V|m&4RfgxW5YLTu1UA%uDDTt+*1425HR;Ei~~fr%{{R-cP(?%GDBX}-F(LkIhdtz z4wsv%eBzRz1t&BJWwlayN$pyeYGqV$FRcOdB{2by-`UJyXl~ z&>FQd|0_)QB|u%BRiMpu9}M)JrFgE~MYi4w3P<{#GG())CV@D&P+(U0(0N5{4VzY3 z0{co0G1~LEgc32tp{Q(ye;H0{g$?QPQVb_&5SIcPvsEJFZl616C$ng3g;#PVrE*qLhg1>Z_D3pBZ7a z5LKh2ZUBt-N!q9_bj&>E7Zg{wA*9p|V5?d>x;f-|tXk#vFqxmpPgT*s(#J$U!mJ|Ju2jy=nJmwd>Mtz`}!hc&u!{RwX zJW4uA#P3!IkM{BzMX^&TG9JH zvUK7qJ7woZoKwvZSWDC?uk!U%Xd{P1KBI&(t%`&jX}RD&bw6bVqlU?m5JDrT*7?~V zb@ck*JERwFfDPbepQlxicsFf^;<@`-SJjp+l&wf_wr~Ava+ayFYp7Xe%H5xx;?y<| zmv|+b)@kQlviAf14B{TCtslROZ{3?cuB_R0E)xkIQk;zS(3VqscIigzsXF~V+b=H3 zc{o~6s`kwAa!;DM)Pk*g(OPPS{P3#fL2}8!aHIdpcIW0T)w%dQ#pwE;6q?g-RS%)X z>}YzlfbLnhz^9qnq#S(O;_18jYRDh$DpoEV_1S17+7Zd&g?*Fg^3dpphj+xlEw#TkGZ=SE$&VIlCD;nL@8j?2Sz6B`lpx-RzyL-_wh!@Htj3 zJ%=EANcznyRri#wS+^9`qo$kv6TymbT?4H`|Jwna7Gzs@F^r`H@+~^#D>98*Lv-(i zP)Yy74qwrJ6;37Rs~09Qa!WN-bv;vxlqgWdRC-lqA|PAt;0g zMg~Emu}Wp&N10&(*bqcy`hWi;T1nfGlyH%{cNY20UW>MX*N3F#=crN~edNoli$cqa z-0Cb0h=>%NZGzy#Xqo3vwx;7l_H&~#Of?NP>#n@iB>}71>Z*adHSeRQ1EVUNkzprr z^8^Ig_}ruf!AC1UR?h!Di>Y)NAm>Sqii(QaVf!th`*@jEhc@~JLzfGRgG*yo&%60a z!;Z{KRd)pq9psObVert5tpECTOsc1r{uAvdTwBu7DR(qJm{G!bA>T%L?z6#kieu7%h?Sw)={kd z=JV6nZ@XD^bc!4-6%QVz#edcMDzy6JpYzXfjUwA%*L_cN2ohi+;dmpiCZ1y^_pP8s z&RY^_h+%14`}_HKqa&IL)=@Bv0_zbd($ZjTuUqeUgV?+|hl$ z45Jx3lETLPkl!o^{%D+l_--RGO52*}WKAx(mCcO`$qLMUjmB=QSNckNP zE1e1m+Vdgd*cu<}$3E|x-rby!xhcrx41#MEV}_5ASfsKtmt;IB&TN&^GXix{;C_p$|%jdd$CN7E5RyA7DVrWZ3v#YUNFDb{j9_ug_`-)%4V#EO|I#B4#?* zfodl>)#uF_OWBI|j;TCQi{EMp;53f4z_egT0;SC2_z;l>5G2DvHbd`X;9ru%(CV z)vS-k{@Gh}`n^<X^qItZ}Yf0kTZ#m#wM-|bI*uEK}nWAO4Ee)R$a;9G4 zwJcWg{FYl}SN{q9Eb#L)MN^L;Gx@I>z{lQMYal22`~}(5MJocSfqY(+9N*$*rI=G4 zqmi}yRyeX)&_+@aoWsrow6o0F-Vk;;hN{QPu_ z)sj}x7-#Gx)9#)HGIfrReE~3XT)(H9=ffWo*2CTp8iAWLUayr~0VTwcN~i z(19aL9I%N+alhbe2pwCLq(y!~*1Zwg^m+nJRkTe8$BU*lMO~vfDW^}`_-8K;u#5bB zbv+mN)q8{`)74{qH3XwvzAhiO-67};izXiW6k2o&8fvC$%gizEP=)^2q_Wr3t`9i2 z1+0d0bC8YuR#%t9`aGIckTMZAJH~%jcsV~_WzhLTEmq^y4*iI>LdFj&54DC+_2lE= zHS(5?EdAr%J_=e$>LE$-t$Q$>s;w`ko+qdlt7GPhW9rtFWTfbb^ZX#*A^!ctN9@|9 z4LGu^eD=Bc$XY3K9gnD_8XG(KBZ$5eNN8}(oVICSdMPgZmus#@b`p*vEH(ehxmgrm`4LClR_nd|PcK*UU z{Bm_Se#h@B&T4*Nu@4Ue8j@%Wtw3925|5aD%i$Oy?$es?*krNq2b_Z}I@GH9?&AjQ z5`vRL+gr~@Z|d1cR}vX)m~Taoxzx+ESH522l{a7Kn0AC<>W!-L8WvNMDgb+L6s()| zDy-!V)Mo{fCGHr=Q>H4_@@n#bG9NE#SMf?ierW%cFSgo_-blW>TrG#~>W#>d6K4$# zZo0uwZ8?{!T<^W_*XIZNm5x`P!njZQ2P2bvpuei7KP2=#6c?j+!66*WkxjN`h&9h_ zOpHz%4BSVOf`zI%)PhvGhE%2du#gs!vfuzxF}ka5I#1KfFCrAJDPt*uA13;NOEOi# zS83`oG(^_N)cEIIzSO&lj5&$Y@2{|Fg8CTf#>DC#lRN!C!=ON|V9K{>=i{0}z z`EWPyML(2&T){7ayw_}5 z1~ln3Sc$8?&m~cDY7IC~S4~;a=Lq3a*<@8!Wheyz>0fyhu+odF@KopD{QEx-ZA%JJ zk8O|^9j=<|qb@1mlNR6QVJ5;ewGYgBS7n*=p6jJPx$b+sz?>+aV~sM#f+}t6`rJ(^ z8pq0->+MY5W*m*Sr zCijsswrsTgSWi-}WiOc@9g!DFaZ^4^(eD-Eb5_jD?aZ?C`fl*|MNq)Q9>>_Z>L?z9 zzR$(lO328C6lV>~lJZzzO~lOOb)L?(5kDUzI9Vi7SlD&Chz0WR6e55`v~B9k;5E%z zz_MAr)B9nLJdBM&JOLSdQV*lT>RgF=q@KOty+{ffwZeDKU$!ZvFo3|CSE$17)FO_~ zDf-WK0|S9}c{i&neTM!W1}2rF_`DLv%RLn|xGyEz&QlMDd2AZIN?YX)aQBZWc|}n10J26OjFmdtEY|(*sa!-$6}1kl0p3?5^+I+7Pp8FLEM;{TgqJ6yMwR_)3b|dK)oRY7*)Un@nxJi`-W6q5Kl$Thpw9)WIrX|VMcOE^RuZiJD)K%xyPNVgoYo#U`rEX?LH`fHykOXY z@gvaZbfeBcrz^5>bh$i=sK$C(zVxD)^8tSa zA^rp*L)LeyB2#TMI4qi4E_2|=Ah}ehlqIs`+VwPSghkFNyEl}rME~c4xq%;>TEVn) zvW%(`SWAIjG{E1dAwzsox*#i`#K24>=ny;ovGtmg(kFND@MCHj4YqVx08>G3y*74o z^_%Cl?^{{CdwW8>?YJ&4@%yCxJ63x}Zpr;-KJRcPVPu8HXOn)>$j{7Fc1r1H*!Cv` z#u2_V?_D?SNoM<9!+2K&}J|<~-96Ey2gqWRPJOLhwq?1XTX-EHT1CvAZE3KO$KO4zc z(TlCj(htV%W4R?hP`{+!t8>;U+~>7F*SXsI@y{l2KL;FJHK$N1e4B;!XLrX|RgqeW zAyqE6b~^P+$Cl7uLaaT>gK=9$59zOrI4|07W{UkAsUq42xt&u7M&9&&L#B#1_KEki~!aX#iD6m5x!pM30w!nVVIY)*q!>a}(-|od#SldNaSU zTt=Vks@IiTUr0503gJ(I$6h?YQFG^GCx-M{K$YtX6{YdA!wW2B|N9Qh+2RD$-}p>F z0Sg?~Dyph~=QfS_z!*7PbGwL*t*Xf(Ia@cPtgFwlizaX28@%f%DZho!dd#9HnyK3O zOWOFf*;eCJ#*uc6^mMT3jSmScZ!;L=@U z`8YiYV=zOJrJ4NJhQMT0+h7ip>G{sIT=fzp&6=NXH^lj$-_pOzac)Zj3I+GTP_=d4 zv86*mjZI}!pKION3C?vZZW!Ad1TpFTc|Yulzu67Z++cO;HjC)+!ScG_Q47$KUXD$h zcHP!y_civz}xJhY?H3kZ~+|OqH26Wp}R;(XR-&jNpe21$^CV$0ShKG@M0Ft7Si$R*Ixo zE;q2{nnFhZiS9nrn6z<_v~qf;OKE%vlnq?JxlHN{uS}LrmuP@gPe7H)+#||CDa8G= zC$v(p;Kzoxla)W6h+FBx!t{n^Jyq;Mbhs56y=`<{Imm?C7Q;}EO(u<`E~qb;nddNf z=OnuO3Y$--y-LE`M)HFx>*P}a;cUitE=nV(beMU5iVhbSJmU<;xXh9gr^>ijZQNQO z%dUx1L$R@i*t|BmAyxhsb8j-Z%6ZOcS_zte%5yGCx8~9Wj>{oa7f@x>MvMaHF&>rt z)oJ%-Suo8-S@YS!gFb1wxqZ?<~U?Sz0OrjQ2vSgS7MKH&1|k0kLeF z83u~LVZ%}u1}lF9ijJpKBh`xc~-30{+iHHp)Ih5+O5)m^iig(Pn;<-~R z%SEZbu)unhkLEKGv$Wep?60t0f_HvP*jCbDJ4W(gTYC+;+D`XkWVOx-xG12W=$4Iw zx6a2~6-ZcDZc~H=##&Ozu5zlgki_4Y>c<#Fp-?=X6No5PopCiKrKoKR$9vt&lcM<>U57HLGQ z>ggc^FjI-!U)IW-)W6a^-dBapM&A+bJRA^&5jR>TW`dqD~qMP-D z^Bu#3nMptFEfnJ2sw~CjosORko(Ug*zl96^3dHtSn=885jG$VQ@*jVHGdqxz^Q!1&pyPB;S4iECA5tStl$)VLer<5VV zcc;?dxfB7#eIQ}ge9wRzW|rI0fX^Eev$I2e+2}s-2!xE-{eqV$f$$0%ETwqjaO3u* zqpcQ90{>*%Gv>vAmK7Y#7|Ov>L&l!JT<^U3x{E&+FZWJWU3B!k{#+{JW_J zypuI2d};#W&+^Zw4IPVu#2yMF+rc8qTEQr_InD<)`MLE|PE_=KCM;q%SvTLo>;7JJ zeg|+7y>XmS)siz)JlnBAU=?O2X9#TET6a`DV(s2EHxX9z6HT=^H8!@1$Dc6>#M`(W<^_xhFO?3o~aqVIPI%j(LS`7lwCU6xF)Ic??xS6gbmEeezjR16-s&tv2XCb9ObkQEN49eWs{jC%>E=4Qg$13GrI;Sf^s}su6t8 zSCqYg!Z=#QDv>FDN{}~V~wK` zS+OFE2^_Ob-%fowEx0tHxGd$ClwDmf3J5aUTMYzR?hD}aJEsOMk;$fA-Cr?>U-J&>Riiy*?!GN6hpttiM`V*glo``R_%48uz#>HT@!=nu>7gDnN0} zJvDs1;P$CXyR0y7rwX4sKe~|8S-UE~moB+0PeqO1uWY!^B55}aL3H*ng^i8v!c-fZ z7!SlBCNog^TR4GXyKEYLBReI~as zRHo0;zhhLV|0q*WRfVsL1{?2(2B*o~VK?o)V||RFB{K;E>buxt=T1XT0B=4n^`Zbf zz9$46g^@^~FN*XF!_6r?yjJ-3oACRSTXtbXVK?$M-*BW@5Y)Qq_`IT6fXfE#%eAp_ zagH4Su(ff(HEQh%^&TmAzBWSB?P2ih?9@scEWB=hZ0r4*pD-M;=uL-K79vwPEfqT5 zSz#3Y} z>FzK+pe5DF2M~pP2JGND z@=Cq)A4p%f9G2IFbn{+r7=yb>;ew5=s+jjCc4sZa?Tn$2C;V`bxJ3UUww+hEfy@6m zcyo4G!QlKcby!8_zME{BUo(ql9~sAI8;4bYC3-W^=Wqg!WpT`VY`87UUR6$m!5y9V zqNz|LE{6lia?4w(ZQQp)y^{hvQRH?sHz* z$$vm89mR0Rbz096#3irGAhQ%% zic87-Ea3!1?~yv;g%;TDXhocbjLT29{h08wVf4GlF-Kr0_mb#S`jz(?k z`+?PFWOHc-VZQ_M^s!c(c9Un7da0f*TtfM)fMe$0{$%s*bL3{P?2mtG)poN~sPXs1 z)E=)CikjbE4-;%DuvZyHKBMAT$5&U+k|<;;Qq0enSP#cc30yH=Sy^FYVJV|w$nj7! z1hrGdQhuKT0z{$^N)054LF{&ZTLeXqhF23 zFl#{4Z74`X*9J~Z^ekbWXEyx-2o1!t=K(IAg612!4R%WOM2fX9@*Sj0wx#@c6U&q= zWUJmzuyAt9G5EQaPPtl`!^w#52d_VEDozkoB*A4`2Q@rhq5Kxl>&zNzL6~h(oW{L! z8oa%@y=p3txQC`DW)>4-4FA~h=#UZ+hntbKB`|B$b@ zC;mm&@3HRfJJ8)#wO~@V{P?h;f+qP9|7jdR(K_CeyMS^`hwGiwkM^*5#;Wb#@`Tre z_HiSOl6q>IiDEmsp6a>9#^a7$p8IZhU_;{MhObE#QL?D*P~YHG&ZZf7e9yyIyooXjr0gjx{{oR z?1Go-dW`T1T?D*e?pAX5Lic>Or9=;&D(9C;01@n3QDwNnn;}TaNqOv!Uj;&Hglia} z*zv5HLsL-B$+@eY0o6Q`5ygy;|7iX*)~Vy-uy&BF)rtV(N$ut9qf2pnaDqf>jr!EF zkvmQjqi5v?(#mG)xwNNg^MxKoWI(*OeiZd<*yZg~q2j%2?H;MF)IF71_i@f@1`KY% zO{Bs{KMM^3VI%FqZMq2ly*+#VJEU8Bm(MV$T3wcw6Mur;e%vtgTf|PLm+y^#IhEt@ zN0&u;o*Uf4W^f$u-f^^YHhn(%^5wV8_@n!si_z)ejDPmS0_N3vZV8*3nOaWdFItTt zI(ifHQRW6vYd~@`VTUvp| z=0Fwdv&nn>cRd&-7E!W#LGO`L0TYP88(CDDp|LmnWo6*DIr2umnmr(x}u$_PmMmON$O zbThv3`|Rm-fq$aoa(>!*LB#n4s9r`nf#;sIT+4hLyEyD7|5}2w@s#Yvvn`6!pj6iO ztlx1zmH##xc<)+*HJB~U_NBkDx+F4vF-pSlj+h|AFalq(kHzub$78m{m0KJ zxow~G6V%|77lUZ+V8YfMg^}$pwc9NJJ;=xkT8OkhhAE&S!g~LZge||C1}{WV;Tu5=IS*2^ZW5d$1GQmzKpwwl#ovDVbSj5E_cqWaF7A`fex=av2H zs=MBo7sAJX1o&*nMqN4RbA2<@*4)m&Vg6ay?9h-Q=aAkw`!ZMWY;e>4FqzlXv(+O?me7n2l#}8U?E= zkw;?t-P-y;xz^5?mdq2tjba%jTgFOJV!|LYgk~B@_gcl~L`BRN0RW7uFdl&iNB&@L z42}Ge^GXnr^#!?+hySGIH_*yo`C;k*sRxlrv=zooH-6or@JgWKw|xXOGNa1m5zE2I z58OymDV(}XX?!UHRYG{F4LbYP#u(hF*PMrb=OTQhUUv)$v90+fZw@JR{Z{3iua)4xmooUwep z31BfLa}xueheX@%qZKv)#Qd{9Uxa9fdR*D)&4yziOAE>6S^DFE0&)J%Gh(k=&o!t- z^D@UwAvx#w7?q$Uaz^6CpD)Yzrf5Ah8HjGL#a{$}FQ92YNv9nJg+sae9#;}kkXfeL z)!8RLFPYCjDjH+0tdm&}HtUl%ZzKFXSach7Zzo6RRuAx9K!?}{!1&G+?qcfCL7ioU zw(!c4CO>yYt?+V_ACJrFzR2xLIe@U<#))#;khf~tK|HXP#PpAS(>4{s;OmsV<9X=| zz@WG24r^t&m{e4)YwG9@<;rvWTyQ&CDUSt#9z|s`4X#2e26hYnonPr7HYMaexpGY& za-(fy^dl-;OJ(+`;bOE}3_cwN(VHrj5wtEW@~70a1La?ClAssofW@LCW^#N7-@Qh6%3Pz(ftknSeW%0!+{TX2*4U&Dc%{Qx(D_+T zOC=2+q)dyw3GrD+7}aYXWez8W(h0DOQo$>ZHs`M;W);?-NFshr zTQ949U#Hn8odQRG#MtHHa(1M#@$s2F2)|I-1DHD2?DaI<`OP?k>FfUq|2w3QUZRcu zXO$DRoAYEAS$TK}pg*}tV4a4WT<~qBHiDYQ>xU!N zbF13n;Y4?A=<}xl7b1l4qCmL!7Hy7sw&e3B`&A;)$j76^=2z?a7?xCHm0?JTP&8AI z-`0fuo6-$vZgn{xRy42fRIdxOl)f;5lU)M*`(Xu0iYR^hJ>wnKRpWZ#x6lMGOr1o> zYubRuZwe3G&W!R;>D`ce2gj1egGhkRc77Hg<+pw6x1}KeD~B*{8|u&iki^bTxRv$x z^{$xI%ZOx0r8xVYjAtQ)WWEWx6rN0jlnhL|cs7NvdT3YMd~LqiY4FtNn)FzCh>ic~ z0_k~kU^9Et!GczVNI(Focpv$bku^iY!di=lnJwO&8fz0cadb+-k!nMR@zW&~G3##irjZX}w8n>7(R1S!=;{8ZQkuscl5elzp9F;{Tu)B#Na}_Q#+*zIYMAQsR!SE^B|-~8)kr=sn7J|MXtRsXhoi$h~F{5>zgh}iK*gW!@y zNfgWe6z|577p!u5_BrYXRijvRdZl*@th#&~Q*s487a|A?z(^N}j!@>u0MoYqlbJ%~ zY{Slr^>1>!1`>%V}YO1)Paii?@yCPf3*VKvHTs%TOQo*hp>B&3I z;+E^{06NhFex^fUo+_Q)u2u*bqd4#~s#K1{XZOF#TE3RxoUR;2!;hUFa$EJkfgLT^ zA-;sQOIhE0g6mD$>U!ABO&8JJFw#+U7#3#GZI_(y$ps>F!{SXrn5+L6)qpxWHD+LL zB6k{Y0Pz~drb8CHR1falRH*7?9jyYRkbpRPi7?87<8K7vDdClZPS1%i+y09qu}cDI zBFyW4>hj;pFt8Gsg*13gfhawZ;JT0XU8X)zE&OHs8fgwE-uN%{ z(cZr0Jfd5(7WRGs9TbQp!Fx~t@FJsJk=mZHDeCyamMf=K`tcR4zr4-Smx z1fEX=0}goxQxC8LH`v*9@Ui^CDu`gDY|GgvCI(hb-36DaLqj6M5`fG8a%F`4vBAd2 zBQf=szqipwkq}H`HsWRU4+K)e2P=P9tkAq}BXX=-1q0$pH(;v8FRj-U_M4MMEU~Dn zW;tj92D2q*VGS(pS24%z)#=rSH*fM32ECe68L5dBkdV%cNn28t+yMcfzJ#5*eFwTUZ>YA>lM2#3i;oPG=oDMt6<2(!fWyQ z)yeLSdRhG4-D5#mcHa7@`I$F$n9x=SOg)-yMmax|^Bw82z{+fOBbkTQEHWHT<5|ES zul{EMHucf1|5weyLdt`cmLoQjkaw2b?p!s40T;D#*moLCQ7cF4F4tSF{HHvriM8Tj z$oK!LNnJ=eg1gi8R(9>5KSpwiPhZ`C{8mv~`t!EH-Qn-XlHs(%Q?7Tmz|<%d$#bb% zj6+Ck1BV?4g4yCd;ZV{);pqS13?;S605i$n!A98A=y~M%bj^oKieL&^dXVrZ8$Z7W zgGAUuk7!ytSQH(f7K#eF$1qx~^=#UahzZOmiwnM5#48Izre7Czk*V6WFeT9%r7o}>*3)63vP$q!ql#JE zo!5* zlU77SMJ0>dxKmT+7X8=nM%Y_4?3%15SLa6?Ls`MiSI5(Im)c96;e5uOcUD$c-$~)n zPoA3o-1zf_YwRQ5eHOJ4A@wrlZWRULobU(yJ4fTaeSJrJ?Sx%|_xw$k5=e{mnmjEx zMhlW&c<`*ixZ8ky6Xm8~4*va9Q&cP)oWFnjR;Blu6Afs1JHExCrv!T!YLw~$>utw6rE8 zuA3_>d3~;q#)U5T+Lt0>qM1fNVFCih208wcbUw}z#+1j*)C8NLSRdi|2>KjG%D%Ah zJz(X%xVX5``C1mJ_B*%Q$0R2g3BLbZMvgC~gjkwD{5B{Cc31|M-Dj2`t+t)sIC>67 z$~^ykW;Tkw4P{1IE9WCYpLwk*p3x{HT#vAfz)m2q^I8hC5w(s> zo!Z}xf~IQh=~kQ}Vy#D|1}<9@N?DQ-V+(D8U?|}D3n@pM=TbO7L7|Rx)X3~^!)`N6 zE~fvd^@;CUkL6SHtE|VBk&z^tZ3hJeG(r~bpYGyb39YZMPgPpR0FFx;qzXm9dQ@Tn z-eU3-7EdUAbvOe4PDNFZo#GBp7Ln)-`fs{m@q@AP@rAMpPuMs(!ltbM{{6f0g{yhN z*^OR&LZ=OA+qVs@X*`~?J`z$r6a|YnzM7|g-mu1O-O>CWFb~woFkNFm&YG8!Y2SSI zc%yVSSMEXoH@F6kkaNhnHI@{|{{QR)q=>lOw)Y#^KouDuOkrbVdu|lu868YP%7pCd zdvKuf%)&w$<}~l}c+PiZ71q4=b2>mUwdrbGP(g-{e>!1*A!W~o!QvGfug^EBUbvTm z5t4Nmi7<+c{iUw1L#mL}p)5)3Uy0n+#?W2{+FZertBn~s?Cza+0Hqu0A+x*h5t*8r za<_mlCwjsHR01&q@8czL0jgq(bHJueNh$%g?+Jtiq;RM*lfz`OsLdQ^1v)7}dO zU>l)R0<;ix`6(c-o#xsr{l9a@-8HrnPtFKu(4qU-mkvsFwuJ? zJUH@xP&h9%6vjS|3(-dbaYkFNkGMFsE4!^e0r&V$j{jd?#Z(YX=PQ6zAlM1#mN4!@ zsL9ENqo{$9RA31B9C&Y)`~II=d%OAM-d-8u{f_%UHBh7?h`|Sp~e3} zJg`yFs)Zt-q675R$^3W#Hoi^Qgc#_Zr+V!OPIE($6a_e-^1F9M!23q0VgfIW!iLyO z8#90Wcnt;}ASuAr84rLqtmYa$s*Sf$z$thwhZ+BS6#aKRu3MU$K-Mp-9-e={nyYGQ zfk6id8Ux4cI9wkA$Q1U`-N`|JtW>|0f6l$#^$rB!+<@tGFs&zxp+>1IP$)QL6%7^y zZ2~bDViaBuZB16(_dK!+2B76s4~OViz;3PiN?EFEYNqf`jD7G(XPA38yAPiKawUQ8E(pkv>Q?~dN_uXqJ*rPGr3y%y zlB#NU-TPH500!q9MWvN?vqH8LWv_yQf(ntyYMRnH9v+@iVEKH&Xo2M`p6tw`n!MZq z&K8LU6ZH1>mebs8eW_UHA1K)tniU;QjCK(aL4Nnfu~(V(Q=vEe`ST|m7gs{I@Uhm} zVuaa=Up`G&)RI7NPmdD7MpU_}tjo@f*4f$F##~c_@A*htq0hx}@2_8#i4+hvzJNQ( z7}?p`M=sC)p&`CR^ZV<0-;0vxwo};w0hs6z!XY<*b5mepzrqfOzXOqG^rms@ekda=TR}>`M+zfz*kjSWPy`Ob z)pDtv=5zeD`ScHdiGGV-GQa&8XpX4q={5g*%O<1h?bG58=C$gPRQz_ujg2p=>}Iuf z>z$Q+eVY*?*X}O6bGkqvReGYbL@KE#BpeG1U4&jV(n>XN8iHr1i*_7d}$` z=I{eg=y<{y=$kx-ix=(M7`THq*w))qRYFIjDr%aVr-8%EBW{#RWf$b};Lw}>{PUw{ zt7-P!_lW%dli_$yN?be$27@hoq=s>OT|&pECxbDWnU6U28Ye)SAWD2-+qUsLBM`*Z zeok862?f7%m2d9{K~*-r%n5v4>o4%D$op@5e%ginfAqduID5;uf%C6mzQaPΞX?fL0a1gi%4ZUd~4 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