From 22d64ae08fbf75162e0b6b0ca031be736343d92c Mon Sep 17 00:00:00 2001 From: Wenqing Zhang Date: Fri, 4 Dec 2020 02:47:40 +0800 Subject: [PATCH] Merge pull request #17570 from HannibalAPE:text_det_recog_demo [GSoC] High Level API and Samples for Scene Text Detection and Recognition * APIs and samples for scene text detection and recognition * update APIs and tutorial for Text Detection and Recognition * API updates: (1) put decodeType into struct Voc (2) optimize the post-processing of DB * sample update: (1) add transformation into scene_text_spotting.cpp (2) modify text_detection.cpp with API update * update tutorial * simplify text recognition API update tutorial * update impl usage in recognize() and detect() * dnn: refactoring public API of TextRecognitionModel/TextDetectionModel * update provided models update opencv.bib * dnn: adjust text rectangle angle * remove points ordering operation in model.cpp * update gts of DB test in test_model.cpp * dnn: ensure to keep text rectangle angle - avoid 90/180 degree turns * dnn(text): use quadrangle result in TextDetectionModel API * dnn: update Text Detection API (1) keep points' order consistent with (bl, tl, tr, br) in unclip (2) update contourScore with boundingRect --- doc/opencv.bib | 23 + doc/tutorials/dnn/dnn_OCR/dnn_OCR.markdown | 3 +- .../dnn/dnn_text_spotting/detect_test1.jpg | Bin 0 -> 41415 bytes .../dnn/dnn_text_spotting/detect_test2.jpg | Bin 0 -> 92092 bytes .../dnn_text_spotting/dnn_text_spotting.markdown | 316 +++++++++ .../dnn_text_spotting/text_det_test_results.jpg | Bin 0 -> 49278 bytes .../dnn/dnn_text_spotting/text_rec_test.png | Bin 0 -> 2911 bytes doc/tutorials/dnn/table_of_content_dnn.markdown | 12 +- modules/dnn/include/opencv2/dnn/dnn.hpp | 249 +++++++ modules/dnn/src/model.cpp | 780 ++++++++++++++++++++- modules/dnn/test/test_common.hpp | 8 + modules/dnn/test/test_common.impl.hpp | 46 ++ modules/dnn/test/test_model.cpp | 220 ++++++ samples/data/alphabet_36.txt | 36 + samples/data/alphabet_94.txt | 94 +++ samples/dnn/scene_text_detection.cpp | 151 ++++ samples/dnn/scene_text_recognition.cpp | 144 ++++ samples/dnn/scene_text_spotting.cpp | 169 +++++ samples/dnn/text_detection.cpp | 271 +++---- 19 files changed, 2340 insertions(+), 182 deletions(-) create mode 100644 doc/tutorials/dnn/dnn_text_spotting/detect_test1.jpg create mode 100644 doc/tutorials/dnn/dnn_text_spotting/detect_test2.jpg create mode 100644 doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown create mode 100644 doc/tutorials/dnn/dnn_text_spotting/text_det_test_results.jpg create mode 100644 doc/tutorials/dnn/dnn_text_spotting/text_rec_test.png create mode 100644 samples/data/alphabet_36.txt create mode 100644 samples/data/alphabet_94.txt create mode 100644 samples/dnn/scene_text_detection.cpp create mode 100644 samples/dnn/scene_text_recognition.cpp create mode 100644 samples/dnn/scene_text_spotting.cpp diff --git a/doc/opencv.bib b/doc/opencv.bib index 54396d6..6212ea5 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -1261,3 +1261,26 @@ pages={281--305}, year={1987} } +@inproceedings{liao2020real, + author={Liao, Minghui and Wan, Zhaoyi and Yao, Cong and Chen, Kai and Bai, Xiang}, + title={Real-time Scene Text Detection with Differentiable Binarization}, + booktitle={Proc. AAAI}, + year={2020} +} +@article{shi2016end, + title={An end-to-end trainable neural network for image-based sequence recognition and its application to scene text recognition}, + author={Shi, Baoguang and Bai, Xiang and Yao, Cong}, + journal={IEEE transactions on pattern analysis and machine intelligence}, + volume={39}, + number={11}, + pages={2298--2304}, + year={2016}, + publisher={IEEE} +} +@inproceedings{zhou2017east, + title={East: an efficient and accurate scene text detector}, + author={Zhou, Xinyu and Yao, Cong and Wen, He and Wang, Yuzhi and Zhou, Shuchang and He, Weiran and Liang, Jiajun}, + booktitle={Proceedings of the IEEE conference on Computer Vision and Pattern Recognition}, + pages={5551--5560}, + year={2017} +} diff --git a/doc/tutorials/dnn/dnn_OCR/dnn_OCR.markdown b/doc/tutorials/dnn/dnn_OCR/dnn_OCR.markdown index 43c86ac..ddf40c9 100644 --- a/doc/tutorials/dnn/dnn_OCR/dnn_OCR.markdown +++ b/doc/tutorials/dnn/dnn_OCR/dnn_OCR.markdown @@ -1,6 +1,7 @@ # How to run custom OCR model {#tutorial_dnn_OCR} @prev_tutorial{tutorial_dnn_custom_layers} +@next_tutorial{tutorial_dnn_text_spotting} ## Introduction @@ -43,4 +44,4 @@ The input of text recognition model is the output of the text detection model, w DenseNet_CTC has the smallest parameters and best FPS, and it is suitable for edge devices, which are very sensitive to the cost of calculation. If you have limited computing resources and want to achieve better accuracy, VGG_CTC is a good choice. -CRNN_VGG_BiLSTM_CTC is suitable for scenarios that require high recognition accuracy. \ No newline at end of file +CRNN_VGG_BiLSTM_CTC is suitable for scenarios that require high recognition accuracy. diff --git a/doc/tutorials/dnn/dnn_text_spotting/detect_test1.jpg b/doc/tutorials/dnn/dnn_text_spotting/detect_test1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b154dfc4ec400192101853e1b9fbda0af52139b7 GIT binary patch literal 41415 zcmc%wg+|1qzC?OqM$(lFo2EpH~>B} z5CDWoPXLetWB>^=kOFi74M2tTG{_VWzz8t^U*G{0f#(1}GW@?-5s(Db014nFGN>Wb zo`4acf%HbmR0QA#I3Pm=Pyi$%eE~28bR)wf@Q9*K`2TMP{(S%l!KgOq5L6T<07Qs_ zN{I4r4B1`)73F^p`2RT+5Goov1}6C5X8`=43<2OfB_FZq7|UV6aUB#TpQR=t)U`6t z#5S?PDQ@e`Y|B+vgVV`X1vNku3Z_`+EsK<%yXjphxV;URR&I{A!yt->`m(F2rsd$I zk5j%)oM6H0=>$=1$wjHdS1wkS|a}_`&z3FXqb=)UD9in@@6lUL@dy%&%LGSB*+X@7lj&Mpl8EC z)Qg2GWi39pH!tJex^(yYrwxFsq8Y{FT=EfW!!Xxb%Sw45K%uQ|Sz$9B$7v~2mrG%f zm@6RB4GL=MY5D9LZ#m^_CEQkd}_xDZp&W6lHydVG~`4Fwh!H`EG>CSbdD>@PEa+6+m zkz}uugSXsWO(V*mGs)c8IN-nLzNX&i-_Q<4fr0@DuS~u)LGICwO#CDK56>kGut8@5 zLF5wEY8W7KbAbmMvySoZ`sK90Lo@AEGE-p=6Esg7mF=cx@MTMHbNI>@Dtm8*tt%8@ z002aG!raY6jjW|rdHog~ML0@NLq?Sk!X%ruK+|XLn==u7ebo1D((1BWogqg@$HY0E z=F7JwTI?(H<=4$_cmC7ZHo`h$)JS2;tV%=#m5!!DuX>hm#RFv~#u#~3NX9-u310ye zBx98v2ri3jKkMq1fLggE&SY$LF42b{He&WjraIvpTkCxIP-;hXk_jX!B?~F<;_-s0 zv*X8z1V`jlREt(nm>nBlqKJyeZi;4OR4BNm{y_raLAExI5DND29oOp3y~kVA;wyF5Rtp2J{WEQveI7 z@nDX$RGX2U_Whf%ZLC7-<%PR&y3z%Zi$jelr%KFARmSKX6=$b3m3oOElZB3DW%29E z%8KbDG8iT|*M|)h$4ftVECb<kro6eiIav;&4m`sz zRH>#3>HQI%f@&$a8Gb*&Gbu9+D6)XC05{p>IE~5tgt@&W+0aM2{MyYnFu;fIje zzHA>YE-rCIeSI>d_4}eOeF8P_0|#0(ny7DVmXYveximrf$iyz5c(}IukQ=57JCHc- zz_^vc3bND3BZ!CHZ?b3EJ;U`qoWi==XbJTvlplB|(c7N7%Q0|!e0;Lw=~bvg0g_>o zW#q$BBgXbD@Ou^VMPdr|`05t^I{;{pCt>oP*SQ7h)Mlv{s4dxZ>{EIv)OWp28ZacIZ@wY%IJH26*w9NSKXiNG**XuH8|=303#ZV zft5)W0vyr35T8GcCf?~;ybbNaCX~VJJa)xSVo}p&(gT$XHx1V3>Ej4-Iu~b`dRAqJ z&upi0TH-mUQ^;CM(n`{bA78Zj57&SInU^X$u|$FiNCYZ9+7ltPEX((fw5$P!4TSY7 zqtRH@w7@8=2R%XWD>Y`7Ue%Z9V|Q?F%4=Tu>jkFaUQbfIzVr9LYH2ByufV_qK!7So zmI4Sfft;XpG$(O$Xz}vm7|Vc2m`SZ}oehe@61$|Toz>E)w4{?W!B^rqh4y@<$m_}z zd&w$|FqP_Raqw{^548>uoB&hA60^_BfGwUR=)Bn+^4E4wg8bLPGWc?G@xvN6yz*Kk zj3MCF6_@tHXuWBBDK8g;b79!PZBSJx`g=w9!A*CM3a=;z4-C9Sw4_`nhyafkz4ZLy zEFS!i)Zc*-29z0AFvc)z&gTl5PYPXMx2k$p?Ns&NP4h1oN0Tx%pT4L%Gc!9TwXc4? zX~|hCcuW+JKTLt^!lI@MkV#lY7LFwA{T`6|`!^Yo$w?F@huFS3%H$*?rc0U&XP#b{S8~nE%br)E?QdfoZhnMVc@qdmV+b-(pWF_3TermE zzmT4}>8%OaJbAR-;sG>Kn6LndCI{MZ9;Y1eU48ReI4xQfA0c7BG><4t0bYEodQa%7 z%f$fQ<3&sQw7rGYvy@4e)k@QG787D#)t>sR-=TijAy&@hTmq5mOSj@estf=GOhrt@ zV)yDB;DQ$R_nz4NqHDGmTUX+j-7T&#yW6juq5Rht+azP z%IU(KqhhkQ873Mw5g|k4W-ElIr9OQ7=jR>F`4Am(i}%*eCxN+;s)p@n*>g7`LT61C z=8y1)r-d}3v-9)QPPGbKE!l-l4BkpmhKyirelq?es4r-SL5*aBSU*7+Et@lmBR7=F za=U2DG}Jv5k?_^2(aMTDyw$jxht6XHH>$qg+x9THr11h+ zkW*nw@#9190M9?5;NUjIMoj31(jE5c%1lx4*9`o!_~g#!?y7HGT})li=3HIQn#zhU z8WS`0(Z2T2D{5ea6acbkp;jfPH6h{u0M(=GpC&E`Y0p`cLCWZ&gyn35Ylx7DdSTnm z<}(G&fU8ix>^Fj!9g4h|Et}_?C^^D~&yS!x9&gW<{i+j5-ZC@Ke@)Tkztn5ckf(6< zNu-l*y?l7l>93ibI-~fKCHkAM!on}-FW>uH)ypuC{Vm&t;oEmq2p+TUMNzKF9z!eG zhZ$pGko%G2$zpc181CKiD?Sbq=cxl5P~I4MZXCUnXFL^5 z?MnbF7EMSv0U~7U%X{(rpp7_7_u6PvrAyo@0hYl8k#3%IuDX-%qv5erQrE?t?es^N z2~0ev{&I;t5pPRwrI2B!Mg5|ef{#ONkFZ)1qg+N3LHP`5G9y|7_I-r4<&4VJhkF$xOjwpPYh~x&)RKa1K)cJJvI`OO*cP; z9Ccg!#iny1B0a^bVPgA_o0#B;rixr@NbozR;^pebdOrzK(T*{)^${uSh5L{{tL8-@ zm^ntYKI5>n3a2V@IrW#lAFo5SwC(;TxmLTp1NmoVd5htsd(bxHvB{eY(bbpclS&oM z$cNvQK9=YTU=f##5OCES4rv=(D;C`NT;JHic+=juo`sc)wWKi!%7(=0R1g)<@o~oD zE4=5u|Epi+Jb!ZWGaSE&l~^@f5rR*Jw*O3+fFjf~yVlj>83t9rp=--8ZvW%;)Eg~X zu8{X3lcBc(w|AZ`nfWiI>7R*Y&q+$X$-JYL*b=EYNKr5#V?&FDnf8{71r3}$42Eh? z-1YXqP>#v@iKty&!Y=TS|H7Bchq{EJq~v4Hi*2@N(QjO7?mh2mDXvc9>?@i}V}Tv8 zS>M=LZ^g<9S8BE(|u+%IZxcC?CO<^`BsN?zV< z*0ghcXT$E+x(WL8SmN>M^56FsU zR8Tl5%aBTsy*?bj9k;rV>be+kPGlvD_MLwe3l&~!0^TI;|G>X27e5bv3?d)c_y0S0 zU$T?&^ybAadH%2Z<4B+5&~l*%?avXZ5uW#S<@?sIcRR6XzXKv@w|>*d{L;RC{e*KA z;^Vg$cpGfB6%c0KvvZl^oI9_lzPckR*|9weyigzbTlwhwB=C*-&g#u9?|t}R>y7&-`>)cq zV*dcpACdD&{zvcn2kj4$!H?Dx5^r{1{dNDFd@GIny`NZMY7L%U`dI$rxLkL^)ik>( zt`fGTp+GED-9s!op|Qk_`=haCDqDj1wRg;?O%suA(bXa&oli%IR;Nc-e_ZdehapbM zZ7bEsA?8n?x$dQ%A7`aY`MiRNno&r__?Tr-W%rP&kys{w-TSf zKDJt$Y4MLsY^^K_%y2q%i{=s<6~0`=>eX|OdaadfE?@QY^UYD-SG&Z)vQo<)Ux9;= zCxRevYV1b@@un}`KOpUK^2F?Ea=K&R?~nTDhn?uha`_{^2xD_a#YRZ5{EG5U^*4(Pu~i2nGIOv%{C=1)%hpSWf@pB9p;Io6=~1>!K5$SU!Ive_#LTk-e1Se zQdILTL*|23vPCb6J^xHFurZT+`qQZUgc)c1_JssrqnbgfCH$13HS5RKiP)fh{>R@| zFK?l6xQvh%XObaG6Ooah!?Y>CrQ>y^^&atL-w(v%>lbpi8X6zJc>;Juo!yKIS|Jm4 z_2A=YH0oPD;YPjg&Mk+gvBM^{0ijg9^z%)Ppbzo7-Wd zE{jcv$pwk}k}4(U)j zL`1wj;J$M;^$hWAhJ6`3pi2kE_iaxC3oVkjqY6RBpgY;pg$M&`{@+utzacmmm(|{r(s;mSrC-VF}`0X zu}%N!Y2wS%!#w7~ANx!l3}1{Ob4$tzH#X7>IrK6g5a%zGD!$Q|o0eMXvk&)a_o6?K z1PL$Uh&qa>Wf}-wm#*?ju`H00Ax`6 z`1|)i__zY8LXSIDsjy}&LevyXs^!!w{v^Xt{6qx%$G+;gLPv^&?14_P`=h1KjUQ+5 z&2W24vKN}1m7asQh_na%Ry%u`gm)_faerdi$9l{vqHdw4uO*!9L+wZ)Ql^}iKXAPj z_9er%Y{&KNB)WToxTx?&xQTO#z3B6{J~E$luhaT8Ey@GFOtaI#{yQnWTr{=-DVPoodkql#nX!3oefDmiu*a5 z(_~z-imYr!7$3c>H~K>!A<}Uh$$U#YuE}32#N_zAKjq-Hg zows;&rKY@_xA^>8?AH)Zd@0=hwQ}yARKo1*fggR@E=yDky;XIF95YoFg&+4G%G4(F9zTJQoxy<0_OhYWlI z1*+>kLj4bqLfy-Sz17k$=dGdFYud(&C(Gq;?&#`gL+Rz``bx_TbI;_9=aU7@4E(MT z?nnJYE!R?!8)aS;Wf9Jiwzp!Lgl5bJTJA+|FJ#`%KjYitay+Ze`lJ>7E9_Uzq|<{y zXj!|vRq4z7Mfa5#Ki*!o7dtl!U@vGZ~vI+zE@YcD8+Ef zC48kAvLm{AYc09=yZ^my?EaW>_0R263P|n}`wW<{THmx%#-#%x8FbQFG0!)7)TX0X zZzEn^(-BeT7j}1cyuUmo-aOB-Dp0K^_PqG3MAKDIhiBlXHBMwpYvIXb*e&*vhv1UX z>|{O7>UZaxl1S6Z8}ic|AIJ1ypZZAW^B~HXx`CB%Q$bAyhU~yI3AzDG;l`H;u6GZ< zHe9sAANug{JBoX_#a;(jw{6x3Ok3D{UHZ-+C4Bw<0uI;gTwA>9f8l-d5XK#TrR0Ce zA+|8>_oBFVP#P!yxcj?=wV$;#P5Wedvhr2kye zgx`ZbUzggXoRH(m8ltPEtl{geW~i|KvW0%AfsMzf5IW^3;lQ7_)>`+QH(}QDSBM0} z&iUP56O7Yujn+E<;O73f=+i$S<@B+i!k(tr*N5TiaeMoBNu!5L>W>;Vlv*+Vj5yK# zdH<`r?xh*7jt9YI7bU2v#srvdFbkUHm61r5-$&Cj_fdY^jCJ4 zI?mSkxQzJBM02?d`-0vwna&62-GweO`ObX%@&&&E50Cqb##@u?Vo>X-do!eGht}Ms zx2|I_1;G;`Mew3=au0LJy0**TKft}m{p)rAU$cSBGvvPG@$H|8!EIcDy)yQ1lk;CQ zgde@iS_a%LSAEz-zXjCX2vo{go~UFAq;Ap{p()s3*JhYG_Q8>w`XrBHLkJ zfsf+v`}VsL;@t?P`y=t26mkMSLW?pf6*|r&A08YX z+`Q+Lp&e4X`x&$uZ1LU5;+K=P(Uw@qKBIl6*-5p5W1ddSr7~|F`E+kHq?FmRYppe+ z_~3_VdTHCSSYI&y`uSQt^un;N*mu{@R{*@bUn0r^>beydf9w5iwFU!I*px8U`j@D|=K>l_3GV5H4 z@55hjHWxtX?sfO(r`y(gi}oq5DrejougmU_^Gq~b-AumIYAc^W4v5JdeQe`q&mm)N zt(D5XUq&we{y$EZX^nhCi9%^DCGJ|uSpppwPlC6}$?2I&zRLiHR=@pEIy`TPClPsi z0E*?n=Zno#&sL{fzpshY`8+|IeO2>nAG|tqSmfr&HD^s> z_XTre!ux0GrNX<%JJQ-6k$#uycI4h>hEmB;a$^GV!Ga#!2a73%jZLhiiyH4+#CsQoW}@9xM~)>g{i-&`T~ zgZq6F*eC_F)c!|W3=V&sO5TLS?v{pUqtxRF%~bTJ1+v2NpD+2%kcOiA8_Wl9owS9i zY{Ej%TK-6iU(~Lv3L&na`kLMAUD9X8x{vpc&(jD0^oi1*jcgze|2w zmMGy1#6$nw^XkTgme%@+us{0M^;TITAHUv;_Fqa#NwhH+7gKQXcP$9`6+DWxh7;x- zyS{d>#tzc1cNX1?r{+;F<#GyXGW?`ena5J(#>;MMUut+oe$8;&YAJHEQ(tul$BtGC zks_B|(^?O(-)#EvNZYrg1iyZi49lJydPdG8@g?jbPg#mF-hQ+0vP@J`>>=ET$h}^v zbGI39F7%4aH)&kM6n$BZe^SV=xu+Fh?Yw)KOqa77CiuXY``T!WR{RRNu!vuEOvt@? zx~dThmz2;yx%NIOl?n*=>bG)VE6y%23h^noE>9QjNaCAzx4d1wIV!rCthYP!>|R~H z59f=EPZpSDA6Gc;YM=f3Dn{_~9AC{?NV6^V(5mp_*__UnC$clB&uWj)Y+Q^mvu_GL zsRp{2Q@RFIPNxFGwFU`Fm$!~?9P`D?<_1HJ>_4jG&Rt&YM;+&Pmr~t+xp$pDv!_@x z>c7DdNan*H=z5>ZKL>QJVp*KCH)7@xnk$an)RNczz8ccHH##}KNTv4K4OZEagp2MN zQHS!al;z(YrR)VhZaY!zr8gdjX=yxM&#Cy|?iGKwbes?R>GyM#Eba}=&z)jNlfypU zWRG%O@@}W9noD59NqMkCbo?-Cz2s2Cb!|8R5jF}J2=ycYKK3H1)Xdc(~WlHM`a=bHWN|zhi0qr zYiwDQg5Fr2Kmjh{5YR0>Xv8Rt%Zk%iirs?a2~{^>8CIfAO7lxyGl~- z1bF79d^)?8XnpqP_;vr&`$nssiu=d@R*{5!^wT}W+EKF`@4;j>p5#A(=i|~>=kjvN zyHP>ew>}VNE~=fzsx>Z!nK+poyQ1?lUo697Uqw+X%ZoZVQgV+g3kHp}ydV;R>F8X=qEBKL-)o(xefh-`v z|GW3x$;GYZleEyx%5C4nOPxP28qOw*yY?cC#4i@y6SxlYw3iRQL2>;Kqpzo(9N<0i zM#MOZwS`=jr)UZq`vt`n1^yQ^NhM3^>c>0v;ljst<~?%nfRcdC@SK6|kz62F?ylsAqaXnxt}e6#EQfSdE-3mN)1y|yJM zepDs<9Ij7992|ukhVuH99I3)+^uFH$NUjMIO zcQ!YRi=UNkSxV(Q?$1%#z#rqDmj!M;h^h_qBXAP+i&OWVMOjLpg;X&Mk(q}a<&1H? zCGa?4db?TW8z-u@W$xY5DIQ3+)yYZmEyuge>z4$+W1XZ>cd2IDUh&c;#XK}aCxq3PG`w=JEuCI6ILzufqK6T0>5Y$GAR_RRcjd2MUw*#FkuvETV6@~}81AqCg> zhy_tHiR(9Im|vk_PsyMZo$8a>V>&DTj6EP5-hK6Q{`zv$-VC>$RPAmuJzd zM4=F0%V09W&t~%SXoj1%vqmMg=JdeOxJtn(zqWDN+)RuXWHZgVZu+{RZV7CqjBxT! zKij+$=NZkG?CXAvd_KS9^+tXLX+v;vrDj+7HdbRV;aT}%@RP)I_DS$4*X%|UTk5_< zHl?F;x~S#4N@c@5sz&^6kwBrO_<&a_*_JTWQP-nSa0z1*P2(4xu#@;;xqIWr$wVLT zMCc!H^Qp<|g`bj<@~xUKKoJ^?QrOeBclOuszCIHcKjH~<#G4uDDEB#6&@m&i8MhSb%Yl{J@XQ7JXnUALja!n_6F!;DAUcNTAd55{kfy=eWkmW?Frb(zKu zMXGBp-Iz=u+-&g!`z{Trd~C!td1&5Oh>Gp#MZ!==qT>dkDBWC10bCn;k@aONR`%Wsde zw%41-W7Sa+qbhNZ1M1MgkRzV4Mse5QxA-V#vROBAT;Z=Pb8$R@Yj01{@x5O)JgBvK zt#yfWQ!`XjTYK?p+4Db|0ElmL?uUdoGvaVLJI?orparQ2%xtpN%{sWgP1_Q&>$B6E zK7;^Hw(WppSs;C#_VwJXnRT1l(Pmg&A5{#K?;-S}han-YoVbmtr$_qyN(K#NFOS4D4PIs$a1?NuNc)XST2m7v z&FLurXG-_~tmgj564C_mKXbbO+0udjGqu{~&Noz1S50J+0YK!hYmSo9*m3M&rwd7j zB^6l!S_So%Kx#BPuwd55!F0kKtE2#sasdG_L=r9dVg+fu9PwRLAP#kt$kXvmBABHM`hT^SmIw8Z!j-%5(+tQMe!IA_34DgkX zmswC#gB)EY#VyLVcsl?<--g*k^fs_bK%2PaW)_KAG5{b1H=RH+s?pJ@f;muiVF@64 z_YR!3V=y=7QzT06Q)Q&;o???_2hi8lN^Sr_2NF{kp{6W3^O|fXQglFQfQK&woq?o% z!Q3O0L7!I+nuBp)b5sBT7;&bW>(dH~Dj-r_c1!;zdjdg|>J>eHHe_Ih!~!M9whFMgoqFyz z+hPYl53GA#hmgJSJzp#YmN%>%=7_4NZzPbj`qS$et1PT{SR-6pN_^}9tpNt4-w-jY z7%JIZ)DB4WvlI)h)!n9^?B4#I+nZVbI>1-<#nP=pkk;V?Mmr9FP%~I*!w85kyda1nqE6=NU^Dm6Io>Y|BY^eau$pNYfe5%L)8}ADc5RPa({8K?sBB zrVjr})HLsHH&2Rp?ifdM-Bu@5zQuv$3ZDyCRf;TG8m$7rDDS++c&}Vm)asG|6JakE zG0f6v7|^IwW~;M7tHA%HkcoR*n>_#1qZz=kEm;lmq|DQhmb2fS$k>{@~!vLcY_pqHrrMb2r1S-xOkW%tz>Vwi4rztVM$4h|?A)C!85$zG zEIGqWP58$eAM=Aagm}`tHFg1DRvnh*sLv{6Li_Q9idB}(mbYPu44_(I*2OZ{!QaEm z$SI!9uZq+B&SA^)#LI^ji(Ej6Y|~@5ny`^%ib~m`Fo`v{c|yMKd`l}(ML>uJw$d=~ z*+J>9z(^uC!35&oWQT9L^nS`xvSsC7qEv4>A~)o7L0NTXcQaDz;xRAY4sfhU%FP_FC95SyNo&W97OF0P!Yx0IFWUe7ho%s9szk_}XoKz?2oHOWj_ zgpRB(wbkY%?arJ=&8ZGasb>lx5;X@b*+%TzrSqD3fNFiLLv(hjimRS=6fw*xSGe2RCD000(31#aX6a56;3C=fKN z|1$i7$f$t@rPwVF3ILQ;$Q+6vxUEZ31^{?$U647QPg#oUa>zog+y!JQI!lxzJM!to z10{lDM^9<8WKz|ECOQi1&0m@ps*6=I+?TTo?1=24=LfjGcTrd`df znoouZ3h=riYb`VIa~rY%NDjx5v@xoPQt>$7G5|6a6hbmc6y=&yLh*!UfJ{(iM%s|g zDy*1AU+7}UkU{S(v_#e-Nvv|fsrnR~Nr-MH#B_{&V~~f&@LaLoi6uEsZYi&!0t)D| z;tr#CIpJoZW9zEHu)zRFrUpQ2suGQj&f(H!g9ETWXQhnJ;8OVr=xVBaxNRV%u$0A~ zMYh4f{}I6EHp+G5Qj1QU7+2MCSSpO+f}+@T8J)P{pmA}`pQt1YaGRROL$JyudWS_H z|F%5Mt{zu{IY{SV1}UIeM`CGugvu)1|3qI6#h^x7y@PU4WeR*M4RR(XL zo}!7OJe&RA)zXFz?nZ>Q>fGfY>yn=lk@=So521t4-X3t0jnQ0~wUd#=6|iK_-6n$( zbT5~)`muI!*;Z1%}H2n8s$j91@qT$hzEL2c>3V~FVESAW$Eq+Pj8upuOU(;7IHo%NyWM#*dK42adH_JyX{iHz zihCC_+{e|)ppmA==tjOIk|f9;|gNM0^P@M zyD--7k3#I}YTQPeWD0XmmTE*yoX1p7oXv6FIM~XZR2;eH#R8`D=CuHdD~nkbj2-w^ zmrT*j$TF*Rf*eK}$=&3tDT;*Nq-HvWUgm{twnCn=ik(a>-Bq76NE)mR7Ud|rv-XKi zsgrAayK7Xa-g0-V)RJL}^+C`KHNLvDRDG^i)syK=GoXT^;#5K^vF9cNatn2hQrJlq zR^E~gqol~l)Ts-ldO2(P=Bdklu4-uXQq`5k{cHj&TAUeVeS45tEki?VP&bR zw2{T(GtH~vN4MnetYw-(v6bhn;ly1e)5y|Pl8G}9@W<;w$Cg#m0jb(x`0^{RHmS0h zHKV^FhmBpbu=^(Yc3@~{mV=zF+=lPHQ62v`~%`^ zgQaIF98s#PB;US6qn}m(%PdE4 zQ>6V9Cq1U}!aDySS_)gV8Qh`aw5>;?F*I2$7kjxiG)$Qp%QP^UH&QHC{vW__yEw0! zP2-%h7E(~J|bU_Mx1i$4o`5!5(9TWUzy#~5pbsl4?M zV64Wq`&L{Pw*H*~ng+&Cc)}nMs{UiB7oj!O{z|XuMhKd@ldhW_FW>5We_pdp-is4RyKU2o7(W;2Xzj{1GZW{WIJU z`6VEpLUU`zMTK_+(j=uF9VjA$XW_e$Cc_Qs$3=ok!dcn07SOJ!)pZNoHFv39v8{9leV%d zU#cy#bIYc9AKr(Tnrxt}Fuc{ng>VaJX2cxch9?TDlX02P1w&BKP;3CaH0un(hFj$yQmHI6V(&$)pg=J1G;ee&0Lf#iW~73I@FS0fh*a%g(W6ZR zGH4Jrf>M1V==hWAJ)dtT|7o%Oti#4oiTJtXfiGjbg8V>Sy~B2NJ&5x7f{D#k)xG2 z@;P0qU!DUur_tEN`*?^ z3t}xNwVq7_|QDr(^*-5_Y^aUj?a5#8ljYfx%Q1}cjin&%Hw!ls zoMm>ZB)3mdD%fW_;C!76mj^Za4$+KT(YSmu7-;A?-(=AU?S=se3hXbcgU9sWXNwC` zslf*`#qHke|6$Ps(fKbr{5RQTMiYrlonfUq3k#ec<)ql{q>kQbiSH=hF>xTFi!nE+ znuFzBwvvq9gaI(VZvCpzO8O5#N%tjx(XofYkPKBHz_OWH`NihEew45En%> zL1ty;K>O#cV1`8|u5%SmFK~F9g9C>gP|2i-(?>DL>by9i*farxO&&c#nr~09|lWhBK*wjxRRxoDQ?0DkH&3tOk|8DapYJ) z$FATaw2>Z;bh|f~o~xb|wg2F6gWW)=!~PF|(r#r3MG(B>Y#gDYZu`s~4kxb0<(
;gly%DVs#y5QXlg_c*tM(dh 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 E2hn-)!Tu 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

KFWX=U+6 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

    X=jLCp!v6pc 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 Uw`#ViEju6o*|j*=RsaA1 literal 0 HcmV?d00001 diff --git a/doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown b/doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown new file mode 100644 index 0000000..0aa66f9 --- /dev/null +++ b/doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown @@ -0,0 +1,316 @@ +# High Level API: TextDetectionModel and TextRecognitionModel {#tutorial_dnn_text_spotting} + +@prev_tutorial{tutorial_dnn_OCR} + +## Introduction +In this tutorial, we will introduce the APIs for TextRecognitionModel and TextDetectionModel in detail. + +--- +#### TextRecognitionModel: + +In the current version, @ref cv::dnn::TextRecognitionModel only supports CNN+RNN+CTC based algorithms, +and the greedy decoding method for CTC is provided. +For more information, please refer to the [original paper](https://arxiv.org/abs/1507.05717) + +Before recognition, you should `setVocabulary` and `setDecodeType`. +- "CTC-greedy", the output of the text recognition model should be a probability matrix. + The shape should be `(T, B, Dim)`, where + - `T` is the sequence length + - `B` is the batch size (only support `B=1` in inference) + - and `Dim` is the length of vocabulary +1('Blank' of CTC is at the index=0 of Dim). + +@ref cv::dnn::TextRecognitionModel::recognize() is the main function for text recognition. +- The input image should be a cropped text image or an image with `roiRects` +- Other decoding methods may supported in the future + +--- + +#### TextDetectionModel: + +@ref cv::dnn::TextDetectionModel API provides these methods for text detection: +- cv::dnn::TextDetectionModel::detect() returns the results in std::vector> (4-points quadrangles) +- cv::dnn::TextDetectionModel::detectTextRectangles() returns the results in std::vector (RBOX-like) + +In the current version, @ref cv::dnn::TextDetectionModel supports these algorithms: +- use @ref cv::dnn::TextDetectionModel_DB with "DB" models +- and use @ref cv::dnn::TextDetectionModel_EAST with "EAST" models + +The following provided pretrained models are variants of DB (w/o deformable convolution), +and the performance can be referred to the Table.1 in the [paper]((https://arxiv.org/abs/1911.08947)). +For more information, please refer to the [official code](https://github.com/MhLiao/DB) + +--- + +You can train your own model with more data, and convert it into ONNX format. +We encourage you to add new algorithms to these APIs. + + +## Pretrained Models + +#### TextRecognitionModel: + +``` +crnn.onnx: +url: https://drive.google.com/uc?export=dowload&id=1ooaLR-rkTl8jdpGy1DoQs0-X0lQsB6Fj +sha: 270d92c9ccb670ada2459a25977e8deeaf8380d3, +alphabet_36.txt: https://drive.google.com/uc?export=dowload&id=1oPOYx5rQRp8L6XQciUwmwhMCfX0KyO4b +parameter setting: -rgb=0; +description: The classification number of this model is 36 (0~9 + a~z). + The training dataset is MJSynth. + +crnn_cs.onnx: +url: https://drive.google.com/uc?export=dowload&id=12diBsVJrS9ZEl6BNUiRp9s0xPALBS7kt +sha: a641e9c57a5147546f7a2dbea4fd322b47197cd5 +alphabet_94.txt: https://drive.google.com/uc?export=dowload&id=1oKXxXKusquimp7XY1mFvj9nwLzldVgBR +parameter setting: -rgb=1; +description: The classification number of this model is 94 (0~9 + a~z + A~Z + punctuations). + The training datasets are MJsynth and SynthText. + +crnn_cs_CN.onnx: +url: https://drive.google.com/uc?export=dowload&id=1is4eYEUKH7HR7Gl37Sw4WPXx6Ir8oQEG +sha: 3940942b85761c7f240494cf662dcbf05dc00d14 +alphabet_3944.txt: https://drive.google.com/uc?export=dowload&id=18IZUUdNzJ44heWTndDO6NNfIpJMmN-ul +parameter setting: -rgb=1; +description: The classification number of this model is 3944 (0~9 + a~z + A~Z + Chinese characters + special characters). + The training dataset is ReCTS (https://rrc.cvc.uab.es/?ch=12). +``` + +More models can be found in [here](https://drive.google.com/drive/folders/1cTbQ3nuZG-EKWak6emD_s8_hHXWz7lAr?usp=sharing), +which are taken from [clovaai](https://github.com/clovaai/deep-text-recognition-benchmark). +You can train more models by [CRNN](https://github.com/meijieru/crnn.pytorch), and convert models by `torch.onnx.export`. + +#### TextDetectionModel: + +``` +- DB_IC15_resnet50.onnx: +url: https://drive.google.com/uc?export=dowload&id=17_ABp79PlFt9yPCxSaarVc_DKTmrSGGf +sha: bef233c28947ef6ec8c663d20a2b326302421fa3 +recommended parameter setting: -inputHeight=736, -inputWidth=1280; +description: This model is trained on ICDAR2015, so it can only detect English text instances. + +- DB_IC15_resnet18.onnx: +url: https://drive.google.com/uc?export=dowload&id=1sZszH3pEt8hliyBlTmB-iulxHP1dCQWV +sha: 19543ce09b2efd35f49705c235cc46d0e22df30b +recommended parameter setting: -inputHeight=736, -inputWidth=1280; +description: This model is trained on ICDAR2015, so it can only detect English text instances. + +- DB_TD500_resnet50.onnx: +url: https://drive.google.com/uc?export=dowload&id=19YWhArrNccaoSza0CfkXlA8im4-lAGsR +sha: 1b4dd21a6baa5e3523156776970895bd3db6960a +recommended parameter setting: -inputHeight=736, -inputWidth=736; +description: This model is trained on MSRA-TD500, so it can detect both English and Chinese text instances. + +- DB_TD500_resnet18.onnx: +url: https://drive.google.com/uc?export=dowload&id=1vY_KsDZZZb_svd5RT6pjyI8BS1nPbBSX +sha: 8a3700bdc13e00336a815fc7afff5dcc1ce08546 +recommended parameter setting: -inputHeight=736, -inputWidth=736; +description: This model is trained on MSRA-TD500, so it can detect both English and Chinese text instances. + +``` + +We will release more models of DB [here](https://drive.google.com/drive/folders/1qzNCHfUJOS0NEUOIKn69eCtxdlNPpWbq?usp=sharing) in the future. + +``` +- EAST: +Download link: https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_detection.tar.gz?dl=1 +This model is based on https://github.com/argman/EAST +``` + +## Images for Testing + +``` +Text Recognition: +url: https://drive.google.com/uc?export=dowload&id=1nMcEy68zDNpIlqAn6xCk_kYcUTIeSOtN +sha: 89205612ce8dd2251effa16609342b69bff67ca3 + +Text Detection: +url: https://drive.google.com/uc?export=dowload&id=149tAhIcvfCYeyufRoZ9tmc2mZDKE_XrF +sha: ced3c03fb7f8d9608169a913acf7e7b93e07109b +``` + +## Example for Text Recognition + +Step1. Loading images and models with a vocabulary + +```cpp + // Load a cropped text line image + // you can find cropped images for testing in "Images for Testing" + int rgb = IMREAD_COLOR; // This should be changed according to the model input requirement. + Mat image = imread("path/to/text_rec_test.png", rgb); + + // Load models weights + TextRecognitionModel model("path/to/crnn_cs.onnx"); + + // The decoding method + // more methods will be supported in future + model.setDecodeType("CTC-greedy"); + + // Load vocabulary + // vocabulary should be changed according to the text recognition model + std::ifstream vocFile; + vocFile.open("path/to/alphabet_94.txt"); + CV_Assert(vocFile.is_open()); + String vocLine; + std::vector vocabulary; + while (std::getline(vocFile, vocLine)) { + vocabulary.push_back(vocLine); + } + model.setVocabulary(vocabulary); +``` + +Step2. Setting Parameters + +```cpp + // Normalization parameters + double scale = 1.0 / 127.5; + Scalar mean = Scalar(127.5, 127.5, 127.5); + + // The input shape + Size inputSize = Size(100, 32); + + model.setInputParams(scale, inputSize, mean); +``` +Step3. Inference +```cpp + std::string recognitionResult = recognizer.recognize(image); + std::cout << "'" << recognitionResult << "'" << std::endl; +``` + +Input image: + +![Picture example](text_rec_test.png) + +Output: +``` +'welcome' +``` + + +## Example for Text Detection + +Step1. Loading images and models +```cpp + // Load an image + // you can find some images for testing in "Images for Testing" + Mat frame = imread("/path/to/text_det_test.png"); +``` + +Step2.a Setting Parameters (DB) +```cpp + // Load model weights + TextDetectionModel_DB model("/path/to/DB_TD500_resnet50.onnx"); + + // Post-processing parameters + float binThresh = 0.3; + float polyThresh = 0.5; + uint maxCandidates = 200; + double unclipRatio = 2.0; + model.setBinaryThreshold(binThresh) + .setPolygonThreshold(polyThresh) + .setMaxCandidates(maxCandidates) + .setUnclipRatio(unclipRatio) + ; + + // Normalization parameters + double scale = 1.0 / 255.0; + Scalar mean = Scalar(122.67891434, 116.66876762, 104.00698793); + + // The input shape + Size inputSize = Size(736, 736); + + model.setInputParams(scale, inputSize, mean); +``` + +Step2.b Setting Parameters (EAST) +```cpp + TextDetectionModel_EAST model("EAST.pb"); + + float confThreshold = 0.5; + float nmsThreshold = 0.4; + model.setConfidenceThreshold(confThresh) + .setNMSThreshold(nmsThresh) + ; + + double detScale = 1.0; + Size detInputSize = Size(320, 320); + Scalar detMean = Scalar(123.68, 116.78, 103.94); + bool swapRB = true; + model.setInputParams(detScale, detInputSize, detMean, swapRB); +``` + + +Step3. Inference +```cpp + std::vector> detResults; + model.detect(detResults); + + // Visualization + polylines(frame, results, true, Scalar(0, 255, 0), 2); + imshow("Text Detection", image); + waitKey(); +``` + +Output: + +![Picture example](text_det_test_results.jpg) + +## Example for Text Spotting + +After following the steps above, it is easy to get the detection results of an input image. +Then, you can do transformation and crop text images for recognition. +For more information, please refer to **Detailed Sample** +```cpp + // Transform and Crop + Mat cropped; + fourPointsTransform(recInput, vertices, cropped); + + String recResult = recognizer.recognize(cropped); +``` + +Output Examples: + +![Picture example](detect_test1.jpg) + +![Picture example](detect_test2.jpg) + +## Source Code +The [source code](https://github.com/opencv/opencv/blob/master/modules/dnn/src/model.cpp) +of these APIs can be found in the DNN module. + +## Detailed Sample +For more information, please refer to: +- [samples/dnn/scene_text_recognition.cpp](https://github.com/opencv/opencv/blob/master/samples/dnn/scene_text_recognition.cpp) +- [samples/dnn/scene_text_detection.cpp](https://github.com/opencv/opencv/blob/master/samples/dnn/scene_text_detection.cpp) +- [samples/dnn/text_detection.cpp](https://github.com/opencv/opencv/blob/master/samples/dnn/text_detection.cpp) +- [samples/dnn/scene_text_spotting.cpp](https://github.com/opencv/opencv/blob/master/samples/dnn/scene_text_spotting.cpp) + +#### Test with an image +Examples: +```bash +example_dnn_scene_text_recognition -mp=path/to/crnn_cs.onnx -i=path/to/an/image -rgb=1 -vp=/path/to/alphabet_94.txt +example_dnn_scene_text_detection -mp=path/to/DB_TD500_resnet50.onnx -i=path/to/an/image -ih=736 -iw=736 +example_dnn_scene_text_spotting -dmp=path/to/DB_IC15_resnet50.onnx -rmp=path/to/crnn_cs.onnx -i=path/to/an/image -iw=1280 -ih=736 -rgb=1 -vp=/path/to/alphabet_94.txt +example_dnn_text_detection -dmp=path/to/EAST.pb -rmp=path/to/crnn_cs.onnx -i=path/to/an/image -rgb=1 -vp=path/to/alphabet_94.txt +``` + +#### Test on public datasets +Text Recognition: + +The download link for testing images can be found in the **Images for Testing** + + +Examples: +```bash +example_dnn_scene_text_recognition -mp=path/to/crnn.onnx -e=true -edp=path/to/evaluation_data_rec -vp=/path/to/alphabet_36.txt -rgb=0 +example_dnn_scene_text_recognition -mp=path/to/crnn_cs.onnx -e=true -edp=path/to/evaluation_data_rec -vp=/path/to/alphabet_94.txt -rgb=1 +``` + +Text Detection: + +The download links for testing images can be found in the **Images for Testing** + +Examples: +```bash +example_dnn_scene_text_detection -mp=path/to/DB_TD500_resnet50.onnx -e=true -edp=path/to/evaluation_data_det/TD500 -ih=736 -iw=736 +example_dnn_scene_text_detection -mp=path/to/DB_IC15_resnet50.onnx -e=true -edp=path/to/evaluation_data_det/IC15 -ih=736 -iw=1280 +``` diff --git a/doc/tutorials/dnn/dnn_text_spotting/text_det_test_results.jpg b/doc/tutorials/dnn/dnn_text_spotting/text_det_test_results.jpg new file mode 100644 index 0000000000000000000000000000000000000000..173840f72949ae6c7003ff384cba48f9e49491a6 GIT binary patch literal 49278 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 BoE!iE literal 0 HcmV?d00001 diff --git a/doc/tutorials/dnn/dnn_text_spotting/text_rec_test.png b/doc/tutorials/dnn/dnn_text_spotting/text_rec_test.png new file mode 100644 index 0000000000000000000000000000000000000000..c3226376e417a7fb660825194d7738eac2ce05a6 GIT binary patch literal 2911 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 JPDHLkV1oYSp;-U` literal 0 HcmV?d00001 diff --git a/doc/tutorials/dnn/table_of_content_dnn.markdown b/doc/tutorials/dnn/table_of_content_dnn.markdown index 0a66d04..0ed9774 100644 --- a/doc/tutorials/dnn/table_of_content_dnn.markdown +++ b/doc/tutorials/dnn/table_of_content_dnn.markdown @@ -79,4 +79,14 @@ Deep Neural Networks (dnn module) {#tutorial_table_of_content_dnn} *Author:* Zihao Mu - In this tutorial you will learn how to use opencv_dnn module using custom OCR models. \ No newline at end of file + In this tutorial you will learn how to use opencv_dnn module using custom OCR models. + +- @subpage tutorial_dnn_text_spotting + + *Languages:* C++ + + *Compatibility:* \> OpenCV 4.5 + + *Author:* Wenqing Zhang + + In these tutorial, we'll introduce how to use the high-level APIs for text recognition and text detection diff --git a/modules/dnn/include/opencv2/dnn/dnn.hpp b/modules/dnn/include/opencv2/dnn/dnn.hpp index 5467c98..3ece129 100644 --- a/modules/dnn/include/opencv2/dnn/dnn.hpp +++ b/modules/dnn/include/opencv2/dnn/dnn.hpp @@ -1326,6 +1326,255 @@ CV__DNN_INLINE_NS_BEGIN float confThreshold = 0.5f, float nmsThreshold = 0.0f); }; + +/** @brief This class represents high-level API for text recognition networks. + * + * TextRecognitionModel allows to set params for preprocessing input image. + * TextRecognitionModel creates net from file with trained weights and config, + * sets preprocessing input, runs forward pass and return recognition result. + * For TextRecognitionModel, CRNN-CTC is supported. + */ +class CV_EXPORTS_W_SIMPLE TextRecognitionModel : public Model +{ +public: + CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first) + TextRecognitionModel(); + + /** + * @brief Create Text Recognition model from deep learning network + * Call setDecodeType() and setVocabulary() after constructor to initialize the decoding method + * @param[in] network Net object + */ + CV_WRAP TextRecognitionModel(const Net& network); + + /** + * @brief Create text recognition model from network represented in one of the supported formats + * Call setDecodeType() and setVocabulary() after constructor to initialize the decoding method + * @param[in] model Binary file contains trained weights + * @param[in] config Text file contains network configuration + */ + CV_WRAP inline + TextRecognitionModel(const std::string& model, const std::string& config = "") + : TextRecognitionModel(readNet(model, config)) { /* nothing */ } + + /** + * @brief Set the decoding method of translating the network output into string + * @param[in] decodeType The decoding method of translating the network output into string: {'CTC-greedy': greedy decoding for the output of CTC-based methods} + */ + CV_WRAP + TextRecognitionModel& setDecodeType(const std::string& decodeType); + + /** + * @brief Get the decoding method + * @return the decoding method + */ + CV_WRAP + const std::string& getDecodeType() const; + + /** + * @brief Set the vocabulary for recognition. + * @param[in] vocabulary the associated vocabulary of the network. + */ + CV_WRAP + TextRecognitionModel& setVocabulary(const std::vector& vocabulary); + + /** + * @brief Get the vocabulary for recognition. + * @return vocabulary the associated vocabulary + */ + CV_WRAP + const std::vector& getVocabulary() const; + + /** + * @brief Given the @p input frame, create input blob, run net and return recognition result + * @param[in] frame The input image + * @return The text recognition result + */ + CV_WRAP + std::string recognize(InputArray frame) const; + + /** + * @brief Given the @p input frame, create input blob, run net and return recognition result + * @param[in] frame The input image + * @param[in] roiRects List of text detection regions of interest (cv::Rect, CV_32SC4). ROIs is be cropped as the network inputs + * @param[out] results A set of text recognition results. + */ + CV_WRAP + void recognize(InputArray frame, InputArrayOfArrays roiRects, CV_OUT std::vector& results) const; +}; + + +/** @brief Base class for text detection networks + */ +class CV_EXPORTS_W TextDetectionModel : public Model +{ +protected: + CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first) + TextDetectionModel(); + +public: + + /** @brief Performs detection + * + * Given the input @p frame, prepare network input, run network inference, post-process network output and return result detections. + * + * Each result is quadrangle's 4 points in this order: + * - bottom-left + * - top-left + * - top-right + * - bottom-right + * + * Use cv::getPerspectiveTransform function to retrive image region without perspective transformations. + * + * @note If DL model doesn't support that kind of output then result may be derived from detectTextRectangles() output. + * + * @param[in] frame The input image + * @param[out] detections array with detections' quadrangles (4 points per result) + * @param[out] confidences array with detection confidences + */ + CV_WRAP + void detect( + InputArray frame, + CV_OUT std::vector< std::vector >& detections, + CV_OUT std::vector& confidences + ) const; + + /** @overload */ + CV_WRAP + void detect( + InputArray frame, + CV_OUT std::vector< std::vector >& detections + ) const; + + /** @brief Performs detection + * + * Given the input @p frame, prepare network input, run network inference, post-process network output and return result detections. + * + * Each result is rotated rectangle. + * + * @note Result may be inaccurate in case of strong perspective transformations. + * + * @param[in] frame the input image + * @param[out] detections array with detections' RotationRect results + * @param[out] confidences array with detection confidences + */ + CV_WRAP + void detectTextRectangles( + InputArray frame, + CV_OUT std::vector& detections, + CV_OUT std::vector& confidences + ) const; + + /** @overload */ + CV_WRAP + void detectTextRectangles( + InputArray frame, + CV_OUT std::vector& detections + ) const; +}; + +/** @brief This class represents high-level API for text detection DL networks compatible with EAST model. + * + * Configurable parameters: + * - (float) confThreshold - used to filter boxes by confidences, default: 0.5f + * - (float) nmsThreshold - used in non maximum suppression, default: 0.0f + */ +class CV_EXPORTS_W_SIMPLE TextDetectionModel_EAST : public TextDetectionModel +{ +public: + CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first) + TextDetectionModel_EAST(); + + /** + * @brief Create text detection algorithm from deep learning network + * @param[in] network Net object + */ + CV_WRAP TextDetectionModel_EAST(const Net& network); + + /** + * @brief Create text detection model from network represented in one of the supported formats. + * An order of @p model and @p config arguments does not matter. + * @param[in] model Binary file contains trained weights. + * @param[in] config Text file contains network configuration. + */ + CV_WRAP inline + TextDetectionModel_EAST(const std::string& model, const std::string& config = "") + : TextDetectionModel_EAST(readNet(model, config)) { /* nothing */ } + + /** + * @brief Set the detection confidence threshold + * @param[in] confThreshold A threshold used to filter boxes by confidences + */ + CV_WRAP + TextDetectionModel_EAST& setConfidenceThreshold(float confThreshold); + + /** + * @brief Get the detection confidence threshold + */ + CV_WRAP + float getConfidenceThreshold() const; + + /** + * @brief Set the detection NMS filter threshold + * @param[in] nmsThreshold A threshold used in non maximum suppression + */ + CV_WRAP + TextDetectionModel_EAST& setNMSThreshold(float nmsThreshold); + + /** + * @brief Get the detection confidence threshold + */ + CV_WRAP + float getNMSThreshold() const; +}; + +/** @brief This class represents high-level API for text detection DL networks compatible with DB model. + * + * Related publications: @cite liao2020real + * Paper: https://arxiv.org/abs/1911.08947 + * For more information about the hyper-parameters setting, please refer to https://github.com/MhLiao/DB + * + * Configurable parameters: + * - (float) binaryThreshold - The threshold of the binary map. It is usually set to 0.3. + * - (float) polygonThreshold - The threshold of text polygons. It is usually set to 0.5, 0.6, and 0.7. Default is 0.5f + * - (double) unclipRatio - The unclip ratio of the detected text region, which determines the output size. It is usually set to 2.0. + * - (int) maxCandidates - The max number of the output results. + */ +class CV_EXPORTS_W_SIMPLE TextDetectionModel_DB : public TextDetectionModel +{ +public: + CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first) + TextDetectionModel_DB(); + + /** + * @brief Create text detection algorithm from deep learning network. + * @param[in] network Net object. + */ + CV_WRAP TextDetectionModel_DB(const Net& network); + + /** + * @brief Create text detection model from network represented in one of the supported formats. + * An order of @p model and @p config arguments does not matter. + * @param[in] model Binary file contains trained weights. + * @param[in] config Text file contains network configuration. + */ + CV_WRAP inline + TextDetectionModel_DB(const std::string& model, const std::string& config = "") + : TextDetectionModel_DB(readNet(model, config)) { /* nothing */ } + + CV_WRAP TextDetectionModel_DB& setBinaryThreshold(float binaryThreshold); + CV_WRAP float getBinaryThreshold() const; + + CV_WRAP TextDetectionModel_DB& setPolygonThreshold(float polygonThreshold); + CV_WRAP float getPolygonThreshold() const; + + CV_WRAP TextDetectionModel_DB& setUnclipRatio(double unclipRatio); + CV_WRAP double getUnclipRatio() const; + + CV_WRAP TextDetectionModel_DB& setMaxCandidates(int maxCandidates); + CV_WRAP int getMaxCandidates() const; +}; + //! @} CV__DNN_INLINE_NS_END } diff --git a/modules/dnn/src/model.cpp b/modules/dnn/src/model.cpp index 16f7d31..fae235a 100644 --- a/modules/dnn/src/model.cpp +++ b/modules/dnn/src/model.cpp @@ -4,7 +4,6 @@ #include "precomp.hpp" #include -#include #include #include @@ -37,9 +36,10 @@ public: virtual void setPreferableBackend(Backend backendId) { net.setPreferableBackend(backendId); } virtual void setPreferableTarget(Target targetId) { net.setPreferableTarget(targetId); } - /*virtual*/ + virtual void initNet(const Net& network) { + CV_TRACE_FUNCTION(); net = network; outNames = net.getUnconnectedOutLayersNames(); @@ -91,6 +91,7 @@ public: /*virtual*/ void processFrame(InputArray frame, OutputArrayOfArrays outs) { + CV_TRACE_FUNCTION(); if (size.empty()) CV_Error(Error::StsBadSize, "Input size not specified"); @@ -103,6 +104,7 @@ public: Mat imInfo(Matx13f(size.height, size.width, 1.6f)); net.setInput(imInfo, "im_info"); } + net.forward(outs, outNames); } }; @@ -545,4 +547,778 @@ void DetectionModel::detect(InputArray frame, CV_OUT std::vector& classIds, CV_Error(Error::StsNotImplemented, "Unknown output layer type: \"" + lastLayer->type + "\""); } +struct TextRecognitionModel_Impl : public Model::Impl +{ + std::string decodeType; + std::vector vocabulary; + + TextRecognitionModel_Impl() + { + CV_TRACE_FUNCTION(); + } + + TextRecognitionModel_Impl(const Net& network) + { + CV_TRACE_FUNCTION(); + initNet(network); + } + + inline + void setVocabulary(const std::vector& inputVoc) + { + vocabulary = inputVoc; + } + + inline + void setDecodeType(const std::string& type) + { + decodeType = type; + } + + virtual + std::string decode(const Mat& prediction) + { + CV_TRACE_FUNCTION(); + CV_Assert(!prediction.empty()); + if (decodeType.empty()) + CV_Error(Error::StsBadArg, "TextRecognitionModel: decodeType is not specified"); + if (vocabulary.empty()) + CV_Error(Error::StsBadArg, "TextRecognitionModel: vocabulary is not specified"); + + std::string decodeSeq; + if (decodeType == "CTC-greedy") + { + CV_CheckEQ(prediction.dims, 3, ""); + CV_CheckType(prediction.type(), CV_32FC1, ""); + const int vocLength = (int)(vocabulary.size()); + CV_CheckLE(prediction.size[1], vocLength, ""); + bool ctcFlag = true; + int lastLoc = 0; + for (int i = 0; i < prediction.size[0]; i++) + { + const float* pred = prediction.ptr(i); + int maxLoc = 0; + float maxScore = pred[0]; + for (int j = 1; j < vocLength + 1; j++) + { + float score = pred[j]; + if (maxScore < score) + { + maxScore = score; + maxLoc = j; + } + } + + if (maxLoc > 0) + { + std::string currentChar = vocabulary.at(maxLoc - 1); + if (maxLoc != lastLoc || ctcFlag) + { + lastLoc = maxLoc; + decodeSeq += currentChar; + ctcFlag = false; + } + } + else + { + ctcFlag = true; + } + } + } else if (decodeType.length() == 0) { + CV_Error(Error::StsBadArg, "Please set decodeType"); + } else { + CV_Error_(Error::StsBadArg, ("Unsupported decodeType: %s", decodeType.c_str())); + } + + return decodeSeq; + } + + virtual + std::string recognize(InputArray frame) + { + CV_TRACE_FUNCTION(); + std::vector outs; + processFrame(frame, outs); + CV_CheckEQ(outs.size(), (size_t)1, ""); + return decode(outs[0]); + } + + virtual + void recognize(InputArray frame, InputArrayOfArrays roiRects, CV_OUT std::vector& results) + { + CV_TRACE_FUNCTION(); + results.clear(); + if (roiRects.empty()) + { + auto s = recognize(frame); + results.push_back(s); + return; + } + + std::vector rects; + roiRects.copyTo(rects); + + // Predict for each RoI + Mat input = frame.getMat(); + for (size_t i = 0; i < rects.size(); i++) + { + Rect roiRect = rects[i]; + Mat roi = input(roiRect); + auto s = recognize(roi); + results.push_back(s); + } + } + + static inline + TextRecognitionModel_Impl& from(const std::shared_ptr& ptr) + { + CV_Assert(ptr); + return *((TextRecognitionModel_Impl*)ptr.get()); + } +}; + +TextRecognitionModel::TextRecognitionModel() +{ + impl = std::static_pointer_cast(makePtr()); +} + +TextRecognitionModel::TextRecognitionModel(const Net& network) +{ + impl = std::static_pointer_cast(std::make_shared(network)); +} + +TextRecognitionModel& TextRecognitionModel::setDecodeType(const std::string& decodeType) +{ + TextRecognitionModel_Impl::from(impl).setDecodeType(decodeType); + return *this; +} + +const std::string& TextRecognitionModel::getDecodeType() const +{ + return TextRecognitionModel_Impl::from(impl).decodeType; +} + +TextRecognitionModel& TextRecognitionModel::setVocabulary(const std::vector& inputVoc) +{ + TextRecognitionModel_Impl::from(impl).setVocabulary(inputVoc); + return *this; +} + +const std::vector& TextRecognitionModel::getVocabulary() const +{ + return TextRecognitionModel_Impl::from(impl).vocabulary; +} + +std::string TextRecognitionModel::recognize(InputArray frame) const +{ + return TextRecognitionModel_Impl::from(impl).recognize(frame); +} + +void TextRecognitionModel::recognize(InputArray frame, InputArrayOfArrays roiRects, CV_OUT std::vector& results) const +{ + TextRecognitionModel_Impl::from(impl).recognize(frame, roiRects, results); +} + + +///////////////////////////////////////// Text Detection ///////////////////////////////////////// + +struct TextDetectionModel_Impl : public Model::Impl +{ + TextDetectionModel_Impl() {} + + TextDetectionModel_Impl(const Net& network) + { + CV_TRACE_FUNCTION(); + initNet(network); + } + + virtual + std::vector< std::vector > detect(InputArray frame, CV_OUT std::vector& confidences) + { + CV_TRACE_FUNCTION(); + std::vector rects = detectTextRectangles(frame, confidences); + std::vector< std::vector > results; + for (const RotatedRect& rect : rects) + { + Point2f vertices[4] = {}; + rect.points(vertices); + std::vector result = { vertices[0], vertices[1], vertices[2], vertices[3] }; + results.emplace_back(result); + } + return results; + } + + virtual + std::vector< std::vector > detect(InputArray frame) + { + CV_TRACE_FUNCTION(); + std::vector confidences; + return detect(frame, confidences); + } + + virtual + std::vector detectTextRectangles(InputArray frame, CV_OUT std::vector& confidences) + { + CV_Error(Error::StsNotImplemented, ""); + } + + virtual + std::vector detectTextRectangles(InputArray frame) + { + CV_TRACE_FUNCTION(); + std::vector confidences; + return detectTextRectangles(frame, confidences); + } + + static inline + TextDetectionModel_Impl& from(const std::shared_ptr& ptr) + { + CV_Assert(ptr); + return *((TextDetectionModel_Impl*)ptr.get()); + } +}; + + +TextDetectionModel::TextDetectionModel() + : Model() +{ + // nothing +} + +static +void to32s( + const std::vector< std::vector >& detections_f, + CV_OUT std::vector< std::vector >& detections +) +{ + detections.resize(detections_f.size()); + for (size_t i = 0; i < detections_f.size(); i++) + { + const auto& contour_f = detections_f[i]; + std::vector contour(contour_f.size()); + for (size_t j = 0; j < contour_f.size(); j++) + { + contour[j].x = cvRound(contour_f[j].x); + contour[j].y = cvRound(contour_f[j].y); + } + swap(detections[i], contour); + } +} + +void TextDetectionModel::detect( + InputArray frame, + CV_OUT std::vector< std::vector >& detections, + CV_OUT std::vector& confidences +) const +{ + std::vector< std::vector > detections_f = TextDetectionModel_Impl::from(impl).detect(frame, confidences); + to32s(detections_f, detections); + return; +} + +void TextDetectionModel::detect( + InputArray frame, + CV_OUT std::vector< std::vector >& detections +) const +{ + std::vector< std::vector > detections_f = TextDetectionModel_Impl::from(impl).detect(frame); + to32s(detections_f, detections); + return; +} + +void TextDetectionModel::detectTextRectangles( + InputArray frame, + CV_OUT std::vector& detections, + CV_OUT std::vector& confidences +) const +{ + detections = TextDetectionModel_Impl::from(impl).detectTextRectangles(frame, confidences); + return; +} + +void TextDetectionModel::detectTextRectangles( + InputArray frame, + CV_OUT std::vector& detections +) const +{ + detections = TextDetectionModel_Impl::from(impl).detectTextRectangles(frame); + return; +} + + +struct TextDetectionModel_EAST_Impl : public TextDetectionModel_Impl +{ + float confThreshold; + float nmsThreshold; + + TextDetectionModel_EAST_Impl() + : confThreshold(0.5f) + , nmsThreshold(0.0f) + { + CV_TRACE_FUNCTION(); + } + + TextDetectionModel_EAST_Impl(const Net& network) + : TextDetectionModel_EAST_Impl() + { + CV_TRACE_FUNCTION(); + initNet(network); + } + + void setConfidenceThreshold(float confThreshold_) { confThreshold = confThreshold_; } + float getConfidenceThreshold() const { return confThreshold; } + + void setNMSThreshold(float nmsThreshold_) { nmsThreshold = nmsThreshold_; } + float getNMSThreshold() const { return nmsThreshold; } + + // TODO: According to article EAST supports quadrangles output: https://arxiv.org/pdf/1704.03155.pdf +#if 0 + virtual + std::vector< std::vector > detect(InputArray frame, CV_OUT std::vector& confidences) CV_OVERRIDE +#endif + + virtual + std::vector detectTextRectangles(InputArray frame, CV_OUT std::vector& confidences) CV_OVERRIDE + { + CV_TRACE_FUNCTION(); + std::vector results; + + std::vector outs; + processFrame(frame, outs); + CV_CheckEQ(outs.size(), (size_t)2, ""); + Mat geometry = outs[0]; + Mat scoreMap = outs[1]; + + CV_CheckEQ(scoreMap.dims, 4, ""); + CV_CheckEQ(geometry.dims, 4, ""); + CV_CheckEQ(scoreMap.size[0], 1, ""); + CV_CheckEQ(geometry.size[0], 1, ""); + CV_CheckEQ(scoreMap.size[1], 1, ""); + CV_CheckEQ(geometry.size[1], 5, ""); + CV_CheckEQ(scoreMap.size[2], geometry.size[2], ""); + CV_CheckEQ(scoreMap.size[3], geometry.size[3], ""); + + CV_CheckType(scoreMap.type(), CV_32FC1, ""); + CV_CheckType(geometry.type(), CV_32FC1, ""); + + std::vector boxes; + std::vector scores; + const int height = scoreMap.size[2]; + const int width = scoreMap.size[3]; + for (int y = 0; y < height; ++y) + { + const float* scoresData = scoreMap.ptr(0, 0, y); + const float* x0_data = geometry.ptr(0, 0, y); + const float* x1_data = geometry.ptr(0, 1, y); + const float* x2_data = geometry.ptr(0, 2, y); + const float* x3_data = geometry.ptr(0, 3, y); + const float* anglesData = geometry.ptr(0, 4, y); + for (int x = 0; x < width; ++x) + { + float score = scoresData[x]; + if (score < confThreshold) + continue; + + float offsetX = x * 4.0f, offsetY = y * 4.0f; + float angle = anglesData[x]; + float cosA = std::cos(angle); + float sinA = std::sin(angle); + float h = x0_data[x] + x2_data[x]; + float w = x1_data[x] + x3_data[x]; + + Point2f offset(offsetX + cosA * x1_data[x] + sinA * x2_data[x], + offsetY - sinA * x1_data[x] + cosA * x2_data[x]); + Point2f p1 = Point2f(-sinA * h, -cosA * h) + offset; + Point2f p3 = Point2f(-cosA * w, sinA * w) + offset; + boxes.push_back(RotatedRect(0.5f * (p1 + p3), Size2f(w, h), -angle * 180.0f / (float)CV_PI)); + scores.push_back(score); + } + } + + // Apply non-maximum suppression procedure. + std::vector indices; + NMSBoxes(boxes, scores, confThreshold, nmsThreshold, indices); + + confidences.clear(); + confidences.reserve(indices.size()); + + // Re-scale + Point2f ratio((float)frame.cols() / size.width, (float)frame.rows() / size.height); + bool isUniformRatio = std::fabs(ratio.x - ratio.y) <= 0.01f; + for (uint i = 0; i < indices.size(); i++) + { + auto idx = indices[i]; + + auto conf = scores[idx]; + confidences.push_back(conf); + + RotatedRect& box0 = boxes[idx]; + + if (isUniformRatio) + { + RotatedRect box = box0; + box.center.x *= ratio.x; + box.center.y *= ratio.y; + box.size.width *= ratio.x; + box.size.height *= ratio.y; + results.emplace_back(box); + } + else + { + Point2f vertices[4] = {}; + box0.points(vertices); + for (int j = 0; j < 4; j++) + { + vertices[j].x *= ratio.x; + vertices[j].y *= ratio.y; + } + RotatedRect box = minAreaRect(Mat(4, 1, CV_32FC2, (void*)vertices)); + + // minArea() rect is not normalized, it may return rectangles rotated by +90/-90 + float angle_diff = std::fabs(box.angle - box0.angle); + while (angle_diff >= (90 + 45)) + { + box.angle += (box.angle < box0.angle) ? 180 : -180; + angle_diff = std::fabs(box.angle - box0.angle); + } + if (angle_diff > 45) // avoid ~90 degree turns + { + std::swap(box.size.width, box.size.height); + if (box.angle < box0.angle) + box.angle += 90; + else if (box.angle > box0.angle) + box.angle -= 90; + } + // CV_DbgAssert(std::fabs(box.angle - box0.angle) <= 45); + + results.emplace_back(box); + } + } + + return results; + } + + static inline + TextDetectionModel_EAST_Impl& from(const std::shared_ptr& ptr) + { + CV_Assert(ptr); + return *((TextDetectionModel_EAST_Impl*)ptr.get()); + } +}; + + +TextDetectionModel_EAST::TextDetectionModel_EAST() + : TextDetectionModel() +{ + impl = std::static_pointer_cast(makePtr()); +} + +TextDetectionModel_EAST::TextDetectionModel_EAST(const Net& network) + : TextDetectionModel() +{ + impl = std::static_pointer_cast(makePtr(network)); +} + +TextDetectionModel_EAST& TextDetectionModel_EAST::setConfidenceThreshold(float confThreshold) +{ + TextDetectionModel_EAST_Impl::from(impl).setConfidenceThreshold(confThreshold); + return *this; +} +float TextDetectionModel_EAST::getConfidenceThreshold() const +{ + return TextDetectionModel_EAST_Impl::from(impl).getConfidenceThreshold(); +} + +TextDetectionModel_EAST& TextDetectionModel_EAST::setNMSThreshold(float nmsThreshold) +{ + TextDetectionModel_EAST_Impl::from(impl).setNMSThreshold(nmsThreshold); + return *this; +} +float TextDetectionModel_EAST::getNMSThreshold() const +{ + return TextDetectionModel_EAST_Impl::from(impl).getNMSThreshold(); +} + + + +struct TextDetectionModel_DB_Impl : public TextDetectionModel_Impl +{ + float binaryThreshold; + float polygonThreshold; + double unclipRatio; + int maxCandidates; + + TextDetectionModel_DB_Impl() + : binaryThreshold(0.3f) + , polygonThreshold(0.5f) + , unclipRatio(2.0f) + , maxCandidates(0) + { + CV_TRACE_FUNCTION(); + } + + TextDetectionModel_DB_Impl(const Net& network) + : TextDetectionModel_DB_Impl() + { + CV_TRACE_FUNCTION(); + initNet(network); + } + + void setBinaryThreshold(float binaryThreshold_) { binaryThreshold = binaryThreshold_; } + float getBinaryThreshold() const { return binaryThreshold; } + + void setPolygonThreshold(float polygonThreshold_) { polygonThreshold = polygonThreshold_; } + float getPolygonThreshold() const { return polygonThreshold; } + + void setUnclipRatio(double unclipRatio_) { unclipRatio = unclipRatio_; } + double getUnclipRatio() const { return unclipRatio; } + + void setMaxCandidates(int maxCandidates_) { maxCandidates = maxCandidates_; } + int getMaxCandidates() const { return maxCandidates; } + + + virtual + std::vector detectTextRectangles(InputArray frame, CV_OUT std::vector& confidences) CV_OVERRIDE + { + CV_TRACE_FUNCTION(); + std::vector< std::vector > contours = detect(frame, confidences); + std::vector results; results.reserve(contours.size()); + for (size_t i = 0; i < contours.size(); i++) + { + auto& contour = contours[i]; + RotatedRect box = minAreaRect(contour); + + // minArea() rect is not normalized, it may return rectangles with angle=-90 or height < width + const float angle_threshold = 60; // do not expect vertical text, TODO detection algo property + bool swap_size = false; + if (box.size.width < box.size.height) // horizontal-wide text area is expected + swap_size = true; + else if (std::fabs(box.angle) >= angle_threshold) // don't work with vertical rectangles + swap_size = true; + if (swap_size) + { + std::swap(box.size.width, box.size.height); + if (box.angle < 0) + box.angle += 90; + else if (box.angle > 0) + box.angle -= 90; + } + + results.push_back(box); + } + return results; + } + + std::vector< std::vector > detect(InputArray frame, CV_OUT std::vector& confidences) CV_OVERRIDE + { + CV_TRACE_FUNCTION(); + std::vector< std::vector > results; + + std::vector outs; + processFrame(frame, outs); + CV_Assert(outs.size() == 1); + Mat binary = outs[0]; + + // Threshold + Mat bitmap; + threshold(binary, bitmap, binaryThreshold, 255, THRESH_BINARY); + + // Scale ratio + float scaleHeight = (float)(frame.rows()) / (float)(binary.size[0]); + float scaleWidth = (float)(frame.cols()) / (float)(binary.size[1]); + + // Find contours + std::vector< std::vector > contours; + bitmap.convertTo(bitmap, CV_8UC1); + findContours(bitmap, contours, RETR_LIST, CHAIN_APPROX_SIMPLE); + + // Candidate number limitation + size_t numCandidate = std::min(contours.size(), (size_t)(maxCandidates > 0 ? maxCandidates : INT_MAX)); + + for (size_t i = 0; i < numCandidate; i++) + { + std::vector& contour = contours[i]; + + // Calculate text contour score + if (contourScore(binary, contour) < polygonThreshold) + continue; + + // Rescale + std::vector contourScaled; contourScaled.reserve(contour.size()); + for (size_t j = 0; j < contour.size(); j++) + { + contourScaled.push_back(Point(int(contour[j].x * scaleWidth), + int(contour[j].y * scaleHeight))); + } + + // Unclip + RotatedRect box = minAreaRect(contourScaled); + + // minArea() rect is not normalized, it may return rectangles with angle=-90 or height < width + const float angle_threshold = 60; // do not expect vertical text, TODO detection algo property + bool swap_size = false; + if (box.size.width < box.size.height) // horizontal-wide text area is expected + swap_size = true; + else if (std::fabs(box.angle) >= angle_threshold) // don't work with vertical rectangles + swap_size = true; + if (swap_size) + { + std::swap(box.size.width, box.size.height); + if (box.angle < 0) + box.angle += 90; + else if (box.angle > 0) + box.angle -= 90; + } + + Point2f vertex[4]; + box.points(vertex); // order: bl, tl, tr, br + std::vector approx; + for (int j = 0; j < 4; j++) + approx.emplace_back(vertex[j]); + std::vector polygon; + unclip(approx, polygon, unclipRatio); + results.push_back(polygon); + } + + confidences = std::vector(contours.size(), 1.0f); + return results; + } + + // According to https://github.com/MhLiao/DB/blob/master/structure/representers/seg_detector_representer.py (2020-10) + static double contourScore(const Mat& binary, const std::vector& contour) + { + Rect rect = boundingRect(contour); + int xmin = std::max(rect.x, 0); + int xmax = std::min(rect.x + rect.width, binary.cols - 1); + int ymin = std::max(rect.y, 0); + int ymax = std::min(rect.y + rect.height, binary.rows - 1); + + Mat binROI = binary(Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1)); + + Mat mask = Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8U); + std::vector roiContour; + for (size_t i = 0; i < contour.size(); i++) { + Point pt = Point(contour[i].x - xmin, contour[i].y - ymin); + roiContour.push_back(pt); + } + std::vector> roiContours = {roiContour}; + fillPoly(mask, roiContours, Scalar(1)); + double score = cv::mean(binROI, mask).val[0]; + + return score; + } + + // According to https://github.com/MhLiao/DB/blob/master/structure/representers/seg_detector_representer.py (2020-10) + static void unclip(const std::vector& inPoly, std::vector &outPoly, const double unclipRatio) + { + double area = contourArea(inPoly); + double length = arcLength(inPoly, true); + double distance = area * unclipRatio / length; + + size_t numPoints = inPoly.size(); + std::vector> newLines; + for (size_t i = 0; i < numPoints; i++) { + std::vector newLine; + Point pt1 = inPoly[i]; + Point pt2 = inPoly[(i - 1) % numPoints]; + Point vec = pt1 - pt2; + float unclipDis = (float)(distance / norm(vec)); + Point2f rotateVec = Point2f(vec.y * unclipDis, -vec.x * unclipDis); + newLine.push_back(Point2f(pt1.x + rotateVec.x, pt1.y + rotateVec.y)); + newLine.push_back(Point2f(pt2.x + rotateVec.x, pt2.y + rotateVec.y)); + newLines.push_back(newLine); + } + + size_t numLines = newLines.size(); + for (size_t i = 0; i < numLines; i++) { + Point2f a = newLines[i][0]; + Point2f b = newLines[i][1]; + Point2f c = newLines[(i + 1) % numLines][0]; + Point2f d = newLines[(i + 1) % numLines][1]; + Point2f pt; + Point2f v1 = b - a; + Point2f v2 = d - c; + double cosAngle = (v1.x * v2.x + v1.y * v2.y) / (norm(v1) * norm(v2)); + + if( fabs(cosAngle) > 0.7 ) { + pt.x = (b.x + c.x) * 0.5; + pt.y = (b.y + c.y) * 0.5; + } else { + double denom = a.x * (double)(d.y - c.y) + b.x * (double)(c.y - d.y) + + d.x * (double)(b.y - a.y) + c.x * (double)(a.y - b.y); + double num = a.x * (double)(d.y - c.y) + c.x * (double)(a.y - d.y) + d.x * (double)(c.y - a.y); + double s = num / denom; + + pt.x = a.x + s*(b.x - a.x); + pt.y = a.y + s*(b.y - a.y); + } + + + outPoly.push_back(pt); + } + } + + + static inline + TextDetectionModel_DB_Impl& from(const std::shared_ptr& ptr) + { + CV_Assert(ptr); + return *((TextDetectionModel_DB_Impl*)ptr.get()); + } +}; + + +TextDetectionModel_DB::TextDetectionModel_DB() + : TextDetectionModel() +{ + impl = std::static_pointer_cast(makePtr()); +} + +TextDetectionModel_DB::TextDetectionModel_DB(const Net& network) + : TextDetectionModel() +{ + impl = std::static_pointer_cast(makePtr(network)); +} + +TextDetectionModel_DB& TextDetectionModel_DB::setBinaryThreshold(float binaryThreshold) +{ + TextDetectionModel_DB_Impl::from(impl).setBinaryThreshold(binaryThreshold); + return *this; +} +float TextDetectionModel_DB::getBinaryThreshold() const +{ + return TextDetectionModel_DB_Impl::from(impl).getBinaryThreshold(); +} + +TextDetectionModel_DB& TextDetectionModel_DB::setPolygonThreshold(float polygonThreshold) +{ + TextDetectionModel_DB_Impl::from(impl).setPolygonThreshold(polygonThreshold); + return *this; +} +float TextDetectionModel_DB::getPolygonThreshold() const +{ + return TextDetectionModel_DB_Impl::from(impl).getPolygonThreshold(); +} + +TextDetectionModel_DB& TextDetectionModel_DB::setUnclipRatio(double unclipRatio) +{ + TextDetectionModel_DB_Impl::from(impl).setUnclipRatio(unclipRatio); + return *this; +} +double TextDetectionModel_DB::getUnclipRatio() const +{ + return TextDetectionModel_DB_Impl::from(impl).getUnclipRatio(); +} + +TextDetectionModel_DB& TextDetectionModel_DB::setMaxCandidates(int maxCandidates) +{ + TextDetectionModel_DB_Impl::from(impl).setMaxCandidates(maxCandidates); + return *this; +} +int TextDetectionModel_DB::getMaxCandidates() const +{ + return TextDetectionModel_DB_Impl::from(impl).getMaxCandidates(); +} + + }} // namespace diff --git a/modules/dnn/test/test_common.hpp b/modules/dnn/test/test_common.hpp index 3bc8fc3..ea6b3bd 100644 --- a/modules/dnn/test/test_common.hpp +++ b/modules/dnn/test/test_common.hpp @@ -113,6 +113,14 @@ void normAssertDetections( double confThreshold = 0.0, double scores_diff = 1e-5, double boxes_iou_diff = 1e-4); +// For text detection networks +// Curved text polygon is not supported in the current version. +// (concave polygon is invalid input to intersectConvexConvex) +void normAssertTextDetections( + const std::vector>& gtPolys, + const std::vector>& testPolys, + const char *comment = "", double boxes_iou_diff = 1e-4); + void readFileContent(const std::string& filename, CV_OUT std::vector& content); #ifdef HAVE_INF_ENGINE diff --git a/modules/dnn/test/test_common.impl.hpp b/modules/dnn/test/test_common.impl.hpp index cf1b558..4627e94 100644 --- a/modules/dnn/test/test_common.impl.hpp +++ b/modules/dnn/test/test_common.impl.hpp @@ -177,6 +177,52 @@ void normAssertDetections( testBoxes, comment, confThreshold, scores_diff, boxes_iou_diff); } +// For text detection networks +// Curved text polygon is not supported in the current version. +// (concave polygon is invalid input to intersectConvexConvex) +void normAssertTextDetections( + const std::vector>& gtPolys, + const std::vector>& testPolys, + const char *comment /*= ""*/, double boxes_iou_diff /*= 1e-4*/) +{ + std::vector matchedRefBoxes(gtPolys.size(), false); + for (uint i = 0; i < testPolys.size(); ++i) + { + const std::vector& testPoly = testPolys[i]; + bool matched = false; + double topIoU = 0; + for (uint j = 0; j < gtPolys.size() && !matched; ++j) + { + if (!matchedRefBoxes[j]) + { + std::vector intersectionPolygon; + float intersectArea = intersectConvexConvex(testPoly, gtPolys[j], intersectionPolygon, true); + double iou = intersectArea / (contourArea(testPoly) + contourArea(gtPolys[j]) - intersectArea); + topIoU = std::max(topIoU, iou); + if (1.0 - iou < boxes_iou_diff) + { + matched = true; + matchedRefBoxes[j] = true; + } + } + } + if (!matched) { + std::cout << cv::format("Unmatched-det:") << testPoly << std::endl; + std::cout << "Highest IoU: " << topIoU << std::endl; + } + EXPECT_TRUE(matched) << comment; + } + + // Check unmatched groundtruth. + for (uint i = 0; i < gtPolys.size(); ++i) + { + if (!matchedRefBoxes[i]) { + std::cout << cv::format("Unmatched-gt:") << gtPolys[i] << std::endl; + } + EXPECT_TRUE(matchedRefBoxes[i]); + } +} + void readFileContent(const std::string& filename, CV_OUT std::vector& content) { const std::ios::openmode mode = std::ios::in | std::ios::binary; diff --git a/modules/dnn/test/test_model.cpp b/modules/dnn/test/test_model.cpp index 58a8814..852ae00 100644 --- a/modules/dnn/test/test_model.cpp +++ b/modules/dnn/test/test_model.cpp @@ -113,6 +113,155 @@ public: model.segment(frame, mask); normAssert(mask, exp, "", norm, norm); } + + void testTextRecognitionModel(const std::string& weights, const std::string& cfg, + const std::string& imgPath, const std::string& seq, + const std::string& decodeType, const std::vector& vocabulary, + const Size& size = {-1, -1}, Scalar mean = Scalar(), + double scale = 1.0, bool swapRB = false, bool crop = false) + { + checkBackend(); + + Mat frame = imread(imgPath, IMREAD_GRAYSCALE); + + TextRecognitionModel model(weights, cfg); + model.setDecodeType(decodeType) + .setVocabulary(vocabulary) + .setInputSize(size).setInputMean(mean).setInputScale(scale) + .setInputSwapRB(swapRB).setInputCrop(crop); + + model.setPreferableBackend(backend); + model.setPreferableTarget(target); + + std::string result = model.recognize(frame); + EXPECT_EQ(result, seq) << "Full frame: " << imgPath; + + std::vector rois; + rois.push_back(Rect(0, 0, frame.cols, frame.rows)); + rois.push_back(Rect(0, 0, frame.cols, frame.rows)); // twice + std::vector results; + model.recognize(frame, rois, results); + EXPECT_EQ((size_t)2u, results.size()) << "ROI: " << imgPath; + EXPECT_EQ(results[0], seq) << "ROI[0]: " << imgPath; + EXPECT_EQ(results[1], seq) << "ROI[1]: " << imgPath; + } + + void testTextDetectionModelByDB(const std::string& weights, const std::string& cfg, + const std::string& imgPath, const std::vector>& gt, + float binThresh, float polyThresh, + uint maxCandidates, double unclipRatio, + const Size& size = {-1, -1}, Scalar mean = Scalar(), + double scale = 1.0, bool swapRB = false, bool crop = false) + { + checkBackend(); + + Mat frame = imread(imgPath); + + TextDetectionModel_DB model(weights, cfg); + model.setBinaryThreshold(binThresh) + .setPolygonThreshold(polyThresh) + .setUnclipRatio(unclipRatio) + .setMaxCandidates(maxCandidates) + .setInputSize(size).setInputMean(mean).setInputScale(scale) + .setInputSwapRB(swapRB).setInputCrop(crop); + + model.setPreferableBackend(backend); + model.setPreferableTarget(target); + + // 1. Check common TextDetectionModel API through RotatedRect + std::vector results; + model.detectTextRectangles(frame, results); + + EXPECT_GT(results.size(), (size_t)0); + + std::vector< std::vector > contours; + for (size_t i = 0; i < results.size(); i++) + { + const RotatedRect& box = results[i]; + Mat contour; + boxPoints(box, contour); + std::vector contour2i(4); + for (int i = 0; i < 4; i++) + { + contour2i[i].x = cvRound(contour.at(i, 0)); + contour2i[i].y = cvRound(contour.at(i, 1)); + } + contours.push_back(contour2i); + } +#if 0 // test debug + Mat result = frame.clone(); + drawContours(result, contours, -1, Scalar(0, 0, 255), 1); + imshow("result", result); // imwrite("result.png", result); + waitKey(0); +#endif + normAssertTextDetections(gt, contours, "", 0.05f); + + // 2. Check quadrangle-based API + // std::vector< std::vector > contours; + model.detect(frame, contours); + +#if 0 // test debug + Mat result = frame.clone(); + drawContours(result, contours, -1, Scalar(0, 0, 255), 1); + imshow("result_contours", result); // imwrite("result_contours.png", result); + waitKey(0); +#endif + normAssertTextDetections(gt, contours, "", 0.05f); + } + + void testTextDetectionModelByEAST(const std::string& weights, const std::string& cfg, + const std::string& imgPath, const std::vector& gt, + float confThresh, float nmsThresh, + const Size& size = {-1, -1}, Scalar mean = Scalar(), + double scale = 1.0, bool swapRB = false, bool crop = false) + { + const double EPS_PIXELS = 3; + + checkBackend(); + + Mat frame = imread(imgPath); + + TextDetectionModel_EAST model(weights, cfg); + model.setConfidenceThreshold(confThresh) + .setNMSThreshold(nmsThresh) + .setInputSize(size).setInputMean(mean).setInputScale(scale) + .setInputSwapRB(swapRB).setInputCrop(crop); + + model.setPreferableBackend(backend); + model.setPreferableTarget(target); + + std::vector results; + model.detectTextRectangles(frame, results); + + EXPECT_EQ(results.size(), (size_t)1); + for (size_t i = 0; i < results.size(); i++) + { + const RotatedRect& box = results[i]; +#if 0 // test debug + Mat contour; + boxPoints(box, contour); + std::vector contour2i(4); + for (int i = 0; i < 4; i++) + { + contour2i[i].x = cvRound(contour.at(i, 0)); + contour2i[i].y = cvRound(contour.at(i, 1)); + } + std::vector< std::vector > contours; + contours.push_back(contour2i); + + Mat result = frame.clone(); + drawContours(result, contours, -1, Scalar(0, 0, 255), 1); + imshow("result", result); //imwrite("result.png", result); + waitKey(0); +#endif + const RotatedRect& gtBox = gt[i]; + EXPECT_NEAR(box.center.x, gtBox.center.x, EPS_PIXELS); + EXPECT_NEAR(box.center.y, gtBox.center.y, EPS_PIXELS); + EXPECT_NEAR(box.size.width, gtBox.size.width, EPS_PIXELS); + EXPECT_NEAR(box.size.height, gtBox.size.height, EPS_PIXELS); + EXPECT_NEAR(box.angle, gtBox.angle, 1); + } + } }; TEST_P(Test_Model, Classify) @@ -446,6 +595,77 @@ TEST_P(Test_Model, Segmentation) testSegmentationModel(weights_file, config_file, inp, exp, norm, size, mean, scale, swapRB); } +TEST_P(Test_Model, TextRecognition) +{ + if (target == DNN_TARGET_OPENCL_FP16) + applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL_FP16); + + std::string imgPath = _tf("text_rec_test.png"); + std::string weightPath = _tf("onnx/models/crnn.onnx", false); + std::string seq = "welcome"; + + Size size{100, 32}; + double scale = 1.0 / 127.5; + Scalar mean = Scalar(127.5); + std::string decodeType = "CTC-greedy"; + std::vector vocabulary = {"0","1","2","3","4","5","6","7","8","9", + "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"}; + + testTextRecognitionModel(weightPath, "", imgPath, seq, decodeType, vocabulary, size, mean, scale); +} + +TEST_P(Test_Model, TextDetectionByDB) +{ + if (target == DNN_TARGET_OPENCL_FP16) + applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL_FP16); + + std::string imgPath = _tf("text_det_test1.png"); + std::string weightPath = _tf("onnx/models/DB_TD500_resnet50.onnx", false); + + // GroundTruth + std::vector> gt = { + { Point(142, 193), Point(136, 164), Point(213, 150), Point(219, 178) }, + { Point(136, 165), Point(122, 114), Point(319, 71), Point(330, 122) } + }; + + Size size{736, 736}; + double scale = 1.0 / 255.0; + Scalar mean = Scalar(122.67891434, 116.66876762, 104.00698793); + + float binThresh = 0.3; + float polyThresh = 0.5; + uint maxCandidates = 200; + double unclipRatio = 2.0; + + testTextDetectionModelByDB(weightPath, "", imgPath, gt, binThresh, polyThresh, maxCandidates, unclipRatio, size, mean, scale); +} + +TEST_P(Test_Model, TextDetectionByEAST) +{ + if (target == DNN_TARGET_OPENCL_FP16) + applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL_FP16); + + std::string imgPath = _tf("text_det_test2.jpg"); + std::string weightPath = _tf("frozen_east_text_detection.pb", false); + + // GroundTruth + std::vector gt = { + RotatedRect(Point2f(657.55f, 409.5f), Size2f(316.84f, 62.45f), -4.79) + }; + + // Model parameters + Size size{320, 320}; + double scale = 1.0; + Scalar mean = Scalar(123.68, 116.78, 103.94); + bool swapRB = true; + + // Detection algorithm parameters + float confThresh = 0.5; + float nmsThresh = 0.4; + + testTextDetectionModelByEAST(weightPath, "", imgPath, gt, confThresh, nmsThresh, size, mean, scale, swapRB); +} + INSTANTIATE_TEST_CASE_P(/**/, Test_Model, dnnBackendsAndTargets()); }} // namespace diff --git a/samples/data/alphabet_36.txt b/samples/data/alphabet_36.txt new file mode 100644 index 0000000..7104368 --- /dev/null +++ b/samples/data/alphabet_36.txt @@ -0,0 +1,36 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z diff --git a/samples/data/alphabet_94.txt b/samples/data/alphabet_94.txt new file mode 100644 index 0000000..87c6d67 --- /dev/null +++ b/samples/data/alphabet_94.txt @@ -0,0 +1,94 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +: +; +< += +> +? +@ +[ +\ +] +^ +_ +` +{ +| +} +~ diff --git a/samples/dnn/scene_text_detection.cpp b/samples/dnn/scene_text_detection.cpp new file mode 100644 index 0000000..5b8626c --- /dev/null +++ b/samples/dnn/scene_text_detection.cpp @@ -0,0 +1,151 @@ +#include +#include +#include + +#include +#include +#include + +using namespace cv; +using namespace cv::dnn; + +std::string keys = + "{ help h | | Print help message. }" + "{ inputImage i | | Path to an input image. Skip this argument to capture frames from a camera. }" + "{ modelPath mp | | Path to a binary .onnx file contains trained DB detector model. " + "Download links are provided in doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown}" + "{ inputHeight ih |736| image height of the model input. It should be multiple by 32.}" + "{ inputWidth iw |736| image width of the model input. It should be multiple by 32.}" + "{ binaryThreshold bt |0.3| Confidence threshold of the binary map. }" + "{ polygonThreshold pt |0.5| Confidence threshold of polygons. }" + "{ maxCandidate max |200| Max candidates of polygons. }" + "{ unclipRatio ratio |2.0| unclip ratio. }" + "{ evaluate e |false| false: predict with input images; true: evaluate on benchmarks. }" + "{ evalDataPath edp | | Path to benchmarks for evaluation. " + "Download links are provided in doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown}"; + +int main(int argc, char** argv) +{ + // Parse arguments + CommandLineParser parser(argc, argv, keys); + parser.about("Use this script to run the official PyTorch implementation (https://github.com/MhLiao/DB) of " + "Real-time Scene Text Detection with Differentiable Binarization (https://arxiv.org/abs/1911.08947)\n" + "The current version of this script is a variant of the original network without deformable convolution"); + if (argc == 1 || parser.has("help")) + { + parser.printMessage(); + return 0; + } + + float binThresh = parser.get("binaryThreshold"); + float polyThresh = parser.get("polygonThreshold"); + uint maxCandidates = parser.get("maxCandidate"); + String modelPath = parser.get("modelPath"); + double unclipRatio = parser.get("unclipRatio"); + int height = parser.get("inputHeight"); + int width = parser.get("inputWidth"); + + if (!parser.check()) + { + parser.printErrors(); + return 1; + } + + // Load the network + CV_Assert(!modelPath.empty()); + TextDetectionModel_DB detector(modelPath); + detector.setBinaryThreshold(binThresh) + .setPolygonThreshold(polyThresh) + .setUnclipRatio(unclipRatio) + .setMaxCandidates(maxCandidates); + + double scale = 1.0 / 255.0; + Size inputSize = Size(width, height); + Scalar mean = Scalar(122.67891434, 116.66876762, 104.00698793); + detector.setInputParams(scale, inputSize, mean); + + // Create a window + static const std::string winName = "TextDetectionModel"; + + if (parser.get("evaluate")) { + // for evaluation + String evalDataPath = parser.get("evalDataPath"); + CV_Assert(!evalDataPath.empty()); + String testListPath = evalDataPath + "/test_list.txt"; + std::ifstream testList; + testList.open(testListPath); + CV_Assert(testList.is_open()); + + // Create a window for showing groundtruth + static const std::string winNameGT = "GT"; + + String testImgPath; + while (std::getline(testList, testImgPath)) { + String imgPath = evalDataPath + "/test_images/" + testImgPath; + std::cout << "Image Path: " << imgPath << std::endl; + + Mat frame = imread(samples::findFile(imgPath), IMREAD_COLOR); + CV_Assert(!frame.empty()); + Mat src = frame.clone(); + + // Inference + std::vector> results; + detector.detect(frame, results); + + polylines(frame, results, true, Scalar(0, 255, 0), 2); + imshow(winName, frame); + + // load groundtruth + String imgName = testImgPath.substr(0, testImgPath.length() - 4); + String gtPath = evalDataPath + "/test_gts/" + imgName + ".txt"; + // std::cout << gtPath << std::endl; + std::ifstream gtFile; + gtFile.open(gtPath); + CV_Assert(gtFile.is_open()); + + std::vector> gts; + String gtLine; + while (std::getline(gtFile, gtLine)) { + size_t splitLoc = gtLine.find_last_of(','); + String text = gtLine.substr(splitLoc+1); + if ( text == "###\r" || text == "1") { + // ignore difficult instances + continue; + } + gtLine = gtLine.substr(0, splitLoc); + + std::regex delimiter(","); + std::vector v(std::sregex_token_iterator(gtLine.begin(), gtLine.end(), delimiter, -1), + std::sregex_token_iterator()); + std::vector loc; + std::vector pts; + for (auto && s : v) { + loc.push_back(atoi(s.c_str())); + } + for (size_t i = 0; i < loc.size() / 2; i++) { + pts.push_back(Point(loc[2 * i], loc[2 * i + 1])); + } + gts.push_back(pts); + } + polylines(src, gts, true, Scalar(0, 255, 0), 2); + imshow(winNameGT, src); + + waitKey(); + } + } else { + // Open an image file + CV_Assert(parser.has("inputImage")); + Mat frame = imread(samples::findFile(parser.get("inputImage"))); + CV_Assert(!frame.empty()); + + // Detect + std::vector> results; + detector.detect(frame, results); + + polylines(frame, results, true, Scalar(0, 255, 0), 2); + imshow(winName, frame); + waitKey(); + } + + return 0; +} diff --git a/samples/dnn/scene_text_recognition.cpp b/samples/dnn/scene_text_recognition.cpp new file mode 100644 index 0000000..29b1444 --- /dev/null +++ b/samples/dnn/scene_text_recognition.cpp @@ -0,0 +1,144 @@ +#include +#include + +#include +#include +#include + +using namespace cv; +using namespace cv::dnn; + +String keys = + "{ help h | | Print help message. }" + "{ inputImage i | | Path to an input image. Skip this argument to capture frames from a camera. }" + "{ modelPath mp | | Path to a binary .onnx file contains trained CRNN text recognition model. " + "Download links are provided in doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown}" + "{ RGBInput rgb |0| 0: imread with flags=IMREAD_GRAYSCALE; 1: imread with flags=IMREAD_COLOR. }" + "{ evaluate e |false| false: predict with input images; true: evaluate on benchmarks. }" + "{ evalDataPath edp | | Path to benchmarks for evaluation. " + "Download links are provided in doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown}" + "{ vocabularyPath vp | alphabet_36.txt | Path to recognition vocabulary. " + "Download links are provided in doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown}"; + +String convertForEval(String &input); + +int main(int argc, char** argv) +{ + // Parse arguments + CommandLineParser parser(argc, argv, keys); + parser.about("Use this script to run the PyTorch implementation of " + "An End-to-End Trainable Neural Network for Image-based SequenceRecognition and Its Application to Scene Text Recognition " + "(https://arxiv.org/abs/1507.05717)"); + if (argc == 1 || parser.has("help")) + { + parser.printMessage(); + return 0; + } + + String modelPath = parser.get("modelPath"); + String vocPath = parser.get("vocabularyPath"); + int imreadRGB = parser.get("RGBInput"); + + if (!parser.check()) + { + parser.printErrors(); + return 1; + } + + // Load the network + CV_Assert(!modelPath.empty()); + TextRecognitionModel recognizer(modelPath); + + // Load vocabulary + CV_Assert(!vocPath.empty()); + std::ifstream vocFile; + vocFile.open(samples::findFile(vocPath)); + CV_Assert(vocFile.is_open()); + String vocLine; + std::vector vocabulary; + while (std::getline(vocFile, vocLine)) { + vocabulary.push_back(vocLine); + } + recognizer.setVocabulary(vocabulary); + recognizer.setDecodeType("CTC-greedy"); + + // Set parameters + double scale = 1.0 / 127.5; + Scalar mean = Scalar(127.5, 127.5, 127.5); + Size inputSize = Size(100, 32); + recognizer.setInputParams(scale, inputSize, mean); + + if (parser.get("evaluate")) + { + // For evaluation + String evalDataPath = parser.get("evalDataPath"); + CV_Assert(!evalDataPath.empty()); + String gtPath = evalDataPath + "/test_gts.txt"; + std::ifstream evalGts; + evalGts.open(gtPath); + CV_Assert(evalGts.is_open()); + + String gtLine; + int cntRight=0, cntAll=0; + TickMeter timer; + timer.reset(); + + while (std::getline(evalGts, gtLine)) { + size_t splitLoc = gtLine.find_first_of(' '); + String imgPath = evalDataPath + '/' + gtLine.substr(0, splitLoc); + String gt = gtLine.substr(splitLoc+1); + + // Inference + Mat frame = imread(samples::findFile(imgPath), imreadRGB); + CV_Assert(!frame.empty()); + timer.start(); + std::string recognitionResult = recognizer.recognize(frame); + timer.stop(); + + if (gt == convertForEval(recognitionResult)) + cntRight++; + + cntAll++; + } + std::cout << "Accuracy(%): " << (double)(cntRight) / (double)(cntAll) << std::endl; + std::cout << "Average Inference Time(ms): " << timer.getTimeMilli() / (double)(cntAll) << std::endl; + } + else + { + // Create a window + static const std::string winName = "Input Cropped Image"; + + // Open an image file + CV_Assert(parser.has("inputImage")); + Mat frame = imread(samples::findFile(parser.get("inputImage")), imreadRGB); + CV_Assert(!frame.empty()); + + // Recognition + std::string recognitionResult = recognizer.recognize(frame); + + imshow(winName, frame); + std::cout << "Predition: '" << recognitionResult << "'" << std::endl; + waitKey(); + } + + return 0; +} + +// Convert the predictions to lower case, and remove other characters. +// Only for Evaluation +String convertForEval(String & input) +{ + String output; + for (uint i = 0; i < input.length(); i++){ + char ch = input[i]; + if ((int)ch >= 97 && (int)ch <= 122) { + output.push_back(ch); + } else if ((int)ch >= 65 && (int)ch <= 90) { + output.push_back((char)(ch + 32)); + } else { + continue; + } + } + + return output; +} diff --git a/samples/dnn/scene_text_spotting.cpp b/samples/dnn/scene_text_spotting.cpp new file mode 100644 index 0000000..548289d --- /dev/null +++ b/samples/dnn/scene_text_spotting.cpp @@ -0,0 +1,169 @@ +#include +#include + +#include +#include +#include + +using namespace cv; +using namespace cv::dnn; + +std::string keys = + "{ help h | | Print help message. }" + "{ inputImage i | | Path to an input image. Skip this argument to capture frames from a camera. }" + "{ detModelPath dmp | | Path to a binary .onnx model for detection. " + "Download links are provided in doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown}" + "{ recModelPath rmp | | Path to a binary .onnx model for recognition. " + "Download links are provided in doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown}" + "{ inputHeight ih |736| image height of the model input. It should be multiple by 32.}" + "{ inputWidth iw |736| image width of the model input. It should be multiple by 32.}" + "{ RGBInput rgb |0| 0: imread with flags=IMREAD_GRAYSCALE; 1: imread with flags=IMREAD_COLOR. }" + "{ binaryThreshold bt |0.3| Confidence threshold of the binary map. }" + "{ polygonThreshold pt |0.5| Confidence threshold of polygons. }" + "{ maxCandidate max |200| Max candidates of polygons. }" + "{ unclipRatio ratio |2.0| unclip ratio. }" + "{ vocabularyPath vp | alphabet_36.txt | Path to benchmarks for evaluation. " + "Download links are provided in doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown}"; + +void fourPointsTransform(const Mat& frame, const Point2f vertices[], Mat& result); +bool sortPts(const Point& p1, const Point& p2); + +int main(int argc, char** argv) +{ + // Parse arguments + CommandLineParser parser(argc, argv, keys); + parser.about("Use this script to run an end-to-end inference sample of textDetectionModel and textRecognitionModel APIs\n" + "Use -h for more information"); + if (argc == 1 || parser.has("help")) + { + parser.printMessage(); + return 0; + } + + float binThresh = parser.get("binaryThreshold"); + float polyThresh = parser.get("polygonThreshold"); + uint maxCandidates = parser.get("maxCandidate"); + String detModelPath = parser.get("detModelPath"); + String recModelPath = parser.get("recModelPath"); + String vocPath = parser.get("vocabularyPath"); + double unclipRatio = parser.get("unclipRatio"); + int height = parser.get("inputHeight"); + int width = parser.get("inputWidth"); + int imreadRGB = parser.get("RGBInput"); + + if (!parser.check()) + { + parser.printErrors(); + return 1; + } + + // Load networks + CV_Assert(!detModelPath.empty()); + TextDetectionModel_DB detector(detModelPath); + detector.setBinaryThreshold(binThresh) + .setPolygonThreshold(polyThresh) + .setUnclipRatio(unclipRatio) + .setMaxCandidates(maxCandidates); + + CV_Assert(!recModelPath.empty()); + TextRecognitionModel recognizer(recModelPath); + + // Load vocabulary + CV_Assert(!vocPath.empty()); + std::ifstream vocFile; + vocFile.open(samples::findFile(vocPath)); + CV_Assert(vocFile.is_open()); + String vocLine; + std::vector vocabulary; + while (std::getline(vocFile, vocLine)) { + vocabulary.push_back(vocLine); + } + recognizer.setVocabulary(vocabulary); + recognizer.setDecodeType("CTC-greedy"); + + // Parameters for Detection + double detScale = 1.0 / 255.0; + Size detInputSize = Size(width, height); + Scalar detMean = Scalar(122.67891434, 116.66876762, 104.00698793); + detector.setInputParams(detScale, detInputSize, detMean); + + // Parameters for Recognition + double recScale = 1.0 / 127.5; + Scalar recMean = Scalar(127.5); + Size recInputSize = Size(100, 32); + recognizer.setInputParams(recScale, recInputSize, recMean); + + // Create a window + static const std::string winName = "Text_Spotting"; + + // Input data + Mat frame = imread(samples::findFile(parser.get("inputImage"))); + std::cout << frame.size << std::endl; + + // Inference + std::vector< std::vector > detResults; + detector.detect(frame, detResults); + + if (detResults.size() > 0) { + // Text Recognition + Mat recInput; + if (!imreadRGB) { + cvtColor(frame, recInput, cv::COLOR_BGR2GRAY); + } else { + recInput = frame; + } + std::vector< std::vector > contours; + for (uint i = 0; i < detResults.size(); i++) + { + const auto& quadrangle = detResults[i]; + CV_CheckEQ(quadrangle.size(), (size_t)4, ""); + + contours.emplace_back(quadrangle); + + std::vector quadrangle_2f; + for (int j = 0; j < 4; j++) + quadrangle_2f.emplace_back(quadrangle[j]); + + // Transform and Crop + Mat cropped; + fourPointsTransform(recInput, &quadrangle_2f[0], cropped); + + std::string recognitionResult = recognizer.recognize(cropped); + std::cout << i << ": '" << recognitionResult << "'" << std::endl; + + putText(frame, recognitionResult, quadrangle[3], FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255), 2); + } + polylines(frame, contours, true, Scalar(0, 255, 0), 2); + } else { + std::cout << "No Text Detected." << std::endl; + } + imshow(winName, frame); + waitKey(); + + return 0; +} + +void fourPointsTransform(const Mat& frame, const Point2f vertices[], Mat& result) +{ + const Size outputSize = Size(100, 32); + + Point2f targetVertices[4] = { + Point(0, outputSize.height - 1), + Point(0, 0), + Point(outputSize.width - 1, 0), + Point(outputSize.width - 1, outputSize.height - 1) + }; + Mat rotationMatrix = getPerspectiveTransform(vertices, targetVertices); + + warpPerspective(frame, result, rotationMatrix, outputSize); + +#if 0 + imshow("roi", result); + waitKey(); +#endif +} + +bool sortPts(const Point& p1, const Point& p2) +{ + return p1.x < p2.x; +} diff --git a/samples/dnn/text_detection.cpp b/samples/dnn/text_detection.cpp index e1314a7..76989dc 100644 --- a/samples/dnn/text_detection.cpp +++ b/samples/dnn/text_detection.cpp @@ -2,22 +2,23 @@ Text detection model: https://github.com/argman/EAST Download link: https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_detection.tar.gz?dl=1 - CRNN Text recognition model taken from here: https://github.com/meijieru/crnn.pytorch - How to convert from pb to onnx: - Using classes from here: https://github.com/meijieru/crnn.pytorch/blob/master/models/crnn.py - - More converted onnx text recognition models can be downloaded directly here: + Text recognition models can be downloaded directly here: Download link: https://drive.google.com/drive/folders/1cTbQ3nuZG-EKWak6emD_s8_hHXWz7lAr?usp=sharing - And these models taken from here:https://github.com/clovaai/deep-text-recognition-benchmark + and doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown + How to convert from pb to onnx: + Using classes from here: https://github.com/meijieru/crnn.pytorch/blob/master/models/crnn.py import torch from models.crnn import CRNN - model = CRNN(32, 1, 37, 256) model.load_state_dict(torch.load('crnn.pth')) dummy_input = torch.randn(1, 1, 32, 100) torch.onnx.export(model, dummy_input, "crnn.onnx", verbose=True) + + For more information, please refer to doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown and doc/tutorials/dnn/dnn_OCR/dnn_OCR.markdown */ +#include +#include #include #include @@ -27,21 +28,20 @@ using namespace cv; using namespace cv::dnn; const char* keys = - "{ help h | | Print help message. }" - "{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera.}" - "{ model m | | Path to a binary .pb file contains trained detector network.}" - "{ ocr | | Path to a binary .pb or .onnx file contains trained recognition network.}" - "{ width | 320 | Preprocess input image by resizing to a specific width. It should be multiple by 32. }" - "{ height | 320 | Preprocess input image by resizing to a specific height. It should be multiple by 32. }" - "{ thr | 0.5 | Confidence threshold. }" - "{ nms | 0.4 | Non-maximum suppression threshold. }"; - -void decodeBoundingBoxes(const Mat& scores, const Mat& geometry, float scoreThresh, - std::vector& detections, std::vector& confidences); - -void fourPointsTransform(const Mat& frame, Point2f vertices[4], Mat& result); - -void decodeText(const Mat& scores, std::string& text); + "{ help h | | Print help message. }" + "{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera.}" + "{ detModel dmp | | Path to a binary .pb file contains trained detector network.}" + "{ width | 320 | Preprocess input image by resizing to a specific width. It should be multiple by 32. }" + "{ height | 320 | Preprocess input image by resizing to a specific height. It should be multiple by 32. }" + "{ thr | 0.5 | Confidence threshold. }" + "{ nms | 0.4 | Non-maximum suppression threshold. }" + "{ recModel rmp | | Path to a binary .onnx file contains trained CRNN text recognition model. " + "Download links are provided in doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown}" + "{ RGBInput rgb |0| 0: imread with flags=IMREAD_GRAYSCALE; 1: imread with flags=IMREAD_COLOR. }" + "{ vocabularyPath vp | alphabet_36.txt | Path to benchmarks for evaluation. " + "Download links are provided in doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown}"; + +void fourPointsTransform(const Mat& frame, const Point2f vertices[], Mat& result); int main(int argc, char** argv) { @@ -57,10 +57,12 @@ int main(int argc, char** argv) float confThreshold = parser.get("thr"); float nmsThreshold = parser.get("nms"); - int inpWidth = parser.get("width"); - int inpHeight = parser.get("height"); - String modelDecoder = parser.get("model"); - String modelRecognition = parser.get("ocr"); + int width = parser.get("width"); + int height = parser.get("height"); + int imreadRGB = parser.get("RGBInput"); + String detModelPath = parser.get("detModel"); + String recModelPath = parser.get("recModel"); + String vocPath = parser.get("vocabularyPath"); if (!parser.check()) { @@ -68,14 +70,39 @@ int main(int argc, char** argv) return 1; } - CV_Assert(!modelDecoder.empty()); - // Load networks. - Net detector = readNet(modelDecoder); - Net recognizer; - - if (!modelRecognition.empty()) - recognizer = readNet(modelRecognition); + CV_Assert(!detModelPath.empty() && !recModelPath.empty()); + TextDetectionModel_EAST detector(detModelPath); + detector.setConfidenceThreshold(confThreshold) + .setNMSThreshold(nmsThreshold); + + TextRecognitionModel recognizer(recModelPath); + + // Load vocabulary + CV_Assert(!vocPath.empty()); + std::ifstream vocFile; + vocFile.open(samples::findFile(vocPath)); + CV_Assert(vocFile.is_open()); + String vocLine; + std::vector vocabulary; + while (std::getline(vocFile, vocLine)) { + vocabulary.push_back(vocLine); + } + recognizer.setVocabulary(vocabulary); + recognizer.setDecodeType("CTC-greedy"); + + // Parameters for Recognition + double recScale = 1.0 / 127.5; + Scalar recMean = Scalar(127.5, 127.5, 127.5); + Size recInputSize = Size(100, 32); + recognizer.setInputParams(recScale, recInputSize, recMean); + + // Parameters for Detection + double detScale = 1.0; + Size detInputSize = Size(width, height); + Scalar detMean = Scalar(123.68, 116.78, 103.94); + bool swapRB = true; + detector.setInputParams(detScale, detInputSize, detMean, swapRB); // Open a video file or an image file or a camera stream. VideoCapture cap; @@ -83,15 +110,8 @@ int main(int argc, char** argv) CV_Assert(openSuccess); static const std::string kWinName = "EAST: An Efficient and Accurate Scene Text Detector"; - namedWindow(kWinName, WINDOW_NORMAL); - - std::vector outs; - std::vector outNames(2); - outNames[0] = "feature_fusion/Conv_7/Sigmoid"; - outNames[1] = "feature_fusion/concat_3"; - Mat frame, blob; - TickMeter tickMeter; + Mat frame; while (waitKey(1) < 0) { cap >> frame; @@ -101,162 +121,57 @@ int main(int argc, char** argv) break; } - blobFromImage(frame, blob, 1.0, Size(inpWidth, inpHeight), Scalar(123.68, 116.78, 103.94), true, false); - detector.setInput(blob); - tickMeter.start(); - detector.forward(outs, outNames); - tickMeter.stop(); + std::cout << frame.size << std::endl; - Mat scores = outs[0]; - Mat geometry = outs[1]; + // Detection + std::vector< std::vector > detResults; + detector.detect(frame, detResults); - // Decode predicted bounding boxes. - std::vector boxes; - std::vector confidences; - decodeBoundingBoxes(scores, geometry, confThreshold, boxes, confidences); - - // Apply non-maximum suppression procedure. - std::vector indices; - NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices); - - Point2f ratio((float)frame.cols / inpWidth, (float)frame.rows / inpHeight); - - // Render text. - for (size_t i = 0; i < indices.size(); ++i) - { - RotatedRect& box = boxes[indices[i]]; - - Point2f vertices[4]; - box.points(vertices); - - for (int j = 0; j < 4; ++j) - { - vertices[j].x *= ratio.x; - vertices[j].y *= ratio.y; + if (detResults.size() > 0) { + // Text Recognition + Mat recInput; + if (!imreadRGB) { + cvtColor(frame, recInput, cv::COLOR_BGR2GRAY); + } else { + recInput = frame; } - - if (!modelRecognition.empty()) + std::vector< std::vector > contours; + for (uint i = 0; i < detResults.size(); i++) { - Mat cropped; - fourPointsTransform(frame, vertices, cropped); + const auto& quadrangle = detResults[i]; + CV_CheckEQ(quadrangle.size(), (size_t)4, ""); - cvtColor(cropped, cropped, cv::COLOR_BGR2GRAY); + contours.emplace_back(quadrangle); - Mat blobCrop = blobFromImage(cropped, 1.0/127.5, Size(), Scalar::all(127.5)); - recognizer.setInput(blobCrop); + std::vector quadrangle_2f; + for (int j = 0; j < 4; j++) + quadrangle_2f.emplace_back(quadrangle[j]); - tickMeter.start(); - Mat result = recognizer.forward(); - tickMeter.stop(); + Mat cropped; + fourPointsTransform(recInput, &quadrangle_2f[0], cropped); - std::string wordRecognized = ""; - decodeText(result, wordRecognized); - putText(frame, wordRecognized, vertices[1], FONT_HERSHEY_SIMPLEX, 1.5, Scalar(0, 0, 255)); - } + std::string recognitionResult = recognizer.recognize(cropped); + std::cout << i << ": '" << recognitionResult << "'" << std::endl; - for (int j = 0; j < 4; ++j) - line(frame, vertices[j], vertices[(j + 1) % 4], Scalar(0, 255, 0), 1); + putText(frame, recognitionResult, quadrangle[3], FONT_HERSHEY_SIMPLEX, 1.5, Scalar(0, 0, 255), 2); + } + polylines(frame, contours, true, Scalar(0, 255, 0), 2); } - - // Put efficiency information. - std::string label = format("Inference time: %.2f ms", tickMeter.getTimeMilli()); - putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0)); - imshow(kWinName, frame); - - tickMeter.reset(); } return 0; } -void decodeBoundingBoxes(const Mat& scores, const Mat& geometry, float scoreThresh, - std::vector& detections, std::vector& confidences) -{ - detections.clear(); - CV_Assert(scores.dims == 4); CV_Assert(geometry.dims == 4); CV_Assert(scores.size[0] == 1); - CV_Assert(geometry.size[0] == 1); CV_Assert(scores.size[1] == 1); CV_Assert(geometry.size[1] == 5); - CV_Assert(scores.size[2] == geometry.size[2]); CV_Assert(scores.size[3] == geometry.size[3]); - - const int height = scores.size[2]; - const int width = scores.size[3]; - for (int y = 0; y < height; ++y) - { - const float* scoresData = scores.ptr(0, 0, y); - const float* x0_data = geometry.ptr(0, 0, y); - const float* x1_data = geometry.ptr(0, 1, y); - const float* x2_data = geometry.ptr(0, 2, y); - const float* x3_data = geometry.ptr(0, 3, y); - const float* anglesData = geometry.ptr(0, 4, y); - for (int x = 0; x < width; ++x) - { - float score = scoresData[x]; - if (score < scoreThresh) - continue; - - // Decode a prediction. - // Multiple by 4 because feature maps are 4 time less than input image. - float offsetX = x * 4.0f, offsetY = y * 4.0f; - float angle = anglesData[x]; - float cosA = std::cos(angle); - float sinA = std::sin(angle); - float h = x0_data[x] + x2_data[x]; - float w = x1_data[x] + x3_data[x]; - - Point2f offset(offsetX + cosA * x1_data[x] + sinA * x2_data[x], - offsetY - sinA * x1_data[x] + cosA * x2_data[x]); - Point2f p1 = Point2f(-sinA * h, -cosA * h) + offset; - Point2f p3 = Point2f(-cosA * w, sinA * w) + offset; - RotatedRect r(0.5f * (p1 + p3), Size2f(w, h), -angle * 180.0f / (float)CV_PI); - detections.push_back(r); - confidences.push_back(score); - } - } -} - -void fourPointsTransform(const Mat& frame, Point2f vertices[4], Mat& result) +void fourPointsTransform(const Mat& frame, const Point2f vertices[], Mat& result) { const Size outputSize = Size(100, 32); - Point2f targetVertices[4] = {Point(0, outputSize.height - 1), - Point(0, 0), Point(outputSize.width - 1, 0), - Point(outputSize.width - 1, outputSize.height - 1), - }; + Point2f targetVertices[4] = { + Point(0, outputSize.height - 1), + Point(0, 0), Point(outputSize.width - 1, 0), + Point(outputSize.width - 1, outputSize.height - 1) + }; Mat rotationMatrix = getPerspectiveTransform(vertices, targetVertices); warpPerspective(frame, result, rotationMatrix, outputSize); } - -void decodeText(const Mat& scores, std::string& text) -{ - static const std::string alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"; - Mat scoresMat = scores.reshape(1, scores.size[0]); - - std::vector elements; - elements.reserve(scores.size[0]); - - for (int rowIndex = 0; rowIndex < scoresMat.rows; ++rowIndex) - { - Point p; - minMaxLoc(scoresMat.row(rowIndex), 0, 0, 0, &p); - if (p.x > 0 && static_cast(p.x) <= alphabet.size()) - { - elements.push_back(alphabet[p.x - 1]); - } - else - { - elements.push_back('-'); - } - } - - if (elements.size() > 0 && elements[0] != '-') - text += elements[0]; - - for (size_t elementIndex = 1; elementIndex < elements.size(); ++elementIndex) - { - if (elementIndex > 0 && elements[elementIndex] != '-' && - elements[elementIndex - 1] != elements[elementIndex]) - { - text += elements[elementIndex]; - } - } -} \ No newline at end of file -- 2.7.4