From eba58cee4cc1573a0bbef071370697f016992b32 Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Tue, 23 Aug 2016 19:08:28 +0200 Subject: [PATCH 01/16] This commit contains all significant contributions until 23/8/2016 Implemented ImagePreprocessor a geneology of functors for preprocessing samples before they are fed to a CNN. Implemented TextImageClassifer as an abstraction of any object that provides a vector of probabillities given an image. A gluing class providing compatibillity to callbacks for OCRBeamsearchDecoder allows to plug any such object to the existing algorithms. Implemented DeepCNN a class that is supposed to load pretrained caffe models. Although several backends have been planed the only fully ope- rational one is the one employing caffe its self. GPU is supported. Implemented a class using a DeepCNN that implements word-spotting Implemented a python and a C++ demo demonstraiting the WorSpotting Class Implemented a class that generates sythetic text samples at high speeds and with a full pipeline of realistic distortions for training Word-spotting CNN's who need millios of samples. Implemented a python demo demonstraiting the usage of the text synthesizer --- modules/text/CMakeLists.txt | 79 ++- modules/text/FindCaffe.cmake | 14 + modules/text/FindGlog.cmake | 10 + modules/text/FindProtobuf.cmake | 10 + modules/text/FindQT5.cmake | 14 + modules/text/doc/DeepCNN_classdiagram.pdf | Bin 0 -> 40002 bytes modules/text/include/opencv2/text/ocr.hpp | 373 +++++++++- .../include/opencv2/text/text_synthesizer.hpp | 139 ++++ .../text/samples/cropped_word_recognition.cpp | 11 +- modules/text/samples/dictnet_demo.cpp | 95 +++ modules/text/samples/dictnet_demo.py | 83 +++ modules/text/samples/text_synthesiser.py | 67 ++ modules/text/src/ocr_beamsearch_decoder.cpp | 177 ++++- modules/text/src/ocr_holistic.cpp | 639 ++++++++++++++++++ modules/text/src/text_synthesizer.cpp | 571 ++++++++++++++++ modules/text/text_config.hpp.in | 8 +- 16 files changed, 2266 insertions(+), 24 deletions(-) create mode 100644 modules/text/FindCaffe.cmake create mode 100755 modules/text/FindGlog.cmake create mode 100755 modules/text/FindProtobuf.cmake create mode 100644 modules/text/FindQT5.cmake create mode 100644 modules/text/doc/DeepCNN_classdiagram.pdf create mode 100644 modules/text/include/opencv2/text/text_synthesizer.hpp create mode 100644 modules/text/samples/dictnet_demo.cpp create mode 100644 modules/text/samples/dictnet_demo.py create mode 100644 modules/text/samples/text_synthesiser.py create mode 100644 modules/text/src/ocr_holistic.cpp create mode 100644 modules/text/src/text_synthesizer.cpp diff --git a/modules/text/CMakeLists.txt b/modules/text/CMakeLists.txt index 173e87f0677..b7210eb2143 100644 --- a/modules/text/CMakeLists.txt +++ b/modules/text/CMakeLists.txt @@ -1,5 +1,5 @@ set(the_description "Text Detection and Recognition") -ocv_define_module(text opencv_ml opencv_highgui opencv_imgproc opencv_core opencv_features2d WRAP python) +ocv_define_module(text opencv_ml opencv_highgui opencv_imgproc opencv_core opencv_features2d opencv_calib3d WRAP python) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}) @@ -23,3 +23,80 @@ endif() if(${Tesseract_FOUND}) target_link_libraries(opencv_text ${Tesseract_LIBS}) endif() + +#Principal source from which adaptation came is the cnn_3dobj module +find_package(Caffe) + +if(Caffe_FOUND) + message(STATUS "Caffe: YES") + set(HAVE_CAFFE 1) +else() + message(STATUS "Caffe: NO") +endif() + +find_package(Protobuf) +if(Protobuf_FOUND) + message(STATUS "Protobuf: YES") + set(HAVE_PROTOBUF 1) +else() + message(STATUS "Protobuf: NO") +endif() + +find_package(Glog) +if(Glog_FOUND) + message(STATUS "Glog: YES") + set(HAVE_GLOG 1) +else() + message(STATUS "Glog: NO") +endif() + +if(HAVE_CAFFE) +message(STATUS "HAVE CAFFE!!!") +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/include/opencv2/text_config.hpp @ONLY) + + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +if(${Caffe_FOUND}) + + include_directories(${Caffe_INCLUDE_DIR}) + #taken from caffe's cmake + find_package(HDF5 COMPONENTS HL REQUIRED) + include_directories(SYSTEM ${HDF5_INCLUDE_DIRS} ${HDF5_HL_INCLUDE_DIR}) + list(APPEND Caffe_LINKER_LIBS ${HDF5_LIBRARIES}) + find_package(Boost 1.46 REQUIRED COMPONENTS system thread filesystem) + include_directories(SYSTEM ${Boost_INCLUDE_DIR}) + include_directories(SYSTEM /usr/local/cuda-7.5/targets/x86_64-linux/include/) + list(APPEND Caffe_LINKER_LIBS ${Boost_LIBRARIES}) + +endif() + + +if(${Caffe_FOUND}) + #taken from caffe's cmake + target_link_libraries(opencv_text ${Caffe_LIBS} ${Glog_LIBS} ${Protobuf_LIBS} ${HDF5_LIBRARIES} ${Boost_LIBRARIES}) +endif() +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in + ${CMAKE_BINARY_DIR}/text_config.hpp @ONLY) + +find_package(Qt5Gui) +if(Qt5Gui_FOUND) + message(STATUS "text module found Qt5Gui: YES") + set(HAVE_QT5GUI 1) + #foreach(dt5_dep Core Gui Widgets Test Concurrent) + foreach(dt5_dep Gui) + add_definitions(${Qt5${dt5_dep}_DEFINITIONS}) + include_directories(${Qt5${dt5_dep}_INCLUDE_DIRS}) + #list(APPEND HIGHGUI_LIBRARIES ${Qt5${dt5_dep}_LIBRARIES}) + target_link_libraries(opencv_text ${Qt5${dt5_dep}_LIBRARIES}) + endforeach() + +else() + message(STATUS "text module found Qt5Gui: NO") +endif() + + + diff --git a/modules/text/FindCaffe.cmake b/modules/text/FindCaffe.cmake new file mode 100644 index 00000000000..12948f62992 --- /dev/null +++ b/modules/text/FindCaffe.cmake @@ -0,0 +1,14 @@ +# Caffe package for CNN Triplet training +unset(Caffe_FOUND) + +find_path(Caffe_INCLUDE_DIR NAMES caffe/caffe.hpp caffe/common.hpp caffe/net.hpp caffe/proto/caffe.pb.h caffe/util/io.hpp caffe/vision_layers.hpp + HINTS + /usr/local/include) + +find_library(Caffe_LIBS NAMES caffe + HINTS + /usr/local/lib) + +if(Caffe_LIBS AND Caffe_INCLUDE_DIR) + set(Caffe_FOUND 1) +endif() diff --git a/modules/text/FindGlog.cmake b/modules/text/FindGlog.cmake new file mode 100755 index 00000000000..c30e9f4a6ab --- /dev/null +++ b/modules/text/FindGlog.cmake @@ -0,0 +1,10 @@ +#Required for Caffe +unset(Glog_FOUND) + +find_library(Glog_LIBS NAMES glog + HINTS + /usr/local/lib) + +if(Glog_LIBS) + set(Glog_FOUND 1) +endif() diff --git a/modules/text/FindProtobuf.cmake b/modules/text/FindProtobuf.cmake new file mode 100755 index 00000000000..6d0ad56a1f7 --- /dev/null +++ b/modules/text/FindProtobuf.cmake @@ -0,0 +1,10 @@ +#Protobuf package required for Caffe +unset(Protobuf_FOUND) + +find_library(Protobuf_LIBS NAMES protobuf + HINTS + /usr/local/lib) + +if(Protobuf_LIBS) + set(Protobuf_FOUND 1) +endif() diff --git a/modules/text/FindQT5.cmake b/modules/text/FindQT5.cmake new file mode 100644 index 00000000000..37f6346de06 --- /dev/null +++ b/modules/text/FindQT5.cmake @@ -0,0 +1,14 @@ +#Caffe +unset(QT5_FOUND) + +find_path(QT5_INCLUDE_DIR NAMES qt5/QtGui/QFontMetrics qt5/QtGui/QFont qt5/QtGui/QFontDatabase qt5/QtGui/QGuiApplication + HINTS + /usr/local/include) + +find_library(Caffe_LIBS NAMES caffe + HINTS + /usr/local/lib) + +if(Caffe_LIBS AND Caffe_INCLUDE_DIR) + set(Caffe_FOUND 1) +endif() diff --git a/modules/text/doc/DeepCNN_classdiagram.pdf b/modules/text/doc/DeepCNN_classdiagram.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ac94773299dea54a963c207c7bfdd17456ac9f22 GIT binary patch literal 40002 zcma%i1C%9CvuA6jd)l^b+qP}noW5<_w!5co+qP}Hd)nTa|9-pYy|>@l_wK1vRdKVj zBF--|BcdWBN#un^X&7i(AW3Ex23H`N@#*pH3@stKxgqJKO>E7a&GDJ(@&EOMq!YEU zb~bUurxUd{a5fP(F|so@f#l_dbaHkyF|dIItWg~>D$@fA3VqW-azgs1LxcoH6m+0X z>ou({^ex4+ik}w9DfRm`q=~KZzqo|MLU`B%Qpnh&t^bkxXO_Y)tUUY3YP549px2Y#33)h=&fW+Lli zWB5nx|I~G{U3sniJgg-gNcovo*kd*&xMxhFTtP# zNhjuL=VFh~^1nMMnmF0HI2xHa;s3=+1mG;D7RQBe1^ZQ{}K#;8W{d!@t0uyOECV6VERii{fl7!OZ?T8 z;y9dRFDm0fdcYBh_)iL|*_%oz*apS>Q;i3;8*(r6rpb2pMRL0BV_ zH;*}YD9Hc|cruckAoTQ6W* z555;{5>#@AqwU+U?Pb{8$!pk>-wYxYEqo&;RaKlSrc!9QRz;*eSLZ8+R|RXpiLMOj zP>sLI96;->Fl<1TsuX=sS=_qBRJh=j6VSy@Kn9kdO6xyeaH>A1@NRc&ibK0!uabH^ z`aFJb(;BW?t_v@CVqs*f8_m@H_|U*pbAwxovSFtT^m&_P_eFe!r3vQ{H4QueE)_;n5ljirKy3l%5B;G_^`al*f7SNkrV< zpR;B@MvWhIw`iY!9wz^UYqTq1?0M|83@P4wiydTJB@MsxOh}^hCa**L1ZvmIdK88V z$+6>VIpvml<*~urE-0BwU`}hz*aZG*&!Ql}zNAScW(Mouv^2j4w3Z7Qzs*<*6Ler! z0~6PmO8MfJ@y>MA-zNH8x*FB{urxx z8Q&`>G5kCnuU?VSxJffelE7S?_GRhcqVR@C)Ct^9pe!ohl*sF)PCu(4Yq1nF) z=|^%OxYi(;5<&)AA6v8JOIi{U;KCIUa;TtVa z0`aYZvj`eLP!#S34zrLIMwndt1#7PG4HRN}pT4sr14RQJC7N>i=H0X$jTeL!5X;V~ zc~*-QtI{ZyM_-?tPCkQCAE2B-1j!C6FC!fQQGhs>Wm~^}lEU7_;qhb(uf;Zt32zRs zi*p$Rue(2=>|W;hLnF6>C-lnL<3rPNqQoGP5AdN@Kp#=%BctkoS>*>DPA!o#d_Kvo zYlG$L=spcn1tJ)(S$&=B9j332wVL`keMQSDTZ1fZ-xg6t+RWqrNn0h>tA0wW0SqKb ztzSFTgRwru2n{*ALSA&xFTSMHxU2pGKo#}rmOW+>;&PsUGwLA>gDcF(#C6ti<+E3b zB)>QivZ1`a)L2HAgtxLE(MSW`iFJx)uR>G%r7{f0W|{btQ~({!&{9eM(jzxqe}*iy zVe>N09|2w#???n%$ZtIYW4N&bOQAj-s@P)VaoOw2Q-v4RKd!0p=MJRFX4n#{#Q2&n zL4kftphD3w2o@%~wrVfX{SeP-SLL@{qADv)V5Ksf6!ygSYM41YWkX57X=4^}tQE`> z!`R#)Z1_G0PUY}b%s?*x$B)&)>kO56G>Ll)=|%H|NxD7rdTE_wTuNIjY!o^*dT;4< zRbCVd@Xax@c}$^UPWg3^fIgd6W8l&bmeZf7yi{_8G3N@+7^1Es#)Hovh^`)lBDt|> zqujCuE%72^QTo&&+_yC)op_5=neX7*M>`6w@4zw%M+(Us$Un$mBy=ap66|UdIOrr3 zi^a_0D7FJ0yXp~NPiF;?o^vwDGdC;yE<7kQ-}0@B*Wa#Hk+&B&I`6l+u$L$KEKy}~ z9euyEeR6Vm1xuYJwHL{cr?dSMa<(B`B!MTs$CGsrwDD5fPVe7TWPEUNdD+8N!zSM* zX%OF`H0Ezm*gxcQ;U?2C!h}o!B|#_B&=Xm9JsDV&f_LEZpDb^YbExZ}&0cWy*er0^ zgnzBiksE96V1@a1QWBfGg<$=JyY6o_ZWJ>P9lS~EOiR(yEko5YR4xPB3pvhz`8(U9 zqEj0DSb%&T>E1iVG!$+tYBDw|R7 zw00X9_U513sfj&&C9U7Pd#@zuKzZqbcI7GKE-~ji6gtp# zdR&@;Wly}RDs2ba$Tjf|9v{J%In&)qTF$n#>E>Nu21=uffI4Dh1*?9YC%H617rb8a zBn*Q}MEFA{6dUy^ry|GvP$I}=A+&@&4lIq;HS3ISnP`brH>S?-ntJ8@O?gK-HPIpN zne7(NB$D((N#nE5_$fN^mL1dPE;m~!CXqJ4969*b-a06v_Ua9+*AUVFZ=n7Q>wkfo z>7QuhzmF4iVy7+p>7j?Ny&-Ydg&gLcAR@2g`#)!_;dWLWP0fu9*h4x#jM<#&{b-22 z`0g}b*Sa5(8BgS*fA&E0#u{nwjiDM0P{dl&?)Xo7sMv936gKqqHyLMo{8nhBImv)d znZUx<)yBRp0pj=dVyIG}+ zehpqv99f90s`+~%&>)Ru!|s&qHs!0LG~En8R%#e$1DZQYyP3ZfRai27lTWS2f+A%W>1+~OVfO2XtwbF2xo*q$ z-iM>=8G?#SQ`p(bz2?yMTVlfUZdGIHz}~f_b~R7Z@pjlg*y4319SZA3Ha5K|KrLGE zu7~hVi&^8?oxjG=GTSw=ymitr|8}A4f92F3Va0q0kbCmlJc+@+;d^hZ)yd|#)+D9~ zF1w5<-?El$BS1f{;G>1lxhMMNpm4v6-8Wwk4)Z=0=oC zF3rD%?}qeD>z!L*9@C-w+x_5oI@C4oSo_^cYf3ga0Ai0IFGRv4dWF0eb}bBc zMxX)wItVTmT-P{ui&Rup()7zq^{!GCSLuV_nb(A|IBlyt&qaeo(&r1fOLv3i-wc3% z^IgmI*A4yo`ulHPPW0k(L**Z8rePOa?WR26!dWu25 z@>y?oOpXS6@ZJL7lef0pEv^|;&)DXPO`u7aDCj$M%4~hv(NE@co1`z(IX@H%z)CX8 zudVhpioriKyS2h$znzRLXV=eRDx%q(Ao{b*7&K-vCMGuxUEY=esD3-94#F- ze4v>;O24=^F-lw&N@XlT9f!3?>Y!vQ8%&{LCQHh2iukY;*Db^gk2e#dP6#Dn20v05 zQ31+;uY~L8hFP|#Pvb!R5&8mi$TqT|7U3l^M#tbI)XCh6Hm9|qRkmnIQ#z`54ArrT ztV=TW&P(JJ#Slft7s-v5IdZc5`Lhq3p3Bx_L<3C!(`ev7*xk0!3w@BN zzjikY?LXPw)_>XEJB+OfZvAkfyNy7DxIvH1jX2a*xLz?%xs(&}pU#;6E99r(%^O|6 zyRp_akSSgOJao*gc{##5<9kFl*(rdeaxy-bd;G%0L9Kh_5c~M@>V5>N;c@P-8F7QS zqLu5SV%{VVi{s^=#2N<&F8lMJHQLJ*mvXmmSrTRMoqi;2Zk*GV1uMZwVQth;byshl zxjSE%Dq4lkoo}`UB`PU}CTad9m1CLyC6la9kYQBzgBt-Jrcg~2V5CPlfMJ$6D-Jay zox8T%8Nd+?pdS-3bJ1&gV7bICc$cc~hf+_IN0GL>T4#1CKTJ}!HbGJ7azD2*-WOxS zhj~2rqW!%S`biLH&;{9@;>u)avW5++)5PnW*WfG7RM%m>T1!s&wU|J)x}Cjl!Zmg) z>63pm@-u>6_2BfVlWPu=YZyb6IPMfT7B`Ae?%Vs+{Dj8K6VwcL@9n=)DBHg=w*RC% z{{3A0SDuLhpOv1G`9EfpGB?$s?3G2@KHuIq%}l4H{~n}2B#w9p7+NfejHsMu>d5ClDi_MF+%sLPKdCy>g8;mPHiU%vEpwVyG(A5Og4^tu{woI-ge< zoUeRHfoCb3&$ilBv|QTfKU|+qo!Z{szP{c(K%jw`&4B}FGQZWM;;7=j!}u0_<8QT` z`dvgIHedS_=y({YqTE*D!+)CXqZ5jMThwk-m(Ry!lP1RJF=t`xhd$qm{bYrv_x6Y@ z@P-Jp>KZ@j)HU_tLUoaMg+cEeicv)3bg2*2gV>ZFyBTWF;c)4;#K#@#gl27MwF0<8 zcgSm=h*Xt^^M_t@GcdprXn#l($kpsZ;xhZ*WvX~ZLz-M0Yz!lo|H$Mz++SOW^f)^7SsIH zZQpyXhC5KBlOC7N;I6kosL`G4P8~(k4@npjVXazKj<7sLjVN=B2*>_LrXL5{TVOxL zzyGsly=74}Sjj*Mf!(Z}Ibu?B{)bPw(8?w32MyB77;JNxeVfMW#F(r)ANbF{7R_s7 z2ywP!9J(p;>-p%1@p;VG+K&R_N{kdp=T-kE&rl;jO>?YZB>VWvZ)_{r=_2)}_Bl7D zFR|0$W#5to&p93y(fyG0xifm`l*v{|&5geAR z-hF8{d-JWclvl*vJVHDrs#HZO6ked)pBIXYA8S_~sdnYR@h>%_)afTMtW~9nso@9$ z>N~WJ-7rw#H8i#$t83ouZNF=orsb~b#5R95x zVFGz^(J$B+T*1;^HExIzZXk7FC6qZ%^3=hHgy9ESr0zLxfgQ zY)RBPwqYQ3UJ^XYKwTpda`chOQQTM)7&FM6?G%D{?;N(x{Ux0#=JM%YoAk*kmPu5E zS#@S~MGymoh$A><$lu~aBGjmr5N5A)E#KW3GEi^0m3^@c%L%<{aDSMYnU*kg%gEGc z$a#6E9K{4+E*s7^5N4(3AL}=V+_)5b&Q-ig-*xPJsUxjjMb-ls>-u;N>HEydSQs{6 zhB}W)zHMf%nQ3nk z6G>Sn?gs)40TJ-JGrvcRwV#tPAhBQTbEV{co8y!^X zdu@Gp?Tbs25UpPAIhz>i^(Pmd(b%Dx3xjCiI1F@wyxb@Ako|d1*2QB$nRk6{7J6<5 z>Z2+npM%AahpT~zi;027N~qMIK;+aHHqyDXF?y0v%S;G@dP=s%KpnlyNEVSUiv*RDn57`_YC%E~v*CQ6_+xo1!^EU= zvW`c7aq)geS9MghOU`?lI^5pUal3;J6um^LxXZwn*rU(LV;;tOe}TH zNX{gTzuHH3bAFi&OPu@yyXH0i`muHE4UimHj5SUSQUEqg^E(Aj4%aE}B|fM*3*U6o z<8&MyDBG_b$D@U+X{A#g!8)9Wc{7b}c@I0=^nsRZcUYb%*W@zoV)htf14qLv*J(8! z%EaLjmibMWM?X%EQ|ks^MwVTg@pNd1g_^2uJha6&s1^&F5Q(kU21-w6g>iN|j@_6# zGC8ZW^ZTuSu|Ye$$=&Sulzn}^XuPT`08=*P%ATsMstu|R$`Jrl&M=J(@?qeQw>@etBjYUeaT)NR=^syMZ1=xe}ht$#Dyz~JuBf>ICgl-sIy z|C*KAzK`#&BK5%$M^(x`lF$5!3$1PwF0${ZpDS1o?w{$#_R4Cu)NVkuK5@Q(5dH#8 zV?NzPKSJxMITdzr4*u3B_Zo9cYB+?|7Vh#hJ=g)WjaTV5(^SB03^VhCU>IVek;O9V zafsijhG7A{iFgiaE0AtiPrYWNeZ!1llj>Ws1&RP_Zq0yC18Q9yfi*o-R^`gHT1@Jj zt&C9u!`(YvPO{xNT}GEBZI+Ai=^lybUsvXQd$xX*M0zNwwg}F7wV6x6swHs!l)Q3z8AOm!H}LxD zNgD?Tw^PUzE;$$TA?)5M!h{@tIL8M&wuX^;ul#a994>yC zHjAy=uy0n7OkvANg8HUnP#?c{LCkDWuR*mT+YX}wwKF*d)1-phZmJ-#veCqCl^$}j zPxEAveIfac@@e%Q@RL9PGH9c82FV{DibH4s?mPN~JtnOX+r|&^nn0#q$&(*!LD`9h zQ{i+N+-%;9H(EY;Wl|#Xen_yJvAoQYLY|+|IK}RO{CWq5W1!N6v}VEASzv7j8Xh3d zD0Tyw`?^wVRIOg`$tzV|6<*lGpx|IiL9YI|E#12(U~8k4S}-2FE1zuH>G5gqufqZJ zs2B~d^}Z&>t)(2jqK=%)uVTATx%kV9rsp89cg4DUsi1HaY3qqXKX+46%*{qOAFQus zk9VK!6GYy?#acvFaJn8nC87fSuwy}jsHIwqnaQO(Q*YnpTC^Rv=g6VuI#!opO;swx0gB?ydulu6p8WejQ4-grm80QNCdSH{3%yX$&|P#$%SnoSARSi!BJr(SAN+6 zDVs_b{G?c%mC1Q|Y0ss$OVyD|@@!NSb2K!_+jbN}lb{UOP%DwFN?kC!c*Ds(#D8%< z&fH&iB#3n>uaGWR01Un&S%lrRW4!|MRKrsPo(|Lj)Mcjfnk2MaNnYqmej8!Q6-l9z z_p!JuAi{SZ>?~CRO6poz34M8bnWa5gFX9q1a@y0<7%*HQ=5x9`DgDm)rn5rCUT)#;jC_z}@TGkV${jfK`(tg#5sel1Eq~ zAxt<~@s#09FJUbUiLU5v57H1l2(V0_76K>V*Wl45X2Iaj7Y7)K!h5^D5kuLl8Hl00 z>DPRZ>3sGZ-~+#rN5xHi?i%n(dCneiCF#0rQtBYOwISO(?O{zmzkYHJoFnR8 zAH3<6qrP4De9*i%s@)A(Ek39B>P`0ZNnA+oQ6cKd?(u?OS?y*aex(kiq~8wCKKld*S_ z^ZSPkw??F#`7;0!ulzA$nD|o*(UtgC;f$Nop?zRm+>JHPpL*I2B)7cj06pyM z&S9#n$$pQk$54+BKr=Yo-1Crj$9M$R)dm%-c!!f$RErbc)@tAKHUiqYm3?RLb?E*j zqFwo*R=Kl7`GA(=%~1yBz@$Wng+)_KDai6BJAVb$5SG^F-VpGLdw^6!+ih?otV00Q zGG|Q?szqFj=w7wg%0=%vNmX)-lb(8A#k3!>HW z1xh30@kAAZ)8k$yD(~A^OR7#lJq)ju;oeyN3OCk48z+YD2e>VJT9Xqz+{R@R8#46QIeD8o`N9m0Eqh*Tu9l6xW& zndbdr-@EPKVIA@YZn^9?R;(Fl!;I=3DthTk2BDz6y_ql(ha+hb3nJ{55vLOXRRfS= zhy6JD9q3|`)Ko)r2mn-{4kGB(PKD7I?#<|BSoDN9t?$t zYp7`ZB%m2&0>c?n!vq4r9a#|U4XT1USSJ_&O~r!7&AUM9oVlp=#HLGz!NT2eUb@9M zt+VF?&gltT!MN%BrToppS&w;A&T;mGvl4w8VHRm8vF0fytIT^k z!V;WGqlJFQNhhxy@~Fh?F`sVQxqf0LMW1bzDV}CKO}}Jqw~_=R2!gEnLHwrGs(1c) zqtXRH5o~|)hiW!dSR_0h1Qx3VDvII<4pZxO8%`EGb+<*zPm7#J8N_F4vEt;D_a}TeN9`FJ!@k;ZVV-GljM|jvobw@}4W=ctRHp z*7}eXm?nB%TP%>H%k5RJhw3Ofh&L2nqL>fdf{5U(1F`78`>~8I3#E7nrZi%mMqyVxU+@lAKSO##x?ts~9DC&@@2kEha#m|u zIq%ooPZxOM&9UMdzN>NZ!`s1B+ ztgn*z%+a`xt2x?Rl$Yi=sFamZ*r-rimU+kQz0vl1?HfSFC_oM?gHNpFWGdnl=~eYh zhzf=@u4B8k7C?=j*ZXjM2{po1_glbaWvkGCo^ll1aeodh=q-SGc3s=<-2!~vf4RF} zq+VBD02ul3s3T_0AZ9_Vgj5^&GHIk#jSs)gnlsjamk=CDk0Dl#R{vFt5YRiOfvmyE z$clO!NTiBYVm}H(Nj^n52oi4W`e06Zm^f9vXM6<3UU6BLAV5ZRV!Y&xnV%}7Ht}6c zxt6|LZL9pE@uG3AaV3(a$TO3UqY>Z7Id4(zq=0p1B9LPxu4t>nU2|t!;VV@_Wsijw zF9GF5{fp>$0pY+CyoG8km;-=(0L@sVB$=bU`9+n@AckGlZ ztjgaA+obv!6pu!>_}%gVQ7nr*)WSiQCn3ev*G{YHk#|g%wO4Uv7v^Fu{sBkS}zR!>>+rR<8 zo_|A&^PHnbWJszul9PbJgD$J=NQKDMWpmDhnwSVmEf;rKn9N~xR z!i7kSj#0bELvs)QkjO6d6p;Qsv!ZL)|3FS_Or>HT8pj`{7*9%?$}=h<2oj>;Z+ujE zz`VJULMsAO)xH`FIQ{M+Dr4iVRbTVTvCyMG%)A+GQ8Rdvnrt$%#Hzc#f-r;qBdXS% z8dqvknCVu!3{z#_a;#7z<{XY5P8KAku-8+-3K89!S)C3m28`&J@54!HYmu2%N=4Qz zx06`H>mXZ{&G>Ihx}~a3PH)%6eXQDdgG;M>K%@Tsf`dj7u!sKFkduu!pQzDLk}Pf- zUZm7&^QUr#*qsj3fDyqubeEN&Sl>5)VoZ?)W9IPhKa^4v$RUrR;IOb@5EgnA5z$l|L+?TWZlYQc zDJhT$0py6J5phH?R=hAqF+%m|)Z*I~KLz7@af5*w;aGk?Eb!ei%h37K!7Pw53xkw( zP=ORHjA#z`1(*^tWpnv}K82FgcH~9E@VbxQCm{Mh>=Fr2%qxD+$bM`9aA~}Q$}jh` ziZyT54@M*^$DtRxYh!Ol2Wl@eB*b?hW*0r?FzVaLD-BF*^ncB46cIS1dw zFOV80Uj{p;Is(#2+H@FbJ2*Pw^DM#6s^bK()N`i^n2}PZ{?bU0v)S-p zcmhc+p=OjExCVsgpOQ$yh#~<%X^+26UgGP06j@v+an34p!%%+tpUfK!3gWKD7jJpu z6dylvXNAn`6;~T*UY?FBdC<#L`Cr;u83A344tVsJcVrg|y9#!U??gDQ zl`o^?AkcRe2l-v^AlQ={xd%23K~to6o!BlWxS?@RF-HiGDcJx%79^PCiFG!mazAjJ z8yk5Tme<<1!Kd+i1}F#vD{p!QV#RReQM*>%nwk|QBcjN#Rh^;6(QVw*WYpxdF0qbZ!&0M$ zx-yg2Y;4uBYD0c11@eIVJ)TOtrVKaMk zYqN`Iv|aJ%x1U8Yt-sk*ht<_n>r)mge?M6#`i}?*-<6i!~IhjfaE3U`B zesU61w9Z%UPUpcyUQ!aHf{tX`ZQd4-b8}k?%350h3L2wXBTXiwcorI?-TlsjlUErf zNvU2MU^N=}7@GT^H{;Ux)w_exGPS~E%%+C=;PDLeS^OkJ{aYAf9NIw5X&5s{T@KT7%+*HD`u+fNIYHKl)01LQ3w{3 zoy_RERpvOPm2Y35!{cnWcRn45YR0iAjr`R3nD$X)FgjbO!g^ZyV85X`q7z;!{km;G zJl9p^`(ma*xYN*x;jQ)f^tYX>iI_QjQf!b96ApS}J3!v_@^S z#HyubsHK=uJh`4snZJh4bfu=^#F~TQ#Nfz%VvghDqy3gurkbSoepqV7L+V5)2jOjb ze8#t!oj>~fY$RqLl8LeFgxeQJDy6#BiKN`3E1sCfDwQ6VEgdNm3fIzd&J%Lq?QA)= zoF=vush<>8hRF%UN-z*2*3W|$?G%CTsUsucTg3{stPkb43Y__%oR7EE{AjnsRQ(Cf zg=Tj>)pfWsHLa~^Nc!sP5>2QYES_6_$+q+9CM1?B)`)M z2KBf+*i}#@^AU9H+x1Uf$Dx&HIN}fVbVHQsw5l$}$YW_0y}W=kgom7ke*Fsv-<$_` zn7iQ{YMe3*LIN6jd`;=t?g@YKTTux{Et*6bzfS62v937%Z&mw3O_-&^@w3>~2uR|(n9mmKD;Sf;LUA`;@Fyu>Ojh3fdC1A!i6H=UxP1ej%#dTtMq$>I z@3As1-eP&j*z)VHOcN`8ZZSi43hQ>ezHG&3fnG&iFc zyfYx~T#muhXL)uozMG{=ORlLt;O&NlMlo4Abfw?be}uW8YC}7o-gthBSaW}>=_?IM zZu;udxYStnJPC_h`S{uI{%}VX71KLi0fx;E)G-`n8$2G|IxIgNH=zJ@=Sx9IrmB8}jXf+WYo9|qQa$UuCI|Gj7Ia{sdg_{=$4X&?QUF_kMXKwR>1|l3 z%smn(40nydoj-iSz|i;uo_F?)S*=_OJfdt?3(oLM_9k!kj;#f6_*2WW-EQDC_{a!6ylombBX1|Z)!Nh<7_WQl0p`!}51o;a#q-Hhx7&qHpq z;p+Rph2DM)^^vv&sFt{KzxFduJHT+;LV7V-K z2(*F6h&%D=mF5sk0}+w&%NT^M6rL`w+>TvR?_+kJwu z*h(%ExrlR9e&?eOh~qJ(VA<}h_l%a zA|MSo3S(nigY9;N{frR$sXAu${X(Oo;00e^O%Cm7kWP>a-||J+y1zB0zC5@)LjX8V z3piw$w7e5g73>=KeSP|phDj^11nQXPwbtD|PG0LD;K)c$ULbXpxJqa{U>V5yu)&j! z$V)IWMkt|Y?l7o-VMMBbpH2ma)FLOHFOBS!^y;d^NOu+e1gfJPm^2RwcyCYEuRXMnO)HikXtg z#b#F|_+*a`HPO9LreJeMOO16e{VQptj_oW?O>V8Jx85~BqFyH_t4o=z(igbY)NFgl z%(;KyeV+N0b*nA=plB6y0=d*eyzDpcrseY!;N~jm{hGT`WF&Jd5iSVM=trtFG|KZf z7GllKCyBK#PLnoGQ;S55%Z%^$)cZYh_1Zm(^Hdz|r!qo-u~g1gpf*lEE6qr3C9bZ8 zfS#>VQs&a_TX_3BKj1W0q0V8ab}zZj%!j25o^h;AXxYB6djJu9acIq5Zi&w7>qEBj zY4=EI-8lyn?vbN!!%ss(?9f1@3_5;+V~cO#sQ=Mm?SeTK;N;%Q;{Dn^{$Yc zLu|D6K;6lj;}Kgdnn_HLXZ-fK@Br+h1yxJ~=%yc-KF4=A3jEYa8VZpjhl@6TZ^W6D zRKQB3C(T4k`m7a2gZ^#nj*ZW~#Bl0qdiyo@-g=9>l6a-#IgT&U);3+0(eU_P>@(MW zyKgw=`pZJ@sk>y{+kHp98aD)Mp<1G73X8{p$G59h$IK{h=Y2^ z2oNBPeLSV&vB>*i#T{JM>leUYeW-TUnzGG7nxw0c|X!>k1+(hk?EKU1^9v$kG#@Zr{0jw}DP{ zzO+wClyHXoJb%vu4QU$FvOqoZv}sH0tk;V@5BvJpc{iWH@)Nhzl4TeD56|6pyvU@t z(4p9#(U5c*U_u1RKzj;_3I>^k{8|u^P>jC8at?)JT&gj+!7=`q8GY69NQF8tejnZu3Wd`ChPUI8B)8#%d(*CY zkbc!Q$h(jR-i-2aFd-s?>C#%-rb9cdSlFlsa>$i)tN>f;4BOuZmAzOBmlt+jMkq zf$GaYDu)jklo~KYy6G{gBkua5@xuDN*2 z3-1%yl;IZhWjDQY9dTJyyxbstBIZLoCxTp=QnUH61$qjyOL!|^71V!`?wu7ad zFt$wuko8F`dK%J@XF6Ia^t zqI9HrbCyh4NbT%!Me&1U2gF~9xY2-MNfN2oM&5>rlV20dww;4i9SSlSzUiVL-6r2bCQ6BTKU{~JwgMjDt91?(Wh-#~ZBHhteXMQ{D zn3bnpth3~2^~ZCVgH(0(nwPcZ50{GflZ5O2mV4)if**reWt2w=9bwg1pTd_@bUg(} zv;bztJW_>Wjgr~Z1mAjYcSdYEcF;gf{qiP8Il>-UzceD$dO^Mpf12;RNf>qfR41X7 z_y+I_Md5GBZ}$(+bE+9v^!|U2$HA3+?vO?mH}PKJI-YV@Zj`ADi;4WLlTMLkpAD_; zPX-r|-7Y@-0f?U2$dmpXXaQvUAU8;?`?JJ=hMrYu>>1mo?;TnV7;y?Uw;+N;p1nN+ zTl^w~M51`CKU~ygBV)d7ZaC0ghuFfXSt2wtu5GESxe->3Th*TT0Mc% z3{Zh&>=fX~^y~nUk)v0(UVoD@S>v_Qk;sWP_B1C<`Q zId(h15J-MU&QrEZBa5vktq9gR*?+8%|DH{<=AR<2hCjxk7NkAag$vr6jPg3t=Nql> zYBvN!PPpTYT)zpr5;;k1JSIe8;qHAd*KAgl*by;l+ntj?#g_0;I7r8OItt;E zL-J^JEOS^Q7)aNGFq?x0O;;weaEIrNNTjabcZRxx`=lFJ0tw{PXa(c2R#~Iaw?KQPdXW3x| zqSvX)jl?XM0g>3RP7ZfxVhiJEt0GcObQrbf2lpEtSe4xaVQ?`~#@%aIHBSUSXAKgg z8K`AjwZm3EpI(#A${wF`sC4T|JKxu@z&tvEAAjej0Zl{A7`u|hp@}C{CW+PrEfWc) z*~Y0~*9^Zh+>gKXCZWp+NCJNogPV;uD*Xh1t)B39=uPPaZ=xQ&ce?ZD<;wJEDSLlD ziT@_YI_c#gVJZpveeC|n=Ub;0+pofs16L{M&28msTzk0JkdSZr$*>(!R@l=N+ya zAA08PncW+=8)!Xj<>$Ai<{K@0T{>aE9P?_R&>xQ3k2G)FEyZr?(~#Yn&N1MkckYy$Vy@~X4c4057u^OVH*Z;;zSubvUt!lv?<<)Ju(b;T-A zXkmM5+3bO01nKZ0&6M_cy6Ex}PGll)Bqs93%)vCyVHW(_(y%5N?h!q=eAPFATxE=H zf%eab&S!X(!)Q)$FU7thc?$@Q8QgH^0h~QMB5k_cO??m73d5yr@@$Q%Ymzu(IL%@4CUV(0K8S~Dt&p}GZTdDk;6I+ytQ;=r7f*rB%chM?{-tpZVET8+1T zp|J53=G@w+LN*&x6&Pkt>uI*nk3{KOdDGK_j)M@@8A5^rk!}2S4jIYL*YOtaHC~;1vfBJ z(BZu*k2xBHZ3GcQ$MUN2y72^?b5k(BRjfSOIWH z$jxH{_AhJnemEkofgAW>A7F+;E26Y-17czD=@FetUp>5ZX0M4b!LAmcz+*FJt?swi zFtGNKV~uhroasS?IXQsU-hVtJEKE^1aIl;C?hC>S?h*LT=-~T&@6m+z1+)1vHSTK; z8EVq`DMn!&bZ*UGVv~YU=Sp}0z#%B+B6S7I2GZHs#C2 zX!x2sRfSdjo*1SIjHvLs?*S|7hI+k7zU*cx2AF3^dOrYpX@@!(;cSJZ#Hh7Uf+VKQc%J9?2v07#aX?T7T&#J6Y!r2p#77J9FW!^|nz&R>G!4Qy zO?G)G2ODCR2wHj;N)2V5aq2dH)XUuRau8`3no`(#72rB~k|cYJL)JPV+fMuxW&UbH z@uTT_#ycY8?4$`E$uXirYu+>%Gt=XE4g1XsZADBm(>}j61V$l}c@;*RvamGEKXqHq zqlt0SVUoWB-$dg_9gOD&peBfuq@kA?k?3N>xK3`Kfsm?5cIkCG!liwu0-=mOeFJxF z^B^$5oAFYFhsFxUNsN>gOX6qO#r-6I6B;W=M`0AjV`Gh~j*d{rVNfJc$3@CZn2H9D za!WE266=syQ2zL(nYN=4Z%N|kL41gNYezK7OO?<8b0)ACMHJRLkhNpHAtoW%F+$8P z(Fe<^OMGjvc1YkL%Y=kRAj{;i@@*(i>H?%zV;^0MqE-=g2vdw&lGiAWANhphMVOWZ zv7xYFKwDfoXn-6kj+ns^aL+?{(;8_Xb7P-9P4c}eoWYt1`@V&;uP>SxvN14-K;lNC zCL@>Ke|mr93Y7hh?=tA~&i3K!4g~pqTl)Pu_us5d{@uX+U$&tz|9`d^{ol%H26`42 z*8fx*{da8=S*7WEtI<+Tr5o>5))n!$HP??o;;cY;M1KoIkiZlO=)htG5K&|S9C>5( zKKeK@|5;!pkUy)r@JuHH#`x&a)wzbw@C3X-;25{SNF2D}Gk#Z-Ix3sM_w2`?4qq+X zu6AnGWu2}n)tyi7?@O9V@RAU-36>zl21gqnM?Q}`PO^ge-9*Bv+)lgINK(3eSWg1V)zgI!Wen^N9)_$t6=QcpD zu2lDY+Ne1Be5`C=N6#C5J*{lN^>m~?!RLXwk-gS-`&{wFOq%H!LJ~-sA%Vz1` zx%^;!M1qJj3gs^Y1gH<{4lIrld($D5g5o8X7@?`GI0_J_;gGe}hdu~Dh1u!_(##&o zG?1u<5#K-zfOQ?f)V&N}<0ZS7Z%|N>!G%Q(G!F19zQg|Rh-R%nHlc@{B|I$>J|`)7 z5H@hlmESLB@mxqgk#2Ca4r>i#HK6lK*se`Vnd#iq)Sb&`)jZMF-Z$ixZitA8v$JEI z$U!uTjF6c1>trQVR6|44F!O6$U)!e+QG|Px2qTPlC^2p(gFQFuT$sf^>!HJ7j`vj0nP_Ht=_j)@l7US(6?}NJIr_0dJ z_-eaVMV-c*gesp#*`VKdP^|K@!&~J;ki5^ES%^mQK$ak$`>MH#N#Tf=bS8bzudE~7 zUP^%)sH>_|C_LuATG?+{nPQKIb5^z(rG3U*e7Qd-!gbXrmKBzVbHL#+`(wbAgk$w5 z2PhlqH*p9E6Z+ewJ(?P(IrdPU#;=-id-_k(L$se%_~f6UUH zugjaGlx90Y9$GSC<&}D3f8&=;(_iZPXtlEeW3-)yGkG%Ns_msiwIdIBaZ|!rGGn{b8fDd$hcHpNNuimLAYQN8|MJPzTY-mc<;bZ z9^2JdC0j{pX%3hWWN-(18p$6a6jpCnGJygoHdbx|pFltIKE^~{pL1i%I^=RW zFcZJ7=)^L=nK4<;&IAkEfA%OKWWfmEvM1ms2>O$a7K*2ag2~mM!ViV3US@u_&UroS zb$!e?0PU)%{!~B)b4^oPH)TB7={0C46NWo+6!Cj8(H>csq1GS^7sI&AVmiPqqTgWp30wVy~Um@-vZw;L@!0= zUw(_b+q?#q9vwK@0{=LygMf`PeUI_USZvZrt>lxL8jgf22REe6>D2KOE@;!Vs@*N zy0*G8{f-|``2XJ9-`EP90aWpzXTh1C+gIQVy&}(N7_YAPnH!yt6ZprlriB;zv@L+H z=N|<}|JaAwPmPC|o;gor`?cip4tQ%@Zj6t?{O>PKWu@WhDP8P&C+2Yg{SY#;#3eCfqvS#^w@?LPi}PXRA($r(%b@Epu0yv^Ru0MJv0P0rs1mXg4LVmq}8M3@nsGge&kpth3q9VFt2=8yCph!cO*ve6BKU_SC3O zz1u@SvzfcW3;Sag{q$VR?DDBQk9XLdPSh5oPeN^)H8MM~>2SD+B5`GKa*9fII-=+4 z+b^F%6raeGhl0G+Mqn3p|L?qkC6m{JM4iFayd4QRseU>{F|5)-v3Nue%oUA4bSNE& ze`i2h>UMtVryo2j2=Ny?0mIL?ebPkP*`QJbr9C|<&}=$%gChqMlNHA9e1NA#`mgOq zMZ_pO`&YgUkBp#Rz&hL!xpZIu8QU^1s7Xi}6tfGcqGXk`3yq1Bbd>4loFF_Qe^XMF zJEb^T3|&P=7QXe1zQAUz)^1Mk88UrkqO=GhQsA13$)-@&_@$A%Fv?P%7h%a({+tE( z?|&vvJYI;&(z0W7N@H(vo_+SDb}0Ed)=2m;kovzu#rvkF4@rfTi7Ca`whpsrzMA>2IET?6x3AZLwI@sPzN| z=8|lKrBCe8juR3-@f#J)0W)Q>GiF`L@a_+CgJ_~ERz7i6kHX7{tktVT`vVo~>ys7w zwng9sDL;(9igS0l^?OnZ1 z2ZPwPmTrA5Kh5~*!Ish1Fs^<0=`Wp>s3qqdg)i3i+_M;yIcxZkBP7SyNb7{0oeyLFa6+~{C4U7SObc8i|NGV5c@ zg{^LSBbp8o+cAvWh{P$4hBIDDS-9+4Ub}yszM`VJqjA-<-mzva)D z>WA;o^s$39n*0{8*HZd5{K!?h)6Mo>48Ma0rS~;F`A$}+=;TgXoWn{h;sEdQsjPH! zqdU)4TN^XEsu|?*Ym;=&Pt2gbPA%Alzj#=nNmy1G5Gjmql5Sj?fYK0(MiaZsQcC-r zpNoIOv66%0t}2G-Rb(mz_^XePIr~;;kS)M{O4Ktw#8H}v{#AL?H9{^?(mIlaJIV!e z7SD-coC-NZ6q7WnIjTPL`UnqI=e$lAt8RMVm<$vO*ixpxf=xG6 zLoe^#?bMwr_%-BR1ph&jZ>{9jnXhX`0Y&4G$`M@@RTO;)<%uesHoPL(vMya!ddaa# zsj6aG4f~wC)P@YP8C#(j!c&u*l4epj?Wu9r#k~qh%bp^q0JEVKJDlRmJU(%Fds;n z{;y=bh>kiE8ngf$lMCATAd?c6l~dS*DY`RN;_|d~4dl zEs5;2C=pie?z!y7UnXz~T3SnoZ=XSKW;VOYNvy0*O{}ccd#Uy#J?$%z84`@Fd2H$I zaoBuZ?(ZTiWeHTKM2_Jb2+W|BXDc7z$n>P;UY#ukEsZFup-iQ<_*LX+N--f2iv*$i zC@%GdJc4SF;aF1yil`JM{^F(Q{XEA_6OW^H-~lQ49K$?X;+ z%?YNyWn(661_bliAX#&u1gqMvhAr(hfP7tRK*E}_L)uiap;cg5s*B4#VaA(&;FKj} z+1z6F>cnIbqUC-$D0BsY^YCQDcMeZwzQ4Y?;L5u6dSn~*y^~o z$+IL`S39*NnlZC%K^xVm(vrEpnBxXfPb%y84V&3Ppurmp+M<{@Hb;OqjgV$r= z5zfI`AD0IqoY(42W6v{P62svG_7-T3#!tei&WqQtu9$q!(VyI37ZPWiA;A>yXySBU zLAUYOl|9ul^&XDOin*VdSwPX4Y<<*Mk+o;_2G*-2+np7`kJ^~7X7#XAu!Sq!5h1{zdVv2| z48&s$nuwu>LWr?=RSqF5ic7q}qDauVSt1L>qc%?0bXMFXqM0UyZ~8Q1$qD#%Ip$;L@#!b4Oz>ac-T; zI2c-gudeRs69R4MXE%gAS}Pzz06+C_y-r{~Ydn^(9w@GW*v5!Q>^X=Z+6AK|Nov9s zOYTLvc}Q4zYFSF?Oko*MB8EZlX=|K)J#oD%3^KR>vCUk?s2jHOBNw_$fV9I!yoH?v zz~kb5!RxdJW;TtM3C}~}jzLj;sYQP=KqyCGsud|tbNw}m%;ybu8C4|^zoxh@v9|@m z;YnGWpKQ^PvszEwb|AA{QDrANw1zU$bPPfK{Y+R2o;1!powr8HI{SfvoSU6(LpHkb*){G|Uh*!#>t}Q5p(a za9&Z8im*s*5l?Kc)_J_zfrEMk|jSwfTKMQAs4 zw64s2FRy-TVN0&(H50x_{bEXeiH;K};*61xlS70;cR0fS#!`+))AMF=10OTFXr7|G zxJ0HTjClkDsf27M2NLCsfd(cTJsXCP<`SBxT*^g4`Zgk2p*k(BlbzTu?eRW?R93q2 zmj)VxC~Z_n(T9f@c>hxS{$T%x^5;4GvqB3hJfvUDWBM!JmWzBY&$fX~6J% zKm4K7suFFuqEg~Oq%b^-Vjs5zqb|nD;0&ibkk(p4kD>3O<4nJ9cQf@SK#pFDvGryR ze0mF&4?(>6h~>F&_XXRJ*xF}Gk2FmgGnTobHZCl?pPD{P*~l?DYEu{;au>@INwXef zYLmnw8PkY*E)=md(hlg7WQ$Xwl8!Z38SHEnakGphRg6?nx(Z_qAHzy8aD(e5`GuAQ9qH8PbL83ma~#D zv_qrDkR2S6ykU{On>R%{CZLDImtSP{*vfwF)!(D3lX2zwk<>R40;8?uG4tvFtz6RC zP&c#K&4@?62o__)szM4Ar=0y`_LkxllBlrYSwW`)r^Va2r`lrW7Vb7)>NgRc+{Msz zXVWsV&t$RNc=RV!@Yn=|&ONZ$+e0mNJ{BksLjYs5e^B zRNtV`gpUs-mY>eGzt&K$e7d!U`H4Zu)GArpnNHS;I|;u=4mn~>%y~gC7`b@nlCKNy z5ulsv-}m+u&LgZ&J?2l@-#|oBMyy9JQA0~U^H|AaPeObVtLwS@ znJk4lq3}vE4ACeW+pO@6g#$v}$Q3i8;?!b#eN~zgpn71obFwy&j9O7*A`T{U4 zC&Kd1(RskmIkk5jy|ljem_N97wno~klLVA@8&`c;Vew!q=ShImog+n1#12^D8r%p> z>l3w04o-O=87FPN2|7tL!UiQ5`O8ERo@yMuC5P$lnGxVkqw6R__%yHdN9v3d6<9p{A&G$q;|66#^7Z9J zBe||n2PZ$1VRYg@IBnL4nCN1#Se3$CsGBZ}9)`MJr@obQTdFRoHR^uI3G6fILsV^@&}<~+3T3or&W82E zgFV7#aq>Q^Q6K-ZM>YOs;kRJ0k3iAS9WfMV%)2I|Qj;EX;vO5(Fhd-%mOd+}c`IRN zmHYe6YF;1|UxW>^!GgB>8A{3tFHt`+Th?$*R7u#a9I~r3kS8CgC*U$!cvkA2OuYqq zyMFxYyJH-AJ1sGF`^9Vvq$0C6D{{?NGjZ1A>pj(xF<5qe+QSCCre5dKWBR)z!))Or z8bdjIR!XOc)S$Ttm6U^{8YjaNKE&bQoa!i_RnG^6N}-G!Up`*v-%6`pw5xWv}G9;vOW+F2q>@J=|nS7Jp}S9M?OU+vvx zapek|B?=#SfbNM?%l<|61!RlGXrpE!oM$Dn-RSF-g;A|B|xA83?%$esm%&|S)=Ouh#?S62^v zj%{Tr7s41}#9iiihbp{5NXH*g2r?SJ3QWD# zXb62oAxs7#>9pgG`aKxcn2o^AXVxPkF(}ta?ARI zx_4x$S(RH00p@RdQ?tbge{lUjNGR=3=6R;#)|>1TJ>-Vt(~M2qxwi#&HcI;Pcy`O^zgm%W&g^x8 zomM{8%fa0^a@2!t@7=P6v~b{T>l@Ie#XrEnqpg?-ka)LSk17x zHEn8bnVNW(3+VExn}W8pUtRcB^%SsX3)gX5e?j#L>V9>2>p^;18n`l9y9M{tx2Jjy z-%pLx?C{!vtI5&XxiqOe9b9Gl$08!CD-lY|`nCjpVJ#=QH{$FTC}avJ`(*Z08^x+R z@Fcsb{v>}_gZfhuK7+%0P?F7YRo}n(39~Y6;2p+%@C_R~cQ1`?xpxn`3I702e3!MibVhHbD#62lX2s(%(G zT&nf2##2XdnL`G@ff~d7)tY((o)8C|VaHZ0$jCkW5@0bW4AF%(3OM2oeGw`cdZ&gx zcLmpwtfV#r^|B1u0|pdNVQK@2=io-cGHfaPToU+eism}*OMCH(-It5O{XR5+b-+Hr zPv5R~=buHTZ%@4y!|NLt_cpa_SG2TB&2+9FhZwF|wxdV6J#BzMXE3Wuamc`c3dM0) z@b`h*Kmfpk%>Z=I@hIbsa92?Zqkl%+6o3=XcEF4T1 zZ_+5}u>+BO{r3?dnAKdfH2f|mpy$#CYp97&FMTizo^yE+v@P`eVu~aV-=c@flvtjK%3;6&dImj5J|5 zbnclzrGS=&$rM)l^Urk1=ZX3g_M4PMIPnNGXT3&dwiqH+o31R~SL4X1qF+h+KLq|bK5C~X)!+-z-0mm^g zM~5?==VB0R#rDA(LGTPBVD^@d5;2fnPuvcgG#cROiC8C$bH4;fM($+1c;9?%e{8=@ zRaLjQJUhu{J>)3MQV@y|Hi1a!+a6t(!#CTyryy*I2nC)w-Dsm>e9a@IA&e}j=OC4( z>$zvto?;7W>u^}Td~T?T+GaXWu{>oUTs()nH%ttr4fv7_Mjc4XfwHCQ%~p-P=hXSg ziXPP`y=?1yWgx_aKHYDmOlHZBt^rl5>38KjK95sb6tiGlnAPHywj1d#rLS>!_i|mA zd4@xf^s|Q{HY1lUA^K7yT0p@inveTXHGF`?6}B{qhagyaHu9$L)q>jO<mP4s0$MjagI;ctL{8!mjk?f?yHR_L#&DSqMceD}NYx7^(>dj67 zjU*~^zr;r`s_yVl%F(^#+a`~&KVM+?c^}jD0Xd-xRKFEh6`LNLGft{XpkIv+UaG9T zVl>TrT6{h_&R1-!z8x`TOUOLj4GsFD;ML$-OFXx#CC;W~hf3gdv^5A(+N zy&k_Jkh5lMZfi?WzdCig2ySeqeb`#9W9rnKnz$^bvxXc`=B|wR6s}CAc2rCZ_Z;Pb z`7b6U==2~LW@VhWTzNS!Om9D!KxNk?ki=dL2=05NoXd}2S!cv`BezWXY^tZE?jjkd zgrPu?O^k{d7cj3Emz6(xnLTzUx#>3Qir~#B+$Y3>pF>$b!Zi>w|IWWhAgFu+Nu76@ zZG7`9LbqJLZ~BDs^+TKUQ7Lx+%tJLn?Wu>-i0im+@BS;!96W1%p6ANtkH3XcrqSK1 zhCXMy=55{pqh;8~##h3v=lt2xhby2)S4XGIaw+=SJHFmYj?y>I&B&t;C78FtW`kMYZs+?EbxTro z9cEA%ECg36Ad3YfX5(W2(TE1wAkxY>Qx&vXSGG=fa+QBF?gsbdZ-uq0>M{Js3^$R{Zm9Dsji_BJz}a&P1h$^s)-CKn^@&5#pnNqrm|eYQLg zr{sp{gQRlDygo8E4}|<(J0saX4yN6e<-{ei#M0)Cf=~m}GFfT5)dep>RCUE7dK`;q z|FC36wuj|H;z<8L@&L*|kyE5>;W6f9i1)+5d=Nydr8|n#&wu-I#&F)xGPxtYAXI1p zH=iiQ2NNPI)@MC>_vS4#KjuM3#p%;SUx?x5GgmZs!E!Ax8xw**)*R9N1TaZH0UL<11swWWQaKu`r*I+GB1wXTR@f5zT z&wwT40WBTPKAf>X<&Z4sn9e6F$y|uJAOj54b}#WIVNz%i=nz;Yp;wB61r>E5OC3oq zL+cpPEo#Boh@mAz*kHVzX40@-nF=!?^9F1RJDU<;0di83ufXnL>6~!6aTjvD)lJPoa6|+*TIyzDfX-RDBLLrF(pQDRwwf-s>2bQ$$%i5^ zI&Mz)tn?IHqs}MFa@%@ESCX4B_*-RE3;%2HKIr=9l#DBAyVX#Tq+XxYuD?;i`dagS zi5%ZoGQo^KV}zdn3FgOVyMFEbO-$j(tkzE9L@f9q3c%|SFm~?3?%$-?gL%G>8xSRe z^%}#qV(PuLeDUk0hk05vSRkvSm?sw^^8?BfNf zvdLMY7irTPgc@vG<1;ddguvQsfI&MQ5zQ>7lxlAg)u{rjp(V;cpdo+FH^N>YO-B^J zDgZMnL0?^fQv@-tI20!{7$?SU!D0(=Fs?PhfN_QW#EcGv9lUT*?>gJ|qF3%KRMUUH z%u+uc_dk^yWK!ubp&zY(1WwYGwyh`~AgM|}kq?soIXFN@+DCTM=De<}uDdR) zF1t3|;aTBvOPf)WVqLf7Uzv7$eoACsno31UxMofq)I549rT5eEd|cp zkA53Sn+bhCzQrMRN9&#Q^>OKXe#8j}Tv@H$A!+KczTrO(35bQ@_8Yv#O0ZEN-SQYF$dJs_blyy1kU|Tth;w{lWYxU((HV_` zMFF_%2)}=p>8E63_e6%ihCZK9$UC_h z3#`(3i#&(RLq(>G7$#Ne6tF0%W{dc+WbBQ8!Q#lK!;_jHQp%f1{Be>qS|fmoXsA zsKP;brjSkDPs?TKs$5|XFm{G^*hTD`B;|g(3;ttNK8vq3^i4yeXC=da0>K$eX<83M zDV#!Rb{<0X^L7krAHb0vwSr^Y2fT+da_o-2t^>b~@rk^5?64&GocEj)B;g>OtGyUSQ6C8`p!wJBfLm zHil7qoTO%9)fRd_Km0LE(VQ&@)pVX!kZwsar@?9vx<6MZLFREvsiVi*HZI1f>Q0>{ zY9kaWzlhspmLR!8FH2tI1uSd)LVg=H+t?*W%^wL@4!Ec=D*V-%)@ZGl^fG;-18cH` zp>m-*G^64er7|U6>)~!DyQ-FyO6$(x;%}Z=6IWfc`u5=x)ur-ew}x`%46>=^S_C4_6@lC?J##jTW9C8iNt&S41p5pXgrH!fXTjQ> zI|>vtOrLVwR5yMpM+5ene84%QIYKJWmS)%a<+B#{oS9d)Xtw8$(CjWb2or`|PrM{# zA}r&6r9Y(@VHBHzcKF#$1T2-LypL#H8P$bub34#AkoaIqK$w1rep`J$yu9bWxW?RB zG845u$5K7OA5K5pKQs8@o?a3cb51rxjbBn8unUYHmpxf;{O&TNiZ@0JuNU5vgZ^~! z1To$*7d@%aB3~h6vbRjai$C5tniaUTeE}!b2Q6@4(GTUA4nJ1TKNCC{Cnj?c?`J)` zZg^{ghP&kD!eROq@7uB^u@uiUeQ_6q9gKldvr>pRFRMKOo*-!mmWWA0={stY>%ywS zAg|ztl{r{zo{Z|~Wj3Xi_Cejeomy?el*G^^!|YX>YUB)uK}SZSYGPUSV<@wsseQ{k z7DLf6Da@==(<$ba1)+4A^B@mRfv!dY4$)}y$CHBOXk+qK$az67bH?aRSpWo-cb1Rz zf(4|_utv%#>G_CebF3`=Jbmmwmm``~s?hmZz+!Y0z!B=~JJL(K95I{dP;i+LMg;y9 zic@^M&s7Lb>GuQ)#v~Vmw{SKYv?9|eCA1wQ!m-DwzpH&ZyDK3znPYRGmaP0(kIoF|qWUI;n(} zXy;?%jd9HcLxG#J*h5TPIE;Ra(ocqvZKdPEnW%DkJmIYFoATEw!Lic$BHeyrjWS-E zNf?lI#HR}W?LuR6Y;!_eHF3y51EZExfbHBs+%s7YspR-kGDg&9qAP!e1#&_w&{v|9nqmNaf zD*3IZu%KJ^yh=)j7NXkic0Tt(kBd&k0M8sFf}8pi=Og^b`M|C~o>q5wQ5A&@~`KC-o$-4g!!nTHP1yyyRj8xxc{( z6MnkF0mdK(k61_pnoRpFvp#C;?-mD(cDcW+cI4p<&xnuI$t=3U{=9&u|J8*s@B9~h zG0su-XkMg6t+N=07oeB|Q10>LYP9PBXqKie>i39NlFgq?5@4bAd`lVVnf0qdeUgId@m-qgIldBs<&5kyS9%Ta@r1~8esYI6 z;e1c-A;aO1>=wWutcoAq8sK=OySuVjJEJ?`E}RUE;ELZW)-^ zapw&{UD;u(d;Z#H>dmnq%rUe_Tj9s{XO6*GOEc#9Do)6f^JAA^Tg#B+FbPRm^=5*U zuw;VFs%U%(_RHwv&j;IOQWreFr7TypBED!G`iExbXL4*wW z;`G6d?8eNH*YZdI6{a6)5Kg;wtcdEufv*Cmty}^F*NVK%1=WnaMY4TH3IWE^b?)=w z{=^RP#(8!$f9}PjXKC95)jkQrVA-!=1e-Z{JweuI?{;13b;8M(^)ut(ncAa->)`kl z^vlA@-ZU_kcU(0H#x4Z4I7aK&NL0pGl5r3KSW^q_@o039+Vk7Jy{$)l(@p3MOv zzZm`mAFS?8xfn7u!nv5|y6EtS29|AaP`2A1o+n%xAGViRY&BFCJ7V|c^_@2)L=g_Y zV~>!U2g85IVYT=bI;-{}}Mb)Zl!;I5v@EpFfx#USptW zadf(8Z4DN(QA}RD!67rU{s@vfoJRuIv&(>bv6o$jV|&Cf1m417a6hHX#tFu5G%}@2 zk$q?~#F*-w5G5;gS=SfEm#ThfjI5)eVs0cVN0F&-8pM{WAAHA%;TDh(<G7#zsR3ZuU@fwi=K;^iK>+2sQNu;ShS$o^x`2_oOf%aYq5 z|A9UKhwcHE|DovR{~0!%>}(AGjXfWExO@E*Y<{|2&H-*}H(Fd-P25>>$o26ELg~L~ z_iN##)$1h)VuJ{z1(FejVIq+Y!Gu9FLt(h$`a<2|nBWYlLrWEnF~BhOj~Pe7z2SEv z?TG9i2@e(ykF(Bg(Gb5bUfh2^+c{h=maE!dvadX+%LM-6XEJO^1hu34AZmVVpJO~B z!*>Y8ajKOnhc1`z8p^;yd6Gc9GL5!BZ+bbeohh**h&$``e>8+)8@`>Gb$dc@KrAcJ z0kA$i^Dzf82VICwe}wVKWk$X~R^_X8x=p`oQZVkA5KqWs{vC{JOPrplK{(j-ej`ty zSL;z0ha(Z%B0uX;<5%i(I@VTUeyjzCs)Y$yx|gU=Jn!48w&u!pN)$$@Ocg^Djh*wZ zut%m&KV@A^G^cNw)@)o>qe}i*hF<*yx|PG|dEezGN>NY}Rt$nUfX^Z6^@=51oia6R zgK`PdBNb@UOLHx>^>=^w&l=}NSX;VoY?;Qa{nNmq#ZR$6x4pf1c!{oe4?Ghi@H{|H zy8e24iCdGHDq)YRF*b?85&;VuVl@fY02iRq8yC-&e#J{c55gn`Po=vi0m$Ob0Tz>3 z8S92zZbvW4Kn(@@7-Q{@RX5r-ZN1vDye5FK@j*W2x*2;C$6+4}q^ZJe5=l@zt7=P_ zsi`o>P#w%C<(F@>@%i1L-B{0=7CCiZ5UlwHeoj7n#md&!Z-W|eQtDXIfFHW$P+1XS zrO{3SB#VfI2xTeWs`u8B!sXJbcS}8O+tox~1>V@SS@We>)j4X)x}(DlAI-oZ_?)W6?NG*)~C<6W;PI3IFriIF=nrSzn#fbD0FU7h6d2mQ$w*r}Ut170XLt!QkRr)qd zHo=x^`C%AYngxeUSr=E##QX-PLY-djtQ#L!yj&^52MU9^A zZkt9&>7@*|tM3JTpWz>?w{{nF`DUeoa@Gq;iJO8~k;X4E zLxQwIZu=fhQorCj86z^(U!kLsXV23ak;9WPmxXY$0fcAcPTw2_)OTm{%H@O4?rSij#{L%)AS18mhfP9%q_O$+SgMcRg9ul$HK0vwHqxEKcr{S>8OCe4|~HF3=! zCyg67DiKWjpqZJ|B7M3*Ar#t{y4MzMbHNhgL^7VJ5K@OkkPBqljA+`wI9bHOxwyx{ zy?YQ~1>?3~+P-0-Bt!FmVn*>1?;5o9|I(2iH-l-F>C@2L`mAkXbYeKmx?9X8>uB!H z_8RZ0DrWCyXnekR`vK|30*qG80P7|y{VLd>{jEk2no@7cE$}v$(!y$p5 zF7Q&8YzlRd)(_znRY3AaMVL-apn#Uz8o`2!oVvp6aS^lM-yMaPy#tCiAl&T(w2r3T zw6<8(j`CO^XT;C$U%yfBoZlKgcg)2Zh$#S%W-iXX9DCdH!rk3i-c;}|@lxNc4CA@z zkzB{FWvrpJ+$$4erRozp`=XrEhTIePDByuJyW$_`wUN@_Z zLkxC%eJN0QyUPO|tbvEN5D_0o=HR49eoz@x!O!MSg>fwB4IlYX62G%V$mZ^z?-Yv) z6O*Be%A`k;+C|jk*9EZ518Rr6$;t^`(APxWAUl+F9GXQu4OJ=!#I*;-byy~{QIo{) zPeGXcs+}-UA7OxFw~j-l3L!&j$i95D!Mj|na?su$gRZy*5sKU$lQ5pD90|`#5Y{>Wt8Gs9sP?~QQ62_$VJD- zBP8x$WPVpvS$Ye?Az^FGdIDBeY1gq}co zcNgQ&=vddW-&obFGdq<`%2}-4nCCO5=1rGsS!QaJ6+@u4;`P z3T)E>>@5|wmKRY83j)!guASIMLr1$bHo4EFoFLYA;ubHZp=^;RNqrQ8R0i!WOCV#_ z%!Qm5q_Gb(O#$uFCX7f-v=7lvb>Br?y=|Hyl?st37A`JLjIQgpN`PuiSe8hq_pW-I zCC-f{UCsM;(N5MKipTdRXz!*B?Hi%DAJiY_C;jn4SA}T#nrbukVCMGYxKh2qHo}bk zo$x~a=b!GYK9o%;SI>lyOS6tCo4SBm1sfzSIfBt&Gq z=ZalCilHykt4@_V#aOJnz!=nkV6_6}`7sOLbL8VzTTCD;F;Y-URk(@x1^{*Y8(BOv zeE0Sg&r)^l{CRYe1F$-Od*euF3+5fzxyCq^8jZqAX(_PHa`H-6m=hd{&A7ue+I<=M z$k^L{6~6PC(Anqh((1hFui8A#Wa`Aa1$9NqGE}gr%BV>@Sdyv`njfBzZeEX%oHbnpEM5yuc60r>@E_x-d?k4|qoFR5kuZZ0k%(fLgp)Oe@cF$W;eRC3 z0{Q+f*pXE!2QgoNTe^N zC6yYIDXP2$p&D1U3dPSRGhmXkDJqmX)LE%T2R$TOdG93n6K(t@d3}fOp}fLhqW5^i z)a{+gg>@s6uNy@!<}udM$8j7IVF6U8+N&-m&95-TE=eSg5l{#Gs?Ygb9?#Y*1FPH^ z&WTJa^`1YpNUVKCo%S82U;0gxpM{NA!*V|Q%#uoUqRG2EjH`PrKl|lb?Ur*39=P!}nSWOS{{qGQ><3K&Z-4Qltvr z@|0lRVu+d}H9jK{zjM@h5PPhT*ZUixYq}+VzEG-k-l2Z1=NHF1#?_|KN}g`~-u672 zARdxUpW!3Z%cQnssEk;Xx`XPLgh@+=PGvCOui7;zQN`7XvZ*PTh+sKgR%@p6s2Fih z&Yn19%w)4$jE6F-G?Y(h5x>*o(0prZ4(T`KIFHd}?$1O-nKi|2$;$6?}T}$g&o7x~SLlvR1U$7w|(*JIi=pa6T?2d>R?B zPYLNwL37M;rRghuqWZ1EyBG(F)NPW**OwR>e5yU$ds2+6D#j$aK9$_hv~U&#VSp>2yURdS{)xLp<@u+L+;Y=k&7aRT z4y$;wX|=3YHB%HA*Kv|~cFN8Z?T7f$WyXi#yvt4Y&>C-(W_9vn!yI%Dq)XcY=^je^ z*Y+4Y9oHhVE)ja^Z8va@&VU%*xL2R6)l9b9jgP@Xw+tNZ@7K!%>^9W_N}ns>D|T!U zFW>TdAD3;uJoAEE6U$ZffZAJx^+Cl)(J&df*WM_5!dA+Ba5e%7k$hVrlAd3MS&}l4 zfkcXV4M9CWN1IDSQVEse#ELU{DQ=R8!n>>WSbaO%*|sQZ#G$0x^-c%USIG-Cm2JwN7fZe3Y66czE0I}};ONz4vA08VizGyk`m)zgf&p3gRT zG#ks;e=maIr}bGx^*kS$I_E=JQrg8HJWJ*Xv!<4}L=5%XCR>4NFy-O1xaVPpiD`P1 z{aV0EQKNjLr(h4s1rE}nx8m1F&KUUJSmP}NzkVAJ7|EI94jEZAh*5}|726xHe4l@jp!QEkS83@jxgAE=C z1RXqRkO09UxF-Zca1R6v4gmr|g9e8@&Xu?BTlc{nv*naxP zb5PcdU2>jOf0klJBBQpe)|7mdf6x;~4brPyo0Q(Q zkPL#~-#1Pq8tdyBjgLQLgYGOh|1!sB?Ov7Cebwkbz!FLWZ@?{36?A^&pB|C6O-8mf znz~;P$8N)R_lKhz1`bBc`3l=)W0yOke26$^XM8|T#KEAis)zBMt5#DRw zYG^(E-Gbcc4`BC_sdhnz7S4NVf9gmGX+rv(F3ledwvoL#0c(k~vfBjl5Zgs|tXjR` zP`|SwO6Tw8%39!K)O)>v?0PEeU|1>3>14o>3VUeoV>^%t@2*mgz3#a0IHW@0)%m^c zi6Bd_IJ<9*DGRv^s}dUt+}IKMFa_xra$5hnrLt~s*Bv6mbn*svkNrJrm&CYJ)wx8K zk;Mg{hJwC%sY0;|JT3Y3Xj2)!&w-I!AaA!oF?MrBEo@$13}1BG%45A)W2(nk6yQh>DC@v&CfaV9l!cKALd)szuW9drkFqiZOPTSg9_P2>tg%WwH(y(d^q zLpQXNJ$VzUml%HEKkgHeE^ga)yMDxEp1dI?xl%T4*=OSx_%+gwCGU&b*yBw735WI* zhbEoz#K{kLoanKyU*<_$GmbDDccNcMvr%o5B)mtk?S5HiE9_%J-|uIVjyyyp6WUpB zFDp;-38&=iD4wU#q0}YA{j`&fBz?c-il5-MDgsjY?Kru6&V5b<#(H%j@{TjWR*vtf zdr*~^plo3Rk@vAD)mNmbysCcZP094*otKfqIeWkw?LijbyaR*tn1g_L(Krt*{ zWVD2X!jktCYqac?hkK(F;b^#Fp-L56TMgG9MEU(s6kTN@ARXatyCw?OEJUz^kzakN6BVyN`A(lNqi7U9rk%K3e9)-z{bUONV8Hi zdG?}@@sHZ;7u&yrZl8xgb|}`)(p?w#*y%VOm z=`%>%QbCMA5wGaq-pNmTNKM#ESoUlIeL~m|F4@yU}sv63QZk$W5UGt!7 zA6si?z+3n>1B3RiQ9eDTM23YsV1%!C;XiOatbM9TyS$K4J!2DM*mKpLET^zhTo?flf#5Xfgj!XevM9Q8mo7=LeuCW z@+9Z+Y<(b#jnFf(Lq}4g=j8#Egv0PY>+(4+f%mH;RB1h7FQ9J}#LxkAZ~SO}UdYpS z=-P+t@uvY_vR}x8V=Lzfy*`7`JxD-f>?o27%>o(y4hrw_2271^%r(J}YytYSJ!wN) zNP>rA2++_ULFGTKr;Y(pmtt^>QfDTn07{T{WnR8x!&}}PS#v# z1Nuj6-^d}Ewd8tGe|?NqUbp&T1qr28sb=6C?1&7fEfaZ(sn1E{DaVS7 znVH#z{|q1wFby(V#23OP8Y^7OPsB*4Q60150X(uq1d|+NgE2d?hlZ8gCu)stmHpVFzU`&n8)hl}}D_mFYv zoBe$OFpGMzJqGQsk&J7GgvZL!bGQ}FXsU7e<Z1H*2>$UssWqqA|LpNY2!zi?qu`_sTU(NFcHR!MVs;cvxnYClB&4o`|^! z@|Bi8XZO4fGx(iM?~X5P`#YIe)@`R>sWp(8>n880w#3MG zRrIbtPvIE%6i;D||BpmJ?qdO;;iig>??0tQ8a{kH9eF1$RMSGtJmpa`*7xVK?Q4as zQz>H3HbRwHus9Vj9iOHQdG;U7YS0LX*if?7J2^H2^Y@DC+008$Zfz5+iLHU@ za~qhEw)^E7ve(?5Od=?pfa9)a6W1C1r~wHGY)# z$V6c|>E&LV=*#xu>tIovrQ(pEDhIAJG+L6Lnd_08_3~6UeSC_XJ5x+sObwve6H0m; zeXjnmbsQ^Q3j1ui`7bFyMMb(!xw5Y}obcI=T)M8v9Zmq8H%XZ`s*zzQuVqR3yN69U zc&EfoA`-}`d$D&51&q|thUNu3!|!50cAsp!TxW0c2y4=p2F!C-WuAXwH#km;J;xS7 zk(nbIU1v{}sB;VGz2V1-rev;ZB7kTlF^qs%=W(gCt)KMETq7 zZd9@c!-@{I&5{#qy4d8n`ieJ9#ySyhbMnaa$=*F@O6Jns3Dai4hf?3#Lg%UG z8PkZGde(g`4c$>3-(IRU!?Sn({oZdm7l-qZPp0hMipIgAGbdEy)lGkTW|N*S4lL?% zjrK3jT1VIzPX#zzv%yy1swZ2Gl-4a2l!`nLq?_(Ca$;{H_SyA0u&SO`Pf%nkY_b*Y zDgg5w(dzOa$%33=H7B{1=fr%Mg4>Y&4lYCGRO2htwl?0%_r?Y)N{3t+__|DkZO5ND z+#m6kmsg!A$#4nYTvTi}BVq&V)QL7!Ct?K!^|eLrEgkiJ9M)tka(C$NC^tFImKx$L z%^h#Qt~~X;fy_R^e=-vnr`;ein>5&=V^2TOYw$m;fd*84Ls6CKDo-394o2?g;Q?DS&sM%JK-B(^_y#X<&WtXjuGwVxKIWAic ztJ|DoMjKT_tJ~;V_67c&O!sLh`RfkU@--Qh#Zkpj z^D#6WwPTEHFr@jz*5QP>=MK@Ba{J?*5F)YlEMT5!+Z%%DGJZ+QPHFZP2W$6U*7H)< zlVWBTCP^o&@t+XEeqtVPJGtrvkv1wIY0f1tUD}|##QXdc<=J?l6mN#ceysQIM=HsardyLlA<@EvX*6d z6a}^90nZ;L|B!o(8Ehqt0zm~7!rI&Y;myPJKZj24Zi?34 z2q2fDxDW^|3W5p=fuDe$2tjy3LYyEF=YyP+cp&V6g38uNF9d<0j)9Vi zfU*w~X=Cl?_LnXOj!s^{hx_k3@d1qxD6faDfe-<(Fjz!PNK}MhNI>+Ty8cZ=pr(^A z0{Ebmh@c(9S5OgQ<7Dl|4RrCa_IC6_SOb0kv4p6AkO25Wg8Q%P6@;7JU%*Sze+gg= z0z42vL2VmneQ#F+K^PDM{tJbP^6~}>K|~1t5xW4vU|})f->(0R(R^6j%lpB8t_1!I zeI~B%i=rg=W0sze$sZ+Na_G{++@<1Ew%kg9XCKeboehn83H%1fJH(*#ij%6X`6&pp2YhUz^TUNR5B;iR1 zjNxR1I$46Sh*CY$Yw5SLs1)Y6U(g;{bh6Q@Tge>kjD7hW{O=>`ivHAR=6Y4gqOzP{ zv@L6)dX`?a-NszDFF}{g`b|!~n-z1NH_FF__G?}?^hr@z@wZ}>fZwx&b;(|2IX6@ls6)$~>t5P>Rsw!;rT!NFel8jHa%Z>B7As1oc&nJktPQXlK%`KGjUnO%4S z95XM#!Mk`vaKuN~IJrE{9Cw?o`U^Ah!6-FxmO~CT?J1Tl2n|ioNk@Kd@9$KZqG@?; z+E!g2yiFs*(24QtFYu}yooA(M<=>*4Ti+|=-Qb0o;~P6K;NBb0`($^j#p&1M-Pltc zZN`xZ*M7?RCF2(P4Iwig72QxAglu=nTl9Yo*l6-rUdoH*xqW3!{B zYN|7CjYh@|`I-Txrog9IK`o~v)6b2~%t*~?b6xmOEuZcAzFV|>bvG}w$}+H5obm19 zFXW}0sc|*T54+)q-gnIElu07*pIXSbV|_s;5b z#0tOYcAy5Q7SU~iVD`4)Z3*rH$d;1!duLlSw{uoFV)MKXRmxig5QV5 zOXFp&TxEhQoI73|IWyH|7iZz$S;Jx!|U6?TdO@@dOY8C zwB4<$3WOFVU*0CZ;csAYDVAk#c%+MoaFiu7sDA%Nr+^?$)?^7z`!co5Nu18CyCis5 zU^9h!U-mV@kV+)BJ{ok2p zu%@5A#4eT8_&quZKU~n&OA~8Z^Uhp`BRiF(n_A-rivs#Q_c`)3`_H=qdY8Muc3{j#+3Gj>+MaKL_aP|Xb+AR^<@v%&HmXQ$g3w;SiP_*~i?r|i4sDKvCv z13aTMn5mq}!^yN;Ow|mlc%HNLEcGDdmc@#D=@@c}S41ootUUW+`*6c%Io(6&3k&wx zk#{Xp;B$e$y8hn2S50!k{Lor9g+6X|a9?l58x3b}%BdVk^&@>yad))SpA7K^v9x_e zr{O>=xqM;r0(|P0(r0V77{)A}jWL1u#7y;$Jf}QEHpQ;L)p1&GYQ%_(#;N^Q5Aqeq z`ZRvZ>7?@xBJ?EBaHY&_AnhZ*Xd|K08_zc_GgF{ON;S>vR|#{P zk$IkXxXSmrh5y0%L;ua!ndl+x2?W*L>=6DyGZ0W%%$z{*0q%!*cp?xq2AY9^LO}4t zoSwV8H&FO5U-|H1r{-qw4ix_DB>8Kg2sD!fLlwZvP(h9<;0r-d*%uxhK-3}kxVE1_nyj;4MpVRPTJ*%&Z<~I> zcYSQW #include +#include +#include + + namespace cv { @@ -66,12 +70,27 @@ class CV_EXPORTS_W BaseOCR { public: virtual ~BaseOCR() {}; + virtual void run(Mat& image, std::string& output_text, std::vector* component_rects=NULL, std::vector* component_texts=NULL, std::vector* component_confidences=NULL, int component_level=0) = 0; virtual void run(Mat& image, Mat& mask, std::string& output_text, std::vector* component_rects=NULL, std::vector* component_texts=NULL, std::vector* component_confidences=NULL, int component_level=0) = 0; + + /** @brief Main functionality of the OCR Hierarchy. Subclasses provide default parameters for + * all parameters other than the input image. + */ + virtual String run(InputArray image){ + std::string res; + std::vector component_rects; + std::vector component_confidences; + std::vector component_texts; + Mat inputImage=image.getMat(); + this->run(inputImage,res,&component_rects,&component_texts,&component_confidences,OCR_LEVEL_WORD); + return res; + } + }; /** @brief OCRTesseract class provides an interface with the tesseract-ocr API (v3.02.02) in C++. @@ -337,6 +356,11 @@ CV_EXPORTS_W Mat createOCRHMMTransitionsTable(const String& vocabulary, std::vec be found at the demo sample: */ + + +/* Forward declaration of class that can be used to generate an OCRBeamSearchDecoder::ClassifierCallbac */ +class TextImageClassifier; + class CV_EXPORTS_W OCRBeamSearchDecoder : public BaseOCR { public: @@ -364,8 +388,8 @@ class CV_EXPORTS_W OCRBeamSearchDecoder : public BaseOCR */ virtual void eval( InputArray image, std::vector< std::vector >& recognition_probabilities, std::vector& oversegmentation ); - int getWindowSize() {return 0;} - int getStepSize() {return 0;} + virtual int getWindowSize() {return 0;} + virtual int getStepSize() {return 0;} }; public: @@ -421,6 +445,7 @@ class CV_EXPORTS_W OCRBeamSearchDecoder : public BaseOCR @param beam_size Size of the beam in Beam Search algorithm. */ + static Ptr create(const Ptr classifier,// The character classifier with built in feature extractor const std::string& vocabulary, // The language vocabulary (chars when ascii english text) // size() must be equal to the number of classes @@ -441,6 +466,42 @@ class CV_EXPORTS_W OCRBeamSearchDecoder : public BaseOCR int mode = OCR_DECODER_VITERBI, // HMM Decoding algorithm (only Viterbi for the moment) int beam_size = 500); // Size of the beam in Beam Search algorithm + /** @brief + + @param classifier The character classifier with built in feature extractor + + @param alphabet The language alphabet one char per symbol. alphabet.size() must be equal to the number of classes + of the classifier + + @param transition_probabilities_table Table with transition probabilities between character + pairs. cols == rows == alphabet.size(). + + @param emission_probabilities_table Table with observation emission probabilities. cols == + rows == alphabet.size(). + + @param windowWidth The width of the windows to which the sliding window will be iterated. The height will + be the height of the image. The windows might be resized to fit the classifiers input by the classifiers preprocessor + + @param windowStep The step for the sliding window + + @param mode HMM Decoding algorithm (only Viterbi for the moment) + + @param beam_size Size of the beam in Beam Search algorithm + */ + CV_WRAP static Ptr create(const Ptr classifier, // The character classifier with built in feature extractor + String alphabet, // The language alphabet one char per symbol + // size() must be equal to the number of classes + InputArray transition_probabilities_table, // Table with transition probabilities between character pairs + // cols == rows == alphabet.size() + InputArray emission_probabilities_table, // Table with observation emission probabilities + // cols == rows == alphabet.size() + int windowWidth, // The width of the windows to which the sliding window will be iterated. + // The height will be the height of the image. The windows might be resized to + // fit the classifiers input by the classifiers preprocessor + int windowStep = 1 , // The step for the sliding window + int mode = OCR_DECODER_VITERBI, // HMM Decoding algorithm (only Viterbi for the moment) + int beam_size = 500); // Size of the beam in Beam Search algorithm + protected: Ptr classifier; @@ -465,6 +526,314 @@ CV_EXPORTS_W Ptr loadOCRBeamSearchClas //! @} + +//Classifiers should provide diferent backends +//For the moment only caffe is implemeted +enum{ + OCR_HOLISTIC_BACKEND_NONE, + OCR_HOLISTIC_BACKEND_CAFFE +}; + +class TextImageClassifier; + +/** + * @brief The ImagePreprocessor class + */ +class CV_EXPORTS_W ImagePreprocessor{ +protected: + virtual void preprocess_(const Mat& input,Mat& output,Size outputSize,int outputChannels)=0; + +public: + virtual ~ImagePreprocessor(){} + + /** @brief this method in provides public acces to the preprocessing with respect to a specific + * classifier + * + * This method's main use would be to use the preprocessor without a classifier. + * + * @param input + * + * @param output + * + * @param sz + * + * @param outputChannels + */ + CV_WRAP void preprocess(InputArray input,OutputArray output,Size sz,int outputChannels); + + /** @brief + * + * @return shared pointer to generated preprocessor + */ + CV_WRAP static Ptr createResizer(); + + /** @brief + * + * @return shared pointer to generated preprocessor + */ + CV_WRAP static Ptr createImageStandarizer(double sigma); + + /** @brief + * + * @return shared pointer to generated preprocessor + */ + CV_WRAP static Ptr createImageMeanSubtractor(InputArray meanImg); + + friend class TextImageClassifier; +}; + +/** @brief Abstract class that implements the classifcation of text images. + * + * The interface is generic enough to describe any image classifier. And allows + * to take advantage of compouting in batches. While word classifiers are the default + * networks, any image classifers should work. + * + */ +class CV_EXPORTS_W TextImageClassifier +{ +protected: + Size inputGeometry_; + int channelCount_; + Ptr preprocessor_; + /** @brief all image preprocessing is handled here including whitening etc. + * + * @param input the image to be preprocessed for the classifier. If the depth + * is CV_U8 values should be in [0,255] otherwise values are assumed to be in [0,1] + * + * @param output reference to the image to be fed to the classifier, the preprocessor will + * resize the image to the apropriate size and convert it to the apropriate depth\ + * + * The method preprocess should never be used externally, it is up to classify and classifyBatch + * methods to employ it. + */ + virtual void preprocess(const Mat& input,Mat& output); +public: + virtual ~TextImageClassifier() {} + + /** @brief + */ + CV_WRAP virtual void setPreprocessor(Ptr ptr); + + /** @brief + */ + CV_WRAP Ptr getPreprocessor(); + + /** @brief produces a class confidence row-vector given an image + */ + CV_WRAP virtual void classify(InputArray image, OutputArray classProbabilities) = 0; + + /** @brief produces a matrix containing class confidence row-vectors given an collection of images + */ + CV_WRAP virtual void classifyBatch(InputArrayOfArrays image, OutputArray classProbabilities) = 0; + + /** @brief simple getter method returning the number of channels each input sample has + */ + CV_WRAP virtual int getInputChannelCount(){return this->channelCount_;} + + /** @brief simple getter method returning the size of the input sample + */ + CV_WRAP virtual Size getInputSize(){return this->inputGeometry_;} + + /** @brief simple getter method returning the size of the oputput row-vector + */ + CV_WRAP virtual int getOutputSize()=0; + + /** @brief simple getter method returning the size of the minibatches for this classifier. + * If not applicabe this method should return 1 + */ + CV_WRAP virtual int getMinibatchSize()=0; + + friend class ImagePreprocessor; +}; + + + +class CV_EXPORTS_W DeepCNN:public TextImageClassifier +{ + /** @brief Class that uses a pretrained caffe model for word classification. + * + * This network is described in detail in: + * Max Jaderberg et al.: Reading Text in the Wild with Convolutional Neural Networks, IJCV 2015 + * http://arxiv.org/abs/1412.1842 + */ +public: + virtual ~DeepCNN() {}; + + /** @brief Constructs a DeepCNN object from a caffe pretrained model + * + * @param archFilename is the path to the prototxt file containing the deployment model architecture description. + * + * @param weightsFilename is the path to the pretrained weights of the model in binary fdorm. This file can be + * very large, up to 2GB. + * + * @param minibatchSz the maximum number of samples that can processed in parallel. In practice this parameter + * has an effect only when computing in the GPU and should be set with respect to the memory available in the GPU. + * + * @param backEnd integer parameter selecting the coputation framework. For now OCR_HOLISTIC_BACKEND_CAFFE is + * the only option + */ + CV_WRAP static Ptr create(String archFilename,String weightsFilename,Ptr preprocessor,int minibatchSz=100,int backEnd=OCR_HOLISTIC_BACKEND_CAFFE); + + CV_WRAP static Ptr createDictNet(String archFilename,String weightsFilename,int backEnd=OCR_HOLISTIC_BACKEND_CAFFE); + +}; + + +/** @brief Prompts Caffe on the computation device beeing used + * + * Caffe can only be controlled globally on whether the GPU or the CPU is used has a + * global behavior. This function queries the current state of caffe. + * If the module is built without caffe, this method throws an exception. + * + * @return true if caffe is computing on the GPU, false if caffe is computing on the CPU + */ +CV_EXPORTS_W bool getCaffeGpuMode(); + +/** @brief Sets the computation device beeing used by Caffe + * + * Caffe can only be controlled globally on whether the GPU or the CPU is used has a + * global behavior. This function queries the current state of caffe. + * If the module is built without caffe, this method throws an exception. + * + * @param useGpu set to true for caffe to be computing on the GPU, false if caffe is + * computing on the CPU + */ +CV_EXPORTS_W void setCaffeGpuMode(bool useGpu); + +/** @brief Provides runtime information on whether Caffe support was compiled in. + * + * The text module API is the same regardless of whether CAffe was available or not + * During compilation. When methods that require Caffe are invocked while Caffe support + * is not compiled in, exceptions are thrown. This method allows to test whether the + * text module was built with caffe during runtime. + * + * @return true if Caffe support for the the text module was provided during compilation, + * false if Caffe was unavailable. + */ +CV_EXPORTS_W bool getCaffeAvailable(); + + +/** @brief OCRHolisticWordRecognizer class provides the functionallity of segmented wordspotting. + * Given a predefined vocabulary , a TextImageClassifier is employed to select the most probable + * word given an input image. + * + * This class implements the logic of providing transcriptions given a vocabulary and and an image + * classifer. + */ +class CV_EXPORTS_W OCRHolisticWordRecognizer : public BaseOCR +{ +public: + virtual void run(Mat& image, std::string& output_text, std::vector* component_rects=NULL, + std::vector* component_texts=NULL, std::vector* component_confidences=NULL, + int component_level=OCR_LEVEL_WORD)=0; + + /** @brief Recognize text using a segmentation based word-spotting/classifier cnn. + + Takes image on input and returns recognized text in the output_text parameter. Optionally + provides also the Rects for individual text elements found (e.g. words), and the list of those + text elements with their confidence values. + + @param image Input image CV_8UC1 or CV_8UC3 + + @param mask is totally ignored and is only available for compatibillity reasons + + @param output_text Output text of the the word spoting, always one that exists in the dictionary. + + @param component_rects Not applicable for word spotting can be be NULL if not, a single elemnt will + be put in the vector. + + @param component_texts Not applicable for word spotting can be be NULL if not, a single elemnt will + be put in the vector. + + @param component_confidences Not applicable for word spotting can be be NULL if not, a single elemnt will + be put in the vector. + + @param component_level must be OCR_LEVEL_WORD. + */ + + virtual void run(Mat& image, Mat& mask, std::string& output_text, std::vector* component_rects=NULL, + std::vector* component_texts=NULL, std::vector* component_confidences=NULL, + int component_level=OCR_LEVEL_WORD)=0; + + + /** + @brief Method that provides a quick and simple interface to a single word image classifcation + + @param inputImage an image expected to be a CV_U8C1 or CV_U8C3 of any size assumed to contain a single word + + @param transcription an opencv string that will store the detected word transcription + + @param confidence a double that will be updated with the confidence the classifier has for the selected word + */ + CV_WRAP virtual void recogniseImage(InputArray inputImage,CV_OUT String& transcription,CV_OUT double& confidence)=0; + + /** + @brief Method that provides a quick and simple interface to a multiple word image classifcation taking advantage + the classifiers parallel capabilities. + + @param inputImageList an list of images expected to be a CV_U8C1 or CV_U8C3 each image can be of any size and is assumed + to contain a single word. + + @param transcriptions a vector of opencv strings that will store the detected word transcriptions, one for each + input image + + @param confidences a vector of double that will be updated with the confidence the classifier has for each of the + selected words. + */ + CV_WRAP virtual void recogniseImageBatch(InputArrayOfArrays inputImageList,CV_OUT std::vector& transcriptions,CV_OUT std::vector& confidences)=0; + + + /** + @brief simple getted for the vocabulary employed + */ + CV_WRAP virtual const std::vector& getVocabulary()=0; + + /** @brief + */ + CV_WRAP virtual Ptr getClassifier()=0; + + /** @brief Creates an instance of the OCRHolisticWordRecognizer class. + + @param classifierPtr an instance of TextImageClassifier, normaly a DeepCNN instance + @param vocabularyFilename the relative or absolute path to the file containing all words in the vocabulary. Each text line + in the file is assumed to be a single word. The number of words in the vocabulary must be exactly the same as the outputSize + of the classifier. + */ + CV_WRAP static Ptr create(Ptr classifierPtr,String vocabularyFilename); + + + /** @brief Creates an instance of the OCRHolisticWordRecognizer class and implicitly also a DeepCNN classifier. + + @param modelArchFilename the relative or absolute path to the prototxt file describing the classifiers architecture. + @param modelWeightsFilename the relative or absolute path to the file containing the pretrained weights of the model in caffe-binary form. + @param vocabularyFilename the relative or absolute path to the file containing all words in the vocabulary. Each text line + in the file is assumed to be a single word. The number of words in the vocabulary must be exactly the same as the outputSize + of the classifier. + */ + CV_WRAP static Ptr create(String modelArchFilename, String modelWeightsFilename, String vocabularyFilename); + + /** @brief + * + * @param classifierPtr + * + * @param vocabulary + */ + CV_WRAP static Ptr create(Ptr classifierPtr,const std::vector& vocabulary); + + /** @brief + * + * @param modelArchFilename + * + * @param modelWeightsFilename + * + * @param vocabulary + */ + CV_WRAP static Ptr create(String modelArchFilename, String modelWeightsFilename, const std::vector& vocabulary); +}; + + } } + + #endif // _OPENCV_TEXT_OCR_HPP_ diff --git a/modules/text/include/opencv2/text/text_synthesizer.hpp b/modules/text/include/opencv2/text/text_synthesizer.hpp new file mode 100644 index 00000000000..bc100aa85c0 --- /dev/null +++ b/modules/text/include/opencv2/text/text_synthesizer.hpp @@ -0,0 +1,139 @@ +#ifndef TEXT_SYNTHESIZER_HPP +#define TEXT_SYNTHESIZER_HPP + + + +namespace cv +{ +namespace text +{ + +enum{ + CV_TEXT_SYNTHESIZER_SCRIPT_ANY=1, + CV_TEXT_SYNTHESIZER_SCRIPT_LATIN=2, + CV_TEXT_SYNTHESIZER_SCRIPT_GREEK=3, + CV_TEXT_SYNTHESIZER_SCRIPT_CYRILLIC=4, + CV_TEXT_SYNTHESIZER_SCRIPT_ARABIC=5, + CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW=6 +}; + +//TextSynthesizer::blendRandom depends upon these +//enums and should be updated if the change +enum { + CV_TEXT_SYNTHESIZER_BLND_NORMAL = 100, + CV_TEXT_SYNTHESIZER_BLND_OVERLAY = 200 +}; + +enum { + CV_TEXT_SYNTHESIZER_BLND_A_MAX=0, + CV_TEXT_SYNTHESIZER_BLND_A_MULT=1, + CV_TEXT_SYNTHESIZER_BLND_A_SUM=2, + CV_TEXT_SYNTHESIZER_BLND_A_MIN=3, + CV_TEXT_SYNTHESIZER_BLND_A_MEAN=4 +}; + +/** @brief class that renders synthetic text images for training a CNN on + * word spotting + * + * This functionallity is based on "Synthetic Data and Artificial Neural + * Networks for Natural Scene Text Recognition" by Max Jaderberg. + * available at + * + * @note + * - (Python) a demo generating some samples in Greek can be found in: + * + */ +class CV_EXPORTS_W TextSynthesizer{ +protected: + int resHeight_; + int maxResWidth_; + + double underlineProbabillity_; + double italicProbabillity_; + double boldProbabillity_; + double maxPerspectiveDistortion_; + + double shadowProbabillity_; + double maxShadowOpacity_; + int maxShadowSize_; + int maxShadowHoffset_; + int maxShadowVoffset_; + + double borderProbabillity_; + int maxBorderSize_; + + double curvingProbabillity_; + double maxHeightDistortionPercentage_; + double maxCurveArch_; + + double finalBlendAlpha_; + double finalBlendProb_; + TextSynthesizer(int maxSampleWidth,int sampleHeight); +public: + CV_WRAP int getMaxSampleWidth(){return maxResWidth_;} + CV_WRAP int getSampleHeight(){return resHeight_;} + + CV_WRAP double getUnderlineProbabillity(){return underlineProbabillity_;} + CV_WRAP double getItalicProballity(){return italicProbabillity_;} + CV_WRAP double getBoldProbabillity(){return boldProbabillity_;} + CV_WRAP double getMaxPerspectiveDistortion(){return maxPerspectiveDistortion_;} + + CV_WRAP double getShadowProbabillity(){return shadowProbabillity_;} + CV_WRAP double getMaxShadowOpacity(){return maxShadowOpacity_;} + CV_WRAP int getMaxShadowSize(){return maxShadowSize_;} + CV_WRAP int getMaxShadowHoffset(){return maxShadowHoffset_;} + CV_WRAP int getMaxShadowVoffset(){return maxShadowVoffset_;} + + CV_WRAP double getBorderProbabillity(){return borderProbabillity_;} + CV_WRAP int getMaxBorderSize(){return maxBorderSize_;} + + CV_WRAP double getCurvingProbabillity(){return curvingProbabillity_;} + CV_WRAP double getMaxHeightDistortionPercentage(){return maxHeightDistortionPercentage_;} + CV_WRAP double getMaxCurveArch(){return maxCurveArch_;} + CV_WRAP double getBlendAlpha(){return finalBlendAlpha_;} + CV_WRAP double getBlendProb(){return finalBlendProb_;} + + CV_WRAP void setUnderlineProbabillity(double v){underlineProbabillity_=v;} + CV_WRAP void setItalicProballity(double v){italicProbabillity_=v;} + CV_WRAP void setBoldProbabillity(double v){boldProbabillity_=v;} + CV_WRAP void setMaxPerspectiveDistortion(double v){maxPerspectiveDistortion_=v;} + + CV_WRAP void setShadowProbabillity(double v){shadowProbabillity_=v;} + CV_WRAP void setMaxShadowOpacity(double v){maxShadowOpacity_=v;} + CV_WRAP void setMaxShadowSize(int v){maxShadowSize_=v;} + CV_WRAP void setMaxShadowHoffset(int v){maxShadowHoffset_=v;} + CV_WRAP void setMaxShadowVoffset(int v){maxShadowVoffset_=v;} + + CV_WRAP void setBorderProbabillity(double v){borderProbabillity_=v;} + CV_WRAP void setMaxBorderSize(int v){maxBorderSize_=v;} + + CV_WRAP void setCurvingProbabillity(double v){curvingProbabillity_=v;} + CV_WRAP void setMaxHeightDistortionPercentage(double v){maxHeightDistortionPercentage_=v;} + CV_WRAP void setMaxCurveArch(double v){maxCurveArch_=v;} + CV_WRAP void setBlendAlpha(double v){finalBlendAlpha_=v;} + CV_WRAP void setBlendProb(double v){finalBlendProb_=v;} + + CV_WRAP virtual void addFontFiles(const std::vector& fntList)=0; + CV_WRAP virtual std::vector listAvailableFonts()=0; + + CV_WRAP virtual void addBgSampleImage(const Mat& image)=0; + + + CV_WRAP virtual void getColorClusters(CV_OUT Mat& clusters)=0; + CV_WRAP virtual void setColorClusters(Mat clusters)=0; + + CV_WRAP virtual void generateBgSample(CV_OUT Mat& sample)=0; + + CV_WRAP virtual void generateTxtSample(String caption,CV_OUT Mat& sample,CV_OUT Mat& sampleMask)=0; + + CV_WRAP virtual void generateSample(String caption,CV_OUT Mat& sample)=0; + + CV_WRAP static Ptr create(int script=CV_TEXT_SYNTHESIZER_SCRIPT_LATIN); + virtual ~TextSynthesizer(){} +}; + + + +}}//text //cv + +#endif // TEXT_SYNTHESIZER_HPP diff --git a/modules/text/samples/cropped_word_recognition.cpp b/modules/text/samples/cropped_word_recognition.cpp index 32e3570e586..78f3862a66c 100644 --- a/modules/text/samples/cropped_word_recognition.cpp +++ b/modules/text/samples/cropped_word_recognition.cpp @@ -36,7 +36,7 @@ int main(int argc, char* argv[]) return(0); } - string vocabulary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // must have the same order as the clasifier output classes + string vocabulary = "##0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";//"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // must have the same order as the clasifier output classes vector lexicon; // a list of words expected to be found on the input image lexicon.push_back(string("abb")); lexicon.push_back(string("riser")); @@ -62,11 +62,16 @@ int main(int argc, char* argv[]) Mat emission_p = Mat::eye(62,62,CV_64FC1); + Ptr ocr = OCRBeamSearchDecoder::create( + loadOCRBeamSearchClassifierCNN("OCRBeamSearch_CNN_model_data.xml.gz"), + vocabulary, transition_p, emission_p, OCR_DECODER_VITERBI, 50); + + // Notice we set here a beam size of 50. This is much faster than using the default value (500). // 50 works well with our tiny lexicon example, but may not with larger dictionaries. - Ptr ocr = OCRBeamSearchDecoder::create( +/* Ptr ocr = OCRBeamSearchDecoder::create( loadOCRBeamSearchClassifierCNN("OCRBeamSearch_CNN_model_data.xml.gz"), - vocabulary, transition_p, emission_p, OCR_DECODER_VITERBI, 50); + vocabulary, transition_p, emission_p, OCR_DECODER_VITERBI, 50);*/ double t_r = (double)getTickCount(); string output; diff --git a/modules/text/samples/dictnet_demo.cpp b/modules/text/samples/dictnet_demo.cpp new file mode 100644 index 00000000000..10133c2d9de --- /dev/null +++ b/modules/text/samples/dictnet_demo.cpp @@ -0,0 +1,95 @@ +/* + * dictnet_demo.cpp + * + * Demonstrates simple use of the holistic word classifier in C++ + * + * Created on: June 26, 2016 + * Author: Anguelos Nicolaou + */ + +#include "opencv2/text.hpp" +#include "opencv2/highgui.hpp" +#include "opencv2/imgproc.hpp" + +#include +#include +#include +#include +#include + +inline std::string getHelpStr(std::string progFname){ + std::stringstream out; + out << " Demo of wordspotting CNN for text recognition." << std::endl; + out << " Max Jaderberg et al.: Reading Text in the Wild with Convolutional Neural Networks, IJCV 2015"< ... " << std::endl; + out << " Caffe Model files (dictnet_vgg.caffemodel, dictnet_vgg_deploy.prototxt, dictnet_vgg_labels.txt)"< imageList; + for(int imageIdx=2;imageIdx cnn=cv::text::DeepCNN::createDictNet( + "dictnet_vgg_deploy.prototxt","dictnet_vgg.caffemodel"); + + cv::Ptr wordSpotter= + cv::text::OCRHolisticWordRecognizer::create(cnn,"dictnet_vgg_labels.txt"); + + std::vector wordList; + std::vector outProbabillities; + wordSpotter->recogniseImageBatch(imageList,wordList,outProbabillities); + + std::ofstream out; + out.open(argv[1]); + for(int imgIdx=0;imgIdx #include #include +#define DBG(msg) (std::cerr<<"DBG L:"<<__LINE__<<"\t"<* co vector* component_texts, vector* component_confidences, int component_level) { + DBG("RUN START\n"); CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) ); CV_Assert( (component_level == OCR_LEVEL_TEXTLINE) || (component_level == OCR_LEVEL_WORD) ); output_text.clear(); @@ -76,6 +80,7 @@ void OCRBeamSearchDecoder::run(Mat& image, Mat& mask, string& output_text, vecto vector* component_texts, vector* component_confidences, int component_level) { + DBG("RUN START\n"); CV_Assert(mask.type() == CV_8UC1); CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) ); CV_Assert( (component_level == OCR_LEVEL_TEXTLINE) || (component_level == OCR_LEVEL_WORD) ); @@ -90,11 +95,13 @@ void OCRBeamSearchDecoder::run(Mat& image, Mat& mask, string& output_text, vecto CV_WRAP String OCRBeamSearchDecoder::run(InputArray image, int min_confidence, int component_level) { + DBG("RUN START\n"); std::string output1; std::string output2; vector component_texts; vector component_confidences; Mat image_m = image.getMat(); + DBG("RUN 1\n"); run(image_m, output1, NULL, &component_texts, &component_confidences, component_level); for(unsigned int i = 0; i < component_texts.size(); i++) { @@ -109,6 +116,7 @@ CV_WRAP String OCRBeamSearchDecoder::run(InputArray image, int min_confidence, i CV_WRAP String OCRBeamSearchDecoder::run(InputArray image, InputArray mask, int min_confidence, int component_level) { + DBG("RUN START\n"); std::string output1; std::string output2; vector component_texts; @@ -130,6 +138,7 @@ CV_WRAP String OCRBeamSearchDecoder::run(InputArray image, InputArray mask, int void OCRBeamSearchDecoder::ClassifierCallback::eval( InputArray image, vector< vector >& recognition_probabilities, vector& oversegmentation) { + DBG("eval callback START\n"); CV_Assert(( image.getMat().type() == CV_8UC3 ) || ( image.getMat().type() == CV_8UC1 )); if (!recognition_probabilities.empty()) { @@ -189,7 +198,6 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder ~OCRBeamSearchDecoderImpl() { } - void run( Mat& src, Mat& mask, string& out_sequence, @@ -198,6 +206,7 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder vector* component_confidences, int component_level) { + DBG("RUN START\n"); CV_Assert(mask.type() == CV_8UC1); //nothing to do with a mask here run( src, out_sequence, component_rects, component_texts, component_confidences, @@ -211,7 +220,7 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder vector* component_confidences, int component_level) { - + DBG("RUN START\n"); CV_Assert( (src.type() == CV_8UC1) || (src.type() == CV_8UC3) ); CV_Assert( (src.cols > 0) && (src.rows > 0) ); CV_Assert( component_level == OCR_LEVEL_WORD ); @@ -228,16 +237,28 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder cvtColor(src,src,COLOR_RGB2GRAY); } - + DBG("RUN 1\n"); // TODO if input is a text line (not a word) we may need to split into words here! // do sliding window classification along a croped word image classifier->eval(src, recognition_probabilities, oversegmentation); - + DBG("RUN 1.5\n");std::map m;m[0]='#';m[1]='@';m[2]='0';m[3]='1';m[4]='2';m[5]='3';m[6]='4';m[7]='5';m[8]='6';m[9]='7';m[10]='8';m[11]='9';m[12]='A';m[13]='a';m[14]='B';m[15]='b';m[16]='C';m[17]='c';m[18]='D';m[19]='d';m[20]='E';m[21]='e';m[22]='F';m[23]='f';m[24]='G';m[25]='g';m[26]='H';m[27]='h';m[28]='I';m[29]='i';m[30]='J';m[31]='j';m[32]='K';m[33]='k';m[34]='L';m[35]='l';m[36]='M';m[37]='m';m[38]='N';m[39]='n';m[40]='O';m[41]='o';m[42]='P';m[43]='p';m[44]='Q';m[45]='q';m[46]='R';m[47]='r';m[48]='S';m[49]='s';m[50]='T';m[51]='t';m[52]='U';m[53]='u';m[54]='V';m[55]='v';m[56]='W';m[57]='w';m[58]='X';m[59]='x';m[60]='Y';m[61]='y';m[62]='Z';m[63]='z'; + for(int wNum=0;wNum0) && (best_idx == last_best_idx) && (oversegmentation[i]*step_size < oversegmentation[i-1]*step_size + win_size) ) { @@ -272,7 +293,7 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder continue; } } - + DBG("RUN 5\n"); last_best_idx = best_idx; last_best_p = best_p; i++; @@ -291,7 +312,7 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder recognition_probabilities[i][j] = log(recognition_probabilities[i][j]); } } - + DBG("RUN 6\n"); // initialize the beam with all possible character's pairs int generated_chids = 0; for (size_t i=0; ipush_back(Rect(0,0,src.cols,src.rows)); - component_texts->push_back(out_sequence); - component_confidences->push_back((float)exp(lp)); - + if(component_rects!=NULL){ + component_rects->push_back(Rect(0,0,src.cols,src.rows)); + } + DBG("RUN 8.5\n")<push_back(out_sequence); + } + DBG("RUN 8.7 lp")<<(float)exp(lp)<<"\n"; + if(component_confidences!=NULL){ + component_confidences->push_back((float)exp(lp)); + } + DBG("RUN 9\n"); return; } @@ -777,10 +806,124 @@ double OCRBeamSearchClassifierCNN::eval_feature(Mat& feature, double* prob_estim } Ptr loadOCRBeamSearchClassifierCNN(const String& filename) - { return makePtr(std::string(filename)); } + +/* This class is used to bridge the gap between TextImageClassifier and + * OCRBeamSearchDecoder::ClassifierCallback. In practice it implements the logic + * of invocking a TextImageClassifier in a sliding window. Eventually this functionality + * should be moved inside OCRBeamSearchDecoder. The class has no footprint in public API. + * The method could also provide compatibillitywith the OCRHMMDecoder::ClassifierCallback + * but a letter segmenter will be needed. + */ +class TextImageClassifierBeamSearchCallback: public OCRBeamSearchDecoder::ClassifierCallback + //, public OCRHMMDecoder::ClassifierCallback// A letter segmenter will be needed +{ + //TODO: once opencv supports "enable_shared_from_this" this class shoulb be removed from + //the public API (ocr.hpp) and add a single method in TextImageClassifier returning a + //Ptr +protected: + int stepSize_; + int windowWidth_; + Ptr classifier_; +public: + virtual ~TextImageClassifierBeamSearchCallback() { } + + TextImageClassifierBeamSearchCallback(Ptr classifier,int stepSize,int windowWidth) + :classifier_(classifier),stepSize_(stepSize),windowWidth_(windowWidth) + { + if(windowWidth_<=0) + { + windowWidth_=classifier_->getInputSize().width; + }else + { + windowWidth_=windowWidth_; + } + } + + virtual void eval( InputArray _img, std::vector< std::vector >& recognitionProbabilities, std::vector& oversegmentation ) + { + DBG("EVAL 1\n"); + if (!recognitionProbabilities.empty()) + { + for (size_t i=0; i windowList; + int counter=0; + DBG("EVAL 3\n"); + for(int x=0;x+windowWidth_classifier_->classifyBatch(windowList,windowProbabilities); + recognitionProbabilities.resize(windowProbabilities.rows); + for(int windowNum=0;windowNum(windowNum,clNum));//+.02; + } + //recognitionProbabilities[windowNum][0]=0; + //recognitionProbabilities[windowNum][1]=0; + break; + case CV_32F: + for(int clNum=2;clNum(windowNum,clNum));//+.02; + } + //recognitionProbabilities[windowNum][0]=0; + //recognitionProbabilities[windowNum][1]=0; + break; + default: + CV_Error(Error::StsError,"The network outputs should be either float or double!"); + } + } + DBG("EVAL 5\n"); + } + + virtual int getWindowSize(){return stepSize_;} + + virtual int getStepSize(){return windowWidth_;} + + static Ptr create(Ptr classifier,int stepSize=8,int windowWidth=-1); +}; + + +Ptr OCRBeamSearchDecoder::create(const Ptr classifier, + String alphabet, + InputArray transitionProbabilitiesTable, + InputArray emissionProbabilitiesTable, + int windowWidth, + int windowStep, + int mode, + int beamSize){ + Ptr callback= + Ptr(new + TextImageClassifierBeamSearchCallback(classifier,windowStep,windowWidth)); + + return Ptr(new OCRBeamSearchDecoderImpl(callback, + alphabet, + transitionProbabilitiesTable, + emissionProbabilitiesTable, + decoder_mode(mode), + beamSize) + ); +} + + + } } diff --git a/modules/text/src/ocr_holistic.cpp b/modules/text/src/ocr_holistic.cpp new file mode 100644 index 00000000000..c04349f409e --- /dev/null +++ b/modules/text/src/ocr_holistic.cpp @@ -0,0 +1,639 @@ +#include "precomp.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/core.hpp" + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +#ifdef HAVE_CAFFE +#include "caffe/caffe.hpp" +#endif + +#define DBG(msg) (std::cerr<<"DBG L:"<<__LINE__<<"\t"<preprocess_(inpImg,outImg,sz,outputChannels); + outImg.copyTo(output); +} + + +class ResizerPreprocessor: public ImagePreprocessor{ +protected: + void preprocess_(const Mat& input,Mat& output,Size outputSize,int outputChannels){ + //TODO put all the logic of channel and depth conversions in ImageProcessor class + CV_Assert(outputChannels==1 || outputChannels==3); + CV_Assert(input.channels()==1 || input.channels()==3); + if(input.channels()!=outputChannels) + { + Mat tmpInput; + if(outputChannels==1){ + cvtColor(input,tmpInput,COLOR_BGR2GRAY); + if(input.depth()==CV_8U) + { + tmpInput.convertTo(output,CV_32FC1,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + tmpInput.convertTo(output, CV_32FC1); + } + }else + { + cvtColor(input,tmpInput,COLOR_GRAY2BGR); + if(input.depth()==CV_8U) + { + tmpInput.convertTo(output,CV_32FC3,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + tmpInput.convertTo(output, CV_32FC3); + } + } + }else + { + if(input.channels()==1) + { + if(input.depth()==CV_8U) + { + input.convertTo(output, CV_32FC1,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + input.convertTo(output, CV_32FC1); + } + }else + { + if(input.depth()==CV_8U){ + input.convertTo(output, CV_32FC3,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + input.convertTo(output, CV_32FC3); + } + } + } + if(outputSize.width!=0 && outputSize.height!=0) + { + resize(output,output,outputSize); + } + } +public: + ResizerPreprocessor(){} + ~ResizerPreprocessor(){} +}; + +class StandarizerPreprocessor: public ImagePreprocessor{ +protected: + double sigma_; + void preprocess_(const Mat& input,Mat& output,Size outputSize,int outputChannels){ + //TODO put all the logic of channel and depth conversions in ImageProcessor class + CV_Assert(outputChannels==1 || outputChannels==3); + CV_Assert(input.channels()==1 || input.channels()==3); + if(input.channels()!=outputChannels) + { + Mat tmpInput; + if(outputChannels==1) + { + cvtColor(input,tmpInput,COLOR_BGR2GRAY); + if(input.depth()==CV_8U) + { + tmpInput.convertTo(output,CV_32FC1,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + tmpInput.convertTo(output, CV_32FC1); + } + }else + { + cvtColor(input,tmpInput,COLOR_GRAY2BGR); + if(input.depth()==CV_8U) + { + tmpInput.convertTo(output,CV_32FC3,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + tmpInput.convertTo(output, CV_32FC3); + } + } + }else + { + if(input.channels()==1) + { + if(input.depth()==CV_8U) + { + input.convertTo(output, CV_32FC1,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + input.convertTo(output, CV_32FC1); + } + }else + { + if(input.depth()==CV_8U) + { + input.convertTo(output, CV_32FC3,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + input.convertTo(output, CV_32FC3); + } + } + } + if(outputSize.width!=0 && outputSize.height!=0) + { + resize(output,output,outputSize); + } + Scalar dev,mean; + meanStdDev(output,mean,dev); + subtract(output,mean[0],output); + divide(output,(dev[0]/sigma_),output); + } +public: + StandarizerPreprocessor(double sigma):sigma_(sigma){} + ~StandarizerPreprocessor(){} +}; + +class MeanSubtractorPreprocessor: public ImagePreprocessor{ +protected: + Mat mean_; + void preprocess_(const Mat& input,Mat& output,Size outputSize,int outputChannels){ + //TODO put all the logic of channel and depth conversions in ImageProcessor class + CV_Assert(this->mean_.cols==outputSize.width && this->mean_.rows ==outputSize.height); + CV_Assert(outputChannels==1 || outputChannels==3); + CV_Assert(input.channels()==1 || input.channels()==3); + if(input.channels()!=outputChannels) + { + Mat tmpInput; + if(outputChannels==1) + { + cvtColor(input,tmpInput,COLOR_BGR2GRAY); + if(input.depth()==CV_8U) + { + tmpInput.convertTo(output,CV_32FC1,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + tmpInput.convertTo(output, CV_32FC1); + } + }else + { + cvtColor(input,tmpInput,COLOR_GRAY2BGR); + if(input.depth()==CV_8U) + { + tmpInput.convertTo(output,CV_32FC3,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + tmpInput.convertTo(output, CV_32FC3); + } + } + }else + { + if(input.channels()==1) + { + if(input.depth()==CV_8U) + { + input.convertTo(output, CV_32FC1,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + input.convertTo(output, CV_32FC1); + } + }else + { + if(input.depth()==CV_8U) + { + input.convertTo(output, CV_32FC3,1/255.0); + }else + {//Assuming values are at the desired [0,1] range + input.convertTo(output, CV_32FC3); + } + } + } + if(outputSize.width!=0 && outputSize.height!=0) + { + resize(output,output,outputSize); + } + subtract(output,this->mean_,output); + } +public: + MeanSubtractorPreprocessor(Mat mean) + { + mean.copyTo(this->mean_); + } + + ~MeanSubtractorPreprocessor(){} +}; + + +Ptr ImagePreprocessor::createResizer() +{ + return Ptr(new ResizerPreprocessor); +} + +Ptr ImagePreprocessor::createImageStandarizer(double sigma) +{ + return Ptr(new StandarizerPreprocessor(sigma)); +} + +Ptr ImagePreprocessor::createImageMeanSubtractor(InputArray meanImg) +{ + Mat tmp=meanImg.getMat(); + return Ptr(new MeanSubtractorPreprocessor(tmp)); +} + +//************************************************************************************ +//****************** TextImageClassifier ***************************************** +//************************************************************************************ + +void TextImageClassifier::preprocess(const Mat& input,Mat& output) +{ + this->preprocessor_->preprocess_(input,output,this->inputGeometry_,this->channelCount_); +} + +void TextImageClassifier::setPreprocessor(Ptr ptr) +{ + CV_Assert(!ptr.empty()); + preprocessor_=ptr; +} + +Ptr TextImageClassifier::getPreprocessor() +{ + return preprocessor_; +} + + +class DeepCNNCaffeImpl: public DeepCNN{ +protected: + void classifyMiniBatch(std::vector inputImageList, Mat outputMat) + { + //Classifies a list of images containing at most minibatchSz_ images + CV_Assert(int(inputImageList.size())<=this->minibatchSz_); + CV_Assert(outputMat.isContinuous()); +#ifdef HAVE_CAFFE + net_->input_blobs()[0]->Reshape(inputImageList.size(), 1,this->inputGeometry_.height,this->inputGeometry_.width); + net_->Reshape(); + float* inputBuffer=net_->input_blobs()[0]->mutable_cpu_data(); + float* inputData=inputBuffer; + for(size_t imgNum=0;imgNuminputGeometry_.height, this->inputGeometry_.width, CV_32FC1, inputData); + this->preprocess(inputImageList[imgNum],preprocessed); + preprocessed.copyTo(netInputWraped); + inputData+=(this->inputGeometry_.height*this->inputGeometry_.width); + } + this->net_->ForwardPrefilled(); + const float* outputNetData=net_->output_blobs()[0]->cpu_data(); + float*outputMatData=(float*)(outputMat.data); + memcpy(outputMatData,outputNetData,sizeof(float)*this->outputSize_*inputImageList.size()); +#endif + } + +#ifdef HAVE_CAFFE + Ptr > net_; +#endif + //Size inputGeometry_; + int minibatchSz_;//The existence of the assignment operator mandates this to be nonconst + int outputSize_; +public: + DeepCNNCaffeImpl(const DeepCNNCaffeImpl& dn): + minibatchSz_(dn.minibatchSz_),outputSize_(dn.outputSize_){ + channelCount_=dn.channelCount_; + inputGeometry_=dn.inputGeometry_; + //Implemented to supress Visual Studio warning "assignment operator could not be generated" +#ifdef HAVE_CAFFE + this->net_=dn.net_; +#endif + } + DeepCNNCaffeImpl& operator=(const DeepCNNCaffeImpl &dn) + { +#ifdef HAVE_CAFFE + this->net_=dn.net_; +#endif + this->setPreprocessor(dn.preprocessor_); + this->inputGeometry_=dn.inputGeometry_; + this->channelCount_=dn.channelCount_; + this->minibatchSz_=dn.minibatchSz_; + this->outputSize_=dn.outputSize_; + this->preprocessor_=dn.preprocessor_; + return *this; + //Implemented to supress Visual Studio warning "assignment operator could not be generated" + } + + DeepCNNCaffeImpl(String modelArchFilename, String modelWeightsFilename,Ptr preprocessor, int maxMinibatchSz) + :minibatchSz_(maxMinibatchSz) + { + CV_Assert(this->minibatchSz_>0); + CV_Assert(fileExists(modelArchFilename)); + CV_Assert(fileExists(modelWeightsFilename)); + CV_Assert(!preprocessor.empty()); + this->setPreprocessor(preprocessor); +#ifdef HAVE_CAFFE + this->net_.reset(new caffe::Net(modelArchFilename, caffe::TEST)); + CV_Assert(net_->num_inputs()==1); + CV_Assert(net_->num_outputs()==1); + CV_Assert(this->net_->input_blobs()[0]->channels()==1 + ||this->net_->input_blobs()[0]->channels()==3); + this->channelCount_=this->net_->input_blobs()[0]->channels(); + this->net_->CopyTrainedLayersFrom(modelWeightsFilename); + caffe::Blob* inputLayer = this->net_->input_blobs()[0]; + this->inputGeometry_=Size(inputLayer->width(), inputLayer->height()); + inputLayer->Reshape(this->minibatchSz_,1,this->inputGeometry_.height, this->inputGeometry_.width); + net_->Reshape(); + this->outputSize_=net_->output_blobs()[0]->channels(); + +#else + CV_Error(Error::StsError,"Caffe not available during compilation!"); +#endif + } + + void classify(InputArray image, OutputArray classProbabilities) + { + std::vector inputImageList; + inputImageList.push_back(image.getMat()); + classifyBatch(inputImageList,classProbabilities); + } + + void classifyBatch(InputArrayOfArrays inputImageList, OutputArray classProbabilities) + { + std::vector allImageVector; + inputImageList.getMatVector(allImageVector); + size_t outputSize=size_t(this->outputSize_);//temporary variable to avoid int to size_t arithmentic + size_t minibatchSize=size_t(this->minibatchSz_);//temporary variable to avoid int to size_t arithmentic + classProbabilities.create(Size(int(outputSize),int(allImageVector.size())),CV_32F); + Mat outputMat = classProbabilities.getMat(); + for(size_t imgNum=0;imgNum(allImageVector.size()-imgNum,minibatchSize); + std::vector::const_iterator from=std::vector::const_iterator(allImageVector.begin()+imgNum); + std::vector::const_iterator to=std::vector::const_iterator(allImageVector.begin()+rangeEnd); + std::vector minibatchInput(from,to); + classifyMiniBatch(minibatchInput,outputMat.rowRange(int(imgNum),int(rangeEnd))); + } + } + + int getOutputSize() + { + return this->outputSize_; + } + + int getMinibatchSize() + { + return this->minibatchSz_; + } + + int getBackend() + { + return OCR_HOLISTIC_BACKEND_CAFFE; + } +}; + + +Ptr DeepCNN::create(String archFilename,String weightsFilename,Ptr preprocessor,int minibatchSz,int backEnd) +{ + if(preprocessor.empty()) + { + preprocessor=ImagePreprocessor::createResizer(); + } + switch(backEnd){ + case OCR_HOLISTIC_BACKEND_CAFFE: + return Ptr(new DeepCNNCaffeImpl(archFilename, weightsFilename,preprocessor, minibatchSz)); + break; + case OCR_HOLISTIC_BACKEND_NONE: + default: + CV_Error(Error::StsError,"DeepCNN::create backend not implemented"); + return Ptr(); + break; + } +} + + +Ptr DeepCNN::createDictNet(String archFilename,String weightsFilename,int backEnd) +{ + Ptr preprocessor=ImagePreprocessor::createImageStandarizer(113); + switch(backEnd){ + case OCR_HOLISTIC_BACKEND_CAFFE: + return Ptr(new DeepCNNCaffeImpl(archFilename, weightsFilename,preprocessor, 100)); + break; + case OCR_HOLISTIC_BACKEND_NONE: + default: + CV_Error(Error::StsError,"DeepCNN::create backend not implemented"); + return Ptr(); + break; + } +} + +#ifdef HAVE_CAFFE + +bool getCaffeGpuMode() +{ + return caffe::Caffe::mode()==caffe::Caffe::GPU; +} + +void setCaffeGpuMode(bool useGpu) +{ + if(useGpu) + { + caffe::Caffe::set_mode(caffe::Caffe::GPU); + }else + { + caffe::Caffe::set_mode(caffe::Caffe::CPU); + } +} + +bool getCaffeAvailable() +{ + return true; +} + +#else + +bool DeepCNN::getCaffeGpuMode() +{ + CV_Error(Error::StsError,"Caffe not available during compilation!"); + return 0; +} + +void DeepCNN::setCaffeGpuMode(bool useGpu) +{ + CV_Error(Error::StsError,"Caffe not available during compilation!"); + CV_Assert(useGpu==1);//Compilation directives force +} + +bool DeepCNN::getCaffeAvailable(){ + return 0; +} + +#endif + + +class OCRHolisticWordRecognizerImpl: public OCRHolisticWordRecognizer{ +private: + struct NetOutput{ + //Auxiliary structure that handles the logic of getting class ids and probabillities from + //the raw outputs of caffe + int wordIdx; + float probabillity; + + static bool sorter(const NetOutput& o1,const NetOutput& o2) + {//used with std::sort to provide the most probable class + return o1.probabillity>o2.probabillity; + } + + static void getOutputs(const float* buffer,int nbOutputs,std::vector& res) + { + res.resize(nbOutputs); + for(int k=0;k tmp; + getOutputs(buffer,nbOutputs,tmp); + classNum=tmp[0].wordIdx; + confidence=tmp[0].probabillity; + } + }; +protected: + std::vector labels_; + Ptr classifier_; +public: + OCRHolisticWordRecognizerImpl(Ptr classifierPtr,String vocabularyFilename):classifier_(classifierPtr) + { + CV_Assert(fileExists(vocabularyFilename));//this fails for some rason + std::ifstream labelsFile(vocabularyFilename.c_str()); + if(!labelsFile) + { + CV_Error(Error::StsError,"Could not read Labels from file"); + } + std::string line; + while (std::getline(labelsFile, line)) + { + labels_.push_back(std::string(line)); + } + CV_Assert(this->classifier_->getOutputSize()==int(this->labels_.size())); + } + + OCRHolisticWordRecognizerImpl(Ptr classifierPtr,const std::vector& vocabulary):classifier_(classifierPtr) + { + this->labels_=vocabulary; + CV_Assert(this->classifier_->getOutputSize()==int(this->labels_.size())); + } + + void recogniseImage(InputArray inputImage,CV_OUT String& transcription,CV_OUT double& confidence) + { + Mat netOutput; + this->classifier_->classify(inputImage,netOutput); + int classNum; + NetOutput::getClassification((float*)(netOutput.data),this->classifier_->getOutputSize(),classNum,confidence); + transcription=this->labels_[classNum]; + } + + void recogniseImageBatch(InputArrayOfArrays inputImageList,CV_OUT std::vector& transcriptionVec,CV_OUT std::vector& confidenceVec) + { + Mat netOutput; + this->classifier_->classifyBatch(inputImageList,netOutput); + for(int k=0;kclassifier_->getOutputSize(),classNum,confidence); + transcriptionVec.push_back(this->labels_[classNum]); + confidenceVec.push_back(confidence); + } + } + + + void run(Mat& image, std::string& output_text, std::vector* component_rects=NULL, + std::vector* component_texts=NULL, std::vector* component_confidences=NULL, + int component_level=0) + { + CV_Assert(component_level==OCR_LEVEL_WORD);//Componnents not applicable for word spotting + double confidence; + String transcription; + recogniseImage(image,transcription,confidence); + output_text=transcription.c_str(); + if(component_rects!=NULL) + { + component_rects->resize(1); + (*component_rects)[0]=Rect(0,0,image.size().width,image.size().height); + } + if(component_texts!=NULL) + { + component_texts->resize(1); + (*component_texts)[0]=transcription.c_str(); + } + if(component_confidences!=NULL) + { + component_confidences->resize(1); + (*component_confidences)[0]=float(confidence); + } + } + + void run(Mat& image, Mat& mask, std::string& output_text, std::vector* component_rects=NULL, + std::vector* component_texts=NULL, std::vector* component_confidences=NULL, + int component_level=0) + { + CV_Assert(mask.cols==image.cols && mask.rows== image.rows);//Mask is ignored because the CNN operates on a full image + this->run(image,output_text,component_rects,component_texts,component_confidences,component_level); + } + + std::vector& getVocabulary() + { + return this->labels_; + } + + Ptr getClassifier() + { + return this->classifier_; + } +}; + +Ptr OCRHolisticWordRecognizer::create(Ptr classifierPtr,String vocabularyFilename ) +{ + return Ptr(new OCRHolisticWordRecognizerImpl(classifierPtr,vocabularyFilename)); +} + +Ptr OCRHolisticWordRecognizer::create(String modelArchFilename, String modelWeightsFilename, String vocabularyFilename) +{ + Ptr preprocessor=ImagePreprocessor::createImageStandarizer(113); + Ptr classifierPtr(new DeepCNNCaffeImpl(modelArchFilename,modelWeightsFilename,preprocessor,100)); + return Ptr(new OCRHolisticWordRecognizerImpl(classifierPtr,vocabularyFilename)); +} + +Ptr OCRHolisticWordRecognizer::create(Ptr classifierPtr,const std::vector& vocabulary) +{ + return Ptr(new OCRHolisticWordRecognizerImpl(classifierPtr,vocabulary)); +} + +Ptr OCRHolisticWordRecognizer::create(String modelArchFilename, String modelWeightsFilename,const std::vector& vocabulary){ + Ptr preprocessor=ImagePreprocessor::createImageStandarizer(113); + Ptr classifierPtr(new DeepCNNCaffeImpl(modelArchFilename,modelWeightsFilename,preprocessor,100)); + return Ptr(new OCRHolisticWordRecognizerImpl(classifierPtr,vocabulary)); +} + + +//******************************************************************************************************************** + + +} } //namespace text namespace cv diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp new file mode 100644 index 00000000000..e699d1422d8 --- /dev/null +++ b/modules/text/src/text_synthesizer.cpp @@ -0,0 +1,571 @@ +#include "precomp.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/core.hpp" +#include "opencv2/highgui.hpp" +#include "opencv2/calib3d.hpp" + +#include "opencv2/text/text_synthesizer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +//TODO FIND apropriate +#define CV_IMWRITE_JPEG_QUALITY 1 + +namespace cv{ +namespace text{ + +namespace { +//Unnamed namespace with auxiliary classes and functions used for quick computation + +template void blendRGBA(Mat& out,const Mat &in1,const Mat& in2){ + CV_Assert(out.cols==in1.cols && out.cols==in2.cols); + CV_Assert(out.rows==in1.rows && out.rows==in2.rows); + CV_Assert(out.channels()==4 && in1.channels()==4 && in2.channels()==4); + int lineWidth=out.cols*4; + BL blend; + BL_A blendA; + for(int y=0;y(y); + const P* in1G=in1.ptr

(y)+1; + const P* in1R=in1.ptr

(y)+2; + const P* in1A=in1.ptr

(y)+3; + + const P* in2B=in2.ptr

(y); + const P* in2G=in2.ptr

(y)+1; + const P* in2R=in2.ptr

(y)+2; + const P* in2A=in2.ptr

(y)+3; + + P* outB=out.ptr

(y); + P* outG=out.ptr

(y)+1; + P* outR=out.ptr

(y)+2; + P* outA=out.ptr

(y)+3; + + for(int x=0;x(y); + float* outG=out.ptr(y)+1; + float* outB=out.ptr(y)+2; + + float* topR=top.ptr(y); + float* topG=top.ptr(y)+1; + float* topB=top.ptr(y)+2; + + float* bottomR=bottom.ptr(y); + float* bottomG=bottom.ptr(y)+1; + float* bottomB=bottom.ptr(y)+2; + + for(int x=0;x(y); + float* topG=top.ptr(y); + float* bottomG=bottom.ptr(y); + for(int x=0;x(y); + float* outG=out.ptr(y)+1; + float* outB=out.ptr(y)+2; + + float* topR=top.ptr(y); + float* topG=top.ptr(y)+1; + float* topB=top.ptr(y)+2; + + float* bottomR=top.ptr(y); + float* bottomG=top.ptr(y)+1; + float* bottomB=top.ptr(y)+2; + + float* topMask=topMask_.ptr(y); + float* bottomMask=bottomMask_.ptr(y); + + for(int x=0;x(y); + float* outG=out.ptr(y)+1; + float* outB=out.ptr(y)+2; + + float* topR=top.ptr(y); + float* topG=top.ptr(y)+1; + float* topB=top.ptr(y)+2; + + float* bottomR=bottom.ptr(y); + float* bottomG=bottom.ptr(y)+1; + float* bottomB=bottom.ptr(y)+2; + + float* mask=topMask.ptr(y); + + for(int x=0;x(y); + float* outG=out.ptr(y)+1; + float* outB=out.ptr(y)+2; + + float* mask=topMask.ptr(y); + + for(int x=0;xscript_){ + case CV_TEXT_SYNTHESIZER_SCRIPT_ANY: + qtScriptCode=QFontDatabase::Any; + break; + case CV_TEXT_SYNTHESIZER_SCRIPT_LATIN: + qtScriptCode=QFontDatabase::Latin; + break; + case CV_TEXT_SYNTHESIZER_SCRIPT_GREEK: + qtScriptCode=QFontDatabase::Greek; + break; + case CV_TEXT_SYNTHESIZER_SCRIPT_CYRILLIC: + qtScriptCode=QFontDatabase::Cyrillic; + break; + case CV_TEXT_SYNTHESIZER_SCRIPT_ARABIC: + qtScriptCode=QFontDatabase::Arabic; + break; + case CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW: + qtScriptCode=QFontDatabase::Hebrew; + break; + default: + CV_Error(Error::StsError,"Unsupported script_code"); + break; + } + this->availableFonts_.clear(); + QStringList lst=this->fntDb_->families(qtScriptCode); + for(int k=0;kavailableFonts_.push_back(lst[k].toUtf8().constData()); + } + } + + QFont generateFont(){ + CV_Assert(this->availableFonts_.size()); + QFont fnt(this->availableFonts_[rng_.next() % this->availableFonts_.size()].c_str()); + fnt.setPixelSize(this->resHeight_-2*this->txtPad_); + if((this->rng_.next()%1000)/1000.0underlineProbabillity_){ + fnt.setUnderline(true); + }else{ + fnt.setUnderline(false); + } + if((this->rng_.next()%1000)/1000.0boldProbabillity_){ + fnt.setBold(true); + }else{ + fnt.setBold(false); + } + if((this->rng_.next()%1000)/1000.0italicProbabillity_){ + fnt.setItalic(true); + }else{ + fnt.setItalic(false); + } + return fnt; + } + + void generateTxtPatch(Mat& output,Mat& outputMask,String caption){ + const int maxTxtWidth=this->maxResWidth_; + Mat textImg; + textImg =cv::Mat(this->resHeight_,maxTxtWidth,CV_8UC3,Scalar_(0,0,0)); + QImage qimg((unsigned char*)(textImg.data), textImg.cols, textImg.rows, textImg.step, QImage::Format_RGB888); + QPainter qp(&qimg); + qp.setPen(QColor(255,255,255)); + QFont fnt=this->generateFont(); + QFontMetrics fntMtr(fnt,qp.device()); + QRect bbox=fntMtr.tightBoundingRect(caption.c_str()); + qp.setFont(fnt); + qp.drawText(QPoint(txtPad_,txtPad_+ bbox.height()), caption.c_str()); + qp.end(); + textImg=textImg.colRange(0,min( bbox.width()+2*txtPad_,maxTxtWidth-1)); + Mat textGrayImg; + cvtColor(textImg,textGrayImg,COLOR_RGBA2GRAY); + + //Obtaining color triplet + int colorTriplet=this->rng_.next()%this->colorClusters_.rows; + uchar* cVal=this->colorClusters_.ptr(colorTriplet); + Scalar_ fgText(cVal[0]/255.0,cVal[1]/255.0,cVal[2]/255.0); + Scalar_ fgBorder(cVal[3]/255.0,cVal[4]/255.0,cVal[5]/255.0); + Scalar_ fgShadow(cVal[6]/255.0,cVal[7]/255.0,cVal[8]/255.0); + + Mat floatTxt;Mat floatBorder;Mat floatShadow; + textGrayImg.convertTo(floatTxt, CV_32FC1, 1.0/255.0); + + //Sampling uniform distributionfor sizes + int borderSize=(this->rng_.next()%this->maxBorderSize_)*((this->rng_.next()%10000)/10000.0>this->borderProbabillity_); + int shadowSize=(this->rng_.next()%this->maxShadowSize_)*((this->rng_.next()%10000)/10000.0>this->shadowProbabillity_); + int voffset=(this->rng_.next()%(shadowSize*2+1))-shadowSize; + int hoffset=(this->rng_.next()%(shadowSize*2+1))-shadowSize; + float shadowOpacity=(((this->rng_.next()%10000)*maxShadowOpacity_)/10000.0); + + //generating shadows + generateDilation(floatBorder,floatTxt,borderSize,0,0); + generateDilation(floatShadow,floatBorder,shadowSize,voffset,hoffset); + + Mat floatBordered=Mat(floatTxt.rows,floatTxt.cols,CV_32FC3); + Mat floatShadowed=Mat(floatTxt.rows,floatTxt.cols,CV_32FC3); + Mat floatMixed=Mat(floatTxt.rows,floatTxt.cols,CV_32FC3); + Mat floatMask=Mat(floatTxt.rows,floatTxt.cols,CV_32FC1); + + blendOverlay(floatBordered,fgText,fgBorder, floatTxt); + blendOverlay(floatShadowed,fgShadow,fgShadow, floatTxt); + blendOverlay(floatMixed,floatBordered,floatShadowed, floatBorder); + blendWeighted(floatMask,floatShadow,floatBorder, shadowOpacity,1-shadowOpacity); + floatMixed.copyTo(output);floatMask.copyTo(outputMask); + } + + + void generateDilation(Mat&outputImg,const Mat& inputImg,int dilationSize, int horizOffset,int vertOffset){ + //erosion is defined as a negative dilation size + if (dilationSize==0){ + inputImg.copyTo(outputImg); + }else{ + if(dilationSize>0){ + if(horizOffset==0 && vertOffset==0){ + dilate(inputImg,outputImg,Mat(),Point(-1, -1),dilationSize); + }else{ + Mat tmpMat; + dilate(inputImg,tmpMat,Mat(),Point(-1, -1),dilationSize); + outputImg=Mat(inputImg.rows,inputImg.cols,inputImg.type(),Scalar(0)); + int validWidth=inputImg.cols-abs(horizOffset); + int validHeight=inputImg.rows-abs(vertOffset); + tmpMat(Rect(max(0,-horizOffset),max(0,-vertOffset), validWidth,validHeight)). + copyTo(outputImg(Rect(max(0,horizOffset),max(0,vertOffset), validWidth,validHeight))); + } + }else{ + if(horizOffset==0 && vertOffset==0){ + dilate(inputImg,outputImg,Mat(),Point(-1, -1),-dilationSize); + }else{ + Mat tmpMat; + erode(inputImg,tmpMat,Mat(),Point(-1, -1),-dilationSize); + outputImg=Mat(inputImg.rows,inputImg.cols,inputImg.type(),Scalar(0)); + int validWidth=inputImg.cols-abs(horizOffset); + int validHeight=inputImg.rows-abs(vertOffset); + tmpMat(Rect(max(0,-horizOffset),max(0,-vertOffset), validWidth,validHeight)). + copyTo(outputImg(Rect(max(0,horizOffset),max(0,vertOffset), validWidth,validHeight))); + } + } + } + } + + void randomlyDistortPerspective(const Mat& inputImg,Mat& outputImg){ + int N=this->maxPerspectiveDistortion_; + std::vector src(4);std::vector dst(4); + src[0]=Point2f(0,0);src[1]=Point2f(100,0);src[2]=Point2f(0,100);src[3]=Point2f(100,100); + dst[0]=Point2f(this->rng_.next()%N,this->rng_.next()%N); + dst[1]=Point2f(100-this->rng_.next()%N,this->rng_.next()%N); + dst[2]=Point2f(this->rng_.next()%N,100-this->rng_.next()%N); + dst[3]=Point2f(100-this->rng_.next()%N,100-this->rng_.next()%N); + Mat h=findHomography(src,dst); + warpPerspective(inputImg,outputImg,h,inputImg.size()); + } + + void addCurveDeformation(const Mat& inputImg,Mat& outputImg){ + if ((this->rng_.next()%1000)>1000*this->curvingProbabillity_){ + Mat X=Mat(inputImg.rows,inputImg.cols,CV_32FC1); + Mat Y=Mat(inputImg.rows,inputImg.cols,CV_32FC1); + int xAdd=-this->rng_.next()%inputImg.cols; + float xMult=(this->rng_.next()%10000)*float(maxCurveArch_)/10000; + int sign=(this->rng_.next()%2)?-1:1; + for(int y=0;y(y); + float* yRow=Y.ptr(y); + for(int x=0;x buffer; + std::vector parameters; + parameters.push_back(CV_IMWRITE_JPEG_QUALITY); + parameters.push_back(qualityPercentage % 100); + imencode(".jpg",img,buffer,parameters); + res=imdecode(buffer,0); + } + void initColorClusters(){ + this->colorClusters_=Mat(4,3,CV_8UC3,Scalar(32,32,32)); + + this->colorClusters_.at(0, 0)=Vec3b(192,32,32); + this->colorClusters_.at(0, 1)=Vec3b(192,255,32); + this->colorClusters_.at(0, 2)=Vec3b(0,32,32); + + this->colorClusters_.at(0, 0)=Vec3b(0,32,192); + this->colorClusters_.at(0, 1)=Vec3b(0,255,32); + this->colorClusters_.at(0, 2)=Vec3b(0,0,64); + + this->colorClusters_.at(0, 0)=Vec3b(128,128,128); + this->colorClusters_.at(0, 1)=Vec3b(255,255,255); + this->colorClusters_.at(0, 2)=Vec3b(0,0,0); + + this->colorClusters_.at(0, 0)=Vec3b(255,255,255); + this->colorClusters_.at(0, 1)=Vec3b(128,128,128); + this->colorClusters_.at(0, 2)=Vec3b(0,0,0); + } + + RNG rng_;//Randon number generator used for all distributions + int txtPad_; + Ptr fntDb_; + std::vector availableFonts_; + std::vector availableBgSampleFiles_; + std::vector availableBgSampleImages_; + Mat colorClusters_; + int script_; +public: + TextSynthesizerQtImpl(int script,int maxSampleWidth=400,int sampleHeight=50,uint64 rndState=0): + TextSynthesizer(maxSampleWidth,sampleHeight), + rng_(rndState!=0?rndState:std::time(NULL)), + txtPad_(10){ + CV_Assert(script==CV_TEXT_SYNTHESIZER_SCRIPT_ANY || + script==CV_TEXT_SYNTHESIZER_SCRIPT_LATIN || + script==CV_TEXT_SYNTHESIZER_SCRIPT_GREEK || + script==CV_TEXT_SYNTHESIZER_SCRIPT_CYRILLIC || + script==CV_TEXT_SYNTHESIZER_SCRIPT_ARABIC || + script==CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW); + this->script_=script; + //QT needs to be initialised. Highgui does this + namedWindow("__w"); + waitKey(1); + destroyWindow("__w"); + this->fntDb_=Ptr(new QFontDatabase()); + this->updateFontNameList(); + this->initColorClusters(); + } + + + void generateBgSample(CV_OUT Mat& sample){ + if(this->availableBgSampleImages_.size()!=0){ + Mat& img=availableBgSampleImages_[this->rng_.next()%availableBgSampleImages_.size()]; + int left=this->rng_.next()%(img.cols-maxResWidth_); + int top=this->rng_.next()%(img.rows-resHeight_); + img.colRange(Range(left,left+maxResWidth_)).rowRange(Range(top,top+resHeight_)).copyTo(sample); + }else{ + if(this->availableBgSampleFiles_.size()==0){ + Mat res(this->resHeight_,this->maxResWidth_,CV_8UC3); + this->rng_.fill(res,RNG::UNIFORM,0,256); + res.copyTo(sample); + }else{ + Mat img; + img=imread(this->availableBgSampleFiles_[this->rng_.next()%availableBgSampleFiles_.size()].c_str(),IMREAD_COLOR); + CV_Assert(img.data != NULL); + CV_Assert(img.cols>maxResWidth_ && img.rows> resHeight_); + int left=this->rng_.next()%(img.cols-maxResWidth_); + int top=this->rng_.next()%(img.rows-resHeight_); + img.colRange(Range(left,left+maxResWidth_)).rowRange(Range(top,top+resHeight_)).copyTo(sample); + } + } + if(sample.channels()==4){ + Mat rgb; + cvtColor(sample,rgb,COLOR_RGBA2RGB); + sample=rgb; + } + if(sample.channels()==1){ + Mat rgb; + cvtColor(sample,rgb,COLOR_GRAY2RGB); + sample=rgb; + } + } + + void generateTxtSample(String caption,CV_OUT Mat& sample,CV_OUT Mat& sampleMask){ + generateTxtPatch(sample,sampleMask,caption); + } + + void generateSample(String caption,CV_OUT Mat & sample){ + Mat txtSample; + Mat txtCurved; + Mat txtDistorted; + Mat bgSample; + Mat bgResized; + Mat txtMask; + Mat txtMerged; + Mat floatBg; + std::vector txtChannels; + generateTxtPatch(txtSample,txtMask,caption); + + + split(txtSample,txtChannels); + txtChannels.push_back(txtMask); + merge(txtChannels,txtMerged); + addCurveDeformation(txtMerged,txtCurved); + + randomlyDistortPerspective(txtCurved,txtDistorted); + split(txtDistorted,txtChannels); + txtMask=txtChannels[3]; + txtChannels.pop_back(); + merge(txtChannels,txtSample); + + generateBgSample(bgSample); + bgSample.convertTo(floatBg, CV_32FC3, 1.0/255.0); + bgResized=floatBg.colRange(0,txtSample.cols); + sample=Mat(txtDistorted.rows,txtDistorted.cols,CV_32FC3); + + blendOverlay(sample,txtSample,bgResized,txtMask); + double blendAlpha=this->finalBlendAlpha_*(this->rng_.next()%1000)/1000.0; + if((this->rng_.next()%1000)>1000*this->finalBlendProb_){ + blendWeighted(sample,sample,bgResized,1-blendAlpha,blendAlpha); + } + } + + void getColorClusters(CV_OUT Mat& clusters){ + this->colorClusters_.copyTo(clusters); + } + + void setColorClusters(Mat clusters){ + CV_Assert(clusters.type()==CV_8UC3); + CV_Assert(clusters.cols==3); + clusters.copyTo(this->colorClusters_); + } + + std::vector listAvailableFonts(){ + return this->availableFonts_; + } + + virtual void addBgSampleImage(const Mat& inImg){ + CV_Assert(inImg.cols>maxResWidth_ && inImg.rows> resHeight_); + Mat img; + switch(inImg.type()){ + case CV_8UC1:cvtColor(inImg, img, COLOR_GRAY2RGBA);break; + case CV_8UC3:cvtColor(inImg, img, COLOR_RGB2RGBA);break; + case CV_8UC4:inImg.copyTo(img);break; + default: + CV_Error(Error::StsError,"Only uchar images of 1, 3, or 4 channels are accepted"); + } + this->availableBgSampleImages_.push_back(img); + } + + void addFontFiles(const std::vector& fntList){ + for(int n=0;nupdateFontNameList(); + } + + std::vector listBgSampleFiles(){ + std::vector res(this->availableBgSampleFiles_.size()); + std::copy(this->availableBgSampleFiles_.begin(),this->availableBgSampleFiles_.end(),res.begin()); + return res; + } +}; + +Ptr TextSynthesizer::create(int script){ + Ptr res(new TextSynthesizerQtImpl(script)); + return res; +} + +}} //namespace text,namespace cv diff --git a/modules/text/text_config.hpp.in b/modules/text/text_config.hpp.in index 30089bd3c55..2e64f3bfb72 100644 --- a/modules/text/text_config.hpp.in +++ b/modules/text/text_config.hpp.in @@ -1,7 +1,13 @@ #ifndef __OPENCV_TEXT_CONFIG_HPP__ #define __OPENCV_TEXT_CONFIG_HPP__ +// HAVE CAFFE +#cmakedefine HAVE_CAFFE + // HAVE OCR Tesseract #cmakedefine HAVE_TESSERACT -#endif \ No newline at end of file + + + +#endif From ff3a9399a5db395f4c8d74d8c07000b19760b426 Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Tue, 23 Aug 2016 20:30:49 +0200 Subject: [PATCH 02/16] Corrected warning and an error in the build farm --- modules/text/src/ocr_holistic.cpp | 6 +++--- modules/text/src/text_synthesizer.cpp | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/modules/text/src/ocr_holistic.cpp b/modules/text/src/ocr_holistic.cpp index c04349f409e..89ab90d905c 100644 --- a/modules/text/src/ocr_holistic.cpp +++ b/modules/text/src/ocr_holistic.cpp @@ -463,19 +463,19 @@ bool getCaffeAvailable() #else -bool DeepCNN::getCaffeGpuMode() +bool getCaffeGpuMode() { CV_Error(Error::StsError,"Caffe not available during compilation!"); return 0; } -void DeepCNN::setCaffeGpuMode(bool useGpu) +void setCaffeGpuMode(bool useGpu) { CV_Error(Error::StsError,"Caffe not available during compilation!"); CV_Assert(useGpu==1);//Compilation directives force } -bool DeepCNN::getCaffeAvailable(){ +bool getCaffeAvailable(){ return 0; } diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index e699d1422d8..83f4a95a825 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -71,7 +71,7 @@ template void blendRGBA(Mat& out,const Ma } }//unnamed namespace - +void blendWeighted(Mat& out,Mat& top,Mat& bottom,float topMask,float bottomMask); void blendWeighted(Mat& out,Mat& top,Mat& bottom,float topMask,float bottomMask){ if(out.channels( )==3 && top.channels( )==3 && bottom.channels( )==3 ){ for(int y=0;y(y); @@ -120,9 +121,9 @@ void blendWeighted(Mat& out,Mat& top,Mat& bottom,Mat& topMask_,Mat& bottomMask_) float* topG=top.ptr(y)+1; float* topB=top.ptr(y)+2; - float* bottomR=top.ptr(y); - float* bottomG=top.ptr(y)+1; - float* bottomB=top.ptr(y)+2; + float* bottomR=bottom.ptr(y); + float* bottomG=bottom.ptr(y)+1; + float* bottomB=bottom.ptr(y)+2; float* topMask=topMask_.ptr(y); float* bottomMask=bottomMask_.ptr(y); @@ -136,7 +137,7 @@ void blendWeighted(Mat& out,Mat& top,Mat& bottom,Mat& topMask_,Mat& bottomMask_) } } - +void blendOverlay(Mat& out,Mat& top,Mat& bottom,Mat& topMask); void blendOverlay(Mat& out,Mat& top,Mat& bottom,Mat& topMask){ for(int y=0;y(y); @@ -162,6 +163,7 @@ void blendOverlay(Mat& out,Mat& top,Mat& bottom,Mat& topMask){ } } +void blendOverlay(Mat& out,Scalar topCol,Scalar bottomCol,Mat& topMask); void blendOverlay(Mat& out,Scalar topCol,Scalar bottomCol,Mat& topMask){ float topR=topCol[0]; float topG=topCol[1]; @@ -189,7 +191,7 @@ void blendOverlay(Mat& out,Scalar topCol,Scalar bottomCol,Mat& topMask){ TextSynthesizer::TextSynthesizer(int maxSampleWidth,int sampleHeight): - maxResWidth_(maxSampleWidth),resHeight_(sampleHeight) + resHeight_(sampleHeight),maxResWidth_(maxSampleWidth) { underlineProbabillity_=0.05; italicProbabillity_=.1; @@ -216,7 +218,7 @@ TextSynthesizer::TextSynthesizer(int maxSampleWidth,int sampleHeight): class TextSynthesizerQtImpl: public TextSynthesizer{ protected: void updateFontNameList(){ - QFontDatabase::WritingSystem qtScriptCode; + QFontDatabase::WritingSystem qtScriptCode=QFontDatabase::Any; switch(this->script_){ case CV_TEXT_SYNTHESIZER_SCRIPT_ANY: qtScriptCode=QFontDatabase::Any; @@ -546,7 +548,7 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ } void addFontFiles(const std::vector& fntList){ - for(int n=0;n Date: Tue, 23 Aug 2016 21:09:03 +0200 Subject: [PATCH 03/16] Corrected warnings, cleaned up comments --- .../include/opencv2/cnn_3dobj_config.hpp | 0 modules/text/include/opencv2/text/ocr.hpp | 14 +++-- modules/text/src/ocr_beamsearch_decoder.cpp | 58 +++++-------------- 3 files changed, 24 insertions(+), 48 deletions(-) delete mode 100755 modules/cnn_3dobj/include/opencv2/cnn_3dobj_config.hpp diff --git a/modules/cnn_3dobj/include/opencv2/cnn_3dobj_config.hpp b/modules/cnn_3dobj/include/opencv2/cnn_3dobj_config.hpp deleted file mode 100755 index e69de29bb2d..00000000000 diff --git a/modules/text/include/opencv2/text/ocr.hpp b/modules/text/include/opencv2/text/ocr.hpp index e4b6b771404..18e2eec3fc2 100644 --- a/modules/text/include/opencv2/text/ocr.hpp +++ b/modules/text/include/opencv2/text/ocr.hpp @@ -549,19 +549,20 @@ class CV_EXPORTS_W ImagePreprocessor{ /** @brief this method in provides public acces to the preprocessing with respect to a specific * classifier * - * This method's main use would be to use the preprocessor without a classifier. + * This method's main use would be to use the preprocessor without feeding it to a classifier. + * Debugging is a great part * - * @param input + * @param input an image without any contrains * - * @param output + * @param output in most cases an image of fixed depth size and whitened * - * @param sz + * @param sz the size to which the image would be resize if the preprocessor resizes inputs * - * @param outputChannels + * @param outputChannels the number of channels for the output image */ CV_WRAP void preprocess(InputArray input,OutputArray output,Size sz,int outputChannels); - /** @brief + /** @brief Creates a functor that only resizes the input without * * @return shared pointer to generated preprocessor */ @@ -580,6 +581,7 @@ class CV_EXPORTS_W ImagePreprocessor{ CV_WRAP static Ptr createImageMeanSubtractor(InputArray meanImg); friend class TextImageClassifier; + }; /** @brief Abstract class that implements the classifcation of text images. diff --git a/modules/text/src/ocr_beamsearch_decoder.cpp b/modules/text/src/ocr_beamsearch_decoder.cpp index 752699956ec..ea544708d7f 100644 --- a/modules/text/src/ocr_beamsearch_decoder.cpp +++ b/modules/text/src/ocr_beamsearch_decoder.cpp @@ -49,8 +49,6 @@ #include #include -#define DBG(msg) (std::cerr<<"DBG L:"<<__LINE__<<"\t"<* co vector* component_texts, vector* component_confidences, int component_level) { - DBG("RUN START\n"); CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) ); CV_Assert( (component_level == OCR_LEVEL_TEXTLINE) || (component_level == OCR_LEVEL_WORD) ); output_text.clear(); @@ -80,7 +77,6 @@ void OCRBeamSearchDecoder::run(Mat& image, Mat& mask, string& output_text, vecto vector* component_texts, vector* component_confidences, int component_level) { - DBG("RUN START\n"); CV_Assert(mask.type() == CV_8UC1); CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) ); CV_Assert( (component_level == OCR_LEVEL_TEXTLINE) || (component_level == OCR_LEVEL_WORD) ); @@ -95,13 +91,11 @@ void OCRBeamSearchDecoder::run(Mat& image, Mat& mask, string& output_text, vecto CV_WRAP String OCRBeamSearchDecoder::run(InputArray image, int min_confidence, int component_level) { - DBG("RUN START\n"); std::string output1; std::string output2; vector component_texts; vector component_confidences; Mat image_m = image.getMat(); - DBG("RUN 1\n"); run(image_m, output1, NULL, &component_texts, &component_confidences, component_level); for(unsigned int i = 0; i < component_texts.size(); i++) { @@ -116,7 +110,6 @@ CV_WRAP String OCRBeamSearchDecoder::run(InputArray image, int min_confidence, i CV_WRAP String OCRBeamSearchDecoder::run(InputArray image, InputArray mask, int min_confidence, int component_level) { - DBG("RUN START\n"); std::string output1; std::string output2; vector component_texts; @@ -138,7 +131,6 @@ CV_WRAP String OCRBeamSearchDecoder::run(InputArray image, InputArray mask, int void OCRBeamSearchDecoder::ClassifierCallback::eval( InputArray image, vector< vector >& recognition_probabilities, vector& oversegmentation) { - DBG("eval callback START\n"); CV_Assert(( image.getMat().type() == CV_8UC3 ) || ( image.getMat().type() == CV_8UC1 )); if (!recognition_probabilities.empty()) { @@ -206,7 +198,6 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder vector* component_confidences, int component_level) { - DBG("RUN START\n"); CV_Assert(mask.type() == CV_8UC1); //nothing to do with a mask here run( src, out_sequence, component_rects, component_texts, component_confidences, @@ -220,7 +211,6 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder vector* component_confidences, int component_level) { - DBG("RUN START\n"); CV_Assert( (src.type() == CV_8UC1) || (src.type() == CV_8UC3) ); CV_Assert( (src.cols > 0) && (src.rows > 0) ); CV_Assert( component_level == OCR_LEVEL_WORD ); @@ -237,28 +227,17 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder cvtColor(src,src,COLOR_RGB2GRAY); } - DBG("RUN 1\n"); // TODO if input is a text line (not a word) we may need to split into words here! // do sliding window classification along a croped word image classifier->eval(src, recognition_probabilities, oversegmentation); - DBG("RUN 1.5\n");std::map m;m[0]='#';m[1]='@';m[2]='0';m[3]='1';m[4]='2';m[5]='3';m[6]='4';m[7]='5';m[8]='6';m[9]='7';m[10]='8';m[11]='9';m[12]='A';m[13]='a';m[14]='B';m[15]='b';m[16]='C';m[17]='c';m[18]='D';m[19]='d';m[20]='E';m[21]='e';m[22]='F';m[23]='f';m[24]='G';m[25]='g';m[26]='H';m[27]='h';m[28]='I';m[29]='i';m[30]='J';m[31]='j';m[32]='K';m[33]='k';m[34]='L';m[35]='l';m[36]='M';m[37]='m';m[38]='N';m[39]='n';m[40]='O';m[41]='o';m[42]='P';m[43]='p';m[44]='Q';m[45]='q';m[46]='R';m[47]='r';m[48]='S';m[49]='s';m[50]='T';m[51]='t';m[52]='U';m[53]='u';m[54]='V';m[55]='v';m[56]='W';m[57]='w';m[58]='X';m[59]='x';m[60]='Y';m[61]='y';m[62]='Z';m[63]='z'; - for(int wNum=0;wNum0) && (best_idx == last_best_idx) && (oversegmentation[i]*step_size < oversegmentation[i-1]*step_size + win_size) ) { @@ -293,7 +272,7 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder continue; } } - DBG("RUN 5\n"); + last_best_idx = best_idx; last_best_p = best_p; i++; @@ -312,7 +291,7 @@ class OCRBeamSearchDecoderImpl : public OCRBeamSearchDecoder recognition_probabilities[i][j] = log(recognition_probabilities[i][j]); } } - DBG("RUN 6\n"); + // initialize the beam with all possible character's pairs int generated_chids = 0; for (size_t i=0; ipush_back(Rect(0,0,src.cols,src.rows)); } - DBG("RUN 8.5\n")<push_back(out_sequence); } - DBG("RUN 8.7 lp")<<(float)exp(lp)<<"\n"; + if(component_confidences!=NULL){ component_confidences->push_back((float)exp(lp)); } - DBG("RUN 9\n"); + return; } @@ -832,7 +811,7 @@ class TextImageClassifierBeamSearchCallback: public OCRBeamSearchDecoder::Classi virtual ~TextImageClassifierBeamSearchCallback() { } TextImageClassifierBeamSearchCallback(Ptr classifier,int stepSize,int windowWidth) - :classifier_(classifier),stepSize_(stepSize),windowWidth_(windowWidth) + :stepSize_(stepSize),windowWidth_(windowWidth),classifier_(classifier) { if(windowWidth_<=0) { @@ -845,7 +824,7 @@ class TextImageClassifierBeamSearchCallback: public OCRBeamSearchDecoder::Classi virtual void eval( InputArray _img, std::vector< std::vector >& recognitionProbabilities, std::vector& oversegmentation ) { - DBG("EVAL 1\n"); + if (!recognitionProbabilities.empty()) { for (size_t i=0; i windowList; int counter=0; - DBG("EVAL 3\n"); + for(int x=0;x+windowWidth_classifier_->classifyBatch(windowList,windowProbabilities); recognitionProbabilities.resize(windowProbabilities.rows); @@ -877,21 +855,17 @@ class TextImageClassifierBeamSearchCallback: public OCRBeamSearchDecoder::Classi for(int clNum=2;clNum(windowNum,clNum));//+.02; } - //recognitionProbabilities[windowNum][0]=0; - //recognitionProbabilities[windowNum][1]=0; break; case CV_32F: for(int clNum=2;clNum(windowNum,clNum));//+.02; } - //recognitionProbabilities[windowNum][0]=0; - //recognitionProbabilities[windowNum][1]=0; break; default: CV_Error(Error::StsError,"The network outputs should be either float or double!"); } } - DBG("EVAL 5\n"); + } virtual int getWindowSize(){return stepSize_;} From 24e5152180c5a6f388eabc4669dd633402f5d9b5 Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Wed, 24 Aug 2016 11:41:34 +0200 Subject: [PATCH 04/16] Fixed Cmake for when QT5 is not available, added minimal functionality to the synthesizer with the simple highgui --- modules/text/CMakeLists.txt | 2 +- modules/text/FindQT5.cmake | 14 ----- .../include/opencv2/text/text_synthesizer.hpp | 5 ++ modules/text/src/text_synthesizer.cpp | 62 ++++++++++++++----- modules/text/text_config.hpp.in | 3 + 5 files changed, 57 insertions(+), 29 deletions(-) delete mode 100644 modules/text/FindQT5.cmake diff --git a/modules/text/CMakeLists.txt b/modules/text/CMakeLists.txt index b7210eb2143..f494b400d70 100644 --- a/modules/text/CMakeLists.txt +++ b/modules/text/CMakeLists.txt @@ -51,7 +51,7 @@ else() endif() if(HAVE_CAFFE) -message(STATUS "HAVE CAFFE!!!") +message(STATUS "HAVE CAFFE!") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/include/opencv2/text_config.hpp @ONLY) diff --git a/modules/text/FindQT5.cmake b/modules/text/FindQT5.cmake deleted file mode 100644 index 37f6346de06..00000000000 --- a/modules/text/FindQT5.cmake +++ /dev/null @@ -1,14 +0,0 @@ -#Caffe -unset(QT5_FOUND) - -find_path(QT5_INCLUDE_DIR NAMES qt5/QtGui/QFontMetrics qt5/QtGui/QFont qt5/QtGui/QFontDatabase qt5/QtGui/QGuiApplication - HINTS - /usr/local/include) - -find_library(Caffe_LIBS NAMES caffe - HINTS - /usr/local/lib) - -if(Caffe_LIBS AND Caffe_INCLUDE_DIR) - set(Caffe_FOUND 1) -endif() diff --git a/modules/text/include/opencv2/text/text_synthesizer.hpp b/modules/text/include/opencv2/text/text_synthesizer.hpp index bc100aa85c0..f12444109c2 100644 --- a/modules/text/include/opencv2/text/text_synthesizer.hpp +++ b/modules/text/include/opencv2/text/text_synthesizer.hpp @@ -68,6 +68,8 @@ class CV_EXPORTS_W TextSynthesizer{ double finalBlendAlpha_; double finalBlendProb_; + + double compressionNoiseProb_; TextSynthesizer(int maxSampleWidth,int sampleHeight); public: CV_WRAP int getMaxSampleWidth(){return maxResWidth_;} @@ -92,6 +94,8 @@ class CV_EXPORTS_W TextSynthesizer{ CV_WRAP double getMaxCurveArch(){return maxCurveArch_;} CV_WRAP double getBlendAlpha(){return finalBlendAlpha_;} CV_WRAP double getBlendProb(){return finalBlendProb_;} + CV_WRAP double getCompressionNoiseProb(){return compressionNoiseProb_;} + CV_WRAP void setUnderlineProbabillity(double v){underlineProbabillity_=v;} CV_WRAP void setItalicProballity(double v){italicProbabillity_=v;} @@ -112,6 +116,7 @@ class CV_EXPORTS_W TextSynthesizer{ CV_WRAP void setMaxCurveArch(double v){maxCurveArch_=v;} CV_WRAP void setBlendAlpha(double v){finalBlendAlpha_=v;} CV_WRAP void setBlendProb(double v){finalBlendProb_=v;} + CV_WRAP void getCompressionNoiseProb(double v){compressionNoiseProb_=v;} CV_WRAP virtual void addFontFiles(const std::vector& fntList)=0; CV_WRAP virtual std::vector listAvailableFonts()=0; diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index 83f4a95a825..a2492dcaeab 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -21,17 +21,19 @@ #include #include #include + +#ifdef HAVE_QT5GUI #include #include #include #include #include - +#endif //TODO FIND apropriate #define CV_IMWRITE_JPEG_QUALITY 1 - +#define CV_LOAD_IMAGE_COLOR 1 namespace cv{ namespace text{ @@ -213,11 +215,13 @@ TextSynthesizer::TextSynthesizer(int maxSampleWidth,int sampleHeight): finalBlendAlpha_=.6; finalBlendProb_=.3; + compressionNoiseProb_=.3; } class TextSynthesizerQtImpl: public TextSynthesizer{ protected: void updateFontNameList(){ +#ifdef HAVE_QT5GUI QFontDatabase::WritingSystem qtScriptCode=QFontDatabase::Any; switch(this->script_){ case CV_TEXT_SYNTHESIZER_SCRIPT_ANY: @@ -247,8 +251,13 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ for(int k=0;kavailableFonts_.push_back(lst[k].toUtf8().constData()); } +#else + //CV_Error(Error::StsError,"QT5 not available, TextSynthesiser is not fully functional."); + //Maybe just warn +#endif } +#ifdef HAVE_QT5GUI QFont generateFont(){ CV_Assert(this->availableFonts_.size()); QFont fnt(this->availableFonts_[rng_.next() % this->availableFonts_.size()].c_str()); @@ -270,11 +279,12 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ } return fnt; } - +#endif void generateTxtPatch(Mat& output,Mat& outputMask,String caption){ const int maxTxtWidth=this->maxResWidth_; Mat textImg; textImg =cv::Mat(this->resHeight_,maxTxtWidth,CV_8UC3,Scalar_(0,0,0)); +#ifdef HAVE_QT5GUI QImage qimg((unsigned char*)(textImg.data), textImg.cols, textImg.rows, textImg.step, QImage::Format_RGB888); QPainter qp(&qimg); qp.setPen(QColor(255,255,255)); @@ -285,9 +295,19 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ qp.drawText(QPoint(txtPad_,txtPad_+ bbox.height()), caption.c_str()); qp.end(); textImg=textImg.colRange(0,min( bbox.width()+2*txtPad_,maxTxtWidth-1)); +#else + int fontFace = FONT_HERSHEY_SCRIPT_SIMPLEX; + double fontScale = 1; + int thickness = 2; + int baseline = 0; + Size textSize = getTextSize(caption, fontFace, fontScale, thickness, &baseline); + putText(textImg, caption, Point(this->txtPad_,this->resHeight_-this->txtPad_), + FONT_HERSHEY_SCRIPT_SIMPLEX, fontScale, Scalar_(255,255,255), thickness, 8); + textImg=textImg.colRange(0,min( textSize.width+2*txtPad_,maxTxtWidth-1)); + //TODO Warn without throuwing an exception +#endif Mat textGrayImg; - cvtColor(textImg,textGrayImg,COLOR_RGBA2GRAY); - + cvtColor(textImg,textGrayImg,COLOR_RGB2GRAY); //Obtaining color triplet int colorTriplet=this->rng_.next()%this->colorClusters_.rows; uchar* cVal=this->colorClusters_.ptr(colorTriplet); @@ -389,14 +409,20 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ } } - void addCompressionArtifacts(const Mat& img,Mat& res,unsigned int qualityPercentage){ - std::vector buffer; - std::vector parameters; - parameters.push_back(CV_IMWRITE_JPEG_QUALITY); - parameters.push_back(qualityPercentage % 100); - imencode(".jpg",img,buffer,parameters); - res=imdecode(buffer,0); + void addCompressionArtifacts(Mat& img){ + if(this->rng_.next()%10000 buffer; + std::vector parameters; + parameters.push_back(CV_IMWRITE_JPEG_QUALITY); + parameters.push_back(this->rng_.next() % 100); + Mat ucharImg; + img.convertTo(ucharImg,CV_8UC3,255); + imencode(".jpg",ucharImg,buffer,parameters); + ucharImg=imdecode(buffer,CV_LOAD_IMAGE_COLOR); + ucharImg.convertTo(img,CV_32FC3,1.0/255); + } } + void initColorClusters(){ this->colorClusters_=Mat(4,3,CV_8UC3,Scalar(32,32,32)); @@ -419,7 +445,9 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ RNG rng_;//Randon number generator used for all distributions int txtPad_; +#ifdef HAVE_QT5GUI Ptr fntDb_; +#endif std::vector availableFonts_; std::vector availableBgSampleFiles_; std::vector availableBgSampleImages_; @@ -441,7 +469,9 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ namedWindow("__w"); waitKey(1); destroyWindow("__w"); +#ifdef HAVE_QT5GUI this->fntDb_=Ptr(new QFontDatabase()); +#endif this->updateFontNameList(); this->initColorClusters(); } @@ -496,12 +526,10 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ std::vector txtChannels; generateTxtPatch(txtSample,txtMask,caption); - split(txtSample,txtChannels); txtChannels.push_back(txtMask); merge(txtChannels,txtMerged); addCurveDeformation(txtMerged,txtCurved); - randomlyDistortPerspective(txtCurved,txtDistorted); split(txtDistorted,txtChannels); txtMask=txtChannels[3]; @@ -518,6 +546,7 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ if((this->rng_.next()%1000)>1000*this->finalBlendProb_){ blendWeighted(sample,sample,bgResized,1-blendAlpha,blendAlpha); } + addCompressionArtifacts(sample); } void getColorClusters(CV_OUT Mat& clusters){ @@ -548,6 +577,7 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ } void addFontFiles(const std::vector& fntList){ +#ifdef HAVE_QT5GUI for(size_t n=0;nupdateFontNameList(); +#else + CV_Assert(fntList.size()>0);//to supress compilation warning + CV_Error(Error::StsError,"QT5 not available, TextSynthesiser is not fully functional."); +#endif } std::vector listBgSampleFiles(){ diff --git a/modules/text/text_config.hpp.in b/modules/text/text_config.hpp.in index 2e64f3bfb72..e4736593f2d 100644 --- a/modules/text/text_config.hpp.in +++ b/modules/text/text_config.hpp.in @@ -1,6 +1,9 @@ #ifndef __OPENCV_TEXT_CONFIG_HPP__ #define __OPENCV_TEXT_CONFIG_HPP__ +// HAVE QT5 +#cmakedefine HAVE_QT5GUI + // HAVE CAFFE #cmakedefine HAVE_CAFFE From f040b1307ab6335757b9e638f459852c08ce84d8 Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Wed, 24 Aug 2016 12:46:34 +0200 Subject: [PATCH 05/16] Added fake find Qt5Qui to supress cmake warning when Qt5 is not available --- modules/text/FindQt5Gui.cmake | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 modules/text/FindQt5Gui.cmake diff --git a/modules/text/FindQt5Gui.cmake b/modules/text/FindQt5Gui.cmake new file mode 100644 index 00000000000..4c1e5565ee0 --- /dev/null +++ b/modules/text/FindQt5Gui.cmake @@ -0,0 +1,2 @@ +#Needed to supres the Cmake warning when Qt5 is not available +# TODO employ the highgui Cmake elements more elegantly From 27f4d6768e5e6d8d0d38e1e02af8b7d60a2b65fa Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Wed, 24 Aug 2016 14:38:24 +0200 Subject: [PATCH 06/16] Fixed other platform warnings --- modules/text/CMakeLists.txt | 3 -- modules/text/include/opencv2/text/ocr.hpp | 21 +++++++----- modules/text/src/ocr_beamsearch_decoder.cpp | 3 -- modules/text/src/text_synthesizer.cpp | 37 +++++++++++---------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/modules/text/CMakeLists.txt b/modules/text/CMakeLists.txt index f494b400d70..f2b73dee41a 100644 --- a/modules/text/CMakeLists.txt +++ b/modules/text/CMakeLists.txt @@ -97,6 +97,3 @@ if(Qt5Gui_FOUND) else() message(STATUS "text module found Qt5Gui: NO") endif() - - - diff --git a/modules/text/include/opencv2/text/ocr.hpp b/modules/text/include/opencv2/text/ocr.hpp index 18e2eec3fc2..e2204c9775e 100644 --- a/modules/text/include/opencv2/text/ocr.hpp +++ b/modules/text/include/opencv2/text/ocr.hpp @@ -466,12 +466,13 @@ class CV_EXPORTS_W OCRBeamSearchDecoder : public BaseOCR int mode = OCR_DECODER_VITERBI, // HMM Decoding algorithm (only Viterbi for the moment) int beam_size = 500); // Size of the beam in Beam Search algorithm - /** @brief + /** @brief This method allows to plug a classifier that is derivative of TextImageClassifier in to + * OCRBeamSearchDecoder as a ClassifierCallback. - @param classifier The character classifier with built in feature extractor + @param classifier A pointer to a TextImageClassifier decendent @param alphabet The language alphabet one char per symbol. alphabet.size() must be equal to the number of classes - of the classifier + of the classifier. In future editinons it should be replaced with a vector of strings. @param transition_probabilities_table Table with transition probabilities between character pairs. cols == rows == alphabet.size(). @@ -480,7 +481,8 @@ class CV_EXPORTS_W OCRBeamSearchDecoder : public BaseOCR rows == alphabet.size(). @param windowWidth The width of the windows to which the sliding window will be iterated. The height will - be the height of the image. The windows might be resized to fit the classifiers input by the classifiers preprocessor + be the height of the image. The windows might be resized to fit the classifiers input by the classifiers + preprocessor. @param windowStep The step for the sliding window @@ -550,9 +552,9 @@ class CV_EXPORTS_W ImagePreprocessor{ * classifier * * This method's main use would be to use the preprocessor without feeding it to a classifier. - * Debugging is a great part + * Determining the exact behavior of a preprocessor is the main motivation for this. * - * @param input an image without any contrains + * @param input an image without any constraints * * @param output in most cases an image of fixed depth size and whitened * @@ -562,13 +564,16 @@ class CV_EXPORTS_W ImagePreprocessor{ */ CV_WRAP void preprocess(InputArray input,OutputArray output,Size sz,int outputChannels); - /** @brief Creates a functor that only resizes the input without + /** @brief Creates a functor that only resizes and changes the channels of the input + * without further processing. * - * @return shared pointer to generated preprocessor + * @return shared pointer to the generated preprocessor */ CV_WRAP static Ptr createResizer(); /** @brief + * + * @param sigma * * @return shared pointer to generated preprocessor */ diff --git a/modules/text/src/ocr_beamsearch_decoder.cpp b/modules/text/src/ocr_beamsearch_decoder.cpp index ea544708d7f..79ea56a0a47 100644 --- a/modules/text/src/ocr_beamsearch_decoder.cpp +++ b/modules/text/src/ocr_beamsearch_decoder.cpp @@ -816,9 +816,6 @@ class TextImageClassifierBeamSearchCallback: public OCRBeamSearchDecoder::Classi if(windowWidth_<=0) { windowWidth_=classifier_->getInputSize().width; - }else - { - windowWidth_=windowWidth_; } } diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index a2492dcaeab..d201ec91b18 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -167,13 +167,13 @@ void blendOverlay(Mat& out,Mat& top,Mat& bottom,Mat& topMask){ void blendOverlay(Mat& out,Scalar topCol,Scalar bottomCol,Mat& topMask); void blendOverlay(Mat& out,Scalar topCol,Scalar bottomCol,Mat& topMask){ - float topR=topCol[0]; - float topG=topCol[1]; - float topB=topCol[2]; + float topR=float(topCol[0]); + float topG=float(topCol[1]); + float topB=float(topCol[2]); - float bottomR=bottomCol[0]; - float bottomG=bottomCol[1]; - float bottomB=bottomCol[2]; + float bottomR=float(bottomCol[0]); + float bottomG=float(bottomCol[1]); + float bottomB=float(bottomCol[2]); for(int y=0;y(y); @@ -311,9 +311,9 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ //Obtaining color triplet int colorTriplet=this->rng_.next()%this->colorClusters_.rows; uchar* cVal=this->colorClusters_.ptr(colorTriplet); - Scalar_ fgText(cVal[0]/255.0,cVal[1]/255.0,cVal[2]/255.0); - Scalar_ fgBorder(cVal[3]/255.0,cVal[4]/255.0,cVal[5]/255.0); - Scalar_ fgShadow(cVal[6]/255.0,cVal[7]/255.0,cVal[8]/255.0); + Scalar_ fgText(float(cVal[0]/255.0),float(cVal[1]/255.0),float(cVal[2]/255.0)); + Scalar_ fgBorder(float(cVal[3]/255.0),float(cVal[4]/255.0),float(cVal[5]/255.0)); + Scalar_ fgShadow(float(cVal[6]/255.0),float(cVal[7]/255.0),float(cVal[8]/255.0)); Mat floatTxt;Mat floatBorder;Mat floatShadow; textGrayImg.convertTo(floatTxt, CV_32FC1, 1.0/255.0); @@ -323,7 +323,7 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ int shadowSize=(this->rng_.next()%this->maxShadowSize_)*((this->rng_.next()%10000)/10000.0>this->shadowProbabillity_); int voffset=(this->rng_.next()%(shadowSize*2+1))-shadowSize; int hoffset=(this->rng_.next()%(shadowSize*2+1))-shadowSize; - float shadowOpacity=(((this->rng_.next()%10000)*maxShadowOpacity_)/10000.0); + float shadowOpacity=float(((this->rng_.next()%10000)*maxShadowOpacity_)/10000.0); //generating shadows generateDilation(floatBorder,floatTxt,borderSize,0,0); @@ -376,13 +376,13 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ } void randomlyDistortPerspective(const Mat& inputImg,Mat& outputImg){ - int N=this->maxPerspectiveDistortion_; + int N=int(this->maxPerspectiveDistortion_); std::vector src(4);std::vector dst(4); src[0]=Point2f(0,0);src[1]=Point2f(100,0);src[2]=Point2f(0,100);src[3]=Point2f(100,100); - dst[0]=Point2f(this->rng_.next()%N,this->rng_.next()%N); - dst[1]=Point2f(100-this->rng_.next()%N,this->rng_.next()%N); - dst[2]=Point2f(this->rng_.next()%N,100-this->rng_.next()%N); - dst[3]=Point2f(100-this->rng_.next()%N,100-this->rng_.next()%N); + dst[0]=Point2f(float(this->rng_.next()%N),float(this->rng_.next()%N)); + dst[1]=Point2f(float(100-this->rng_.next()%N),float(this->rng_.next()%N)); + dst[2]=Point2f(float(this->rng_.next()%N),float(100-this->rng_.next()%N)); + dst[3]=Point2f(float(100-this->rng_.next()%N),float(100-this->rng_.next()%N)); Mat h=findHomography(src,dst); warpPerspective(inputImg,outputImg,h,inputImg.size()); } @@ -391,15 +391,16 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ if ((this->rng_.next()%1000)>1000*this->curvingProbabillity_){ Mat X=Mat(inputImg.rows,inputImg.cols,CV_32FC1); Mat Y=Mat(inputImg.rows,inputImg.cols,CV_32FC1); - int xAdd=-this->rng_.next()%inputImg.cols; + int xAdd=-int(this->rng_.next()%inputImg.cols); float xMult=(this->rng_.next()%10000)*float(maxCurveArch_)/10000; int sign=(this->rng_.next()%2)?-1:1; for(int y=0;y(y); float* yRow=Y.ptr(y); for(int x=0;xfinalBlendAlpha_*(this->rng_.next()%1000)/1000.0; + float blendAlpha=float(this->finalBlendAlpha_*(this->rng_.next()%1000)/1000.0); if((this->rng_.next()%1000)>1000*this->finalBlendProb_){ blendWeighted(sample,sample,bgResized,1-blendAlpha,blendAlpha); } From d0ec5477bc0d755af8860cf5ad61bf8b63c4d61b Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Wed, 24 Aug 2016 15:51:16 +0200 Subject: [PATCH 07/16] Minor warning supression --- modules/text/include/opencv2/text/ocr.hpp | 41 +++++++++++++++++++++-- modules/text/src/text_synthesizer.cpp | 4 +-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/modules/text/include/opencv2/text/ocr.hpp b/modules/text/include/opencv2/text/ocr.hpp index e2204c9775e..3c963cbdfae 100644 --- a/modules/text/include/opencv2/text/ocr.hpp +++ b/modules/text/include/opencv2/text/ocr.hpp @@ -673,6 +673,8 @@ class CV_EXPORTS_W DeepCNN:public TextImageClassifier * @param weightsFilename is the path to the pretrained weights of the model in binary fdorm. This file can be * very large, up to 2GB. * + * @param preprocessor is a pointer to the instance of a ImagePreprocessor implementing the preprocess_ protecteed method; + * * @param minibatchSz the maximum number of samples that can processed in parallel. In practice this parameter * has an effect only when computing in the GPU and should be set with respect to the memory available in the GPU. * @@ -681,6 +683,31 @@ class CV_EXPORTS_W DeepCNN:public TextImageClassifier */ CV_WRAP static Ptr create(String archFilename,String weightsFilename,Ptr preprocessor,int minibatchSz=100,int backEnd=OCR_HOLISTIC_BACKEND_CAFFE); + /** @brief Constructs a DeepCNN intended to be used for word spotting. + * + * This method loads a pretrained classifier and couples him with a preprocessor that standarises pixels with a + * deviation of 113. The architecture file can be downloaded from: + * + * While the weights can be downloaded from: + * + * The words assigned to the network outputs are available at: + * + * + * @param archFilename is the path to the prototxt file containing the deployment model architecture description. + * When employing OCR_HOLISTIC_BACKEND_CAFFE this is the path to the deploy ".prototxt". + * + * @param weightsFilename is the path to the pretrained weights of the model. When employing + * OCR_HOLISTIC_BACKEND_CAFFE this is the path to the ".caffemodel" file. This file can be very large, the + * pretrained DictNet uses 2GB. + * + * @param preprocessor is a pointer to the instance of a ImagePreprocessor implementing the preprocess_ protecteed method; + * + * @param minibatchSz the maximum number of samples that can processed in parallel. In practice this parameter + * has an effect only when computing in the GPU and should be set with respect to the memory available in the GPU. + * + * @param backEnd integer parameter selecting the coputation framework. For now OCR_HOLISTIC_BACKEND_CAFFE is + * the only option + */ CV_WRAP static Ptr createDictNet(String archFilename,String weightsFilename,int backEnd=OCR_HOLISTIC_BACKEND_CAFFE); }; @@ -725,7 +752,12 @@ CV_EXPORTS_W bool getCaffeAvailable(); * word given an input image. * * This class implements the logic of providing transcriptions given a vocabulary and and an image - * classifer. + * classifer. The classifier has to be any TextImageClassifier but the classifier for which this + * class was built is the DictNet. In order to load it the following files should be downloaded: + + * + * + * */ class CV_EXPORTS_W OCRHolisticWordRecognizer : public BaseOCR { @@ -791,17 +823,18 @@ class CV_EXPORTS_W OCRHolisticWordRecognizer : public BaseOCR /** - @brief simple getted for the vocabulary employed + @brief simple getter for the vocabulary employed */ CV_WRAP virtual const std::vector& getVocabulary()=0; - /** @brief + /** @brief simple getter for the preprocessing functor */ CV_WRAP virtual Ptr getClassifier()=0; /** @brief Creates an instance of the OCRHolisticWordRecognizer class. @param classifierPtr an instance of TextImageClassifier, normaly a DeepCNN instance + @param vocabularyFilename the relative or absolute path to the file containing all words in the vocabulary. Each text line in the file is assumed to be a single word. The number of words in the vocabulary must be exactly the same as the outputSize of the classifier. @@ -812,7 +845,9 @@ class CV_EXPORTS_W OCRHolisticWordRecognizer : public BaseOCR /** @brief Creates an instance of the OCRHolisticWordRecognizer class and implicitly also a DeepCNN classifier. @param modelArchFilename the relative or absolute path to the prototxt file describing the classifiers architecture. + @param modelWeightsFilename the relative or absolute path to the file containing the pretrained weights of the model in caffe-binary form. + @param vocabularyFilename the relative or absolute path to the file containing all words in the vocabulary. Each text line in the file is assumed to be a single word. The number of words in the vocabulary must be exactly the same as the outputSize of the classifier. diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index d201ec91b18..354ba643957 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -399,9 +399,7 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ float* yRow=Y.ptr(y); for(int x=0;x Date: Wed, 24 Aug 2016 18:57:20 +0200 Subject: [PATCH 08/16] minor changes in the syntheciser --- modules/text/CMakeLists.txt | 6 +- .../include/opencv2/text/text_synthesizer.hpp | 147 ++++++++++++++++-- modules/text/src/ocr_holistic.cpp | 6 +- modules/text/src/text_synthesizer.cpp | 54 ++++--- 4 files changed, 174 insertions(+), 39 deletions(-) diff --git a/modules/text/CMakeLists.txt b/modules/text/CMakeLists.txt index f2b73dee41a..d7ffe0a51a4 100644 --- a/modules/text/CMakeLists.txt +++ b/modules/text/CMakeLists.txt @@ -85,7 +85,8 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in find_package(Qt5Gui) if(Qt5Gui_FOUND) message(STATUS "text module found Qt5Gui: YES") - set(HAVE_QT5GUI 1) + set(HAVE_QT5GUI ON) + #foreach(dt5_dep Core Gui Widgets Test Concurrent) foreach(dt5_dep Gui) add_definitions(${Qt5${dt5_dep}_DEFINITIONS}) @@ -97,3 +98,6 @@ if(Qt5Gui_FOUND) else() message(STATUS "text module found Qt5Gui: NO") endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/include/opencv2/text_config.hpp @ONLY) diff --git a/modules/text/include/opencv2/text/text_synthesizer.hpp b/modules/text/include/opencv2/text/text_synthesizer.hpp index f12444109c2..167db8b274a 100644 --- a/modules/text/include/opencv2/text/text_synthesizer.hpp +++ b/modules/text/include/opencv2/text/text_synthesizer.hpp @@ -1,3 +1,46 @@ +/*M////////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +// Copyright (C) 2009, Willow Garage Inc., all rights reserved. +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + #ifndef TEXT_SYNTHESIZER_HPP #define TEXT_SYNTHESIZER_HPP @@ -96,30 +139,106 @@ class CV_EXPORTS_W TextSynthesizer{ CV_WRAP double getBlendProb(){return finalBlendProb_;} CV_WRAP double getCompressionNoiseProb(){return compressionNoiseProb_;} - - CV_WRAP void setUnderlineProbabillity(double v){underlineProbabillity_=v;} - CV_WRAP void setItalicProballity(double v){italicProbabillity_=v;} - CV_WRAP void setBoldProbabillity(double v){boldProbabillity_=v;} - CV_WRAP void setMaxPerspectiveDistortion(double v){maxPerspectiveDistortion_=v;} - - CV_WRAP void setShadowProbabillity(double v){shadowProbabillity_=v;} - CV_WRAP void setMaxShadowOpacity(double v){maxShadowOpacity_=v;} + /** + * @param v the probabillity the text will be generated with an underlined font + */ + CV_WRAP void setUnderlineProbabillity(double v){CV_Assert(v>=0 && v<=1);underlineProbabillity_=v;} + + /** + * @param v the probabillity the text will be generated with italic font instead of regular + */ + CV_WRAP void setItalicProballity(double v){CV_Assert(v>=0 && v<=1);italicProbabillity_=v;} + + /** + * @param v the probabillity the text will be generated with italic font instead of regular + */ + CV_WRAP void setBoldProbabillity(double v){CV_Assert(v>=0 && v<=1);boldProbabillity_=v;} + + /** Perspective deformation is performed by calculating a homgraphy on a square whose edges + * have moved randomly inside it. + + * @param v the percentage of the side of a ractangle each point is allowed moving + */ + CV_WRAP void setMaxPerspectiveDistortion(double v){CV_Assert(v>=0 && v<50);maxPerspectiveDistortion_=v;} + + /** + * @param v the probabillity a shadow will apear under the text. + */ + CV_WRAP void setShadowProbabillity(double v){CV_Assert(v>=0 && v<=1);shadowProbabillity_=v;} + + /** + * @param v the alpha value of the text shadow will be sampled uniformly between 0 and v + */ + CV_WRAP void setMaxShadowOpacity(double v){CV_Assert(v>=0 && v<=1);maxShadowOpacity_=v;} + + /** + * @param v the maximum size of the shadow in pixels. + */ CV_WRAP void setMaxShadowSize(int v){maxShadowSize_=v;} + + /** + * @param v the maximum number of pixels the shadow can be horizontaly off-center. + */ CV_WRAP void setMaxShadowHoffset(int v){maxShadowHoffset_=v;} + + /** + * @param v the maximum number of pixels the shadow can be vertically off-center. + */ CV_WRAP void setMaxShadowVoffset(int v){maxShadowVoffset_=v;} - CV_WRAP void setBorderProbabillity(double v){borderProbabillity_=v;} + /** + * @param v the probabillity of a border apearing around the text as oposed to shadows, + * borders are always opaque and centered. + */ + CV_WRAP void setBorderProbabillity(double v){CV_Assert(v>=0 && v<=1);borderProbabillity_=v;} + + /** + * @param v the size in pixels used for border before geometric distortions. + */ CV_WRAP void setMaxBorderSize(int v){maxBorderSize_=v;} - CV_WRAP void setCurvingProbabillity(double v){curvingProbabillity_=v;} - CV_WRAP void setMaxHeightDistortionPercentage(double v){maxHeightDistortionPercentage_=v;} + /** + * @param v the probabillity the text will be curved. + */ + CV_WRAP void setCurvingProbabillity(double v){CV_Assert(v>=0 && v<=1);curvingProbabillity_=v;} + + /** + * @param v the maximum effect curving will have as a percentage of the samples height + */ + CV_WRAP void setMaxHeightDistortionPercentage(double v){CV_Assert(v>=0 && v<=100);maxHeightDistortionPercentage_=v;} + + /** + * @param v the arch in radians whose cosine will curve the text + */ CV_WRAP void setMaxCurveArch(double v){maxCurveArch_=v;} - CV_WRAP void setBlendAlpha(double v){finalBlendAlpha_=v;} - CV_WRAP void setBlendProb(double v){finalBlendProb_=v;} - CV_WRAP void getCompressionNoiseProb(double v){compressionNoiseProb_=v;} + /** + * @param v the maximum alpha used when blending text to the background with opacity + */ + CV_WRAP void setBlendAlpha(double v){CV_Assert(v>=0 && v<=1);finalBlendAlpha_=v;} + + /** + * @param v the probability the text will be blended with the background with alpha blending. + */ + CV_WRAP void setBlendProb(double v){CV_Assert(v>=0 && v<=1);finalBlendProb_=v;} + + /** + * @param v the probability the sample will be distorted by compression artifacts + */ + CV_WRAP void getCompressionNoiseProb(double v){CV_Assert(v>=0 && v<=1);compressionNoiseProb_=v;} + + + /** @brief adds ttf fonts to the Font Database system + * + * Note for the moment adding non system fonts in X11 systems is not an option. + * + * + * @param v a list of TTF files to be incorporated in to the system. + */ CV_WRAP virtual void addFontFiles(const std::vector& fntList)=0; + CV_WRAP virtual std::vector listAvailableFonts()=0; + CV_WRAP virtual void modifyAvailableFonts(std::vector& fntList)=0; CV_WRAP virtual void addBgSampleImage(const Mat& image)=0; diff --git a/modules/text/src/ocr_holistic.cpp b/modules/text/src/ocr_holistic.cpp index 89ab90d905c..14af7d5e638 100644 --- a/modules/text/src/ocr_holistic.cpp +++ b/modules/text/src/ocr_holistic.cpp @@ -15,14 +15,10 @@ #include #include - - #ifdef HAVE_CAFFE #include "caffe/caffe.hpp" #endif -#define DBG(msg) (std::cerr<<"DBG L:"<<__LINE__<<"\t"< OCRHolisticWordRecognizer::create(String modelArc } -//******************************************************************************************************************** + } } //namespace text namespace cv diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index 354ba643957..ff269b973e2 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -5,6 +5,7 @@ #include "opencv2/calib3d.hpp" #include "opencv2/text/text_synthesizer.hpp" +#include "opencv2/text_config.hpp" #include #include @@ -22,6 +23,8 @@ #include #include + + #ifdef HAVE_QT5GUI #include #include @@ -220,7 +223,11 @@ TextSynthesizer::TextSynthesizer(int maxSampleWidth,int sampleHeight): class TextSynthesizerQtImpl: public TextSynthesizer{ protected: - void updateFontNameList(){ + bool rndProbUnder(double v){ + return this->rng_.next()%10000>10000*v; + } + + void updateFontNameList(std::vector& fntList){ #ifdef HAVE_QT5GUI QFontDatabase::WritingSystem qtScriptCode=QFontDatabase::Any; switch(this->script_){ @@ -246,33 +253,43 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ CV_Error(Error::StsError,"Unsupported script_code"); break; } - this->availableFonts_.clear(); + fntList.clear(); QStringList lst=this->fntDb_->families(qtScriptCode); for(int k=0;kavailableFonts_.push_back(lst[k].toUtf8().constData()); + fntList.push_back(lst[k].toUtf8().constData()); } #else - //CV_Error(Error::StsError,"QT5 not available, TextSynthesiser is not fully functional."); - //Maybe just warn + fntList.clear(); #endif } + void modifyAvailableFonts(std::vector& fntList){ + std::vector dbList; + this->updateFontNameList(dbList); + for(size_t k =0;kavailableFonts_=fntList; + } + #ifdef HAVE_QT5GUI QFont generateFont(){ CV_Assert(this->availableFonts_.size()); QFont fnt(this->availableFonts_[rng_.next() % this->availableFonts_.size()].c_str()); fnt.setPixelSize(this->resHeight_-2*this->txtPad_); - if((this->rng_.next()%1000)/1000.0underlineProbabillity_){ + if(this->rndProbUnder(this->underlineProbabillity_)){ fnt.setUnderline(true); }else{ fnt.setUnderline(false); } - if((this->rng_.next()%1000)/1000.0boldProbabillity_){ + if(this->rndProbUnder(this->boldProbabillity_)){ fnt.setBold(true); }else{ fnt.setBold(false); } - if((this->rng_.next()%1000)/1000.0italicProbabillity_){ + if(this->rndProbUnder(this->italicProbabillity_)){ fnt.setItalic(true); }else{ fnt.setItalic(false); @@ -319,8 +336,8 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ textGrayImg.convertTo(floatTxt, CV_32FC1, 1.0/255.0); //Sampling uniform distributionfor sizes - int borderSize=(this->rng_.next()%this->maxBorderSize_)*((this->rng_.next()%10000)/10000.0>this->borderProbabillity_); - int shadowSize=(this->rng_.next()%this->maxShadowSize_)*((this->rng_.next()%10000)/10000.0>this->shadowProbabillity_); + int borderSize=(this->rng_.next()%this->maxBorderSize_)*this->rndProbUnder(this->borderProbabillity_); + int shadowSize=(this->rng_.next()%this->maxShadowSize_)*this->rndProbUnder(this->shadowProbabillity_); int voffset=(this->rng_.next()%(shadowSize*2+1))-shadowSize; int hoffset=(this->rng_.next()%(shadowSize*2+1))-shadowSize; float shadowOpacity=float(((this->rng_.next()%10000)*maxShadowOpacity_)/10000.0); @@ -388,7 +405,7 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ } void addCurveDeformation(const Mat& inputImg,Mat& outputImg){ - if ((this->rng_.next()%1000)>1000*this->curvingProbabillity_){ + if (this->rndProbUnder(this->curvingProbabillity_)){ Mat X=Mat(inputImg.rows,inputImg.cols,CV_32FC1); Mat Y=Mat(inputImg.rows,inputImg.cols,CV_32FC1); int xAdd=-int(this->rng_.next()%inputImg.cols); @@ -409,7 +426,7 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ } void addCompressionArtifacts(Mat& img){ - if(this->rng_.next()%10000rndProbUnder(this->compressionNoiseProb_)){ std::vector buffer; std::vector parameters; parameters.push_back(CV_IMWRITE_JPEG_QUALITY); @@ -471,7 +488,7 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ #ifdef HAVE_QT5GUI this->fntDb_=Ptr(new QFontDatabase()); #endif - this->updateFontNameList(); + this->updateFontNameList(this->availableFonts_); this->initColorClusters(); } @@ -542,7 +559,7 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ blendOverlay(sample,txtSample,bgResized,txtMask); float blendAlpha=float(this->finalBlendAlpha_*(this->rng_.next()%1000)/1000.0); - if((this->rng_.next()%1000)>1000*this->finalBlendProb_){ + if(this->rndProbUnder(this->finalBlendProb_)){ blendWeighted(sample,sample,bgResized,1-blendAlpha,blendAlpha); } addCompressionArtifacts(sample); @@ -578,13 +595,12 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ void addFontFiles(const std::vector& fntList){ #ifdef HAVE_QT5GUI for(size_t n=0;nfntDb_->addApplicationFont(fntList[n].c_str()); + if(addFontSucces==-1){ + CV_Error(Error::StsError,"Failed to load ttf font. QT5 currently doesn't support this under X11"); } } - this->updateFontNameList(); + this->updateFontNameList(this->availableFonts_); #else CV_Assert(fntList.size()>0);//to supress compilation warning CV_Error(Error::StsError,"QT5 not available, TextSynthesiser is not fully functional."); From 4324a7973bf266390ae3f369f64b669933bd0c29 Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Thu, 25 Aug 2016 10:37:06 +0200 Subject: [PATCH 09/16] fixing the warning in the documentation --- modules/text/include/opencv2/text/ocr.hpp | 2 - .../include/opencv2/text/text_synthesizer.hpp | 106 ++++++++--- modules/text/samples/text_synthesiser.py | 165 ++++++++++++------ modules/text/src/ocr_holistic.cpp | 1 + modules/text/src/text_synthesizer.cpp | 32 ++-- 5 files changed, 217 insertions(+), 89 deletions(-) diff --git a/modules/text/include/opencv2/text/ocr.hpp b/modules/text/include/opencv2/text/ocr.hpp index 3c963cbdfae..f7c192a7723 100644 --- a/modules/text/include/opencv2/text/ocr.hpp +++ b/modules/text/include/opencv2/text/ocr.hpp @@ -700,8 +700,6 @@ class CV_EXPORTS_W DeepCNN:public TextImageClassifier * OCR_HOLISTIC_BACKEND_CAFFE this is the path to the ".caffemodel" file. This file can be very large, the * pretrained DictNet uses 2GB. * - * @param preprocessor is a pointer to the instance of a ImagePreprocessor implementing the preprocess_ protecteed method; - * * @param minibatchSz the maximum number of samples that can processed in parallel. In practice this parameter * has an effect only when computing in the GPU and should be set with respect to the memory available in the GPU. * diff --git a/modules/text/include/opencv2/text/text_synthesizer.hpp b/modules/text/include/opencv2/text/text_synthesizer.hpp index 167db8b274a..998a042dfee 100644 --- a/modules/text/include/opencv2/text/text_synthesizer.hpp +++ b/modules/text/include/opencv2/text/text_synthesizer.hpp @@ -60,21 +60,6 @@ enum{ CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW=6 }; -//TextSynthesizer::blendRandom depends upon these -//enums and should be updated if the change -enum { - CV_TEXT_SYNTHESIZER_BLND_NORMAL = 100, - CV_TEXT_SYNTHESIZER_BLND_OVERLAY = 200 -}; - -enum { - CV_TEXT_SYNTHESIZER_BLND_A_MAX=0, - CV_TEXT_SYNTHESIZER_BLND_A_MULT=1, - CV_TEXT_SYNTHESIZER_BLND_A_SUM=2, - CV_TEXT_SYNTHESIZER_BLND_A_MIN=3, - CV_TEXT_SYNTHESIZER_BLND_A_MEAN=4 -}; - /** @brief class that renders synthetic text images for training a CNN on * word spotting * @@ -225,34 +210,115 @@ class CV_EXPORTS_W TextSynthesizer{ /** * @param v the probability the sample will be distorted by compression artifacts */ - CV_WRAP void getCompressionNoiseProb(double v){CV_Assert(v>=0 && v<=1);compressionNoiseProb_=v;} + CV_WRAP void setCompressionNoiseProb(double v){CV_Assert(v>=0 && v<=1);compressionNoiseProb_=v;} /** @brief adds ttf fonts to the Font Database system * - * Note for the moment adding non system fonts in X11 systems is not an option. + * Note: for the moment adding non system fonts in X11 systems is not an option. * + * Fonts should be added to the system if the are to be used with the syntheciser * - * @param v a list of TTF files to be incorporated in to the system. + * @param fntList a list of TTF files to be incorporated in to the system. */ CV_WRAP virtual void addFontFiles(const std::vector& fntList)=0; + /** @brief retrieves the font family names that are beeing used by the text + * synthesizer + * + * @return a list of strings with the names from which fonts are sampled. + */ CV_WRAP virtual std::vector listAvailableFonts()=0; + + /** @brief updates retrieves the font family names that are randomly sampled + * + * This function indirectly allows you to define arbitrary font occurence + * probabilities. Since fonts are uniformly sampled from this list if a font + * is repeated, its occurence probabillity doubles. + * + * @param fntList a list of strings with the family names from which fonts + * are sampled. Only font families available in the system can be added. + */ CV_WRAP virtual void modifyAvailableFonts(std::vector& fntList)=0; + /** @brief appends an image in to the collection of images from which + * backgrounds are sampled. + * + * This function indirectly allows you to define arbitrary occurence + * probabilities. Since background images are uniformly sampled from this + * list if an image is repeated, its occurence probabillity doubles. + * + * @param image an image to be inserted. It should be an 8UC3 matrix which + * must be least bigger than the generated samples. + */ CV_WRAP virtual void addBgSampleImage(const Mat& image)=0; - + /** @brief provides the data from which text colors are sampled + * + * @param clusters a 8UC3 Matrix whith three columns and N rows + */ CV_WRAP virtual void getColorClusters(CV_OUT Mat& clusters)=0; + + /** @brief defines the data from which text colors are sampled. + * + * Text has three color parameters and in order to be able to sample a joined + * distribution instead of independently sampled, colors are uniformly sampled + * as color triplets from a fixed collection. + * This function indirectly allows you to define arbitrary occurence + * probabilities for every triplet by repeating it samples or polulating with + * samples. + * + * @param clusters a matrix that must be 8UC3, must have 3 columns and any + * number of rows. Text color is the first matrix color, border color is the + * second column and shadow color is the third color. + */ CV_WRAP virtual void setColorClusters(Mat clusters)=0; + /** @brief provides a randomly selected patch exactly as they are provided to text + * syntheciser + * + * @param sample a result variable containing a 8UC3 matrix. + */ CV_WRAP virtual void generateBgSample(CV_OUT Mat& sample)=0; + /** @brief provides the randomly rendered text with border and shadow. + * + * @param caption the string which will be rendered. Multilingual strings in + * UTF8 are suported but some fonts might not support it. The syntheciser should + * be created with a specific script for fonts guarantiing rendering of the script. + * + * @param sample an out variable containing a 32FC3 matrix with the rendered text + * including border and shadow. + * + * @param sampleMask a result parameter which contains the alpha value which is usefull + * for overlaying the text sample on other images. + */ CV_WRAP virtual void generateTxtSample(String caption,CV_OUT Mat& sample,CV_OUT Mat& sampleMask)=0; + + /** @brief generates a random text sample given a string + * + * This is the principal function of the text synthciser + * + * @param caption the transcription to be written. + * + * @param sample the resulting text sample. + */ CV_WRAP virtual void generateSample(String caption,CV_OUT Mat& sample)=0; - CV_WRAP static Ptr create(int script=CV_TEXT_SYNTHESIZER_SCRIPT_LATIN); + /** @brief public constructor for a syntheciser + * + * This constructor assigns only imutable properties of the syntheciser. + * + * @param sampleHeight the height of final samples in pixels + * + * @param maxWidth the maximum width of a sample. Any text requiring more + * width to be rendered will be ignored. + * + * @param script an enumaration which is used to constrain the available fonts + * to the ones beeing able to render strings in that script. + */ + CV_WRAP static Ptr create(int sampleHeight=50, int maxWidth=600, int script=CV_TEXT_SYNTHESIZER_SCRIPT_ANY); virtual ~TextSynthesizer(){} }; diff --git a/modules/text/samples/text_synthesiser.py b/modules/text/samples/text_synthesiser.py index 2e8fccc1f8d..5dcca8e1ad2 100644 --- a/modules/text/samples/text_synthesiser.py +++ b/modules/text/samples/text_synthesiser.py @@ -2,66 +2,127 @@ # -*- coding: utf-8 -*- import cv2 import sys -import os.path -import time -from sys import platform -from commands import getoutput as go import numpy as np +# Global Variable definition -enLexicon=['hello','world','these','samples','they','can','be', - 'english','or','other','scripts'] +helpStr="""Usage: """+sys.argv[0]+""" scenetext_segmented_word01.jpg scenetext_segmented_word02.jpg ... -grLexicon=['Ελληνικές','λέξεις','μπορούν','να','συντεθούν','εξίσου','εύκολα', - 'με','μόνη','διαφορά','την','έλλειψη','γραμματοσειρών'] + This program is a demonstration of the text syntheciser and the effect some of its parameters have. + The file parameters are optional and they are used to sample backgrounds for the sample synthesis. + + In order to quit press (Q) or (q) while the window is in focus. + """ -colorClusters=(np.random.rand(12,3,3)*255).astype('uint8') +colorClusters=(np.random.rand(20,3,3)*255).astype('uint8') +colorClusters[0,:,:]=[[255,0,255],[0,255,0],[128,32,32]] +colorClusters[0,:,:]=[[255,255,255],[0,0,0],[128,128,128]] +colorClusters[0,:,:]=[[0,0,0],[255,255,255],[32,32,32]] +words=['opencv','ανοιχτόCV','открытыйcv','مفتوحcv','פָּתוּחַcv'] +[w[0].upper()+w[1:-2]+w[-2:].upper() for w in words] +synthlist=[cv2.text.TextSynthesizer_create(50,400,k+2) for k in range(5)] +script=0 +s=synthlist[script] +word=words[script] +pause=200 -helpStr="""Usage: """+sys.argv[0]+""" scenetext_segmented_word01.jpg scenetext_segmented_word02.jpg ... +# GUI Callsback functions - This program is a demonstration generates 100 synthetic images and displays 9 of them randomly selected. - It acts as a benchmark as weel, providing an estimate on the resources need for the millions of samples needed - for training word-spotting deep CNNs. The files you provide are used to crop natural patches for background - """ +def updatePerspective(x): + global s + s.setMaxPerspectiveDistortion(x) + +def updateCompression(x): + global s + s.setCompressionNoiseProb(x/100.0) + +def updateCurvProb(x): + global s + s.setCurvingProbabillity(x/100.0) + +def updateCurvPerc(x): + global s + s.setMaxHeightDistortionPercentage(float(x)) + +def updateCurvArch(x): + global s + s.setMaxCurveArch(x/500.0) + +def switchScripts(x): + global s + global word + global synthlist + global script + script=x + s=synthlist[script] + word=words[script] + updateTrackbars() + +def updateTime(x): + global pause + pause=x + +def initialiseSynthesizers(): + global synthlist + global colorClusters + filenames=sys.argv[1:] + for fname in filenames: + img=cv2.imread(fname,cv2.IMREAD_COLOR) + for synth in synthlist: + synth.addBgSampleImage(img) + synth.setColorClusters(colorClusters) + synth.setMaxPerspectiveDistortion(0) + synth.setCompressionNoiseProb(0) + synth.setCurvingProbabillity(0) + +# Other functions + +def initWindows(): + global script + global s + global word + global pause + cv2.namedWindow('Text Synthesizer Demo',cv2.WINDOW_NORMAL) + cv2.resizeWindow('Text Synthesizer Demo',1600,900) + cv2.moveWindow('Text Synthesizer Demo',100,100) + cv2.createTrackbar('Perspective','Text Synthesizer Demo',int(s.getMaxPerspectiveDistortion()),49,updatePerspective) + cv2.createTrackbar('Compression','Text Synthesizer Demo',int(s.getCompressionNoiseProb()*100),100,updateCompression) + cv2.createTrackbar('Curve Prob.','Text Synthesizer Demo',int(s.getCurvingProbabillity()*100),100,updateCurvProb) + cv2.createTrackbar('Curve %','Text Synthesizer Demo',int(s.getMaxHeightDistortionPercentage()),10,updateCurvPerc) + cv2.createTrackbar('Curve rad.','Text Synthesizer Demo',int(s.getMaxCurveArch()*500),100,updateCurvArch) + cv2.createTrackbar('Script','Text Synthesizer Demo',int(script),4,switchScripts) + cv2.createTrackbar('Pause ms','Text Synthesizer Demo',int(pause),500,updateTime) + +def updateTrackbars(): + global script + global s + global word + global pause + cv2.setTrackbarPos('Perspective','Text Synthesizer Demo',int(s.getMaxPerspectiveDistortion())) + cv2.setTrackbarPos('Compression','Text Synthesizer Demo',int(s.getCompressionNoiseProb()*100)) + cv2.setTrackbarPos('Curve Prob.','Text Synthesizer Demo',int(s.getCurvingProbabillity()*100)) + cv2.setTrackbarPos('Curve %','Text Synthesizer Demo',int(s.getMaxHeightDistortionPercentage())) + cv2.setTrackbarPos('Curve rad.','Text Synthesizer Demo',int(s.getMaxCurveArch()*500)) + cv2.setTrackbarPos('Script','Text Synthesizer Demo',int(script)) + cv2.setTrackbarPos('Pause ms','Text Synthesizer Demo',int(pause)) + +def guiLoop(): + global script + global s + global word + global pause + k='' + while ord('q')!=k: + if pause<500: + cv2.imshow('Text Synthesizer Demo',s.generateSample(word)) + k=cv2.waitKey(pause+1) +# Main Programm if __name__=='__main__': - if len(sys.argv)<2: - print helpStr - sys.exit() - synthEng=cv2.text.TextSynthesizer_create(cv2.text.CV_TEXT_SYNTHESIZER_SCRIPT_LATIN) - synthGr=cv2.text.TextSynthesizer_create(cv2.text.CV_TEXT_SYNTHESIZER_SCRIPT_GREEK) - #we make Greek text more distorted with more curves - synthGr.setCurvingProbabillity(.60) - synthEng.setCurvingProbabillity(.05) - - for bgFname in sys.argv[1:]: - img=cv2.imread(bgFname,cv2.IMREAD_COLOR) - synthEng.addBgSampleImage(img) - synthGr.addBgSampleImage(img) - - synthEng.setColorClusters(colorClusters) - synthGr.setColorClusters(colorClusters) - enCaptions=(enLexicon*20)[:100] - grCaptions=(grLexicon*20)[:100] - for k in range(9): - cv2.namedWindow('En%d'%(k+1)) - cv2.moveWindow('En%d'%(k+1),30+(k%3)*500,30+(k/3)*160) - cv2.namedWindow('Gr%d'%(k+1)) - cv2.moveWindow('Gr%d'%(k+1),30+(k%3)*500,530+(k/3)*160) - t=time.time() - #The actual sample generation is synthEng.generateSample(c) - englishSamples=[synthEng.generateSample(c) for c in enCaptions] - greekSamples=[synthGr.generateSample(c) for c in grCaptions] - dur=time.time()-t - print 'Generated 200 samples in ',int(dur*1000),' msec.\n' - for k in range(9): - cv2.imshow('En%d'%(k+1),englishSamples[k]) - cv2.imshow('Gr%d'%(k+1),greekSamples[k]) - - print '\n\nPress (Q) to continue' - k=cv2.waitKey(); - while k!=ord('Q') and k!=ord('q'): - k=cv2.waitKey(); - cv2.destroyAllWindows() + print helpStr + initialiseSynthesizers() + initWindows() + updateTrackbars() + guiLoop() \ No newline at end of file diff --git a/modules/text/src/ocr_holistic.cpp b/modules/text/src/ocr_holistic.cpp index 14af7d5e638..3f92fa0eb83 100644 --- a/modules/text/src/ocr_holistic.cpp +++ b/modules/text/src/ocr_holistic.cpp @@ -15,6 +15,7 @@ #include #include + #ifdef HAVE_CAFFE #include "caffe/caffe.hpp" #endif diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index ff269b973e2..1432facee43 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -23,8 +23,6 @@ #include #include - - #ifdef HAVE_QT5GUI #include #include @@ -216,15 +214,15 @@ TextSynthesizer::TextSynthesizer(int maxSampleWidth,int sampleHeight): maxHeightDistortionPercentage_=5; maxCurveArch_=.1; - finalBlendAlpha_=.6; - finalBlendProb_=.3; + finalBlendAlpha_=.3; + finalBlendProb_=.1; compressionNoiseProb_=.3; } class TextSynthesizerQtImpl: public TextSynthesizer{ protected: bool rndProbUnder(double v){ - return this->rng_.next()%10000>10000*v; + return (this->rng_.next()%10000)<(10000*v); } void updateFontNameList(std::vector& fntList){ @@ -394,14 +392,18 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ void randomlyDistortPerspective(const Mat& inputImg,Mat& outputImg){ int N=int(this->maxPerspectiveDistortion_); - std::vector src(4);std::vector dst(4); - src[0]=Point2f(0,0);src[1]=Point2f(100,0);src[2]=Point2f(0,100);src[3]=Point2f(100,100); - dst[0]=Point2f(float(this->rng_.next()%N),float(this->rng_.next()%N)); - dst[1]=Point2f(float(100-this->rng_.next()%N),float(this->rng_.next()%N)); - dst[2]=Point2f(float(this->rng_.next()%N),float(100-this->rng_.next()%N)); - dst[3]=Point2f(float(100-this->rng_.next()%N),float(100-this->rng_.next()%N)); - Mat h=findHomography(src,dst); - warpPerspective(inputImg,outputImg,h,inputImg.size()); + if(N>0){ + std::vector src(4);std::vector dst(4); + src[0]=Point2f(0,0);src[1]=Point2f(100,0);src[2]=Point2f(0,100);src[3]=Point2f(100,100); + dst[0]=Point2f(float(this->rng_.next()%N),float(this->rng_.next()%N)); + dst[1]=Point2f(float(100-this->rng_.next()%N),float(this->rng_.next()%N)); + dst[2]=Point2f(float(this->rng_.next()%N),float(100-this->rng_.next()%N)); + dst[3]=Point2f(float(100-this->rng_.next()%N),float(100-this->rng_.next()%N)); + Mat h=findHomography(src,dst); + warpPerspective(inputImg,outputImg,h,inputImg.size()); + }else{ + outputImg=inputImg; + } } void addCurveDeformation(const Mat& inputImg,Mat& outputImg){ @@ -614,8 +616,8 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ } }; -Ptr TextSynthesizer::create(int script){ - Ptr res(new TextSynthesizerQtImpl(script)); +Ptr TextSynthesizer::create(int sampleHeight, int maxWidth, int script){ + Ptr res(new TextSynthesizerQtImpl(script, maxWidth,sampleHeight)); return res; } From 5d98768d79db6ec412825015494e23b17fb4914d Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Thu, 1 Sep 2016 20:12:16 +0200 Subject: [PATCH 10/16] Added more scripts and a color cluster image. #cmakedefine of HAVE_QT5GUI pending --- modules/text/CMakeLists.txt | 8 +- modules/text/FindQt5Gui.cmake | 2 - modules/text/include/opencv2/text/ocr.hpp | 3 - .../include/opencv2/text/text_synthesizer.hpp | 56 ++++- modules/text/samples/1000_color_clusters.png | Bin 0 -> 10080 bytes modules/text/samples/text_synthesiser.py | 5 + modules/text/src/text_synthesizer.cpp | 200 ++++++++++++++---- 7 files changed, 221 insertions(+), 53 deletions(-) delete mode 100644 modules/text/FindQt5Gui.cmake create mode 100644 modules/text/samples/1000_color_clusters.png diff --git a/modules/text/CMakeLists.txt b/modules/text/CMakeLists.txt index d7ffe0a51a4..a5dcf2e469d 100644 --- a/modules/text/CMakeLists.txt +++ b/modules/text/CMakeLists.txt @@ -85,8 +85,9 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in find_package(Qt5Gui) if(Qt5Gui_FOUND) message(STATUS "text module found Qt5Gui: YES") - set(HAVE_QT5GUI ON) - + set(HAVE_QT5GUI 1) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/include/opencv2/text_config.hpp @ONLY) #foreach(dt5_dep Core Gui Widgets Test Concurrent) foreach(dt5_dep Gui) add_definitions(${Qt5${dt5_dep}_DEFINITIONS}) @@ -98,6 +99,3 @@ if(Qt5Gui_FOUND) else() message(STATUS "text module found Qt5Gui: NO") endif() - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in - ${CMAKE_CURRENT_SOURCE_DIR}/include/opencv2/text_config.hpp @ONLY) diff --git a/modules/text/FindQt5Gui.cmake b/modules/text/FindQt5Gui.cmake deleted file mode 100644 index 4c1e5565ee0..00000000000 --- a/modules/text/FindQt5Gui.cmake +++ /dev/null @@ -1,2 +0,0 @@ -#Needed to supres the Cmake warning when Qt5 is not available -# TODO employ the highgui Cmake elements more elegantly diff --git a/modules/text/include/opencv2/text/ocr.hpp b/modules/text/include/opencv2/text/ocr.hpp index f7c192a7723..9b09870b5e1 100644 --- a/modules/text/include/opencv2/text/ocr.hpp +++ b/modules/text/include/opencv2/text/ocr.hpp @@ -700,9 +700,6 @@ class CV_EXPORTS_W DeepCNN:public TextImageClassifier * OCR_HOLISTIC_BACKEND_CAFFE this is the path to the ".caffemodel" file. This file can be very large, the * pretrained DictNet uses 2GB. * - * @param minibatchSz the maximum number of samples that can processed in parallel. In practice this parameter - * has an effect only when computing in the GPU and should be set with respect to the memory available in the GPU. - * * @param backEnd integer parameter selecting the coputation framework. For now OCR_HOLISTIC_BACKEND_CAFFE is * the only option */ diff --git a/modules/text/include/opencv2/text/text_synthesizer.hpp b/modules/text/include/opencv2/text/text_synthesizer.hpp index 998a042dfee..25798136d64 100644 --- a/modules/text/include/opencv2/text/text_synthesizer.hpp +++ b/modules/text/include/opencv2/text/text_synthesizer.hpp @@ -52,12 +52,38 @@ namespace text { enum{ - CV_TEXT_SYNTHESIZER_SCRIPT_ANY=1, - CV_TEXT_SYNTHESIZER_SCRIPT_LATIN=2, - CV_TEXT_SYNTHESIZER_SCRIPT_GREEK=3, - CV_TEXT_SYNTHESIZER_SCRIPT_CYRILLIC=4, - CV_TEXT_SYNTHESIZER_SCRIPT_ARABIC=5, - CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW=6 + //based on QFontDatabase::WritingSystem + //Qt is the default backend + CV_TEXT_SYNTHESIZER_SCRIPT_ANY, + CV_TEXT_SYNTHESIZER_SCRIPT_LATIN, + CV_TEXT_SYNTHESIZER_SCRIPT_GREEK, + CV_TEXT_SYNTHESIZER_SCRIPT_CYRILLIC, + CV_TEXT_SYNTHESIZER_SCRIPT_ARMENIAN, + CV_TEXT_SYNTHESIZER_SCRIPT_ARABIC, + CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW, + CV_TEXT_SYNTHESIZER_SCRIPT_SYRIAC, + CV_TEXT_SYNTHESIZER_SCRIPT_THAANA, + CV_TEXT_SYNTHESIZER_SCRIPT_DEVANAGARI, + CV_TEXT_SYNTHESIZER_SCRIPT_BENGALI, + CV_TEXT_SYNTHESIZER_SCRIPT_GURMUKHI, + CV_TEXT_SYNTHESIZER_SCRIPT_GUJARATI, + CV_TEXT_SYNTHESIZER_SCRIPT_ORIYA, + CV_TEXT_SYNTHESIZER_SCRIPT_TAMIL, + CV_TEXT_SYNTHESIZER_SCRIPT_TELUGU, + CV_TEXT_SYNTHESIZER_SCRIPT_KANNADA, + CV_TEXT_SYNTHESIZER_SCRIPT_MALAYALAM, + CV_TEXT_SYNTHESIZER_SCRIPT_SINHALA, + CV_TEXT_SYNTHESIZER_SCRIPT_THAI, + CV_TEXT_SYNTHESIZER_SCRIPT_LAO, + CV_TEXT_SYNTHESIZER_SCRIPT_TIBETAN, + CV_TEXT_SYNTHESIZER_SCRIPT_MYANMAR, + CV_TEXT_SYNTHESIZER_SCRIPT_GEORGIAN, + CV_TEXT_SYNTHESIZER_SCRIPT_KHMER, + CV_TEXT_SYNTHESIZER_SCRIPT_CHINESE_SIMPLIFIED, + CV_TEXT_SYNTHESIZER_SCRIPT_CHINESE_TRADITIONAL, + CV_TEXT_SYNTHESIZER_SCRIPT_JAPANESE, + CV_TEXT_SYNTHESIZER_SCRIPT_KOREAM, + CV_TEXT_SYNTHESIZER_SCRIPT_VIETNAMESE }; /** @brief class that renders synthetic text images for training a CNN on @@ -306,6 +332,24 @@ class CV_EXPORTS_W TextSynthesizer{ */ CV_WRAP virtual void generateSample(String caption,CV_OUT Mat& sample)=0; + /** @brief returns the name of the script beeing used + * + * @return a string with the name of the script + */ + CV_WRAP virtual String getScriptName()=0; + + /** @brief returns the random seed used by the synthesizer + * + * @return an unsigned long integer with the random seed. + */ + CV_WRAP virtual uint64 getRandomSeed()=0; + + /** @brief stets the random seed used by the synthesizer + * + * @param an unsigned long integer with the random seed to be set. + */ + CV_WRAP virtual void setRandomSeed(uint64 s)=0; + /** @brief public constructor for a syntheciser * * This constructor assigns only imutable properties of the syntheciser. diff --git a/modules/text/samples/1000_color_clusters.png b/modules/text/samples/1000_color_clusters.png new file mode 100644 index 0000000000000000000000000000000000000000..e151964dd772dcac535b21fe9cfc2e33c5807234 GIT binary patch literal 10080 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 wC6|ni^XE<9x^D9JZ)vY2m&3AO7?I0Ya|#(*OVf literal 0 HcmV?d00001 diff --git a/modules/text/samples/text_synthesiser.py b/modules/text/samples/text_synthesiser.py index 5dcca8e1ad2..f1f15290d23 100644 --- a/modules/text/samples/text_synthesiser.py +++ b/modules/text/samples/text_synthesiser.py @@ -121,6 +121,11 @@ def guiLoop(): # Main Programm if __name__=='__main__': + colorImg=cv2.imread('1000_color_clusters.png',cv2.IMREAD_COLOR) + #1000_color_clusters.png has the 3 most dominant color clusters + #from the first 1000 samples of MSCOCO-text trainset + if colorImg!=None: + colorClusters=colorImg print helpStr initialiseSynthesizers() initWindows() diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index 1432facee43..081bae21f31 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -1,4 +1,4 @@ -#include "precomp.hpp" +#include "precomp.hpp11" #include "opencv2/imgproc.hpp" #include "opencv2/core.hpp" #include "opencv2/highgui.hpp" @@ -23,6 +23,12 @@ #include #include +//If cmake doesnt detect HAVE_QT5GUI directly +//and you have highgui built with Qt5 uncomment +//the following line +//#define HAVE_QT5GUI + + #ifdef HAVE_QT5GUI #include #include @@ -40,6 +46,13 @@ namespace text{ namespace { //Unnamed namespace with auxiliary classes and functions used for quick computation +template T min_(T v1,T v2){ + return (v1=v2)*v2; +} + +template T max_(T v1,T v2){ + return (v1>v2)*v1+(v1<=v2)*v2; +} template void blendRGBA(Mat& out,const Mat &in1,const Mat& in2){ CV_Assert(out.cols==in1.cols && out.cols==in2.cols); @@ -73,6 +86,106 @@ template void blendRGBA(Mat& out,const Ma } } +#ifdef HAVE_QT5GUI +std::map initQt2CvScriptCodeMap(); +std::map initQt2CvScriptCodeMap(){ + std::map res; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ANY]=QFontDatabase::Any; + res[CV_TEXT_SYNTHESIZER_SCRIPT_LATIN]=QFontDatabase::Latin; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GREEK]=QFontDatabase::Greek; + res[CV_TEXT_SYNTHESIZER_SCRIPT_CYRILLIC]=QFontDatabase::Cyrillic; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ARMENIAN]=QFontDatabase::Armenian; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ARABIC]=QFontDatabase::Arabic; + res[CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW]=QFontDatabase::Hebrew; + res[CV_TEXT_SYNTHESIZER_SCRIPT_SYRIAC]=QFontDatabase::Syriac; + res[CV_TEXT_SYNTHESIZER_SCRIPT_THAANA]=QFontDatabase::Thaana; + res[CV_TEXT_SYNTHESIZER_SCRIPT_DEVANAGARI]=QFontDatabase::Devanagari; + res[CV_TEXT_SYNTHESIZER_SCRIPT_BENGALI]=QFontDatabase::Bengali; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GURMUKHI]=QFontDatabase::Gurmukhi; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GUJARATI]=QFontDatabase::Gujarati; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ORIYA]=QFontDatabase::Oriya; + res[CV_TEXT_SYNTHESIZER_SCRIPT_TAMIL]=QFontDatabase::Tamil; + res[CV_TEXT_SYNTHESIZER_SCRIPT_TELUGU]=QFontDatabase::Telugu; + res[CV_TEXT_SYNTHESIZER_SCRIPT_KANNADA]=QFontDatabase::Kannada; + res[CV_TEXT_SYNTHESIZER_SCRIPT_MALAYALAM]=QFontDatabase::Malayalam; + res[CV_TEXT_SYNTHESIZER_SCRIPT_SINHALA]=QFontDatabase::Sinhala; + res[CV_TEXT_SYNTHESIZER_SCRIPT_THAI]=QFontDatabase::Thai; + res[CV_TEXT_SYNTHESIZER_SCRIPT_LAO]=QFontDatabase::Lao; + res[CV_TEXT_SYNTHESIZER_SCRIPT_TIBETAN]=QFontDatabase::Tibetan; + res[CV_TEXT_SYNTHESIZER_SCRIPT_MYANMAR]=QFontDatabase::Myanmar; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GEORGIAN]=QFontDatabase::Georgian; + res[CV_TEXT_SYNTHESIZER_SCRIPT_KHMER]=QFontDatabase::Khmer; + res[CV_TEXT_SYNTHESIZER_SCRIPT_CHINESE_SIMPLIFIED]=QFontDatabase::SimplifiedChinese; + res[CV_TEXT_SYNTHESIZER_SCRIPT_CHINESE_TRADITIONAL]=QFontDatabase::TraditionalChinese; + res[CV_TEXT_SYNTHESIZER_SCRIPT_JAPANESE]=QFontDatabase::Japanese; + res[CV_TEXT_SYNTHESIZER_SCRIPT_KOREAM]=QFontDatabase::Korean; + res[CV_TEXT_SYNTHESIZER_SCRIPT_VIETNAMESE]=QFontDatabase::Vietnamese; + return res; +} + + +int getQt2CvScriptCode(int cvScriptCode); +int getQt2CvScriptCode(int cvScriptCode){ + static std::map m(initQt2CvScriptCodeMap()); + if(m.find(cvScriptCode)!=m.end()){ + return m[cvScriptCode]; + }else{ + CV_Error(Error::StsError,"Unknown script_code"); + return 0; + } +} +#endif //HAVE_QT5GUI + + +std::map initScriptCode2StringMap(); +std::map initScriptCode2StringMap(){ + std::map res; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ANY]="Any"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_LATIN]="Latin"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GREEK]="Greek"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_CYRILLIC]="Cyrillic"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ARMENIAN]="Armenian"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ARABIC]="Arabic"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW]="Hebrew"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_SYRIAC]="Syriac"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_THAANA]="Thaana"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_DEVANAGARI]="Devanagari"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_BENGALI]="Bengali"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GURMUKHI]="Gurmukhi"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GUJARATI]="Gujarati"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ORIYA]="Oriya"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_TAMIL]="Tamil"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_TELUGU]="Telugu"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_KANNADA]="Kannada"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_MALAYALAM]="Malayalam"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_SINHALA]="Sinhala"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_THAI]="Thai"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_LAO]="Lao"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_TIBETAN]="Tibetan"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_MYANMAR]="Myanmar"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GEORGIAN]="Georgian"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_KHMER]="Khmer"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_CHINESE_SIMPLIFIED]="SimplifiedChinese"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_CHINESE_TRADITIONAL]="TraditionalChinese"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_JAPANESE]="Japanese"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_KOREAM]="Korean"; + res[CV_TEXT_SYNTHESIZER_SCRIPT_VIETNAMESE]="Vietnamese"; + return res; +} + + +String getCvScriptCode2String(int cvScriptCode); +String getCvScriptCode2String(int cvScriptCode){ + static std::map m(initScriptCode2StringMap()); + if(m.find(cvScriptCode)!=m.end()){ + return m[cvScriptCode]; + }else{ + CV_Error(Error::StsError,"Unknown script_code"); + return "Error"; + } +} + + }//unnamed namespace void blendWeighted(Mat& out,Mat& top,Mat& bottom,float topMask,float bottomMask); void blendWeighted(Mat& out,Mat& top,Mat& bottom,float topMask,float bottomMask){ @@ -227,32 +340,8 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ void updateFontNameList(std::vector& fntList){ #ifdef HAVE_QT5GUI - QFontDatabase::WritingSystem qtScriptCode=QFontDatabase::Any; - switch(this->script_){ - case CV_TEXT_SYNTHESIZER_SCRIPT_ANY: - qtScriptCode=QFontDatabase::Any; - break; - case CV_TEXT_SYNTHESIZER_SCRIPT_LATIN: - qtScriptCode=QFontDatabase::Latin; - break; - case CV_TEXT_SYNTHESIZER_SCRIPT_GREEK: - qtScriptCode=QFontDatabase::Greek; - break; - case CV_TEXT_SYNTHESIZER_SCRIPT_CYRILLIC: - qtScriptCode=QFontDatabase::Cyrillic; - break; - case CV_TEXT_SYNTHESIZER_SCRIPT_ARABIC: - qtScriptCode=QFontDatabase::Arabic; - break; - case CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW: - qtScriptCode=QFontDatabase::Hebrew; - break; - default: - CV_Error(Error::StsError,"Unsupported script_code"); - break; - } fntList.clear(); - QStringList lst=this->fntDb_->families(qtScriptCode); + QStringList lst=this->fntDb_->families(QFontDatabase::WritingSystem(getQt2CvScriptCode(this->script_))); for(int k=0;kscript_); + } void generateDilation(Mat&outputImg,const Mat& inputImg,int dilationSize, int horizOffset,int vertOffset){ //erosion is defined as a negative dilation size @@ -393,13 +485,45 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ void randomlyDistortPerspective(const Mat& inputImg,Mat& outputImg){ int N=int(this->maxPerspectiveDistortion_); if(N>0){ + float xa=this->rng_.next()%N; + float xb=this->rng_.next()%N; + float xc=this->rng_.next()%N; + float xd=this->rng_.next()%N; + + float ya=this->rng_.next()%N; + float yb=this->rng_.next()%N; + float yc=this->rng_.next()%N; + float yd=this->rng_.next()%N; + + float left=min_(xa,xd); + float top=min_(ya,yb); + float right=100-min_(xb,xc); + float bottom=100-min_(yc,yd); + + float horizCoef; + float vertCoef; + + if(right-left src(4);std::vector dst(4); - src[0]=Point2f(0,0);src[1]=Point2f(100,0);src[2]=Point2f(0,100);src[3]=Point2f(100,100); - dst[0]=Point2f(float(this->rng_.next()%N),float(this->rng_.next()%N)); - dst[1]=Point2f(float(100-this->rng_.next()%N),float(this->rng_.next()%N)); - dst[2]=Point2f(float(this->rng_.next()%N),float(100-this->rng_.next()%N)); - dst[3]=Point2f(float(100-this->rng_.next()%N),float(100-this->rng_.next()%N)); + src[0]=Point2f(0,0);src[1]=Point2f(100,0);src[2]=Point2f(100,100);src[3]=Point2f(0,100); + dst[0]=Point2f(xa,ya); + dst[1]=Point2f(xb,yb); + dst[2]=Point2f(xc,yc); + dst[3]=Point2f(xd,yd); Mat h=findHomography(src,dst); + std::cerr<<"\nA: "<"<"<"<"<script_=script; //QT needs to be initialised. Highgui does this namedWindow("__w"); @@ -494,6 +613,13 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ this->initColorClusters(); } + uint64 getRandomSeed(){ + return this->rng_.state; + } + + void setRandomSeed(uint64 s){ + this->rng_.state=s; + } void generateBgSample(CV_OUT Mat& sample){ if(this->availableBgSampleImages_.size()!=0){ From 292ff6980ec4089202df4d0db85b1d9906ef1a57 Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Thu, 1 Sep 2016 20:23:12 +0200 Subject: [PATCH 11/16] removed a debugging print out --- modules/text/src/text_synthesizer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index 081bae21f31..f7b1d255f0f 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -523,7 +523,6 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ dst[2]=Point2f(xc,yc); dst[3]=Point2f(xd,yd); Mat h=findHomography(src,dst); - std::cerr<<"\nA: "<"<"<"<"< Date: Fri, 2 Sep 2016 22:51:21 +0200 Subject: [PATCH 12/16] fixed typo in text_synthesizer.cpp, added FindQt5.cmake --- modules/text/FindQT5.cmake | 14 ++++++++++++++ modules/text/src/text_synthesizer.cpp | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 modules/text/FindQT5.cmake diff --git a/modules/text/FindQT5.cmake b/modules/text/FindQT5.cmake new file mode 100644 index 00000000000..37f6346de06 --- /dev/null +++ b/modules/text/FindQT5.cmake @@ -0,0 +1,14 @@ +#Caffe +unset(QT5_FOUND) + +find_path(QT5_INCLUDE_DIR NAMES qt5/QtGui/QFontMetrics qt5/QtGui/QFont qt5/QtGui/QFontDatabase qt5/QtGui/QGuiApplication + HINTS + /usr/local/include) + +find_library(Caffe_LIBS NAMES caffe + HINTS + /usr/local/lib) + +if(Caffe_LIBS AND Caffe_INCLUDE_DIR) + set(Caffe_FOUND 1) +endif() diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index f7b1d255f0f..ddbde8c5680 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -1,4 +1,4 @@ -#include "precomp.hpp11" +#include "precomp.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/core.hpp" #include "opencv2/highgui.hpp" @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -599,7 +600,9 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ TextSynthesizer(maxSampleWidth,sampleHeight), rng_(rndState!=0?rndState:std::time(NULL)), txtPad_(10){ +#ifdef HAVE_QT5GUI CV_Assert(initQt2CvScriptCodeMap().count(script));//making sure script is a valid script code +#endif this->script_=script; //QT needs to be initialised. Highgui does this namedWindow("__w"); From 810d83dab21868143af619352fe04d226d3b0e6f Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Fri, 2 Sep 2016 23:07:56 +0200 Subject: [PATCH 13/16] removed opencv2/text_config.hpp --- modules/text/src/text_synthesizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index ddbde8c5680..2904b03a951 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -5,7 +5,7 @@ #include "opencv2/calib3d.hpp" #include "opencv2/text/text_synthesizer.hpp" -#include "opencv2/text_config.hpp" + #include #include From b0b0bf0ce33895ebcb9700374d61fcd4560f1adb Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Tue, 13 Sep 2016 19:00:56 +0200 Subject: [PATCH 14/16] Poshished up parts of the source code, identation etc. Simplified CMakeLists.txt substituting configure_file with add_definitions. --- modules/text/CMakeLists.txt | 73 +-- modules/text/FindQT5.cmake | 14 - modules/text/include/opencv2/text/ocr.hpp | 420 ++++++++++-------- .../include/opencv2/text/text_synthesizer.hpp | 123 ++--- modules/text/src/text_synthesizer.cpp | 195 ++++---- modules/text/text_config.hpp.in | 9 +- 6 files changed, 436 insertions(+), 398 deletions(-) delete mode 100644 modules/text/FindQT5.cmake diff --git a/modules/text/CMakeLists.txt b/modules/text/CMakeLists.txt index a5dcf2e469d..6653c7a9d65 100644 --- a/modules/text/CMakeLists.txt +++ b/modules/text/CMakeLists.txt @@ -4,34 +4,13 @@ ocv_define_module(text opencv_ml opencv_highgui opencv_imgproc opencv_core openc set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}) find_package(Tesseract) -if(Tesseract_FOUND) - message(STATUS "Tesseract: YES") - set(HAVE_TESSERACT 1) -else() - message(STATUS "Tesseract: NO") -endif() - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in - ${CMAKE_BINARY_DIR}/text_config.hpp @ONLY) - -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -if(${Tesseract_FOUND}) -include_directories(${Tesseract_INCLUDE_DIR}) -endif() - if(${Tesseract_FOUND}) + message(STATUS "Tesseract: YES") + include_directories(${Tesseract_INCLUDE_DIR}) target_link_libraries(opencv_text ${Tesseract_LIBS}) -endif() - -#Principal source from which adaptation came is the cnn_3dobj module -find_package(Caffe) - -if(Caffe_FOUND) - message(STATUS "Caffe: YES") - set(HAVE_CAFFE 1) + add_definitions(-DHAVE_TESSERACT) else() - message(STATUS "Caffe: NO") + message(STATUS "Tesseract: NO") endif() find_package(Protobuf) @@ -50,18 +29,17 @@ else() message(STATUS "Glog: NO") endif() -if(HAVE_CAFFE) -message(STATUS "HAVE CAFFE!") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in - ${CMAKE_CURRENT_SOURCE_DIR}/include/opencv2/text_config.hpp @ONLY) - - -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +find_package(Caffe) +if(Caffe_FOUND) + message(STATUS "Caffe: YES") + set(HAVE_CAFFE 1) +else() + message(STATUS "Caffe: NO") +endif() -if(${Caffe_FOUND}) +if(HAVE_CAFFE AND HAVE_GLOG AND HAVE_PROTOBUF) include_directories(${Caffe_INCLUDE_DIR}) - #taken from caffe's cmake find_package(HDF5 COMPONENTS HL REQUIRED) include_directories(SYSTEM ${HDF5_INCLUDE_DIRS} ${HDF5_HL_INCLUDE_DIR}) list(APPEND Caffe_LINKER_LIBS ${HDF5_LIBRARIES}) @@ -69,33 +47,26 @@ if(${Caffe_FOUND}) include_directories(SYSTEM ${Boost_INCLUDE_DIR}) include_directories(SYSTEM /usr/local/cuda-7.5/targets/x86_64-linux/include/) list(APPEND Caffe_LINKER_LIBS ${Boost_LIBRARIES}) - -endif() - - -if(${Caffe_FOUND}) - #taken from caffe's cmake target_link_libraries(opencv_text ${Caffe_LIBS} ${Glog_LIBS} ${Protobuf_LIBS} ${HDF5_LIBRARIES} ${Boost_LIBRARIES}) -endif() -endif() + add_definitions(-DHAVE_CAFFE) +endif() #HAVE_CAFFE -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in - ${CMAKE_BINARY_DIR}/text_config.hpp @ONLY) +message(STATUS "TEXT CAFFE SEARCH") +if() + message(STATUS "TEXT NO CAFFE CONFLICT") +else() + message(STATUS "TEXT CAFFE CONFLICT") +endif() find_package(Qt5Gui) if(Qt5Gui_FOUND) - message(STATUS "text module found Qt5Gui: YES") - set(HAVE_QT5GUI 1) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/text_config.hpp.in - ${CMAKE_CURRENT_SOURCE_DIR}/include/opencv2/text_config.hpp @ONLY) - #foreach(dt5_dep Core Gui Widgets Test Concurrent) + message(STATUS "text module found Qt5Gui: YES") + add_definitions(-DHAVE_QT5GUI) foreach(dt5_dep Gui) add_definitions(${Qt5${dt5_dep}_DEFINITIONS}) include_directories(${Qt5${dt5_dep}_INCLUDE_DIRS}) - #list(APPEND HIGHGUI_LIBRARIES ${Qt5${dt5_dep}_LIBRARIES}) target_link_libraries(opencv_text ${Qt5${dt5_dep}_LIBRARIES}) endforeach() - else() message(STATUS "text module found Qt5Gui: NO") endif() diff --git a/modules/text/FindQT5.cmake b/modules/text/FindQT5.cmake deleted file mode 100644 index 37f6346de06..00000000000 --- a/modules/text/FindQT5.cmake +++ /dev/null @@ -1,14 +0,0 @@ -#Caffe -unset(QT5_FOUND) - -find_path(QT5_INCLUDE_DIR NAMES qt5/QtGui/QFontMetrics qt5/QtGui/QFont qt5/QtGui/QFontDatabase qt5/QtGui/QGuiApplication - HINTS - /usr/local/include) - -find_library(Caffe_LIBS NAMES caffe - HINTS - /usr/local/lib) - -if(Caffe_LIBS AND Caffe_INCLUDE_DIR) - set(Caffe_FOUND 1) -endif() diff --git a/modules/text/include/opencv2/text/ocr.hpp b/modules/text/include/opencv2/text/ocr.hpp index 9b09870b5e1..109b6671e8d 100644 --- a/modules/text/include/opencv2/text/ocr.hpp +++ b/modules/text/include/opencv2/text/ocr.hpp @@ -65,21 +65,27 @@ enum OCR_LEVEL_TEXTLINE }; -//base class BaseOCR declares a common API that would be used in a typical text recognition scenario +//base class BaseOCR declares a common API that would be used in a typical text +//recognition scenario class CV_EXPORTS_W BaseOCR { -public: + public: virtual ~BaseOCR() {}; - virtual void run(Mat& image, std::string& output_text, std::vector* component_rects=NULL, - std::vector* component_texts=NULL, std::vector* component_confidences=NULL, + virtual void run(Mat& image, std::string& output_text, + std::vector* component_rects=NULL, + std::vector* component_texts=NULL, + std::vector* component_confidences=NULL, int component_level=0) = 0; - virtual void run(Mat& image, Mat& mask, std::string& output_text, std::vector* component_rects=NULL, - std::vector* component_texts=NULL, std::vector* component_confidences=NULL, + + virtual void run(Mat& image, Mat& mask, std::string& output_text, + std::vector* component_rects=NULL, + std::vector* component_texts=NULL, + std::vector* component_confidences=NULL, int component_level=0) = 0; - /** @brief Main functionality of the OCR Hierarchy. Subclasses provide default parameters for - * all parameters other than the input image. + /** @brief Main functionality of the OCR Hierarchy. Subclasses provide + * default parameters for all parameters other than the input image. */ virtual String run(InputArray image){ std::string res; @@ -87,22 +93,24 @@ class CV_EXPORTS_W BaseOCR std::vector component_confidences; std::vector component_texts; Mat inputImage=image.getMat(); - this->run(inputImage,res,&component_rects,&component_texts,&component_confidences,OCR_LEVEL_WORD); + this->run(inputImage,res,&component_rects,&component_texts, + &component_confidences,OCR_LEVEL_WORD); return res; } }; -/** @brief OCRTesseract class provides an interface with the tesseract-ocr API (v3.02.02) in C++. +/** @brief OCRTesseract class provides an interface with the tesseract-ocr API + * (v3.02.02) in C++. Notice that it is compiled only when tesseract-ocr is correctly installed. @note - - (C++) An example of OCRTesseract recognition combined with scene text detection can be found - at the end_to_end_recognition demo: + - (C++) An example of OCRTesseract recognition combined with scene text + detection can be found at the end_to_end_recognition demo: - - (C++) Another example of OCRTesseract recognition combined with scene text detection can be - found at the webcam_demo: + - (C++) Another example of OCRTesseract recognition combined with scene + text detection can be found at the webcam_demo: */ class CV_EXPORTS_W OCRTesseract : public BaseOCR @@ -110,52 +118,73 @@ class CV_EXPORTS_W OCRTesseract : public BaseOCR public: /** @brief Recognize text using the tesseract-ocr API. - Takes image on input and returns recognized text in the output_text parameter. Optionally - provides also the Rects for individual text elements found (e.g. words), and the list of those - text elements with their confidence values. + Takes image on input and returns recognized text in the output_text + parameter. Optionally provides also the Rects for individual text elements + found (e.g. words), and the list of those text elements with their + confidence values. @param image Input image CV_8UC1 or CV_8UC3 + @param output_text Output text of the tesseract-ocr. - @param component_rects If provided the method will output a list of Rects for the individual - text elements found (e.g. words or text lines). - @param component_texts If provided the method will output a list of text strings for the - recognition of individual text elements found (e.g. words or text lines). - @param component_confidences If provided the method will output a list of confidence values - for the recognition of individual text elements found (e.g. words or text lines). + + @param component_rects If provided the method will output a list of Rects + for the individual text elements found (e.g. words or text lines). + + @param component_texts If provided the method will output a list of text + strings for the recognition of individual text elements found (e.g. words or + text lines). + + @param component_confidences If provided the method will output a list of + confidence values for the recognition of individual text elements found + (e.g. words or text lines). + @param component_level OCR_LEVEL_WORD (by default), or OCR_LEVEL_TEXT_LINE. */ - virtual void run(Mat& image, std::string& output_text, std::vector* component_rects=NULL, - std::vector* component_texts=NULL, std::vector* component_confidences=NULL, + virtual void run (Mat& image, std::string& output_text, + std::vector* component_rects=NULL, + std::vector* component_texts=NULL, + std::vector* component_confidences=NULL, int component_level=0); - virtual void run(Mat& image, Mat& mask, std::string& output_text, std::vector* component_rects=NULL, - std::vector* component_texts=NULL, std::vector* component_confidences=NULL, - int component_level=0); + virtual void run (Mat& image, Mat& mask, std::string& output_text, + std::vector* component_rects=NULL, + std::vector* component_texts=NULL, + std::vector* component_confidences=NULL, + int component_level=0); // aliases for scripting - CV_WRAP String run(InputArray image, int min_confidence, int component_level=0); + CV_WRAP String run (InputArray image, int min_confidence, + int component_level=0); - CV_WRAP String run(InputArray image, InputArray mask, int min_confidence, int component_level=0); + CV_WRAP String run(InputArray image, InputArray mask, + int min_confidence, int component_level=0); CV_WRAP virtual void setWhiteList(const String& char_whitelist) = 0; - /** @brief Creates an instance of the OCRTesseract class. Initializes Tesseract. + /** @brief Creates an instance of the OCRTesseract class. Initializes + * Tesseract. + + * @param datapath the name of the parent directory of tessdata ended with + * "/", or NULL to use the system's default directory. + + * @param language an ISO 639-3 code or NULL will default to "eng". + + * @param char_whitelist specifies the list of characters used for + * recognition. NULL defaults to "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". + + * @param oem tesseract-ocr offers different OCR Engine Modes (OEM), by + * default tesseract::OEM_DEFAULT is used. See the tesseract-ocr API + * documentation for other possible values. - @param datapath the name of the parent directory of tessdata ended with "/", or NULL to use the - system's default directory. - @param language an ISO 639-3 code or NULL will default to "eng". - @param char_whitelist specifies the list of characters used for recognition. NULL defaults to - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". - @param oem tesseract-ocr offers different OCR Engine Modes (OEM), by deffault - tesseract::OEM_DEFAULT is used. See the tesseract-ocr API documentation for other possible - values. - @param psmode tesseract-ocr offers different Page Segmentation Modes (PSM) tesseract::PSM_AUTO - (fully automatic layout analysis) is used. See the tesseract-ocr API documentation for other - possible values. + * @param psmode tesseract-ocr offers different Page Segmentation Modes + * (PSM) tesseract::PSM_AUTO (fully automatic layout analysis) is used. See + * the tesseract-ocr API documentation for other possible values. */ - CV_WRAP static Ptr create(const char* datapath=NULL, const char* language=NULL, - const char* char_whitelist=NULL, int oem=3, int psmode=3); + CV_WRAP static Ptr create (const char* datapath=NULL, + const char* language=NULL, + const char* char_whitelist=NULL, + int oem=3, int psmode=3); }; @@ -166,134 +195,156 @@ enum decoder_mode OCR_DECODER_VITERBI = 0 // Other algorithms may be added }; -/** @brief OCRHMMDecoder class provides an interface for OCR using Hidden Markov Models. +/** @brief OCRHMMDecoder class provides an interface for OCR using Hidden Markov + * Models. -@note - - (C++) An example on using OCRHMMDecoder recognition combined with scene text detection can - be found at the webcam_demo sample: - + * @note + * - (C++) An example on using OCRHMMDecoder recognition combined with scene + * text detection can be found at the webcam_demo sample: + * */ -class CV_EXPORTS_W OCRHMMDecoder : public BaseOCR -{ -public: +class CV_EXPORTS_W OCRHMMDecoder : public BaseOCR { + public: /** @brief Callback with the character classifier is made a class. - This way it hides the feature extractor and the classifier itself, so developers can write - their own OCR code. + * This way it hides the feature extractor and the classifier itself, so + * developers can write their own OCR code. - The default character classifier and feature extractor can be loaded using the utility funtion - loadOCRHMMClassifierNM and KNN model provided in - . - */ - class CV_EXPORTS_W ClassifierCallback - { - public: + * The default character classifier and feature extractor can be loaded using + * the utility funtion loadOCRHMMClassifierNM and KNN model provided in + * . + */ + class CV_EXPORTS_W ClassifierCallback{ + public: virtual ~ClassifierCallback() { } - /** @brief The character classifier must return a (ranked list of) class(es) id('s) + /** @brief The character classifier must return a (ranked list of) + * class(es) id('s) - @param image Input image CV_8UC1 or CV_8UC3 with a single letter. - @param out_class The classifier returns the character class categorical label, or list of - class labels, to which the input image corresponds. - @param out_confidence The classifier returns the probability of the input image - corresponding to each classes in out_class. + * @param image Input image CV_8UC1 or CV_8UC3 with a single letter. + * @param out_class The classifier returns the character class + * categorical label, or list of class labels, to which the input image + * corresponds. + + * @param out_confidence The classifier returns the probability of the + * input image corresponding to each classes in out_class. */ - virtual void eval( InputArray image, std::vector& out_class, std::vector& out_confidence); + virtual void eval (InputArray image, std::vector& out_class, + std::vector& out_confidence); }; -public: /** @brief Recognize text using HMM. - Takes binary image on input and returns recognized text in the output_text parameter. Optionally - provides also the Rects for individual text elements found (e.g. words), and the list of those - text elements with their confidence values. + * Takes binary image on input and returns recognized text in the output_text + * parameter. Optionally provides also the Rects for individual text elements + * found (e.g. words), and the list of those text elements with their + * confidence values. - @param image Input binary image CV_8UC1 with a single text line (or word). + * @param image Input binary image CV_8UC1 with a single text line (or word). - @param output_text Output text. Most likely character sequence found by the HMM decoder. + * @param output_text Output text. Most likely character sequence found by + * the HMM decoder. - @param component_rects If provided the method will output a list of Rects for the individual - text elements found (e.g. words). + * @param component_rects If provided the method will output a list of Rects + * for the individual text elements found (e.g. words). - @param component_texts If provided the method will output a list of text strings for the - recognition of individual text elements found (e.g. words). + * @param component_texts If provided the method will output a list of text + * strings for the recognition of individual text elements found (e.g. words) + * . - @param component_confidences If provided the method will output a list of confidence values - for the recognition of individual text elements found (e.g. words). + * @param component_confidences If provided the method will output a list of + * confidence values for the recognition of individual text elements found + * (e.g. words). - @param component_level Only OCR_LEVEL_WORD is supported. - */ - virtual void run(Mat& image, std::string& output_text, std::vector* component_rects=NULL, - std::vector* component_texts=NULL, std::vector* component_confidences=NULL, - int component_level=0); + * @param component_level Only OCR_LEVEL_WORD is supported. + */ + virtual void run (Mat& image, std::string& output_text, + std::vector* component_rects=NULL, + std::vector* component_texts=NULL, + std::vector* component_confidences=NULL, + int component_level=0); /** @brief Recognize text using HMM. - Takes an image and a mask (where each connected component corresponds to a segmented character) - on input and returns recognized text in the output_text parameter. Optionally - provides also the Rects for individual text elements found (e.g. words), and the list of those - text elements with their confidence values. + * Takes an image and a mask (where each connected component corresponds to a + * segmented character) on input and returns recognized text in the + * output_text parameter. Optionally provides also the Rects for individual + * text elements found (e.g. words), and the list of those text elements with + * their confidence values. - @param image Input image CV_8UC1 or CV_8UC3 with a single text line (or word). - @param mask Input binary image CV_8UC1 same size as input image. Each connected component in mask corresponds to a segmented character in the input image. + * @param image Input image CV_8UC1 or CV_8UC3 with a single text line + * (or word). - @param output_text Output text. Most likely character sequence found by the HMM decoder. + * @param mask Input binary image CV_8UC1 same size as input image. Each + * connected component in mask corresponds to a segmented character in the + * input image. - @param component_rects If provided the method will output a list of Rects for the individual - text elements found (e.g. words). + * @param output_text Output text. Most likely character sequence found by + * the HMM decoder. - @param component_texts If provided the method will output a list of text strings for the - recognition of individual text elements found (e.g. words). + * @param component_rects If provided the method will output a list of Rects + * for the individual text elements found (e.g. words). - @param component_confidences If provided the method will output a list of confidence values - for the recognition of individual text elements found (e.g. words). + * @param component_texts If provided the method will output a list of text + * strings for the recognition of individual text elements found (e.g. words) + * . - @param component_level Only OCR_LEVEL_WORD is supported. - */ - virtual void run(Mat& image, Mat& mask, std::string& output_text, std::vector* component_rects=NULL, - std::vector* component_texts=NULL, std::vector* component_confidences=NULL, + * @param component_confidences If provided the method will output a list of + * confidence values for the recognition of individual text elements found + * (e.g. words). + + * @param component_level Only OCR_LEVEL_WORD is supported. + */ + virtual void run(Mat& image, Mat& mask, std::string& output_text, + std::vector* component_rects=NULL, + std::vector* component_texts=NULL, + std::vector* component_confidences=NULL, int component_level=0); // aliases for scripting - CV_WRAP String run(InputArray image, int min_confidence, int component_level=0); + CV_WRAP String run(InputArray image, + int min_confidence, + int component_level=0); - CV_WRAP String run(InputArray image, InputArray mask, int min_confidence, int component_level=0); + CV_WRAP String run(InputArray image, + InputArray mask, + int min_confidence, + int component_level=0); - /** @brief Creates an instance of the OCRHMMDecoder class. Initializes HMMDecoder. + /** @brief Creates an instance of the OCRHMMDecoder class. Initializes + * HMMDecoder. - @param classifier The character classifier with built in feature extractor. + * @param classifier The character classifier with built in feature + * extractor. - @param vocabulary The language vocabulary (chars when ascii english text). vocabulary.size() - must be equal to the number of classes of the classifier. + * @param vocabulary The language vocabulary (chars when ascii english text) + * . vocabulary.size() must be equal to the number of classes of the + * classifier. - @param transition_probabilities_table Table with transition probabilities between character - pairs. cols == rows == vocabulary.size(). + * @param transition_probabilities_table Table with transition probabilities + * between character pairs. cols == rows == vocabulary.size(). - @param emission_probabilities_table Table with observation emission probabilities. cols == - rows == vocabulary.size(). + * @param emission_probabilities_table Table with observation emission + * probabilities. cols == rows == vocabulary.size(). - @param mode HMM Decoding algorithm. Only OCR_DECODER_VITERBI is available for the moment - (). + * @param mode HMM Decoding algorithm. Only OCR_DECODER_VITERBI is available + * for the moment (). */ - static Ptr create(const Ptr classifier,// The character classifier with built in feature extractor - const std::string& vocabulary, // The language vocabulary (chars when ascii english text) - // size() must be equal to the number of classes - InputArray transition_probabilities_table, // Table with transition probabilities between character pairs - // cols == rows == vocabulari.size() - InputArray emission_probabilities_table, // Table with observation emission probabilities - // cols == rows == vocabulari.size() - decoder_mode mode = OCR_DECODER_VITERBI); // HMM Decoding algorithm (only Viterbi for the moment) + static Ptr create( + const Ptr classifier, // The character classifier with built in feature extractor + const std::string& vocabulary, // The language vocabulary (chars when ascii english text) size() must be equal to the number of classes + InputArray transition_probabilities_table, // Table with transition probabilities between character pairs cols == rows == vocabulari.size() + InputArray emission_probabilities_table, // Table with observation emission probabilities cols == rows == vocabulari.size() + decoder_mode mode = OCR_DECODER_VITERBI); // HMM Decoding algorithm (only Viterbi for the moment) - CV_WRAP static Ptr create(const Ptr classifier,// The character classifier with built in feature extractor - const String& vocabulary, // The language vocabulary (chars when ascii english text) - // size() must be equal to the number of classes - InputArray transition_probabilities_table, // Table with transition probabilities between character pairs - // cols == rows == vocabulari.size() - InputArray emission_probabilities_table, // Table with observation emission probabilities - // cols == rows == vocabulari.size() - int mode = OCR_DECODER_VITERBI); // HMM Decoding algorithm (only Viterbi for the moment) + CV_WRAP static Ptr create( + const Ptr classifier, // The character classifier with built in feature extractor + const String& vocabulary, // The language vocabulary (chars when ascii english text) size() must be equal to the number of classes + InputArray transition_probabilities_table, // Table with transition probabilities between character pairs cols == rows == vocabulari.size() + InputArray emission_probabilities_table, // Table with observation emission probabilities cols == rows == vocabulari.size() + int mode = OCR_DECODER_VITERBI); // HMM Decoding algorithm (only Viterbi for the moment) -protected: + protected: Ptr classifier; std::string vocabulary; @@ -302,58 +353,75 @@ class CV_EXPORTS_W OCRHMMDecoder : public BaseOCR decoder_mode mode; }; -/** @brief Allow to implicitly load the default character classifier when creating an OCRHMMDecoder object. +/** @brief Allow to implicitly load the default character classifier when + * creating an OCRHMMDecoder object. -@param filename The XML or YAML file with the classifier model (e.g. OCRHMM_knn_model_data.xml) + * @param filename The XML or YAML file with the classifier model (e.g. + * OCRHMM_knn_model_data.xml) -The KNN default classifier is based in the scene text recognition method proposed by Lukás Neumann & -Jiri Matas in [Neumann11b]. Basically, the region (contour) in the input image is normalized to a -fixed size, while retaining the centroid and aspect ratio, in order to extract a feature vector -based on gradient orientations along the chain-code of its perimeter. Then, the region is classified -using a KNN model trained with synthetic data of rendered characters with different standard font -types. + * The KNN default classifier is based in the scene text recognition method + * proposed by Lukás Neumann & Jiri Matas in [Neumann11b]. Basically, the region + * (contour) in the input image is normalized to a fixed size, while retaining + * the centroid and aspect ratio, in order to extract a feature vector based on + * gradient orientations along the chain-code of its perimeter. Then, the region + * is classified using a KNN model trained with synthetic data of rendered + * characters with different standard font types. */ +CV_EXPORTS_W Ptr loadOCRHMMClassifierNM ( + const String& filename); -CV_EXPORTS_W Ptr loadOCRHMMClassifierNM(const String& filename); +/** @brief Allow to implicitly load the default character classifier when + * creating an OCRHMMDecoder object. -/** @brief Allow to implicitly load the default character classifier when creating an OCRHMMDecoder object. + * @param filename The XML or YAML file with the classifier model (e.g. + * OCRBeamSearch_CNN_model_data.xml.gz) -@param filename The XML or YAML file with the classifier model (e.g. OCRBeamSearch_CNN_model_data.xml.gz) - -The CNN default classifier is based in the scene text recognition method proposed by Adam Coates & -Andrew NG in [Coates11a]. The character classifier consists in a Single Layer Convolutional Neural Network and -a linear classifier. It is applied to the input image in a sliding window fashion, providing a set of recognitions -at each window location. + * The CNN default classifier is based in the scene text recognition method + * proposed by Adam Coates & Andrew NG in [Coates11a]. The character classifier + * consists in a Single Layer Convolutional Neural Network and a linear + * classifier. It is applied to the input image in a sliding window fashion, + * providing a set of recognitions at each window location. */ -CV_EXPORTS_W Ptr loadOCRHMMClassifierCNN(const String& filename); +CV_EXPORTS_W Ptr loadOCRHMMClassifierCNN ( + const String& filename); //! @} -/** @brief Utility function to create a tailored language model transitions table from a given list of words (lexicon). - * +/** @brief Utility function to create a tailored language model transitions + * table from a given list of words (lexicon). + * @param vocabulary The language vocabulary (chars when ascii english text). - * + * @param lexicon The list of words that are expected to be found in a particular image. - * - * @param transition_probabilities_table Output table with transition probabilities between character pairs. cols == rows == vocabulary.size(). - * - * The function calculate frequency statistics of character pairs from the given lexicon and fills the output transition_probabilities_table with them. The transition_probabilities_table can be used as input in the OCRHMMDecoder::create() and OCRBeamSearchDecoder::create() methods. + + * @param transition_probabilities_table Output table with transition + * probabilities between character pairs. cols == rows == vocabulary.size(). + + * The function calculate frequency statistics of character pairs from the given + * lexicon and fills the output transition_probabilities_table with them. The + * transition_probabilities_table can be used as input in the + * OCRHMMDecoder::create() and OCRBeamSearchDecoder::create() methods. * @note - * - (C++) An alternative would be to load the default generic language transition table provided in the text module samples folder (created from ispell 42869 english words list) : + * - (C++) An alternative would be to load the default generic language + * transition table provided in the text module samples folder (created + * from ispell 42869 english words list) : * **/ -CV_EXPORTS void createOCRHMMTransitionsTable(std::string& vocabulary, std::vector& lexicon, OutputArray transition_probabilities_table); - -CV_EXPORTS_W Mat createOCRHMMTransitionsTable(const String& vocabulary, std::vector& lexicon); +CV_EXPORTS void createOCRHMMTransitionsTable ( + std::string& vocabulary, std::vector& lexicon, + OutputArray transition_probabilities_table); +CV_EXPORTS_W Mat createOCRHMMTransitionsTable ( + const String& vocabulary, std::vector& lexicon); /* OCR BeamSearch Decoder */ -/** @brief OCRBeamSearchDecoder class provides an interface for OCR using Beam Search algorithm. +/** @brief OCRBeamSearchDecoder class provides an interface for OCR using Beam + * Search algorithm. @note - - (C++) An example on using OCRBeamSearchDecoder recognition combined with scene text detection can - be found at the demo sample: + - (C++) An example on using OCRBeamSearchDecoder recognition combined with + scene text detection can be found at the demo sample: */ @@ -361,22 +429,22 @@ CV_EXPORTS_W Mat createOCRHMMTransitionsTable(const String& vocabulary, std::vec /* Forward declaration of class that can be used to generate an OCRBeamSearchDecoder::ClassifierCallbac */ class TextImageClassifier; -class CV_EXPORTS_W OCRBeamSearchDecoder : public BaseOCR -{ -public: +class CV_EXPORTS_W OCRBeamSearchDecoder : public BaseOCR{ + + public: /** @brief Callback with the character classifier is made a class. - This way it hides the feature extractor and the classifier itself, so developers can write - their own OCR code. + * This way it hides the feature extractor and the classifier itself, so + * developers can write their own OCR code. - The default character classifier and feature extractor can be loaded using the utility funtion - loadOCRBeamSearchClassifierCNN with all its parameters provided in - . + * The default character classifier and feature extractor can be loaded + * using the utility funtion loadOCRBeamSearchClassifierCNN with all its + * parameters provided in + * . */ - class CV_EXPORTS_W ClassifierCallback - { - public: + class CV_EXPORTS_W ClassifierCallback{ + public: virtual ~ClassifierCallback() { } /** @brief The character classifier must return a (ranked list of) class(es) id('s) @@ -865,12 +933,12 @@ class CV_EXPORTS_W OCRHolisticWordRecognizer : public BaseOCR * * @param vocabulary */ - CV_WRAP static Ptr create(String modelArchFilename, String modelWeightsFilename, const std::vector& vocabulary); + CV_WRAP static Ptr create (String modelArchFilename, String modelWeightsFilename, const std::vector& vocabulary); }; -} -} +}//namespace text +}//namespace cv #endif // _OPENCV_TEXT_OCR_HPP_ diff --git a/modules/text/include/opencv2/text/text_synthesizer.hpp b/modules/text/include/opencv2/text/text_synthesizer.hpp index 25798136d64..7c2ed053448 100644 --- a/modules/text/include/opencv2/text/text_synthesizer.hpp +++ b/modules/text/include/opencv2/text/text_synthesizer.hpp @@ -53,7 +53,7 @@ namespace text enum{ //based on QFontDatabase::WritingSystem - //Qt is the default backend + //Qt is the sole backend CV_TEXT_SYNTHESIZER_SCRIPT_ANY, CV_TEXT_SYNTHESIZER_SCRIPT_LATIN, CV_TEXT_SYNTHESIZER_SCRIPT_GREEK, @@ -98,7 +98,7 @@ enum{ * */ class CV_EXPORTS_W TextSynthesizer{ -protected: + protected: int resHeight_; int maxResWidth_; @@ -125,118 +125,118 @@ class CV_EXPORTS_W TextSynthesizer{ double compressionNoiseProb_; TextSynthesizer(int maxSampleWidth,int sampleHeight); -public: - CV_WRAP int getMaxSampleWidth(){return maxResWidth_;} - CV_WRAP int getSampleHeight(){return resHeight_;} - - CV_WRAP double getUnderlineProbabillity(){return underlineProbabillity_;} - CV_WRAP double getItalicProballity(){return italicProbabillity_;} - CV_WRAP double getBoldProbabillity(){return boldProbabillity_;} - CV_WRAP double getMaxPerspectiveDistortion(){return maxPerspectiveDistortion_;} - - CV_WRAP double getShadowProbabillity(){return shadowProbabillity_;} - CV_WRAP double getMaxShadowOpacity(){return maxShadowOpacity_;} - CV_WRAP int getMaxShadowSize(){return maxShadowSize_;} - CV_WRAP int getMaxShadowHoffset(){return maxShadowHoffset_;} - CV_WRAP int getMaxShadowVoffset(){return maxShadowVoffset_;} - - CV_WRAP double getBorderProbabillity(){return borderProbabillity_;} - CV_WRAP int getMaxBorderSize(){return maxBorderSize_;} - - CV_WRAP double getCurvingProbabillity(){return curvingProbabillity_;} - CV_WRAP double getMaxHeightDistortionPercentage(){return maxHeightDistortionPercentage_;} - CV_WRAP double getMaxCurveArch(){return maxCurveArch_;} - CV_WRAP double getBlendAlpha(){return finalBlendAlpha_;} - CV_WRAP double getBlendProb(){return finalBlendProb_;} - CV_WRAP double getCompressionNoiseProb(){return compressionNoiseProb_;} + public: + CV_WRAP int getMaxSampleWidth () const {return maxResWidth_;} + CV_WRAP int getSampleHeight () const {return resHeight_;} + + CV_WRAP double getUnderlineProbabillity () const {return underlineProbabillity_;} + CV_WRAP double getItalicProballity () const {return italicProbabillity_;} + CV_WRAP double getBoldProbabillity () const {return boldProbabillity_;} + CV_WRAP double getMaxPerspectiveDistortion () const {return maxPerspectiveDistortion_;} + + CV_WRAP double getShadowProbabillity () const {return shadowProbabillity_;} + CV_WRAP double getMaxShadowOpacity () const {return maxShadowOpacity_;} + CV_WRAP int getMaxShadowSize () const {return maxShadowSize_;} + CV_WRAP int getMaxShadowHoffset () const {return maxShadowHoffset_;} + CV_WRAP int getMaxShadowVoffset () const {return maxShadowVoffset_;} + + CV_WRAP double getBorderProbabillity () const {return borderProbabillity_;} + CV_WRAP int getMaxBorderSize () const {return maxBorderSize_;} + + CV_WRAP double getCurvingProbabillity () const {return curvingProbabillity_;} + CV_WRAP double getMaxHeightDistortionPercentage () const {return maxHeightDistortionPercentage_;} + CV_WRAP double getMaxCurveArch () const {return maxCurveArch_;} + CV_WRAP double getBlendAlpha () const {return finalBlendAlpha_;} + CV_WRAP double getBlendProb () const {return finalBlendProb_;} + CV_WRAP double getCompressionNoiseProb () const {return compressionNoiseProb_;} /** * @param v the probabillity the text will be generated with an underlined font */ - CV_WRAP void setUnderlineProbabillity(double v){CV_Assert(v>=0 && v<=1);underlineProbabillity_=v;} + CV_WRAP void setUnderlineProbabillity (double v) {CV_Assert(v >= 0 && v <= 1); underlineProbabillity_ = v;} /** * @param v the probabillity the text will be generated with italic font instead of regular */ - CV_WRAP void setItalicProballity(double v){CV_Assert(v>=0 && v<=1);italicProbabillity_=v;} + CV_WRAP void setItalicProballity (double v) {CV_Assert(v >= 0 && v <= 1); italicProbabillity_ = v;} /** * @param v the probabillity the text will be generated with italic font instead of regular */ - CV_WRAP void setBoldProbabillity(double v){CV_Assert(v>=0 && v<=1);boldProbabillity_=v;} + CV_WRAP void setBoldProbabillity (double v) {CV_Assert(v >= 0 && v <= 1);boldProbabillity_ = v;} /** Perspective deformation is performed by calculating a homgraphy on a square whose edges * have moved randomly inside it. * @param v the percentage of the side of a ractangle each point is allowed moving */ - CV_WRAP void setMaxPerspectiveDistortion(double v){CV_Assert(v>=0 && v<50);maxPerspectiveDistortion_=v;} + CV_WRAP void setMaxPerspectiveDistortion (double v) {CV_Assert(v >= 0 && v < 50); maxPerspectiveDistortion_ = v;} /** * @param v the probabillity a shadow will apear under the text. */ - CV_WRAP void setShadowProbabillity(double v){CV_Assert(v>=0 && v<=1);shadowProbabillity_=v;} + CV_WRAP void setShadowProbabillity (double v) {CV_Assert(v >= 0 && v <= 1); shadowProbabillity_ = v;} /** * @param v the alpha value of the text shadow will be sampled uniformly between 0 and v */ - CV_WRAP void setMaxShadowOpacity(double v){CV_Assert(v>=0 && v<=1);maxShadowOpacity_=v;} + CV_WRAP void setMaxShadowOpacity (double v) {CV_Assert(v >= 0 && v <= 1);maxShadowOpacity_ = v;} /** * @param v the maximum size of the shadow in pixels. */ - CV_WRAP void setMaxShadowSize(int v){maxShadowSize_=v;} + CV_WRAP void setMaxShadowSize (int v) {maxShadowSize_ = v;} /** * @param v the maximum number of pixels the shadow can be horizontaly off-center. */ - CV_WRAP void setMaxShadowHoffset(int v){maxShadowHoffset_=v;} + CV_WRAP void setMaxShadowHoffset (int v) {maxShadowHoffset_ = v;} /** * @param v the maximum number of pixels the shadow can be vertically off-center. */ - CV_WRAP void setMaxShadowVoffset(int v){maxShadowVoffset_=v;} + CV_WRAP void setMaxShadowVoffset (int v) {maxShadowVoffset_ = v;} /** * @param v the probabillity of a border apearing around the text as oposed to shadows, * borders are always opaque and centered. */ - CV_WRAP void setBorderProbabillity(double v){CV_Assert(v>=0 && v<=1);borderProbabillity_=v;} + CV_WRAP void setBorderProbabillity (double v) {CV_Assert(v >= 0 && v <= 1); borderProbabillity_ = v;} /** * @param v the size in pixels used for border before geometric distortions. */ - CV_WRAP void setMaxBorderSize(int v){maxBorderSize_=v;} + CV_WRAP void setMaxBorderSize (int v) {maxBorderSize_ = v;} /** * @param v the probabillity the text will be curved. */ - CV_WRAP void setCurvingProbabillity(double v){CV_Assert(v>=0 && v<=1);curvingProbabillity_=v;} + CV_WRAP void setCurvingProbabillity (double v) {CV_Assert(v >= 0 && v <= 1);curvingProbabillity_ = v;} /** * @param v the maximum effect curving will have as a percentage of the samples height */ - CV_WRAP void setMaxHeightDistortionPercentage(double v){CV_Assert(v>=0 && v<=100);maxHeightDistortionPercentage_=v;} + CV_WRAP void setMaxHeightDistortionPercentage (double v) {CV_Assert(v >= 0 && v <= 100);maxHeightDistortionPercentage_ = v;} /** * @param v the arch in radians whose cosine will curve the text */ - CV_WRAP void setMaxCurveArch(double v){maxCurveArch_=v;} + CV_WRAP void setMaxCurveArch (double v) {maxCurveArch_ = v;} /** * @param v the maximum alpha used when blending text to the background with opacity */ - CV_WRAP void setBlendAlpha(double v){CV_Assert(v>=0 && v<=1);finalBlendAlpha_=v;} + CV_WRAP void setBlendAlpha (double v) {CV_Assert(v >= 0 && v <= 1); finalBlendAlpha_ = v;} /** * @param v the probability the text will be blended with the background with alpha blending. */ - CV_WRAP void setBlendProb(double v){CV_Assert(v>=0 && v<=1);finalBlendProb_=v;} + CV_WRAP void setBlendProb (double v) {CV_Assert(v >= 0 && v <= 1); finalBlendProb_ = v;} /** * @param v the probability the sample will be distorted by compression artifacts */ - CV_WRAP void setCompressionNoiseProb(double v){CV_Assert(v>=0 && v<=1);compressionNoiseProb_=v;} + CV_WRAP void setCompressionNoiseProb (double v) {CV_Assert(v >= 0 && v <= 1); compressionNoiseProb_ = v;} /** @brief adds ttf fonts to the Font Database system @@ -247,14 +247,14 @@ class CV_EXPORTS_W TextSynthesizer{ * * @param fntList a list of TTF files to be incorporated in to the system. */ - CV_WRAP virtual void addFontFiles(const std::vector& fntList)=0; + CV_WRAP virtual void addFontFiles (const std::vector& fntList) = 0; /** @brief retrieves the font family names that are beeing used by the text * synthesizer * * @return a list of strings with the names from which fonts are sampled. */ - CV_WRAP virtual std::vector listAvailableFonts()=0; + CV_WRAP virtual std::vector listAvailableFonts () const = 0; /** @brief updates retrieves the font family names that are randomly sampled * @@ -265,7 +265,7 @@ class CV_EXPORTS_W TextSynthesizer{ * @param fntList a list of strings with the family names from which fonts * are sampled. Only font families available in the system can be added. */ - CV_WRAP virtual void modifyAvailableFonts(std::vector& fntList)=0; + CV_WRAP virtual void modifyAvailableFonts (std::vector& fntList) = 0; /** @brief appends an image in to the collection of images from which * backgrounds are sampled. @@ -277,13 +277,13 @@ class CV_EXPORTS_W TextSynthesizer{ * @param image an image to be inserted. It should be an 8UC3 matrix which * must be least bigger than the generated samples. */ - CV_WRAP virtual void addBgSampleImage(const Mat& image)=0; + CV_WRAP virtual void addBgSampleImage (const Mat& image) = 0; /** @brief provides the data from which text colors are sampled * * @param clusters a 8UC3 Matrix whith three columns and N rows */ - CV_WRAP virtual void getColorClusters(CV_OUT Mat& clusters)=0; + CV_WRAP virtual void getColorClusters (CV_OUT Mat& clusters) const = 0; /** @brief defines the data from which text colors are sampled. * @@ -298,14 +298,14 @@ class CV_EXPORTS_W TextSynthesizer{ * number of rows. Text color is the first matrix color, border color is the * second column and shadow color is the third color. */ - CV_WRAP virtual void setColorClusters(Mat clusters)=0; + CV_WRAP virtual void setColorClusters (Mat clusters) = 0; /** @brief provides a randomly selected patch exactly as they are provided to text * syntheciser * * @param sample a result variable containing a 8UC3 matrix. */ - CV_WRAP virtual void generateBgSample(CV_OUT Mat& sample)=0; + CV_WRAP virtual void generateBgSample (CV_OUT Mat& sample) = 0; /** @brief provides the randomly rendered text with border and shadow. * @@ -319,7 +319,7 @@ class CV_EXPORTS_W TextSynthesizer{ * @param sampleMask a result parameter which contains the alpha value which is usefull * for overlaying the text sample on other images. */ - CV_WRAP virtual void generateTxtSample(String caption,CV_OUT Mat& sample,CV_OUT Mat& sampleMask)=0; + CV_WRAP virtual void generateTxtSample (String caption, CV_OUT Mat& sample, CV_OUT Mat& sampleMask) = 0; /** @brief generates a random text sample given a string @@ -330,25 +330,25 @@ class CV_EXPORTS_W TextSynthesizer{ * * @param sample the resulting text sample. */ - CV_WRAP virtual void generateSample(String caption,CV_OUT Mat& sample)=0; + CV_WRAP virtual void generateSample (String caption, CV_OUT Mat& sample) = 0; /** @brief returns the name of the script beeing used * * @return a string with the name of the script */ - CV_WRAP virtual String getScriptName()=0; + CV_WRAP virtual String getScriptName () = 0; /** @brief returns the random seed used by the synthesizer * * @return an unsigned long integer with the random seed. */ - CV_WRAP virtual uint64 getRandomSeed()=0; + CV_WRAP virtual uint64 getRandomSeed () const = 0; /** @brief stets the random seed used by the synthesizer * - * @param an unsigned long integer with the random seed to be set. + * @param s an unsigned long integer with the random seed to be set. */ - CV_WRAP virtual void setRandomSeed(uint64 s)=0; + CV_WRAP virtual void setRandomSeed (uint64 s) = 0; /** @brief public constructor for a syntheciser * @@ -362,12 +362,15 @@ class CV_EXPORTS_W TextSynthesizer{ * @param script an enumaration which is used to constrain the available fonts * to the ones beeing able to render strings in that script. */ - CV_WRAP static Ptr create(int sampleHeight=50, int maxWidth=600, int script=CV_TEXT_SYNTHESIZER_SCRIPT_ANY); - virtual ~TextSynthesizer(){} -}; + CV_WRAP static Ptr create (int sampleHeight = 50, + int maxWidth = 600, + int script = CV_TEXT_SYNTHESIZER_SCRIPT_ANY); + virtual ~TextSynthesizer () {} +}; -}}//text //cv +}//text +}//cv #endif // TEXT_SYNTHESIZER_HPP diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index 2904b03a951..b08f1561fbb 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -6,7 +6,6 @@ #include "opencv2/text/text_synthesizer.hpp" - #include #include #include @@ -24,12 +23,6 @@ #include #include -//If cmake doesnt detect HAVE_QT5GUI directly -//and you have highgui built with Qt5 uncomment -//the following line -//#define HAVE_QT5GUI - - #ifdef HAVE_QT5GUI #include #include @@ -47,80 +40,80 @@ namespace text{ namespace { //Unnamed namespace with auxiliary classes and functions used for quick computation -template T min_(T v1,T v2){ - return (v1=v2)*v2; +template T min_ (T v1, T v2) { + return (v1 < v2) * v1 + (v1 >= v2) * v2; } -template T max_(T v1,T v2){ - return (v1>v2)*v1+(v1<=v2)*v2; +template T max_(T v1, T v2) { + return (v1 > v2)* v1 + (v1 <= v2) * v2; } -template void blendRGBA(Mat& out,const Mat &in1,const Mat& in2){ - CV_Assert(out.cols==in1.cols && out.cols==in2.cols); - CV_Assert(out.rows==in1.rows && out.rows==in2.rows); - CV_Assert(out.channels()==4 && in1.channels()==4 && in2.channels()==4); - int lineWidth=out.cols*4; +template void blendRGBA(Mat& out, const Mat &in1, const Mat& in2){ + CV_Assert (out.cols == in1.cols && out.cols == in2.cols); + CV_Assert (out.rows == in1.rows && out.rows == in2.rows); + CV_Assert (out.channels() == 4 && in1.channels() == 4 && in2.channels() == 4); + int lineWidth=out.cols * 4; BL blend; BL_A blendA; - for(int y=0;y(y); - const P* in1G=in1.ptr

(y)+1; - const P* in1R=in1.ptr

(y)+2; - const P* in1A=in1.ptr

(y)+3; - - const P* in2B=in2.ptr

(y); - const P* in2G=in2.ptr

(y)+1; - const P* in2R=in2.ptr

(y)+2; - const P* in2A=in2.ptr

(y)+3; - - P* outB=out.ptr

(y); - P* outG=out.ptr

(y)+1; - P* outR=out.ptr

(y)+2; - P* outA=out.ptr

(y)+3; - - for(int x=0;x (y) ; + const P* in1G = in1.ptr

(y) + 1; + const P* in1R = in1.ptr

(y) + 2; + const P* in1A = in1.ptr

(y) + 3; + + const P* in2B = in2.ptr

(y); + const P* in2G = in2.ptr

(y) + 1; + const P* in2R = in2.ptr

(y) + 2; + const P* in2A = in2.ptr

(y) + 3; + + P* outB = out.ptr

(y); + P* outG = out.ptr

(y) + 1; + P* outR = out.ptr

(y) + 2; + P* outA = out.ptr

(y) + 3; + + for(int x = 0; x < lineWidth; x += 4){ + outB[x] = blend(in1B + x, in1A + x, in2B + x, in2A + x); + outG[x] = blend(in1G + x, in1A + x, in2G + x, in2A + x); + outR[x] = blend(in1R + x, in1A + x, in2R + x, in2A + x); + outA[x] = blendA(in1A[x], in2A[x]); } } } #ifdef HAVE_QT5GUI -std::map initQt2CvScriptCodeMap(); -std::map initQt2CvScriptCodeMap(){ +std::map initQt2CvScriptCodeMap () ; +std::map initQt2CvScriptCodeMap () { std::map res; - res[CV_TEXT_SYNTHESIZER_SCRIPT_ANY]=QFontDatabase::Any; - res[CV_TEXT_SYNTHESIZER_SCRIPT_LATIN]=QFontDatabase::Latin; - res[CV_TEXT_SYNTHESIZER_SCRIPT_GREEK]=QFontDatabase::Greek; - res[CV_TEXT_SYNTHESIZER_SCRIPT_CYRILLIC]=QFontDatabase::Cyrillic; - res[CV_TEXT_SYNTHESIZER_SCRIPT_ARMENIAN]=QFontDatabase::Armenian; - res[CV_TEXT_SYNTHESIZER_SCRIPT_ARABIC]=QFontDatabase::Arabic; - res[CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW]=QFontDatabase::Hebrew; - res[CV_TEXT_SYNTHESIZER_SCRIPT_SYRIAC]=QFontDatabase::Syriac; - res[CV_TEXT_SYNTHESIZER_SCRIPT_THAANA]=QFontDatabase::Thaana; - res[CV_TEXT_SYNTHESIZER_SCRIPT_DEVANAGARI]=QFontDatabase::Devanagari; - res[CV_TEXT_SYNTHESIZER_SCRIPT_BENGALI]=QFontDatabase::Bengali; - res[CV_TEXT_SYNTHESIZER_SCRIPT_GURMUKHI]=QFontDatabase::Gurmukhi; - res[CV_TEXT_SYNTHESIZER_SCRIPT_GUJARATI]=QFontDatabase::Gujarati; - res[CV_TEXT_SYNTHESIZER_SCRIPT_ORIYA]=QFontDatabase::Oriya; - res[CV_TEXT_SYNTHESIZER_SCRIPT_TAMIL]=QFontDatabase::Tamil; - res[CV_TEXT_SYNTHESIZER_SCRIPT_TELUGU]=QFontDatabase::Telugu; - res[CV_TEXT_SYNTHESIZER_SCRIPT_KANNADA]=QFontDatabase::Kannada; - res[CV_TEXT_SYNTHESIZER_SCRIPT_MALAYALAM]=QFontDatabase::Malayalam; - res[CV_TEXT_SYNTHESIZER_SCRIPT_SINHALA]=QFontDatabase::Sinhala; - res[CV_TEXT_SYNTHESIZER_SCRIPT_THAI]=QFontDatabase::Thai; - res[CV_TEXT_SYNTHESIZER_SCRIPT_LAO]=QFontDatabase::Lao; - res[CV_TEXT_SYNTHESIZER_SCRIPT_TIBETAN]=QFontDatabase::Tibetan; - res[CV_TEXT_SYNTHESIZER_SCRIPT_MYANMAR]=QFontDatabase::Myanmar; - res[CV_TEXT_SYNTHESIZER_SCRIPT_GEORGIAN]=QFontDatabase::Georgian; - res[CV_TEXT_SYNTHESIZER_SCRIPT_KHMER]=QFontDatabase::Khmer; - res[CV_TEXT_SYNTHESIZER_SCRIPT_CHINESE_SIMPLIFIED]=QFontDatabase::SimplifiedChinese; - res[CV_TEXT_SYNTHESIZER_SCRIPT_CHINESE_TRADITIONAL]=QFontDatabase::TraditionalChinese; - res[CV_TEXT_SYNTHESIZER_SCRIPT_JAPANESE]=QFontDatabase::Japanese; - res[CV_TEXT_SYNTHESIZER_SCRIPT_KOREAM]=QFontDatabase::Korean; - res[CV_TEXT_SYNTHESIZER_SCRIPT_VIETNAMESE]=QFontDatabase::Vietnamese; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ANY] = QFontDatabase::Any; + res[CV_TEXT_SYNTHESIZER_SCRIPT_LATIN] = QFontDatabase::Latin; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GREEK] = QFontDatabase::Greek; + res[CV_TEXT_SYNTHESIZER_SCRIPT_CYRILLIC] = QFontDatabase::Cyrillic; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ARMENIAN] = QFontDatabase::Armenian; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ARABIC] = QFontDatabase::Arabic; + res[CV_TEXT_SYNTHESIZER_SCRIPT_HEBREW] = QFontDatabase::Hebrew; + res[CV_TEXT_SYNTHESIZER_SCRIPT_SYRIAC] = QFontDatabase::Syriac; + res[CV_TEXT_SYNTHESIZER_SCRIPT_THAANA] = QFontDatabase::Thaana; + res[CV_TEXT_SYNTHESIZER_SCRIPT_DEVANAGARI] = QFontDatabase::Devanagari; + res[CV_TEXT_SYNTHESIZER_SCRIPT_BENGALI] = QFontDatabase::Bengali; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GURMUKHI] = QFontDatabase::Gurmukhi; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GUJARATI] = QFontDatabase::Gujarati; + res[CV_TEXT_SYNTHESIZER_SCRIPT_ORIYA] = QFontDatabase::Oriya; + res[CV_TEXT_SYNTHESIZER_SCRIPT_TAMIL] = QFontDatabase::Tamil; + res[CV_TEXT_SYNTHESIZER_SCRIPT_TELUGU] = QFontDatabase::Telugu; + res[CV_TEXT_SYNTHESIZER_SCRIPT_KANNADA] = QFontDatabase::Kannada; + res[CV_TEXT_SYNTHESIZER_SCRIPT_MALAYALAM] = QFontDatabase::Malayalam; + res[CV_TEXT_SYNTHESIZER_SCRIPT_SINHALA] = QFontDatabase::Sinhala; + res[CV_TEXT_SYNTHESIZER_SCRIPT_THAI] = QFontDatabase::Thai; + res[CV_TEXT_SYNTHESIZER_SCRIPT_LAO] = QFontDatabase::Lao; + res[CV_TEXT_SYNTHESIZER_SCRIPT_TIBETAN] = QFontDatabase::Tibetan; + res[CV_TEXT_SYNTHESIZER_SCRIPT_MYANMAR] = QFontDatabase::Myanmar; + res[CV_TEXT_SYNTHESIZER_SCRIPT_GEORGIAN] = QFontDatabase::Georgian; + res[CV_TEXT_SYNTHESIZER_SCRIPT_KHMER] = QFontDatabase::Khmer; + res[CV_TEXT_SYNTHESIZER_SCRIPT_CHINESE_SIMPLIFIED] = QFontDatabase::SimplifiedChinese; + res[CV_TEXT_SYNTHESIZER_SCRIPT_CHINESE_TRADITIONAL] = QFontDatabase::TraditionalChinese; + res[CV_TEXT_SYNTHESIZER_SCRIPT_JAPANESE] = QFontDatabase::Japanese; + res[CV_TEXT_SYNTHESIZER_SCRIPT_KOREAM] = QFontDatabase::Korean; + res[CV_TEXT_SYNTHESIZER_SCRIPT_VIETNAMESE] = QFontDatabase::Vietnamese; return res; } @@ -446,16 +439,17 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ floatMixed.copyTo(output);floatMask.copyTo(outputMask); } - String getScriptName(){ + String getScriptName() { return getCvScriptCode2String(this->script_); } - void generateDilation(Mat&outputImg,const Mat& inputImg,int dilationSize, int horizOffset,int vertOffset){ + void generateDilation(Mat& outputImg, + const Mat& inputImg,int dilationSize, int horizOffset,int vertOffset){ //erosion is defined as a negative dilation size - if (dilationSize==0){ + if (dilationSize==0) { inputImg.copyTo(outputImg); - }else{ - if(dilationSize>0){ + } else { + if (dilationSize > 0) { if(horizOffset==0 && vertOffset==0){ dilate(inputImg,outputImg,Mat(),Point(-1, -1),dilationSize); }else{ @@ -542,7 +536,9 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ float* yRow=Y.ptr(y); for(int x=0;x availableBgSampleImages_; Mat colorClusters_; int script_; -public: - TextSynthesizerQtImpl(int script,int maxSampleWidth=400,int sampleHeight=50,uint64 rndState=0): - TextSynthesizer(maxSampleWidth,sampleHeight), - rng_(rndState!=0?rndState:std::time(NULL)), - txtPad_(10){ + public: + TextSynthesizerQtImpl(int script, + int maxSampleWidth = 400, + int sampleHeight = 50, + uint64 rndState = 0) + : TextSynthesizer(maxSampleWidth, sampleHeight) + , rng_(rndState != 0 ? rndState:std::time(NULL)) + , txtPad_(10) { #ifdef HAVE_QT5GUI CV_Assert(initQt2CvScriptCodeMap().count(script));//making sure script is a valid script code #endif @@ -609,13 +608,13 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ waitKey(1); destroyWindow("__w"); #ifdef HAVE_QT5GUI - this->fntDb_=Ptr(new QFontDatabase()); + this->fntDb_ = Ptr(new QFontDatabase()); #endif this->updateFontNameList(this->availableFonts_); this->initColorClusters(); } - uint64 getRandomSeed(){ + uint64 getRandomSeed () const { return this->rng_.state; } @@ -695,7 +694,7 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ addCompressionArtifacts(sample); } - void getColorClusters(CV_OUT Mat& clusters){ + void getColorClusters(CV_OUT Mat& clusters) const { this->colorClusters_.copyTo(clusters); } @@ -705,19 +704,32 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ clusters.copyTo(this->colorClusters_); } - std::vector listAvailableFonts(){ - return this->availableFonts_; + std::vector listAvailableFonts() const { + std::vector res; + res=this->availableFonts_; + return res; } virtual void addBgSampleImage(const Mat& inImg){ CV_Assert(inImg.cols>maxResWidth_ && inImg.rows> resHeight_); Mat img; switch(inImg.type()){ - case CV_8UC1:cvtColor(inImg, img, COLOR_GRAY2RGBA);break; - case CV_8UC3:cvtColor(inImg, img, COLOR_RGB2RGBA);break; - case CV_8UC4:inImg.copyTo(img);break; - default: - CV_Error(Error::StsError,"Only uchar images of 1, 3, or 4 channels are accepted"); + case CV_8UC1: { + cvtColor(inImg, img, COLOR_GRAY2RGBA); + break; + } + case CV_8UC3: { + cvtColor(inImg, img, COLOR_RGB2RGBA); + break; + } + case CV_8UC4: { + inImg.copyTo(img); + break; + } + default:{ + CV_Error(Error::StsError, + "Only uchar images of 1, 3, or 4 channels are accepted"); + } } this->availableBgSampleImages_.push_back(img); } @@ -749,4 +761,5 @@ Ptr TextSynthesizer::create(int sampleHeight, int maxWidth, int return res; } -}} //namespace text,namespace cv +} //namespace text +} //namespace cv diff --git a/modules/text/text_config.hpp.in b/modules/text/text_config.hpp.in index e4736593f2d..71b32993acf 100644 --- a/modules/text/text_config.hpp.in +++ b/modules/text/text_config.hpp.in @@ -2,15 +2,12 @@ #define __OPENCV_TEXT_CONFIG_HPP__ // HAVE QT5 -#cmakedefine HAVE_QT5GUI +//#cmakedefine HAVE_QT5GUI // HAVE CAFFE -#cmakedefine HAVE_CAFFE +//#cmakedefine HAVE_CAFFE // HAVE OCR Tesseract -#cmakedefine HAVE_TESSERACT - - - +//#cmakedefine HAVE_TESSERACT #endif From f17dcc29dfeea4e7113308ae9b95611c7b3232bc Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Tue, 13 Sep 2016 20:49:50 +0200 Subject: [PATCH 15/16] reverted modules/cnn_3dobj/include/opencv2/cnn_3dobj_config.hpp to an empty file as was originally in the master branch --- modules/cnn_3dobj/include/opencv2/cnn_3dobj_config.hpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 modules/cnn_3dobj/include/opencv2/cnn_3dobj_config.hpp diff --git a/modules/cnn_3dobj/include/opencv2/cnn_3dobj_config.hpp b/modules/cnn_3dobj/include/opencv2/cnn_3dobj_config.hpp new file mode 100755 index 00000000000..e69de29bb2d From 33488ec5ba87cf736b9378969749a02731fe899a Mon Sep 17 00:00:00 2001 From: Anguelos Nicolaou Date: Wed, 14 Sep 2016 07:21:45 +0200 Subject: [PATCH 16/16] Replaced uint64 for synthesizer get/set random seed to a matrix of uint8 --- .../text/include/opencv2/text/text_synthesizer.hpp | 10 ++++++---- modules/text/src/text_synthesizer.cpp | 12 ++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/modules/text/include/opencv2/text/text_synthesizer.hpp b/modules/text/include/opencv2/text/text_synthesizer.hpp index 7c2ed053448..ce898e84639 100644 --- a/modules/text/include/opencv2/text/text_synthesizer.hpp +++ b/modules/text/include/opencv2/text/text_synthesizer.hpp @@ -340,15 +340,17 @@ class CV_EXPORTS_W TextSynthesizer{ /** @brief returns the random seed used by the synthesizer * - * @return an unsigned long integer with the random seed. + * @return a matrix containing a 1 x 8 uint8 matrix containing the state of + * the random seed. */ - CV_WRAP virtual uint64 getRandomSeed () const = 0; + CV_WRAP virtual void getRandomSeed (OutputArray res) const = 0; /** @brief stets the random seed used by the synthesizer * - * @param s an unsigned long integer with the random seed to be set. + * @param state a 1 x 8 matrix of uint8 containing the random state as + * returned by getRandomSeed(); */ - CV_WRAP virtual void setRandomSeed (uint64 s) = 0; + CV_WRAP virtual void setRandomSeed (Mat state) = 0; /** @brief public constructor for a syntheciser * diff --git a/modules/text/src/text_synthesizer.cpp b/modules/text/src/text_synthesizer.cpp index b08f1561fbb..413a422b833 100644 --- a/modules/text/src/text_synthesizer.cpp +++ b/modules/text/src/text_synthesizer.cpp @@ -614,12 +614,16 @@ class TextSynthesizerQtImpl: public TextSynthesizer{ this->initColorClusters(); } - uint64 getRandomSeed () const { - return this->rng_.state; + void getRandomSeed (OutputArray res) const { + Mat tmpMat(1,8,CV_8UC1); + tmpMat.ptr(0)[0] = this->rng_.state; + tmpMat.copyTo(res); } - void setRandomSeed(uint64 s){ - this->rng_.state=s; + void setRandomSeed (Mat state) { + CV_Assert (state.rows == 1 && state.cols == 8); + CV_Assert (state.depth() == CV_8U && state.channels() == 1); + this->rng_.state=state.ptr(0)[0]; } void generateBgSample(CV_OUT Mat& sample){