From bcee96b24da64b3da2416c58cf81badb29b77461 Mon Sep 17 00:00:00 2001 From: Ilya Lavrenov Date: Tue, 21 Jan 2014 17:57:29 +0400 Subject: [PATCH] bioinspired -> opencv_contrib --- .../images/retina_TreeHdr_retina.jpg | Bin 0 -> 150688 bytes .../images/retina_TreeHdr_small.jpg | Bin 0 -> 166872 bytes .../images/studentsSample_input.jpg | Bin 0 -> 80282 bytes .../images/studentsSample_magno.jpg | Bin 0 -> 28228 bytes .../images/studentsSample_parvo.jpg | Bin 0 -> 69443 bytes .../bioinspired/retina_model/retina_model.rst | 418 +++++ .../images/retina_TreeHdr_small.jpg | Bin 0 -> 50051 bytes .../table_of_content_bioinspired.rst | 36 + modules/bioinspired/CMakeLists.txt | 3 + modules/bioinspired/doc/bioinspired.rst | 10 + .../doc/retina/images/retinaInput.jpg | Bin 0 -> 13646 bytes .../retina/images/retinaOutput_default.jpg | Bin 0 -> 22461 bytes .../retina/images/retinaOutput_realistic.jpg | Bin 0 -> 19131 bytes modules/bioinspired/doc/retina/index.rst | 493 +++++ .../include/opencv2/bioinspired.hpp | 50 + .../opencv2/bioinspired/bioinspired.hpp | 48 + .../include/opencv2/bioinspired/retina.hpp | 311 ++++ .../bioinspired/retinafasttonemapping.hpp | 121 ++ .../OpenEXRimages_HDR_Retina_toneMapping.cpp | 304 +++ ...EXRimages_HDR_Retina_toneMapping_video.cpp | 365 ++++ .../bioinspired/samples/cpp/retinaDemo.cpp | 158 ++ .../bioinspired/retina_tutorial.cpp | 149 ++ .../bioinspired/samples/ocl/retina_ocl.cpp | 119 ++ modules/bioinspired/src/basicretinafilter.cpp | 888 +++++++++ modules/bioinspired/src/basicretinafilter.hpp | 678 +++++++ .../bioinspired/src/imagelogpolprojection.cpp | 451 +++++ .../bioinspired/src/imagelogpolprojection.hpp | 244 +++ modules/bioinspired/src/magnoretinafilter.cpp | 212 +++ modules/bioinspired/src/magnoretinafilter.hpp | 246 +++ .../bioinspired/src/opencl/retina_kernel.cl | 779 ++++++++ modules/bioinspired/src/parvoretinafilter.cpp | 233 +++ modules/bioinspired/src/parvoretinafilter.hpp | 264 +++ modules/bioinspired/src/precomp.hpp | 68 + modules/bioinspired/src/retina.cpp | 745 ++++++++ modules/bioinspired/src/retina_ocl.cpp | 1643 +++++++++++++++++ modules/bioinspired/src/retina_ocl.hpp | 634 +++++++ modules/bioinspired/src/retinacolor.cpp | 725 ++++++++ modules/bioinspired/src/retinacolor.hpp | 390 ++++ .../bioinspired/src/retinafasttonemapping.cpp | 318 ++++ modules/bioinspired/src/retinafilter.cpp | 526 ++++++ modules/bioinspired/src/retinafilter.hpp | 548 ++++++ modules/bioinspired/src/templatebuffer.hpp | 555 ++++++ modules/bioinspired/test/test_main.cpp | 3 + modules/bioinspired/test/test_precomp.hpp | 16 + modules/bioinspired/test/test_retina_ocl.cpp | 164 ++ 45 files changed, 12915 insertions(+) create mode 100644 doc/tutorials/bioinspired/retina_model/images/retina_TreeHdr_retina.jpg create mode 100644 doc/tutorials/bioinspired/retina_model/images/retina_TreeHdr_small.jpg create mode 100644 doc/tutorials/bioinspired/retina_model/images/studentsSample_input.jpg create mode 100644 doc/tutorials/bioinspired/retina_model/images/studentsSample_magno.jpg create mode 100644 doc/tutorials/bioinspired/retina_model/images/studentsSample_parvo.jpg create mode 100644 doc/tutorials/bioinspired/retina_model/retina_model.rst create mode 100644 doc/tutorials/bioinspired/table_of_content_bioinspired/images/retina_TreeHdr_small.jpg create mode 100644 doc/tutorials/bioinspired/table_of_content_bioinspired/table_of_content_bioinspired.rst create mode 100644 modules/bioinspired/CMakeLists.txt create mode 100644 modules/bioinspired/doc/bioinspired.rst create mode 100644 modules/bioinspired/doc/retina/images/retinaInput.jpg create mode 100644 modules/bioinspired/doc/retina/images/retinaOutput_default.jpg create mode 100644 modules/bioinspired/doc/retina/images/retinaOutput_realistic.jpg create mode 100644 modules/bioinspired/doc/retina/index.rst create mode 100644 modules/bioinspired/include/opencv2/bioinspired.hpp create mode 100644 modules/bioinspired/include/opencv2/bioinspired/bioinspired.hpp create mode 100644 modules/bioinspired/include/opencv2/bioinspired/retina.hpp create mode 100644 modules/bioinspired/include/opencv2/bioinspired/retinafasttonemapping.hpp create mode 100644 modules/bioinspired/samples/cpp/OpenEXRimages_HDR_Retina_toneMapping.cpp create mode 100644 modules/bioinspired/samples/cpp/OpenEXRimages_HDR_Retina_toneMapping_video.cpp create mode 100644 modules/bioinspired/samples/cpp/retinaDemo.cpp create mode 100644 modules/bioinspired/samples/cpp/tutorial_code/bioinspired/retina_tutorial.cpp create mode 100644 modules/bioinspired/samples/ocl/retina_ocl.cpp create mode 100644 modules/bioinspired/src/basicretinafilter.cpp create mode 100644 modules/bioinspired/src/basicretinafilter.hpp create mode 100644 modules/bioinspired/src/imagelogpolprojection.cpp create mode 100644 modules/bioinspired/src/imagelogpolprojection.hpp create mode 100644 modules/bioinspired/src/magnoretinafilter.cpp create mode 100644 modules/bioinspired/src/magnoretinafilter.hpp create mode 100644 modules/bioinspired/src/opencl/retina_kernel.cl create mode 100644 modules/bioinspired/src/parvoretinafilter.cpp create mode 100644 modules/bioinspired/src/parvoretinafilter.hpp create mode 100644 modules/bioinspired/src/precomp.hpp create mode 100644 modules/bioinspired/src/retina.cpp create mode 100644 modules/bioinspired/src/retina_ocl.cpp create mode 100644 modules/bioinspired/src/retina_ocl.hpp create mode 100644 modules/bioinspired/src/retinacolor.cpp create mode 100644 modules/bioinspired/src/retinacolor.hpp create mode 100644 modules/bioinspired/src/retinafasttonemapping.cpp create mode 100644 modules/bioinspired/src/retinafilter.cpp create mode 100644 modules/bioinspired/src/retinafilter.hpp create mode 100644 modules/bioinspired/src/templatebuffer.hpp create mode 100644 modules/bioinspired/test/test_main.cpp create mode 100644 modules/bioinspired/test/test_precomp.hpp create mode 100644 modules/bioinspired/test/test_retina_ocl.cpp diff --git a/doc/tutorials/bioinspired/retina_model/images/retina_TreeHdr_retina.jpg b/doc/tutorials/bioinspired/retina_model/images/retina_TreeHdr_retina.jpg new file mode 100644 index 0000000000000000000000000000000000000000..251557e834b0ac025edfd1acf88509f016b11f39 GIT binary patch literal 150688 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 z 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�jh`XckekaO996v^dR}_F8^k&;MjkGafr&atjf(JO7v@OVO3pU1J9ZeH4W@xB zyKgpOq0QlaGB?D{mpf36B-+mEk(kt;_0Hqm_xk;amx}aUZ3kD+(los&B#MSyq->H% z<*WnwXaZ<`D*3*o93O57WUIVCtIw`ve2i-{V?d!;irnCW2qTi#Xi)u00?4Pu+DGvT z7bC)6AJf9iERQ?qW=7LO#3?ZwaHnVlf}q%7530vYW}7zy6i25>BO)lB6?hB{9(eCZ zpSq5y%hhtRuxFJrWqgzh?EA^#nwsp<_WtAP()e0+HV}DiFPFtL=-I;;#J&v2a@b{Y zr<)bxF%xHSqA!#0b-@7kDze9i;L~KrfwKm8nRN0YgeM)y7R3EU0rcmCvGu(CeIGR> z$(JRJk$HI2fanuzt9qF50Wl4=*^Lplh_8lq#goNrrooSkic&m`Y;j1E@)v57PSU{N zH$lJh-TW}5NGpt7c!!r7qPnc8xiN`G{{Zt=(%0E+n(xq_Tyo~g9z<%2Sh({40HmIM ztP$APf9=uQPMtmruAvJs^COUCpE8MVdE@GTx8I?Qk$fI5 z$JeRJD&S^C;n=-ykb*qe!M8l0((HTj$J3j1YzNBJVZq9XHu-ldUj+86_63*@J-TgC z38c3RsvB=}fW{%wnu6~qMn;&8H>0VB@?x-5) zHe0g4!8vP*Fw~lKw{ru=;axx=*<#g76?r_G{{Z#6we<}nA79VG((#>^X$~y7;g6^Q z$b=JS!v$*labCmd504gBQ^2LMPCCla&VX6-Ry~{&ESDe}p}U@k{xu$Ew;o#J$BbW4 zNf^Y(F*&T!VkL5RF=yq=Z1MN$Zkkn}{ue0=+5E8^18l3e)wp&%4h0j(aBHMGPs?EBLpD6K ztaoor3wp2gzBv7dSFGuZBsOHNWOmb&cXxR~zC67rUkEb942OM8i zuom4ZaRFK4MAv?FndioMa%J9w0fz)xt*R+N+<5KJew5-*7}S;J2;`EjB%rXa#E>tF z`d{hPq09N%-eE74z=nc4ChOY$fB<*o*HzCH`0$*JX<1ih;G%-q@Ao~gpQpcCOB^L3 zsPgSaKGQO^(}r_%^%5Xcw6H_J%ma!a(hYl>9_QPp7`on_qq?hNt5!w{{VFkYy^1Pwj|Nc zGuD_wPXO&=0H8qa#ST4%efql|*Wq22A8AZ=7&hd)!;s}m1Q94jb%~XD19LTB@9$iY zAvCk(;LE4Yr_9fd2INNpY6F%0FKG9`HWIM68~#NlwIYERcJ4>^<(T&k&SNK;dLwEOP4$g{J2=Oca-o znGPwCqkvUFOH#|Yg<>s$Ab@LWdy(M{cSMo=2k{5O8io#_l#i+&S?3DR#^`Vy*H1_P)8Ff`Ue(H(bn6+e;srW6Zm^S z!e11jZ9`TwPCty8xmoe%O5i9hGk_?u zU_oQ`Ab+ns^PeX#irXEr6@%>-8LP)4-U&HQ}vWSIvtajv$i+%D3v=1HAx!Yg}Ce zef_#ou0tLlfuMsIA!iv-HeC4ty0QLoKuKY2cs5VC?0b~dOQV5GL|i5Kvuap61ep^& zOg!?EKS9-jSn7?EvI~cDF(3tU2N!39XZw-WxcJEwZxpDz znTae1fBt{;>wvI9g(MFCxtvQQD)Yf$fPTTR+}Qeb^CdJuT;W2qN(TFkx^8kf;DgQk zP_CQjV<-^h%F%;te6<^aUgz@h-R-xo=al8%9zn0+D{e=@>VbS^V&*hAV zUo1%FVN>r_#~r)=_tLn@MWE?a!weW%P{%whM5JtcDi-^jqIvf<$M@@vO)*yuaxq$5 zj8=AIAZ?{BtlH5eU%vp?zg6nLGN>u35jU4(XK3V)`q$L;Qed=`Nskf)eYZ+zayuS< N$A8zN*huLs|JhAS^jrV{ literal 0 HcmV?d00001 diff --git a/doc/tutorials/bioinspired/retina_model/images/studentsSample_input.jpg b/doc/tutorials/bioinspired/retina_model/images/studentsSample_input.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a0ac96dde92548ac453f39ec12e770ac42eaaaa5 GIT binary patch literal 80282 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/bioinspired/retina_model/images/studentsSample_magno.jpg b/doc/tutorials/bioinspired/retina_model/images/studentsSample_magno.jpg new file mode 100644 index 0000000000000000000000000000000000000000..935397869dace8b3fb00e8c1b287b8fa1250b36b GIT binary patch literal 28228 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/bioinspired/retina_model/images/studentsSample_parvo.jpg b/doc/tutorials/bioinspired/retina_model/images/studentsSample_parvo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3babfefc37466d5df9331bc45c6e714748e2b04e GIT binary patch literal 69443 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�X5H(Zv{u3R+L3hvp=ysO@vDFuad9kFh5CLZz>i__6_Ox zyDUKQxk6>|@&Jh^@S=4dKalGK-iL0M$vr7+GhLHX?eoQCvh^Nh+QRW`rwKRZe6=O# z0K^kb#mv6YGWOD6sdtqDa1!}$Kl*GHV z{S>Mj`KVI*guX#MIA1cWzS>U_yzoZS(efRv7oVmbf1xr_?5Tx~IJL$1%@V}nwqAXu z4o2yb$!0Z*bY8q&mW;g5CZYu~(JW^ap87pD5xB&KP`ohWxdTlpK~I!CH=aoy$D+eU zVx0}-EKdb%hv~NQy(CWR+=yv7^KWl+FMxMIUvHWTCK!8_xI z>>FzEmX36tgPG+XqS#UoqMBs-c+mH~ZWZ?NH`F!l8T-aTJpuSh#9QtOk#&)3Rd!2j z!+fJGim6;5xzwV!Og!(j`g~cw^Ypw$x#nUVquudof2_gh8mn-w2w-EtLA>dF=j!Lp zZ2BC%o!;}k@HAL;dONm+f^SIVFx6J=zEfKYW$7BfzJ`L73rj4?4oLis5dKahs%dE= z)8SY>%u9|bFOCzsG5*3U(Asd&{WuTraPDyXcvfRlPb=O%9uU7+2#ceCKt?L( zVPBAal@e*5tXX5gTI-rS_aR|bpyrfg^T^*pf44iC922tF+1jutPjWRXj7+jMs2MXa z0Has;+R!(euP~ccO%NY`4I*?6CsvuR3xE4%iY!X1n+$hF6U7E6XM_)M)P|n((I4!; zf@$0D8qa}z8L-ewacb3@wd#KFBFd>{c&WgW@lkFmXHAxPz@KxT_)c!qSfL*QPSj(s zn46IK6KP2Vma)5kbmd5c0x+{1ChVu=tZpa0tVjulur`*kGRDKX2(}){9G#F4kN6>N zD>hMLZKK*W>I&}VAmg;8-~{?wol<4__t&ph=o@f4^vl|6XE9diWV)%NENK_qq0o!Q zK-KnTLo-+-6tak+K#IQ3huJ_S2+LSV>)H4Eor${2GPHmZtuY!rebKjkw)UfPaRZ#v;|3v21Y&>bTs`d5}rxo$@YS=SES-S`SAPP+DI$l`{ z1)9?$aM5nCNZxbcCZ!|5KZB;!vL&XV40(Q+Lz%K;ZoJN#vUzHIiB#`F{E#s)R)Zd{_ufXEmsY~F(K_EGJ{*OcyYJcbQm+1j0iM(0UqgQD6Fw_Q^71)tuIoDfrE=-I#$?jba|78(r1Y7lo-Q3 zfrlIzNN&7{05;xbV1s-!N|05%hF) zXK@&}?rX=v6MJQ9ZzxIg!4X7JnO5Z-Gl+suO&5`jDf3Wl{-}I2ShF%-H#kKbKU3qs zaS~_*=05C5rU&j?j_?it9G~n+9Vd{1zvssccJ*!I>3L1!O-6I@0R97D^wXFb?eX2w z{gB`smlsoPaT4a@o>K+xr87wPhJjX8sC)+Lu>9AGmRxm}XP2s&Ov;9C#6ATthoDxf zh>yOJt2k_u4|Q>N*$hr1tYDQzOqm9^Z9*-d7NEfTT_bx_D$t!$cYb&W-${wNt1Ki% zP+-QQkJuvm2)0F*RCMyfI61K&A&?JJO86%^=J2mY9gWG?qt9sW612@CM`>>9&oT7E z0aacVSAP%(09{LM0*rdZy3so3A+)+lHG<}{NELxlajSm-d?~nw?VpKUe*x&5WC_?K3zB8iQ#{Mt@eN{T%LfFjI=@6Gtmul1q;qg8Po!+ z2MaHHd_UU#v>lEuNlJ~+KQ#hdP5axF88E!(o7eXnyk&U9%|8OWh)BExgO^r+9jGkx!9 zec-=bF25;p(b1s#a|kvMq~y)YcLI9kYx!9FG>W75TZfpf$i^lk6c3DiV|Wiw{NYbt zytbw+u3-I+c64E)AeBWF?M0D0v!K(-muWOijkON?mZW30)__&rRw<7T+6C0JIt4Rn z`T4Gt6nP`KP|ONGnJhtoz2wxvQEQdjE{_!MycBc3QAs4o9TPq;3c*P(c+5#odATh> z(DT)Fj_ELqt1+#bI*c5d_plgC+%ndDB{?Y=6`FQF(g998VllwX@S9REKa`wM>v@}M z3bZq3&$~zaW0NYcsRiSq!LartusNWJ_+VhGZtNcb{Sy^<{s!rACP4=WnNY7ig7bR_ z#ywH^gec)X{@okl$_w>zAAXw(rgnR8xPB~JQV*G@Sj@&6V&9<)IZiPn;LXd7MrX>0easV=^g|u(q&CwCCyl8 zLIWr=#OnNeLd7|LDuNsyvfQL9rf64ib!NM)Y0asMka;!gbV)e{q>M^auR5%L&z0ko zg~fmC|Cr~cmpVE1Q@)yDPX3q+kMV4f-gk`mI-b+1Abre<%*6MRM&DnPXML;=4_H7E zr#bwq>Qsny~1oUCw;}Z#Z~a zunx@i@V5FM=n8J8j^P?qFyorm*f|O}9`IW^A5Nr46d5&V7+&l4mx>lTG6><;1sYAo ziGNt4$x*piCF;zC5Xw%%gSZn@SoDwV+HJ3EX}>5{N-Dz&6)gMUo5cG+*IJIQwUqq+ z16i+7?rx~7mPxUCn02hxN+nB|X*fx0EHCzVwC~cd)|ln{kkfv{{rFr}W+X=)!Y}eL zt5ddMSQ@xF@>{~|PE#?k5{Gl@{~K=%7}|$9*wm{=sJ|5@RCh;F#=MV0U9^FmDlri1?hF2qC+)k`* zS6P-q2>bQeK`~D4go~SKtJ@^$lmszx*qrOZtH78)OEcMLnE49kNezC1A#zs-ZQjlb z5_es@U%XJ!;}Fa+%IFs-cYOVvlGJ`}ib4lAp`MR3Hm|HTDE3X_-?bKpJkc}Sj^4O=jkY1d zMuMw@`S7Mjk)J$6xVO{2enj>5Z-AW(?pd!xactvFW98G6Tx@AEH7QwcgCJ8TlG=2~ zN8#8s*&_+xM|_;k?+QHy{;2oBCX|=p<)+ludEDPw5L?dQC9P#Ucn1$ZM;aU@k6q&b zpr^#BgAa;yh{Xj3Z{>*3cXTyJTAz7z@^>i`S-;5Rs&f9S(F-dXP`wWHdV(yURM>L`~AW{xJ8KvU*5@6(hD!9w^kpJ{-!oPPmVDWIWjVBJ13g~UWIoZ?m7~^^ zIPI)>F8OMeq7&8#;uDDRQbJdETC>VAcN7=euisi*yV(4-C!ow=mXBX`(p2AHVdPOY^sl@6ReP9CVj+`>fu@Alfx<&%-SyPb}4Jp~8Ee zQ;_(wS61P`+iFXy*TGOoo=OEbd9_qIXp=AL7?r9ZP$5qjU~WU_Q9#n=#oN%r+0!Y% zLZQR~+gVH&yhw>0h;TMi4_%s6!FgGO{2-nnKEaStsUTPE_9v!nW91weDpbki;Dj-m z7P}DtDE5+SMio3L1S+$JINN>cwceN$9LMl&l6x)r2RQgD2D8S)(uD47q(mrtBw7TV z3EMBQES~FEfSUbPxGMe-OjYPJ@+W|&*nvR;*A?)VS#D$($5LGXM%~ljwUD;OIQJ{ec(M|UJs*ukH3Ohh(md`gn7#ToZP(PkPQ7Uf zVLLFUW)%*Gjy}_Wf~!I0&RS%-82!C^z{<;Pt?`y^<6C7d4+SKauEhx_Y5_9P!yQwZ zxt!vZO7IDGmz=4aUg>)Mv{UXkIS=&;!e-qdB|8pxEznt}k1+l{^xB@fucP4YVkKhb zthhIthO<~oScmQNY8b${^r&3_*p!3wO1|L?jVsaTt4gIxNdZO%qYaxJwYFfgps4*U zjxn_XtscPhi^^u?Y1d;Ztt;_H!Eq=P-!19{_tAd%0H%n8p%YLt<_3fFDJR=8)K*|e za6<7Q`he)X^F91*K8(=KvTr`2apR^b{G8aSd7ceWH|hKoV2ek95T>X048UcR(!Xr$ChMz^rX zo*hF)UK+QxD<^G|AcH@G%_A8T7=QX=4VF7>N~8n!X-R}R=Xxh}>N$q`JChx6R~Xw8 zLWrli%ux2F`3kG07}~ny)&y^ffWV^>dpB|=Xo1LMpkI?r}I`W zz&_`Va_3|+GiT~lqtVblr5G{vaIC$saRPmc@s;4(&)sb$)IZrR)g;hn>sDlYr#ylj z1msJ??Na-4-_G@}p||=bv#ht70)15F&+&DRnYRs8gCuMGsWXcRz9daFIYb|>A-}XD z37+J&abQzx?DuKk$FWgQF}7I)n^XAcEDSQ-bhefh6n?8QCX)1Oeq3RBAy$exz=&K+j6Fm_N?*^bb8~2s>)H&vMD$O0I~Emp-i}aVD3$JK8*))0pOeGMQf48?FNVw z#)Yy+YAnOZ>Op(v=7LmF?-8z}FDT^6hT&QM_dmc52`RtjQd9k&TeDurvqQ_)M^ZRT z#xyzqqodafp3m-Eps;G$59rlwznMc4h>wf?7r#OO0l>?KadhAN%~5fmktYORei`)A z+r6e8Csc@4Ut(LQAd|vqd*5j&!MTi9NZf>Jw>HJTVf4`2Gy{xJgeUPVFICL4NBgn5 zj4RumvQ9Az$|AdY-Z_bBBS7`3=ojC8DrS zaMSY?`Ta8r6It?o%XM_=QUVt3ElQ=2nK4=7m-6O;H@>ncht0~-dv+!%)mv%~^rVhM z3fy8QY?=lP8U;`DlIo2GC|^j@aRRW)Mw?XeC;yi8x!35=_Yk zx(gr{G|%HWVdxcjHQ0VLIXwb&Eb~BH?>?E_PoxBp3>dsLiU~q>@1$@YA87Jyj7id+Jg~v(WA`_Y>Y}H*LZgKX%}E*M9k2mglyBKi+lvy>c1>sA`s`+hgC9hvpVY7gm#RX0#dPL@0hY zx?b$bu0^?HB5jp{J|uINZMhY|yqSwf(Y}ERWaiM_6^;_x%*nZs z3_Dzob@Z4Qz7ZYh){!d8XUF7i{LDp*MLlm|*+QaGCbKMU3<|Wdw4jqf0{_yQI-1;o zZA76>&dVFs2$H$3be1DiuOo93I9L|j#0kI0PGKQ}HZG3SJ91nV8|r(pC>`hu$|{_I zRs&ZI`-YgRn%l}Rb^jzS4X0Y2H;P_Wwu7s-$hna;UbiN}ryWrZRc$O@w2GGKf>f?S zNW8cEs4qx|YPb%I3>6{Rb_??Di9IG^lg9-QLrq0LIKf}9{e6u^YGuz;9n3J;oGn?r z96)`FLT@_LS0RWmg6===%12QseO*Ho|12Ld4!Iy0{rm+a;0}t{PieNKX5JtkLX&Io z^@%5xTS}bg-EvBLik|*8ha?qLmyVv4X;_fmyr_qcj{&5h_5f>+Z$WNZc?0Tn_d8it zIp277&qpY6AA)8}eG`pwHds8EOloEMF)-w>S^8Fam3(@|l)f`vfF4bOQQoLA6kYfa z89`qSdx~V_mjlSuA%tVQRCrx2AP63Ser_pf-sMz*a=cJ=Dv{(LAetcoJ!#Pz`gnqR zZJm)pYj<2l?toIyc3V0&R+}Q{w%6{RE&Xw3*B;YNiJ;ww0RtZw=u4zB!~_)MbBdg8RFmm=0zhj@Na8Nrj&!j7mNN?7uI)_J)Q z*)Bva*f4i|v+l2yZUoP>!XFH$fFauubr~cw6=ukP_=s46A?bmTk$~>n|;AIQ`V{ zapD0*Ro?I9zeb}Z3DQ(6Q?aAdi?;$A*TQ-$??F^6Ied>3Vm7AK;46_y-wO3!`Ca~n$ZeLlY$Z2fMmiZ9^%vcNYbQUa0q?i_?@(FHA0czVqU0xch*{Jx z?inBFSRWQz3YNa*5THx!5yYwK0eY*cz|pY77SJ*L8hm&3l-4|Th?3}ZPmJbA{CKg{ zmd%0VqbUmEf;SN{{>W;#@2r)j^yGDCzWEBMgp=jh3>cT6!q6$6YIsHv7|P3-hM zO?ETFH5{XhIJ>#hPyv`@`wbn08DDZ~c2#jTXFIvjaI#UWmDahM?Hi>Z&JkgzdBv>#(RJ4^bgqL%m3eif z38}Z9vB8cVsu}mNe7=I@bu^gR+wu}Q_=4BWCwgm|?Ur3rx7>Q3gDuvPunH>#;XB_a8TIt7>qloUG-K*t{R#wrG% z5~9cLqC(hz0L7;$>fm3G73jahc6>?|N>1e=0qk1?V-#4c1I=%2zBmCqmhGfH7HWAT zch;mtl(Np5KZ8-Ahv4HQ{Ip%D&nPD@l|a0!MT*Dk`p_%O*A)@UN@&sQ^^>QaA?}aQclZc=TPLI(vJ|)e^>Q z@3hduEku*8sF4jiDTiIk_!f4I3~XT@dL4;Jz3B3q2z^wPuG)QFK$4uAKN-}SrF?45 zmJLWyOAtfuUF@F~h~1&XfOLt#R@t!?KlzwL@`lyVyk+CIJ=Uz_I&SE&ukDNEr&1bS z{ZY#f_hZE{AQ>!PB|zQf7;^0KQ?W=dq>a@x7xt3~G=({jyou~=_gh`xJwcAKP|Go# zl3;mwm<{cY4~!Xb)pNeT9m=!eEB6`CW^IYasM{s}4*;w{Q@_xv%3XsFcn6%->oiTQ zO13r}`|(-r1jlsKxEVYE4Qc5L-)3~$3EFTmSvd49F_Xz|Ea82MSIb-O@SY0W3 zt(1=A9cmk0cEaY-1hZfyKmdM~9mc<6N7>Ob#AhT|>rr&Bd<2x1qvg?bR)-H_m?Mvp zvo3xXd_KLK`$}&OS{sM!Ka@AO9nV_POiV^Ukl zCx4Po-1FX_)hV)Fd85o%;0~)}x?L~Clca%mM#|VKo`CkLr}%gAUrvA>AH{ObE~=o=D$swqX3^wRQ*DXw@7OdQad(}C$!UkH))eFn^HGOI11zq zzmU)7X}6v`_@$%74ey2;Z!L0sly)btr(dA2S7&xqW&`Hrfk-XoX@7STuGC+YztOH>kmRZ~M=U*h*P)vm zc{3dLi;_9{Y9{dhmZ((Ot7o<=o~B8MmdMr_rD&~L$k=>W@TJoTC5~ibGIvMQ+O;h_ zbKwUHUs6=)K5`Uu#yK^4yaC}*?JSx+K4bp?5Kd~G-w6CMIV>y`Zf?V+VDc#2MrgBH zBSPC(@a)-W^(gu0nFmU@6xv*vWW9{%r_L*v)IJI5<~B&=kOF!G#ZhmAy0im+o#I=l z+aQgyV1ema%OjFnGgxNI`kk!tMIw@tDF=+^qgyB<=z!;~aYMlW01>QJ(#ynh$frAe z#^iD9QNstt{V@P0IsRN_+~j-lSxYCY6`{OyI&ylWw3@{RB^dx7)fus3bF(8n9M>r? ziasY=AZv{Q9|PvWJ!!D~PuE~&O-I8ruN_u4?RGIHex(eqWH1lTkGY8WoXq#Pneh)tYw0$6|7C6i>D`~ zi;bf!Q5jqPWAjkQVi`jS%T2=Mj-*rXA;+e1(z4}c+`_qp$_^I;JXCh+Bs1?T zwDcJ7TJ3JXEJ~Tl=qZxf?Qb#T!4zJkNA9u>Ggj3sF9gs|M$^@JBD>!T{8sTEmwO(j zy0qe0qQd4w>0C^9(e2*9#n&}77UZyWeh2GZwdlsOw2kRibiNb1@z2H&6Zopw#d+cz zorM1Ys!7-aIpFjDaa;}Fxp_Q{Zf@PdIm0$j=Sy}7K2ZDFs6NLOu35nZ4oL4&hr`pu zyTuh&wCe6#xA6tMBnDg@^(VDn8r`emmGE)N^{hE8QKBAF9m1hV_kQUSgLbc47}+Ie zaY~A8=~ng^Ln1npo&_u2u|TT8lkd{7QaNHk$2kMNLmk}fFJu3(MLhVNV=L5AmJ$#82Z6iN;o|SRL!SyB6k~a#vjiUr-y>s6d zbkVE$7WT$;>k%OOW1s0)M~KlFDOSNfjZWIGn|%vIG295j3_DeOSV`XD%PVYoSL~sA zc)DHwldq^I8Fs?Fle5(1amhSapm>cmO-dOwt34@acE~wi1#Ea{!=4k;1owJkKp~Y-cB(lp=WQIh{s*%GvB3q^lqt;S0ME`rD>yN z+R*}}^~Gl@DYBO_^1ikEKKv$@(JVi+XR;&&R(Ody`uDD0`}RTb=Am@9mU{Zh86#+R z3RCbVzQvzL({BM=t7IUaK^ z(DeyzU5-zdai2=fyYQ#Qi#bTp{8^RSPRT&S9Pl{@ABATsZ32_*dPA&t1nn5bM({Mp zVFwHC*0?Kg6Z}@uphVR4I4%kT9pPfTy$>YSt8a~qY=mpx9l5t;C0lCl41JAc;@NjQ zHh~l6RLRFQk=w?3C_&9)X}=YG6{(<+brBXvD=;d5TGzDkH;1oa5?Ja`NUXV1l7pID zvSYeKuNxZy8Y45Cay+`Nl^iB z_OnP8#^S0y!KimBSgkT_k18@xdZ{eZ#g>O5v(#0`N!1HKEAA?zrt{?!!99qj&ou3D zNmzZhwW`1riHiZ(HGbd5(u4^-ARP09S&mf!U6~5JFb}P2s!A@*mlX9n z$b4g_N~@RJLB};-9OnSj1=ZXP#w(T^dSKS7rlfM7o*j0E zZh7Mr@HdcI@V?dO7G5{iZ1Ngn;}`*rYS{cqg;7QFV3W=bQj%|RH0*sL`!@Ka!G0Xn zFAs?Ap$#-?<<8M$p&Pxp&PV<8RKI9j{{R8%9wGkI@WsF;Sbu!6##KXZ>z`a2@(l~( z9-k-AYk9PvQ;M;1t^K*neW*A+x7S+4LNK+CWky`rxhq_fFmaC6Q&iA2ZFbg8KUveE zxPnC@NTxu_!_b4lq&Et(1U!@IdQ+a`YkusUpIYQ{Tb(K_^Pdxb%)SrQCR?uy+)ty= z3xdP?uKtM&Wp=b5`7Hp0_;vPW__v>p0zfd#B5#x=Xf0EKRjLWSk6s zTvunL_`}0qAc8scy=vZNk|6Q4QicQ`gk!BUT=4gXylba6mEtWP8@L?&(9aNMKKM1k zYySYVHki7%{3Lz{)~C{L79for4BK5tLCN3)jyhI6@a$8G>0tSh;J7&VHE&IK3gDmv zX~6s|#m=MqL3j%CGc~jtuAYnKmr|<79bAHU4*X>PJu9y8zsAicQC5dqw3@;|gg=^` zXWE*CWvXK4Xw`i_A1Mn07{)S5thftFMmy zDKj&r=gZKOfz5uG8WmI39~AKvTG66emRT)}Mh;K5YCB77z}R^oBe)bVihMC5?0roJ zNXN`7W$_1xUCeg+%q#~{=8ivFrB1Y7%$HV-`wZK89He=tcV@*%FSRHfLT(P?-9=3e z#*=cu{{U&k!TJFpt0uz1qy*0y3Oa`dwo<5xY7c7>om)_hZFb4e-UgoryE@1N{M&l- z)}->SqXia38U8GaXZBX<14S~N_2!$aQEX2oH@Pf+E0s=6Curb%RPlJtvAN>F2dSzu zX~AP~&5{p5X{n?msSPe2bQw1sMqa$P!o4S)DeEuF)d!5PMCc>GUb*C9c`!QEW^ znrbY5R4IX;b4^_>t-*J3(<6@cO0y@}#hqMUBhs2uf^oPHn~s$4?Yk*ki=aF870Jb9 z@kGr<`evXkYN_pUHUaH+m9 z!1W_MREB*)V^vuK2E)!I7*nkEgF{HY6$LXJJ_k+#0numGKp8L39MsUQn3 zKvCBis#WP*QTrOH=yeWa@^Vxhp2DGPJ4XA~>w-GhJUmZua&X;pqZL2=B~I!Qlb*OM zQmRp7bn6;#;ms-X?_`a*{{VNI%x{L?6}Wa2X>okVJS|HXjG`qG*Pl#O@p!h?{mb631_iByDgM2%5a@u@ncl#rXiRRLIQMOhlD3yE<}Jz`j-iD}wy~hZWD-vt*ERnD3eQzn&98{ABgfst zQ{TdVIkkb7!^Bb+3PQVMBl8u_ET*E_nyh?Y=;&I~Q6XSIDp<6&E^Z`HPip4jKNEDc zQ6Y-&YGK+|Ib-X_255`M9yYUVUg!$QPw?cL=AK_(Ch^76XGcA%g#Zq7#X$Bd6k$MO zc{Pjw01GDSCSj=fdR6C*icb|`J|$=#SZS?iUr^B;iszOZvU?h;)ZbGX*q!S;N7+ekGW_S$^Q zx?6NTfW>oX#Fq&9+mHt}W)BwH{Jvq%4=YX7iv<_ZlKa3O7}n$_?@3MVz+@QyRjc98 z*ne60kA9kmge+yY2#kxUtl@l;ah&YVnB>D_{ zR}&tg@ry&1+Tz`BBRD27R}1V92lKCU_}TF?UmkeE=Uz9?@PXx8KyirBV4tZZcdi)R z#Qy*==RJL@D{{A?wG`BKICS{6u3O5mYhDkzj17fRwP##gu#?V^w_Zl>D_zC9;g=z?&T64}rIDBtKp4g;`!t=yn4*(O zZp_ilK?96QOt$fUx2DX-E0-!drH2;Bgrci9P-RLs!rRI_1NFG z_|*Dh887(*t2JzB-yJ+9t43v)RE|B1xyv>&Upn}+#ggk+Pi+K+B1R}S{{R=|SP2c@ zmoaFIvNkt?#~pjr&TzGvaf?3V@TbJ@0O}tPuk7_*E6>%Q`G2zY7$0iAyYrU$$4rrq z_#9N;Eb(TUsd!*`cE-Zl zp4a2gjI`xPR4xMmcY0R}!BeSIoi=oRW>JSNkE8VKyQpDRxC*X5@f9?>vxyGLbCF&p zrGCg1q?Y;oFM{Y+69X#-ug}Z^YE_d~eaDi4tkl zB7wBC{eA29s<@hWT&Vn-o(iWc=4V|m!e0l@;%Mvu0Z0l5A6}INzXp6EDx|U7d2(Ro zm9d=F4Ljqfhh$TmwA#E6oO@NN`wVXVs!nC0aPalMc6AAfq)wD_&z8IZ>%`Y8vGm~&I7#19Nh zKb7|Q&m4T?iof=Ey^P#3%Vy4UPldiEm6*fg$wMx7hU9%aestR(1AIz_l+UimyU1cy zWo8QffFul$em<4fpBH#@<&rH%K2JQ=gz+wb?p<{#&U$lA)5AZqjIWrktm5GCx5WEs z1lJdEy}?jqRg|*s?}LGn?}1Z6AH_5~9?k+n^R`2e!!_CsV@j0%<4{H*bAv)%S-A67 z=}u}suZ5S{MynN=Q|iu1=JEZsYVD*)Cp|-)pTeV?RPk#vJ;s8?7;gDNCmHTLS8D?a zGQvP|J!)i!a{S>z`qpm+h3smxEOErYif)mD+-S273CEOA6obWQQ#p-hD;~g4E!j^SDg+KcA zI{{${+T4#UuPK)9r17 zM{_q!GZq;?kgTPZ#b~5;!#JFk`E)BSrKpy7+L7(mQ*!WGtCSj! zyKfsw9=NU>qOjJ?kGbJ=h&hV#ng$O|$0cQz<8aBRHo+<8X}8%ca~e&Hn&AjZ3G9iX3XtxwR&% ze4{YA7~tg7Bi40w`MmGp=CeQHFVRCMnzE^L{85q3Or9ptB#omNSR{WCtX%8B-YSI6 z2)s>r7_XVYUtYB}uZeDAVkc3IaCod_);vDQ3^$5TY}BJx((Y3N?Fe2E%qtg(tc{f# zMzZ*~?A%Cz5_lX_-^GddO5mPzShI^p3o2ZpB=9pt;u#r2I&M+U4Px=tW4UV!PE6<` z@hz!brz8$(S6hL`B9siCRM$Lh)UhU9@yJ%|Ni11)2_YEmlUV-%Y0t5*)S%9$9Y)lg z8CN}NMg6N2Fg0c!~fP+r)Eq>gP1}@lgOMLbyLQJ|7sw>cnxh^H|EQ2^y+%L`K`kHc7S( zNJuSSO|XsQ#e{vx_~zr&Y@al6@*{8<$!)^vM>Q5D>HJq~eDTxq&2 z=woY0@Osrr{2!rRD(;UM{^70{PB+l&=4Q%0gDzvcMn1%IMClg5Qbqp&XlA+T{2^(L z_P5f;M?kD<6!50EY89>Z=}zOwbDCU(<~koG04^{X`_vb3A21vqaDJ7Kabx2PM*=(b zaC#Cci>r9T+C7HKIXS^O>r?E*nA>Yt10}a)WRXz|WeJ}w=R6Nu$!`+)hTQ{PXyo&P z6y~Fk$1A^N))GbsUC2!-DGun_c@$t|f!FC%TI!ef*f#UHRUW6B!JEZ;Y>|ADlqWq< z)9n01rb!p?E|g^S&N2DYyweLsnpZv`)LqPmcICMQ)qnU(wOc6(R@{XAywz6KwE2E$ zZV`hX21gXvduL^f%mg3yx>lEM1(Bf#jqgO5ka5+3Ju1oghi3$4S-?;^v#$w$o>CDMlZU^X)=*+*ZEFxm$c)@TBq;j{Om_^KNRt2aWVQ z^z&`C2-st@3i(C|uANNJcvYl44EC##>KeY3xU{~I{{XVBRm_?dne|+H-Gr_O+E9#o z&~G4fwk!}&Iq6?9S$NaMk$l#=gT8wc(zWgWF8H@iOk7M)BLao6k9XCrbX%s`acG3F z#uQdH_k#RAD(?FzR_s>1${&uJtT!tR@bBICy+N&e4~?*0A0k+eTaTfml?>?hF9A&& zsnhkTVlB=Uvx==Ik>hP9GxkkXO^$*=R8!);oNTbUJI74b4NK!ec`kQ@8Ibg-U*bizv~0Kb6G9a7#Sb;kOYvV)y17^iswwZ+HJz;Z-u7Mb z$bo|=%f@s5b4j((5@%-@#IFimv4i_fe9*rtpIYJkW8(Pr>!!1}l!ZnskUEZ?tCrMk z-u~z9Nop2Nz~x3deJe)J*%|{Zt^+9K4)q+WPUT;etz&MMPqZfRSdbf+_d7{DScd+UyP@hg z;vn}ot|Me6$$So({*{Gpf<^NZ433I1!L7|Cx~ADcA1BI+oQh8U0V9Hv`tfa{xw$x4 z(Ly$P$I~XOYTEVM%7QRiPd_JaYv>E#*~h}SwvhRwx zEO;F;U$CW3AD6dMOJl{qw=ZGR!!^sGk%mH?VDzi@+Kt+Xw(3$Q6Zo62y?VUgvzC_y zxU~~*UN>ZpqM)DldWKX+j&qbF88p+g(U{s!TApndy>^ouR@9nCBPx6GQy&oO(d_ay ztBu=mLlP^wpWqF|H!UnKti%z!9<@;X0N3rLQnH*Ak3Co_(xf^E`rGJ+Hy!Z9CoC!715-y_-S-Ut{>V*?24v& z)Z>GYDtmw0M#}4wY_;r9%(m_b0u4ec~?=c(Ts%W3{(t z%Y4N3L7tVj;jME~5JuM%u^$eJSxL;_$ohW#I7qs+UIHgb$U< zoN##^y4S^iCHUv@X4_kq$6VDVw{sve$p=Xo3}l=DdSbnG{7uchlUamHu8cuq!|F-SN%gM07GYX{yOGC_%P{bdv`pU9{wet0>qNJ`J}zaz-Laf< z2R-Z3z7u}g9um=@T_?scJkzNIWH@JYk;lz}-`BN#wX07Y2ke%2EX=tI!SDxe-%91R zy*}#PzNE5C9m}!Ig^K~saw%4=7dIAyl?g@(#hkfl7V$07RgC}x zf7jl=Up365gQ%jF&!@|9FA+6$ajciKjK=WqW2TD>Ph{g`}X zt$08F6Dw_A(I7}3Mzxf-_E0*82aboedDgF_OK8exn{G!!`Nw}+@+xOFIXSJN>C?-p zIa=m*_J!f;i6+W3&pBGoe+GC`W+ds!oOANxuE}ll5Z4{}%VQM<)558=-erehp19t4 z-$SWou|`Dy01tdCBYe7gsRM#5J-!-fDy)&SC?_KZw;NEiiAigfJoVpH-&oUMbV+rC z*E_2>n)5A<778&jWcXzSzdSmm$+X}A)fAosxwep)wfV;!bN>L=ttos(ra-v+aU=}z z)l%ohnq=rBX}~$>C#_*D!AnaTtTl=fcwb+FRd_Y_JaLdIhfVR2kb$Zr@xaDARhRK* znzHTtTHtowP``_G@XXQNF8;VJSUgSo8Y*RW{{V#AB3t(^M_-uKQ|n$Ti|1`NHvnTh zs&DvBwA6f=*XHb6qi+~#&AGnS835-Ss~J^`=u(SB5%{yjDurjzSnTG6H&t0YYvJYsH@l2v^UY@3DI!?@A!#N*GUC!?_A#2UJagg8 zmsUxcPBM2P6_q!P{3`OBf3(Gtybdaz&x?E^3hZ+cKDeUjEY6L5RpHq>-zO*TlTI41 zhxeVDgkyKBbgoKGf55jf`OdMZUVZB}_r(4IU|4_;sifKkbJ3u?(`9h+-Xj(tDGWMQ zn+<+lHbz*kxzF&wO7dwuUEw#{uo+dO?u=@c?~E)Vca!@@=|c~chN-@~6>jMEokvri z?R@ihxsUsG%y^^5uy}UR%RJ$nbWq&al-&6DSw#t9t;^RxKc!^qy5;_%IF|N*Ge3B- zCm)3lZos2+b5{7*;|VnvlEuoyoDZ6%UyuGMwRHyLAdYL3c8+*k?5YcH!sno-$pmIa zSm6c1$>N%lXc|2R!}gEy2*94nHj{ylq`%!Ouq1()qBrQL;w;xHH-eJf5cj@}uae7BQh1J=AXG>a5QlVWZJ zWrKC5$88*{&N6UufkKcqW_<%`@n^%At+8&@6&*>b$Buj_beQ{8Wr*qxeEwcNKG@8v z%ui5Su4&$Pl(HTBm>s0{p|xPPr?%YqtHLp;#L<>LeXBb6#9j?n`$UY4y&E0t%il4; z7D-0zKEwFeQlJtPt;EHYJi!7qr7y~XoVkuteEoNLkBJnPzW98mlJE%DyO3aQIXk+mnipC6%RC66Xiz0QRPsnk$GdXSfVy%O0TBfu=!j3cbb) zoaa3$Z|t(aX|@Fzob4Uy5)nB4q9Hq7UNntNGBX9m(i&kkCqNaT^cwjl!p$?Z|c z2~E)u8*$~cO}5>EBu8k^Z*Xkd;aDa}P`u)|G^?oHgIl8j6AV}mxUB0IN157a0<>sA zeDQWq(1t3ygRtjz;g%Wi+-9mv@c#hE4$Udh?YC_t{{W`9-p>P()8F*3 z-s4IOvHbk={2SMquaU{crrbtLd4%rj3!DQ|!KYr$m3eTAI%L-6*Mt5b+sat@a@p4- z0A9K8&+yg1_(VKR(W?zB3UQDoCkOPV=)rVB4EqN^`FPjJ<%XScv&>#{eNXsQpJbhW zc9GZtjB{NqUK8;QZtE73!)s#!i2k(X@UMtsIR46Qr}(#XQ|zhrNY)w77W!2=Z?ar$ zGKl{7KveNhh6y(+JVSooy9utLyg93+XZ|G&PhMN6>r9VC)V$zk(;Z12#&i1AdkSyt zoBsf8VblI9Irg`aRax!#4^4-ySGuv9UCAA$26;P3uCiMxqfi@8m+x_eC;HQTnJxnb z(&a)paPbk(=g-ts{azRNO_p(n{{Ww;aj?BT zX_s0o7U;8EsN6?5+9^v0)NOWDGdeC;y8F);@dv`)I^OBUy|)12n;mPxemPiJ==$M` z);EkI9I%Xz{{a1Q{zZMa27?Xl!!6WmQGwl(zvEnwj{g8=-vfBc>8@_{_@pErflhy4 zVOn8m!i4WM*kbWtSf-RDzVq|OK?-0jfV8h6AS>(vd4s-fde;WOg%f!v2+siD_ zsR|2@3GQp}4+eh6-UIN>{P%*+2aZrgN+VU+2*xrSnxWzk*pt9|ozcHtG9cTs2u1d; zd_`ImxunxN@Q%G_7)7tV@ITn|;lGF^@o4dC*$9#qK1(Bg*5e=7@~^HeFZ^&{4S6OQ zaZ~&)_$y)I8(6g4D8hhp=V<4e^_?%_>@O1RgXYIvSLRvXVM?|mnIEEMxJlEXlNs>S z_JsJG;7OcglD6h=xzlv+8p@n4HxSKX?(ZQS7X z6&v`k#bCLP`Nu{k&+H0!HKn$ye&bLX54;Y6#ExI48AR~e?SZ9_|y^y&4bCTshN zFnSV6re54?SX75m>UipSsfC^7?iS_ngkeT$Zfw5Do=l9tFv{TaD;EhjxuR;smbcFZ zyM3ZU%&W$IYLeYtNj6!VAn@C`0-tWI$UtJuPD<|o02*LahzQt`xrjTlPhM*=bP1%9 zW;am=W*rDTW}=enX=LAUry%eF>r%qoWig`-ig@{H_o%@`0foi`ZR=LpinlWA7cwA; z_C?XMI{sAq$aY2xVO7s=)TYjO)no^DPp&X2(IP67EQF}_Y>d=$%HwTVWvp^a46#TG zfJS?oWXAg8Q4pYsN6nNZ)r({jE2MvW*y94K-aXB{%!r$?e(Id&oR>i+Ox6go=gi4r z$?1xY;@v?->K}m0z|_eiNo>$u1G&fD$3anBxPn7$u`mnwayom_$=G*DY-w%_MD4Jy zOJ%c~b%oko&RQ7xP770}ibI#kU_S8&tv1rw>@N`8midpVrlQcBzN7CgrSroRgUJLB z)`@LPLavOh&(3jIK50L6_!|Z~^{9)XkXuBj1E|GSg>)s+(vs=nhy!ZaD!{rhqS^}VKoU6^n{O}!fCWy{7qocNEo; zZlqR(Z5igBHQA2sU|5Dv-E3zRtzqs-G_4(=Wwd8-eq~cjD{2z35I`dY3~r^kV+mBY zIcFT7dTLEHP4;$goUb|ULqMI&7Vq|TA8GyOI+}v+BDafm6SD4H6wOI(2a_AeIV87X zLPqgSnL^=*B=)CdE3ih67=A_0;etq{WPzgygDS1_b5h+QXu@0PhWW-gs`hCKo=Bv* z3%Go#+|p_}=NNU6MSkp=?)+xu>j- zBPanS*E^~!SGb->Nn_v+6pkuOb!80GO_nDZ+ePjsn2@AJytrc8-^AIbPYz}(vN4+(CxwRRd-<}c0j9b%Md|c?Mh<3v|Z5RK*!}if}@Jj zWnv_oEI4(~6>Lf(gsyO>|F=gj)IvPX)an4 zau<~&pfs;Es7y%xMh+=9tTZxZX1W{Ehywuh2AAzoTRNoC1~13ctv+dB7O3(uB~3vP zyP3_~z(`ya^r>yDXf%;BMRXx()eB_sNbOqQ9Ro~P&>Vn6K5Vpj*q6;X3KCCCS+<4RnRFJhAnrtPv{kgY&0ydeOKVdJoQv%eW_Dk7;VmcS0~aQYN2fxY-H-It)~bKsa>-;({^I0qsf3OQ~ADMzw{T zIAH>fpn=CqXw&QpfbfDx0OF*N1?{-xG0EBx<$z-yO5_>Hqo*&Ys0cEm`g%|*@O1c3Zai38` zGcmveuO^?gpK#cso)yqsE1fIa~$a!%ek zrsVC&9QUgB)jN`%j9C61_)gp~gGGT$ed4K%@sH)jMIXaI3L}P1A|(qMKov2CBLs2V z9Adf@lOHZlNar-YJ^k}Xx2U?C$5WV(;WvUIg)JfQ{jxx~+X_d8;N)@bQfi(N(R8gc z80{@~Aasl>4Dd)EhZWGa@W6)S9P?4?HZkfJvB2xNdCG7-h(3n7@fBq%Z<(J%!__MP z0BA-joY#l+K{lb~g(P*r_bu;UtKn|~NpTVxlWLyeVAe;4d_4qrih?}g0(tjudi8Av z$~dA?x1jA`iD$K1#x)cAS1yh4@od9?hPEd6$l1w6taV6c}w2cZ~e^IycB1{D=IppAeq*o&s#61dYwy|}QSraZ@4mx|)`zfg2!c`*P<+h4z zsry*A=klG5O9eEhq)tH~VuJl0XPjt+ZP5vON% zXytTNjbm0DfDgL|Wkw;6HY;#5hWSAqYEvJZA(}swXYU$oJkPaAU4Z@Ua!oMkRkngi z_YlAk$8zJXIXuhK!e{%Zg~v*r0`BrAjH|KEalxtD)-+P^mB1Ne=xSuO1$KxcnO7_2 zV;mA{q>)@tcJM~3vEUq&)~`Hq$r|ik;Eb`|gPM-w*`l`$qa5eo6?-vRv_T$i{{YoE z_sOR~T2~J2c|2ftrdvmCa9!LILBRy}t6GZ58Z1^Y02B|K6r1W6(2iLWIXu!C*mNF( znPn-|2}zZeixIm3aZxiuSd%DvGJ4QIA*&R2gvTK#AdCPy)HcRd z49*En-*AqVJl4-60?fHyppI!^w3Z?oTr!?VG_1gA`!w=pWh&ruGg7pcilIvrAsiJn z)P^ftv9Rt|1eWwPrp+#<`$;86ae+i`_Xg}o6c8-UDV#KfXP(BM%mXZhAS83gTACYJ z9J(UlllQ4s(W4xY{ebkVvL@`VM2{YAqTPF_g~98Nw9G*FVPsr05t0u|UDMmh49$frz&$DcSe?A| znRhSnlZs7^BFxQi0}#)WdkTqKc1Ce-S83p}ra>Ajm$+ihyMnY{#~^YY9}GTRQD*fE zrJzLb_E!;3tV>{$y($E^lgup|t4Iz5h0irSQ*A45q^{ABDbOlL#Kz+;a@8+ZG%_o4 zZZUAgpMI228hMKxaf6Pn-ml3FN(g1-rr_SSN@m*1PzK%ASFJmwg=>p6K?t{wZ@e=3 z9VwO%dbf=rIaT9vsU#38g6_bczgm{gG%(71+%pabwOT~BM>W`q*~VbJEMg`}AmdjL;H9DWqF zxF(6CbqLC%1*=y|AjW*grZ8!zaaqT4!I+Ve#VI^z z&~>B*N{yf%J!x^3Ag(@?bss>CE;oA8w%yoV`tm8HVSZ26fg@32UfK>ACqC5V^amoN z5AuWSN;0PdjyqClX^-CG?(&PeUjg8ACeat0$S#~!qKZVvY6C%qsDaLR*@2&FBtvY>u64O^%%8?%59(v9bD z3I-{B$B|Az$JUwk=bX~@HI2Fn&JGEy+FU+liaMRc`c*W^IRn2np`nns0D-_fewEFO zf4XPTaOJ2&14&f*5mA-^f}%G03X1-4-Brq`R9aYs-_mI?}T+wb)ir#fM7sj~Qt~ zM3ZEBAR6>-T0#N(S0As4*f#IdwxK4O6&241@qU0zuAWv8JuA(;SK;~Me?B<35!~0g z_}flbmG%w~rFo}`w7C?Q&1d1w#g3AZ#V+XBGp0rrm59gC5%|~1H-8VlAZXg<&Hb*Q1<0QwMA?Ed>G@aN zzZq_BG|vvTv}v@J{vOry-1j==)%4ERuNYjAO8x4()nyp+g$O69^QNnB;#*IM*0XA2 zO*+|Q+Cs=evB>u0y?P&pyiI%~T`O0TGR#iL5@#dQx*rzkI(LdRwwn6ftAm9=pEY^Z zR*k3liM%x>n|Y88_R>CbMRRlW#@0Hyd0FT%GKlstAO`fQZmnF#CPlypj%ufdFD@^1 z-P1>g2OluST)0y+DGqQCBeiGCXQ^sh5sQC1Rk*ogO0Pjn3P$YGNbD1D88qGgUj6(& zZ~(L26mB*)HSH@p;>M%?d3=|k{q0{?N6K{Vm~#TCzGDk^o`bcdE*5$ z@?=wHfriFAa!zr&n^pwd&}v#l5Q|3*yQn>l7Vu5xv^P>7oD9_8XN_HCSHly|ItqJR z+a$hllb-MDXtl!jE5~mH8AoG<=O_77McvGZqSg?M8$CN!nP6!?b1Lk{dU9!zFf3(| z?Aga1Dq2lgtBi$Y%S$r-=JdvDNn;WrWNd95FRe(*rXeCe@DHyTs3g63u7p?zCm~lI zIsX6(E}|`1#Z9cL#Dk7-c&Vn9AeuE+Vx;qdQ@5C9Q3M00IUQyVH40WpaSJ$#! z`2=M^8*xmE+EEyo%2b|6W7>xT9kMfLE%&Ln&@|k!6pHBPc<@NZ7p+0I>SX=I18i;g zoK*5$fg-$7DLp=3^w^&1B|FrJ+o&A{OM;NpoJ&^0ij|WGE5-#wEbv>rY#=8m1ZTA( zxJA!H9~)^GahOc1NiFHxo2Hm^63G>;=WE7Px82Wbjvdil2oR}O`S* zWa$;i5`pbiVs1H*+sMe|Gd}=lr9l*kjkbF-9DTw%QLI`Sr44TEK5XS&iY)I{!L(>{ z!-J4>Qtxr-ke0GbcwRShpqv3prdwcRMb6X6W5E>Wib+V0ID!oGgG^M0G?F*R-T*UE zs4Ek~je9tedSjLB4HpeGcJd9NlhD*j9ppux0s;~+$|+%MyM<+vAbxJ7P{9z}!@RW2 z<;Y%8Q|)dbNz8M^BS5^fVu)d#WRlX`90E=ky-I^>Aez}sfOEU0L%f~QS4(javOn%0 zKK;%+$E{pPDUwiOtVO=VhRAu6VjHVL$2N%bC&>y9Ac|Niiq=M zkVoCkL~j1gAxofJCn<#E6>7xCI-8gzS$O+{wL9E4E61dVlkIUlkH%M~KU&|?rgLUX zTd_DQ1_n5)Hj=?C4{aV|m(sNK9pPThc7;XG=O25Di%C1&u4K=?7kcSd>>Gm$@DH_A zX%&fP88vBSCrY;jf6FEQRZX^q&e77pXX<|>%b>==$papg(9BLZXCAcvqwjOvQxswG zo~N3%v4r;=vS4KtQ-}@=W2wy^P*39P*P23k#(2-IEiPJ4+KojD*k9J1LiOBpj+BHb zZOhzKRTPd-H1{KCu^>3$frCgR3NQvwts;ZecI{70F&R*M;*-#%xt^jbkZ|J|?MiXM zT32F7KDeaX&B7q->qWuGTOceKsLeRCFyb-wq;H)3!?g-QTn^ppMy`b!2PgT`1}@mg z=4cq@kF6>Yt&(%=K-S_i>^U3?c2R&OMG~*%d(uP%pC)>BptO>efmkmC)22A2$YaOp zQaDl6{#3+8Ly-MxT-7GD7z+dUdiJdyALS(Rp4|N^hdIq$(}@`b_5T3tRv49fO!xSD z(afRhZ>`~q;Rf-_is-bP!of~|AzXXRP11~>6kwmmxAd99fCJa#In6)zsd}H&7#Sx* zCsPf}AHa8~$s;(z^T(x7www??YMJFY`@nI2+Ak z<(oZgo7G~-E1%N3XxPqd;e1Y{$jz{jM_SkR2QM#0>1&y++^?4|4oaT+;O4%c zlamalwmc41D()|Ge~UjAEj6gTt0}>F+xLl_U{{l9zY#9Aje6@yme2-Lb}>DwjSCpx@d1;tlV_9WLipo;3<2 zI0`yfq7xjkZdTfJka1pB`yuG}dPl?m0JY7zjO~&L?OwMQ_8wS|Y7X_KO(kPE>Wa4L zCw2xgoc-fhhMQ}35NUwiypGjgHBmdfN-{?PnvzG#0;QCbh6lAqgS#qRNt5M90mmIW zQzbAYp;7~r)v;C>qyu1!d*i4yTu1>_elou>#XT5oM;uG`Gb0V8oB+qStt>G_!BO7; z4uYk(k{MGGIlveL9csMnvCLLalmWr@G>|r!G%;b=p*TM%%{pXOwfSStd1a=e?qD0{ zAKn!*TeHi(rrHPafzD|(II;*Lb%cZkcm37JN>YL*^JGxc4ulG_>Wv6g1TvlpIH+yl zoZJx65Ho^(Xo3u+91H_g!YsGT7~f_XqL ze($9;Cbb^kTiga|ih;s_I?_!P0erwq9-DJhv^MK1ub41RGV*&F0V>-_7}_z-5^=DS zcM>Qk`C4UApkyv7*H9_BGw&ucmR^?NLC| zMavFY;Npvx73+?a zvHg&2n9U0XhXgirnrk9YZl?ga9R(z9Kbs^kkf8ozi)ahnFzAQ4 zG=p|dE@g`r(*d*&c8aRgk#;|L%Nq5oj1n1$MDq}zPDMdAf_cX2m?JUbJkz+6)R;>u zFWRk1yMygahjX~MOKhBAJ5{!}ClHwxK60_tExD^e}tQObCaKV8Y+7BX=PraS)8r^Nck3TaJNsO@^*79N}Do9*yBA|jR zO*EE>n2Q{cqM0p&!F+BmXB%6f+HwAQt8$f<5hP_hPC@k)CfWxSg!^oAT<(H1y_beP z>q|#@8aEd)l2mn5&{i@ZGUGOhz&I_Ag0(E}ZIxdW7Ep(ABAF|5^a&81BH7r3m{+N) zGA<&JanqEV2(#x{h4lmB~HyEG*UkC$fXglP2GLPHF#_hnlP#l z9sMeZvas+J0JzQvdTV#!4)iA0IZ^r25?iUIVAY|@RDjRkoFf?Fx>RbLj?v9Hql|_8 zXbn=;4i2lFkEJY3rHd6E4>aAUjGWSyDbNa96r6yV#@(R(F;Xkw{NK)_Q=hs&F-{

3q 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 GvH#ifP8$0F literal 0 HcmV?d00001 diff --git a/doc/tutorials/bioinspired/retina_model/retina_model.rst b/doc/tutorials/bioinspired/retina_model/retina_model.rst new file mode 100644 index 00000000000..e8527ee8b6a --- /dev/null +++ b/doc/tutorials/bioinspired/retina_model/retina_model.rst @@ -0,0 +1,418 @@ +.. _Retina_Model: + +Discovering the human retina and its use for image processing +************************************************************* + +Goal +===== + +I present here a model of human retina that shows some interesting properties for image preprocessing and enhancement. +In this tutorial you will learn how to: + +.. container:: enumeratevisibleitemswithsquare + + + discover the main two channels outing from your retina + + + see the basics to use the retina model + + + discover some parameters tweaks + + +General overview +================ + +The proposed model originates from Jeanny Herault's research [herault2010]_ at `Gipsa `_. It is involved in image processing applications with `Listic `_ (code maintainer and user) lab. This is not a complete model but it already present interesting properties that can be involved for enhanced image processing experience. The model allows the following human retina properties to be used : + +* spectral whitening that has 3 important effects: high spatio-temporal frequency signals canceling (noise), mid-frequencies details enhancement and low frequencies luminance energy reduction. This *all in one* property directly allows visual signals cleaning of classical undesired distortions introduced by image sensors and input luminance range. + +* local logarithmic luminance compression allows details to be enhanced even in low light conditions. + +* decorrelation of the details information (Parvocellular output channel) and transient information (events, motion made available at the Magnocellular output channel). + +The first two points are illustrated below : + +In the figure below, the OpenEXR image sample *CrissyField.exr*, a High Dynamic Range image is shown. In order to make it visible on this web-page, the original input image is linearly rescaled to the classical image luminance range [0-255] and is converted to 8bit/channel format. Such strong conversion hides many details because of too strong local contrasts. Furthermore, noise energy is also strong and pollutes visual information. + +.. image:: images/retina_TreeHdr_small.jpg + :alt: A High dynamic range image linearly rescaled within range [0-255]. + :align: center + +In the following image, applying the ideas proposed in [benoit2010]_, as your retina does, local luminance adaptation, spatial noise removal and spectral whitening work together and transmit accurate information on lower range 8bit data channels. On this picture, noise in significantly removed, local details hidden by strong luminance contrasts are enhanced. Output image keeps its naturalness and visual content is enhanced. Color processing is based on the color multiplexing/demultiplexing method proposed in [chaix2007]_. + +.. image:: images/retina_TreeHdr_retina.jpg + :alt: A High dynamic range image compressed within range [0-255] using the retina. + :align: center + + +*Note :* image sample can be downloaded from the `OpenEXR website `_. Regarding this demonstration, before retina processing, input image has been linearly rescaled within 0-255 keeping its channels float format. 5% of its histogram ends has been cut (mostly removes wrong HDR pixels). Check out the sample *opencv/samples/cpp/OpenEXRimages_HighDynamicRange_Retina_toneMapping.cpp* for similar processing. The following demonstration will only consider classical 8bit/channel images. + +The retina model output channels +================================ + +The retina model presents two outputs that benefit from the above cited behaviors. + +* The first one is called the Parvocellular channel. It is mainly active in the foveal retina area (high resolution central vision with color sensitive photo-receptors), its aim is to provide accurate color vision for visual details remaining static on the retina. On the other hand objects moving on the retina projection are blurred. + +* The second well known channel is the Magnocellular channel. It is mainly active in the retina peripheral vision and send signals related to change events (motion, transient events, etc.). These outing signals also help visual system to focus/center retina on 'transient'/moving areas for more detailed analysis thus improving visual scene context and object classification. + +**NOTE :** regarding the proposed model, contrary to the real retina, we apply these two channels on the entire input images using the same resolution. This allows enhanced visual details and motion information to be extracted on all the considered images... but remember, that these two channels are complementary. For example, if Magnocellular channel gives strong energy in an area, then, the Parvocellular channel is certainly blurred there since there is a transient event. + +As an illustration, we apply in the following the retina model on a webcam video stream of a dark visual scene. In this visual scene, captured in an amphitheater of the university, some students are moving while talking to the teacher. + +In this video sequence, because of the dark ambiance, signal to noise ratio is low and color artifacts are present on visual features edges because of the low quality image capture tool-chain. + +.. image:: images/studentsSample_input.jpg + :alt: an input video stream extract sample + :align: center + +Below is shown the retina foveal vision applied on the entire image. In the used retina configuration, global luminance is preserved and local contrasts are enhanced. Also, signal to noise ratio is improved : since high frequency spatio-temporal noise is reduced, enhanced details are not corrupted by any enhanced noise. + +.. image:: images/studentsSample_parvo.jpg + :alt: the retina Parvocellular output. Enhanced details, luminance adaptation and noise removal. A processing tool for image analysis. + :align: center + +Below is the output of the Magnocellular output of the retina model. Its signals are strong where transient events occur. Here, a student is moving at the bottom of the image thus generating high energy. The remaining of the image is static however, it is corrupted by a strong noise. Here, the retina filters out most of the noise thus generating low false motion area 'alarms'. This channel can be used as a transient/moving areas detector : it would provide relevant information for a low cost segmentation tool that would highlight areas in which an event is occurring. + +.. image:: images/studentsSample_magno.jpg + :alt: the retina Magnocellular output. Enhanced transient signals (motion, etc.). A preprocessing tool for event detection. + :align: center + +Retina use case +=============== + +This model can be used basically for spatio-temporal video effects but also in the aim of : + +* performing texture analysis with enhanced signal to noise ratio and enhanced details robust against input images luminance ranges (check out the Parvocellular retina channel output) + +* performing motion analysis also taking benefit of the previously cited properties. + +Literature +========== +For more information, refer to the following papers : + +.. [benoit2010] Benoit A., Caplier A., Durette B., Herault, J., "Using Human Visual System Modeling For Bio-Inspired Low Level Image Processing", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773. DOI + +* Please have a look at the reference work of Jeanny Herault that you can read in his book : + +.. [herault2010] Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. + +This retina filter code includes the research contributions of phd/research collegues from which code has been redrawn by the author : + +* take a look at the *retinacolor.hpp* module to discover Brice Chaix de Lavarene phD color mosaicing/demosaicing and his reference paper: + +.. [chaix2007] B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 + +* take a look at *imagelogpolprojection.hpp* to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. More informations in the above cited Jeanny Heraults's book. + +Code tutorial +============= + +Please refer to the original tutorial source code in file *opencv_folder/samples/cpp/tutorial_code/bioinspired/retina_tutorial.cpp*. + +**Note :** do not forget that the retina model is included in the following namespace : *cv::bioinspired*. + +To compile it, assuming OpenCV is correctly installed, use the following command. It requires the opencv_core *(cv::Mat and friends objects management)*, opencv_highgui *(display and image/video read)* and opencv_bioinspired *(Retina description)* libraries to compile. + +.. code-block:: cpp + + // compile + gcc retina_tutorial.cpp -o Retina_tuto -lopencv_core -lopencv_highgui -lopencv_bioinspired + + // Run commands : add 'log' as a last parameter to apply a spatial log sampling (simulates retina sampling) + // run on webcam + ./Retina_tuto -video + // run on video file + ./Retina_tuto -video myVideo.avi + // run on an image + ./Retina_tuto -image myPicture.jpg + // run on an image with log sampling + ./Retina_tuto -image myPicture.jpg log + +Here is a code explanation : + +Retina definition is present in the bioinspired package and a simple include allows to use it. You can rather use the specific header : *opencv2/bioinspired.hpp* if you prefer but then include the other required openv modules : *opencv2/core.hpp* and *opencv2/highgui.hpp* + +.. code-block:: cpp + + #include "opencv2/opencv.hpp" + +Provide user some hints to run the program with a help function + +.. code-block:: cpp + + // the help procedure + static void help(std::string errorMessage) + { + std::cout<<"Program init error : "< you can use this to fine tune parameters and load them if you save to file 'RetinaSpecificParameters.xml'"<= 3) + { + std::cout<<"RetinaDemo: processing image "<>inputFrame; + }else + { + // bad command parameter + help("bad command parameter"); + return -1; + } + +Once all input parameters are processed, a first image should have been loaded, if not, display error and stop program : + +.. code-block:: cpp + + if (inputFrame.empty()) + { + help("Input media could not be loaded, aborting"); + return -1; + } + +Now, everything is ready to run the retina model. I propose here to allocate a retina instance and to manage the eventual log sampling option. The Retina constructor expects at least a cv::Size object that shows the input data size that will have to be managed. One can activate other options such as color and its related color multiplexing strategy (here Bayer multiplexing is chosen using *enum cv::bioinspired::RETINA_COLOR_BAYER*). If using log sampling, the image reduction factor (smaller output images) and log sampling strengh can be adjusted. + +.. code-block:: cpp + + // pointer to a retina object + cv::Ptr myRetina; + + // if the last parameter is 'log', then activate log sampling (favour foveal vision and subsamples peripheral vision) + if (useLogSampling) + { + myRetina = cv::bioinspired::createRetina(inputFrame.size(), true, cv::bioinspired::RETINA_COLOR_BAYER, true, 2.0, 10.0); + } + else// -> else allocate "classical" retina : + myRetina = cv::bioinspired::createRetina(inputFrame.size()); + +Once done, the proposed code writes a default xml file that contains the default parameters of the retina. This is useful to make your own config using this template. Here generated template xml file is called *RetinaDefaultParameters.xml*. + +.. code-block:: cpp + + // save default retina parameters file in order to let you see this and maybe modify it and reload using method "setup" + myRetina->write("RetinaDefaultParameters.xml"); + +In the following line, the retina attempts to load another xml file called *RetinaSpecificParameters.xml*. If you created it and introduced your own setup, it will be loaded, in the other case, default retina parameters are used. + +.. code-block:: cpp + + // load parameters if file exists + myRetina->setup("RetinaSpecificParameters.xml"); + +It is not required here but just to show it is possible, you can reset the retina buffers to zero to force it to forget past events. + +.. code-block:: cpp + + // reset all retina buffers (imagine you close your eyes for a long time) + myRetina->clearBuffers(); + +Now, it is time to run the retina ! First create some output buffers ready to receive the two retina channels outputs + +.. code-block:: cpp + + // declare retina output buffers + cv::Mat retinaOutput_parvo; + cv::Mat retinaOutput_magno; + +Then, run retina in a loop, load new frames from video sequence if necessary and get retina outputs back to dedicated buffers. + +.. code-block:: cpp + + // processing loop with no stop condition + while(true) + { + // if using video stream, then, grabbing a new frame, else, input remains the same + if (videoCapture.isOpened()) + videoCapture>>inputFrame; + + // run retina filter on the loaded input frame + myRetina->run(inputFrame); + // Retrieve and display retina output + myRetina->getParvo(retinaOutput_parvo); + myRetina->getMagno(retinaOutput_magno); + cv::imshow("retina input", inputFrame); + cv::imshow("Retina Parvo", retinaOutput_parvo); + cv::imshow("Retina Magno", retinaOutput_magno); + cv::waitKey(10); + } + +That's done ! But if you want to secure the system, take care and manage Exceptions. The retina can throw some when it sees irrelevant data (no input frame, wrong setup, etc.). +Then, i recommend to surround all the retina code by a try/catch system like this : + +.. code-block:: cpp + + try{ + // pointer to a retina object + cv::Ptr myRetina; + [---] + // processing loop with no stop condition + while(true) + { + [---] + } + + }catch(cv::Exception e) + { + std::cerr<<"Error using Retina : "< + +Once done open the configuration file *RetinaDefaultParameters.xml* generated by the demo and let's have a look at it. + +.. code-block:: cpp + + + + + 1 + 1 + 7.5e-01 + 9.0e-01 + 5.7e-01 + 0.01 + 0.5 + 7. + 7.5e-01 + + 1 + 0. + 0. + 7. + 2.0e+00 + 9.5e-01 + 0. + 7. + + +Here are some hints but actually, the best parameter setup depends more on what you want to do with the retina rather than the images input that you give to retina. Apart from the more specific case of High Dynamic Range images (HDR) that require more specific setup for specific luminance compression objective, the retina behaviors should be rather stable from content to content. Note that OpenCV is able to manage such HDR format thanks to the OpenEXR images compatibility. + +Then, if the application target requires details enhancement prior to specific image processing, you need to know if mean luminance information is required or not. If not, the the retina can cancel or significantly reduce its energy thus giving more visibility to higher spatial frequency details. + + +Basic parameters +---------------- + +The most simple parameters are the following : + +* **colorMode** : let the retina process color information (if 1) or gray scale images (if 0). In this last case, only the first channel of the input will be processed. + +* **normaliseOutput** : each channel has this parameter, if value is 1, then the considered channel output is rescaled between 0 and 255. Take care in this case at the Magnocellular output level (motion/transient channel detection). Residual noise will also be rescaled ! + +**Note :** using color requires color channels multiplexing/demultipexing which requires more processing. You can expect much faster processing using gray levels : it would require around 30 product per pixel for all the retina processes and it has recently been parallelized for multicore architectures. + +Photo-receptors parameters +-------------------------- + +The following parameters act on the entry point of the retina - photo-receptors - and impact all the following processes. These sensors are low pass spatio-temporal filters that smooth temporal and spatial data and also adjust there sensitivity to local luminance thus improving details extraction and high frequency noise canceling. + +* **photoreceptorsLocalAdaptationSensitivity** between 0 and 1. Values close to 1 allow high luminance log compression effect at the photo-receptors level. Values closer to 0 give a more linear sensitivity. Increased alone, it can burn the *Parvo (details channel)* output image. If adjusted in collaboration with **ganglionCellsSensitivity** images can be very contrasted whatever the local luminance there is... at the price of a naturalness decrease. + +* **photoreceptorsTemporalConstant** this setups the temporal constant of the low pass filter effect at the entry of the retina. High value lead to strong temporal smoothing effect : moving objects are blurred and can disappear while static object are favored. But when starting the retina processing, stable state is reached lately. + +* **photoreceptorsSpatialConstant** specifies the spatial constant related to photo-receptors low pass filter effect. This parameters specify the minimum allowed spatial signal period allowed in the following. Typically, this filter should cut high frequency noise. Then a 0 value doesn't cut anything noise while higher values start to cut high spatial frequencies and more and more lower frequencies... Then, do not go to high if you wanna see some details of the input images ! A good compromise for color images is 0.53 since this won't affect too much the color spectrum. Higher values would lead to gray and blurred output images. + +Horizontal cells parameters +--------------------------- + +This parameter set tunes the neural network connected to the photo-receptors, the horizontal cells. It modulates photo-receptors sensitivity and completes the processing for final spectral whitening (part of the spatial band pass effect thus favoring visual details enhancement). + +* **horizontalCellsGain** here is a critical parameter ! If you are not interested by the mean luminance and focus on details enhancement, then, set to zero. But if you want to keep some environment luminance data, let some low spatial frequencies pass into the system and set a higher value (<1). + +* **hcellsTemporalConstant** similar to photo-receptors, this acts on the temporal constant of a low pass temporal filter that smooths input data. Here, a high value generates a high retina after effect while a lower value makes the retina more reactive. This value should be lower than **photoreceptorsTemporalConstant** to limit strong retina after effects. + +* **hcellsSpatialConstant** is the spatial constant of the low pass filter of these cells filter. It specifies the lowest spatial frequency allowed in the following. Visually, a high value leads to very low spatial frequencies processing and leads to salient halo effects. Lower values reduce this effect but the limit is : do not go lower than the value of **photoreceptorsSpatialConstant**. Those 2 parameters actually specify the spatial band-pass of the retina. + +**NOTE** after the processing managed by the previous parameters, input data is cleaned from noise and luminance in already partly enhanced. The following parameters act on the last processing stages of the two outing retina signals. + +Parvo (details channel) dedicated parameter +------------------------------------------- + +* **ganglionCellsSensitivity** specifies the strength of the final local adaptation occurring at the output of this details dedicated channel. Parameter values remain between 0 and 1. Low value tend to give a linear response while higher values enforces the remaining low contrasted areas. + +**Note :** this parameter can correct eventual burned images by favoring low energetic details of the visual scene, even in bright areas. + +IPL Magno (motion/transient channel) parameters +----------------------------------------------- + +Once image information is cleaned, this channel acts as a high pass temporal filter that only selects signals related to transient signals (events, motion, etc.). A low pass spatial filter smooths extracted transient data and a final logarithmic compression enhances low transient events thus enhancing event sensitivity. + +* **parasolCells_beta** generally set to zero, can be considered as an amplifier gain at the entry point of this processing stage. Generally set to 0. + +* **parasolCells_tau** the temporal smoothing effect that can be added + +* **parasolCells_k** the spatial constant of the spatial filtering effect, set it at a high value to favor low spatial frequency signals that are lower subject to residual noise. + +* **amacrinCellsTemporalCutFrequency** specifies the temporal constant of the high pass filter. High values let slow transient events to be selected. + +* **V0CompressionParameter** specifies the strength of the log compression. Similar behaviors to previous description but here it enforces sensitivity of transient events. + +* **localAdaptintegration_tau** generally set to 0, no real use here actually + +* **localAdaptintegration_k** specifies the size of the area on which local adaptation is performed. Low values lead to short range local adaptation (higher sensitivity to noise), high values secure log compression. diff --git a/doc/tutorials/bioinspired/table_of_content_bioinspired/images/retina_TreeHdr_small.jpg b/doc/tutorials/bioinspired/table_of_content_bioinspired/images/retina_TreeHdr_small.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4ffb43ec47e421d5f59961dc6704d9407b69a967 GIT binary patch literal 50051 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 {4>dGUz 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 MFcIa(YyX-)haN_EaRCcqDhZUb8Z~&4?*Agj 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 OpenCV 2.4 + + *Author:* |Author_AlexB| + + You will learn how to process images and video streams with a model of retina filter for details enhancement, spatio-temporal noise removal, luminance correction and spatio-temporal events detection. + + =============== ====================================================== + + .. |RetinaDemoImg| image:: images/retina_TreeHdr_small.jpg + :height: 90pt + :width: 90pt + + .. raw:: latex + + \pagebreak + +.. toctree:: + :hidden: + + ../retina_model/retina_model diff --git a/modules/bioinspired/CMakeLists.txt b/modules/bioinspired/CMakeLists.txt new file mode 100644 index 00000000000..c800d33ff33 --- /dev/null +++ b/modules/bioinspired/CMakeLists.txt @@ -0,0 +1,3 @@ +set(the_description "Biologically inspired algorithms") +ocv_warnings_disable(CMAKE_CXX_FLAGS -Wundef) +ocv_define_module(bioinspired opencv_core OPTIONAL opencv_highgui opencv_ocl) diff --git a/modules/bioinspired/doc/bioinspired.rst b/modules/bioinspired/doc/bioinspired.rst new file mode 100644 index 00000000000..6bffcdcf282 --- /dev/null +++ b/modules/bioinspired/doc/bioinspired.rst @@ -0,0 +1,10 @@ +******************************************************************** +bioinspired. Biologically inspired vision models and derivated tools +******************************************************************** + +The module provides biological visual systems models (human visual system and others). It also provides derivated objects that take advantage of those bio-inspired models. + +.. toctree:: + :maxdepth: 2 + + Human retina documentation diff --git a/modules/bioinspired/doc/retina/images/retinaInput.jpg b/modules/bioinspired/doc/retina/images/retinaInput.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d3cdeeecbd3048191f733da0e072891d5ee1e123 GIT binary patch literal 13646 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 literal 0 HcmV?d00001 diff --git a/modules/bioinspired/doc/retina/images/retinaOutput_default.jpg b/modules/bioinspired/doc/retina/images/retinaOutput_default.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0b14a5308f12c9773ba9fa6827b76a2cf5174d5b GIT binary patch literal 22461 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 literal 0 HcmV?d00001 diff --git a/modules/bioinspired/doc/retina/images/retinaOutput_realistic.jpg b/modules/bioinspired/doc/retina/images/retinaOutput_realistic.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1bd60f80cced8cf6afbf3f351caf3ae507b8d56c GIT binary patch literal 19131 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 literal 0 HcmV?d00001 diff --git a/modules/bioinspired/doc/retina/index.rst b/modules/bioinspired/doc/retina/index.rst new file mode 100644 index 00000000000..fd487b7f986 --- /dev/null +++ b/modules/bioinspired/doc/retina/index.rst @@ -0,0 +1,493 @@ +Retina : a Bio mimetic human retina model +***************************************** + +.. highlight:: cpp + +Retina +====== +.. ocv:class:: Retina : public Algorithm + +**Note** : do not forget that the retina model is included in the following namespace : *cv::bioinspired*. + +Introduction +++++++++++++ + +Class which provides the main controls to the Gipsa/Listic labs human retina model. This is a non separable spatio-temporal filter modelling the two main retina information channels : + +* foveal vision for detailled color vision : the parvocellular pathway. + +* peripheral vision for sensitive transient signals detection (motion and events) : the magnocellular pathway. + +From a general point of view, this filter whitens the image spectrum and corrects luminance thanks to local adaptation. An other important property is its hability to filter out spatio-temporal noise while enhancing details. +This model originates from Jeanny Herault work [Herault2010]_. It has been involved in Alexandre Benoit phd and his current research [Benoit2010]_, [Strat2013]_ (he currently maintains this module within OpenCV). It includes the work of other Jeanny's phd student such as [Chaix2007]_ and the log polar transformations of Barthelemy Durette described in Jeanny's book. + +**NOTES :** + +* For ease of use in computer vision applications, the two retina channels are applied homogeneously on all the input images. This does not follow the real retina topology but this can still be done using the log sampling capabilities proposed within the class. + +* Extend the retina description and code use in the tutorial/contrib section for complementary explanations. + +Preliminary illustration +++++++++++++++++++++++++ + +As a preliminary presentation, let's start with a visual example. We propose to apply the filter on a low quality color jpeg image with backlight problems. Here is the considered input... *"Well, my eyes were able to see more that this strange black shadow..."* + +.. image:: images/retinaInput.jpg + :alt: a low quality color jpeg image with backlight problems. + :align: center + +Below, the retina foveal model applied on the entire image with default parameters. Here contours are enforced, halo effects are voluntary visible with this configuration. See parameters discussion below and increase horizontalCellsGain near 1 to remove them. + +.. image:: images/retinaOutput_default.jpg + :alt: the retina foveal model applied on the entire image with default parameters. Here contours are enforced, luminance is corrected and halo effects are voluntary visible with this configuration, increase horizontalCellsGain near 1 to remove them. + :align: center + +Below, a second retina foveal model output applied on the entire image with a parameters setup focused on naturalness perception. *"Hey, i now recognize my cat, looking at the mountains at the end of the day !"*. Here contours are enforced, luminance is corrected but halos are avoided with this configuration. The backlight effect is corrected and highlight details are still preserved. Then, even on a low quality jpeg image, if some luminance information remains, the retina is able to reconstruct a proper visual signal. Such configuration is also usefull for High Dynamic Range (*HDR*) images compression to 8bit images as discussed in [benoit2010]_ and in the demonstration codes discussed below. +As shown at the end of the page, parameters change from defaults are : + +* horizontalCellsGain=0.3 + +* photoreceptorsLocalAdaptationSensitivity=ganglioncellsSensitivity=0.89. + +.. image:: images/retinaOutput_realistic.jpg + :alt: the retina foveal model applied on the entire image with 'naturalness' parameters. Here contours are enforced but are avoided with this configuration, horizontalCellsGain is 0.3 and photoreceptorsLocalAdaptationSensitivity=ganglioncellsSensitivity=0.89. + :align: center + +As observed in this preliminary demo, the retina can be settled up with various parameters, by default, as shown on the figure above, the retina strongly reduces mean luminance energy and enforces all details of the visual scene. Luminance energy and halo effects can be modulated (exagerated to cancelled as shown on the two examples). In order to use your own parameters, you can use at least one time the *write(String fs)* method which will write a proper XML file with all default parameters. Then, tweak it on your own and reload them at any time using method *setup(String fs)*. These methods update a *Retina::RetinaParameters* member structure that is described hereafter. XML parameters file samples are shown at the end of the page. + +Here is an overview of the abstract Retina interface, allocate one instance with the *createRetina* functions.:: + + namespace cv{namespace bioinspired{ + + class Retina : public Algorithm + { + public: + // parameters setup instance + struct RetinaParameters; // this class is detailled later + + // main method for input frame processing (all use method, can also perform High Dynamic Range tone mapping) + void run (InputArray inputImage); + + // specific method aiming at correcting luminance only (faster High Dynamic Range tone mapping) + void applyFastToneMapping(InputArray inputImage, OutputArray outputToneMappedImage) + + // output buffers retreival methods + // -> foveal color vision details channel with luminance and noise correction + void getParvo (OutputArray retinaOutput_parvo); + void getParvoRAW (OutputArray retinaOutput_parvo);// retreive original output buffers without any normalisation + const Mat getParvoRAW () const;// retreive original output buffers without any normalisation + // -> peripheral monochrome motion and events (transient information) channel + void getMagno (OutputArray retinaOutput_magno); + void getMagnoRAW (OutputArray retinaOutput_magno); // retreive original output buffers without any normalisation + const Mat getMagnoRAW () const;// retreive original output buffers without any normalisation + + // reset retina buffers... equivalent to closing your eyes for some seconds + void clearBuffers (); + + // retreive input and output buffers sizes + Size getInputSize (); + Size getOutputSize (); + + // setup methods with specific parameters specification of global xml config file loading/write + void setup (String retinaParameterFile="", const bool applyDefaultSetupOnFailure=true); + void setup (FileStorage &fs, const bool applyDefaultSetupOnFailure=true); + void setup (RetinaParameters newParameters); + struct Retina::RetinaParameters getParameters (); + const String printSetup (); + virtual void write (String fs) const; + virtual void write (FileStorage &fs) const; + void setupOPLandIPLParvoChannel (const bool colorMode=true, const bool normaliseOutput=true, const float photoreceptorsLocalAdaptationSensitivity=0.7, const float photoreceptorsTemporalConstant=0.5, const float photoreceptorsSpatialConstant=0.53, const float horizontalCellsGain=0, const float HcellsTemporalConstant=1, const float HcellsSpatialConstant=7, const float ganglionCellsSensitivity=0.7); + void setupIPLMagnoChannel (const bool normaliseOutput=true, const float parasolCells_beta=0, const float parasolCells_tau=0, const float parasolCells_k=7, const float amacrinCellsTemporalCutFrequency=1.2, const float V0CompressionParameter=0.95, const float localAdaptintegration_tau=0, const float localAdaptintegration_k=7); + void setColorSaturation (const bool saturateColors=true, const float colorSaturationValue=4.0); + void activateMovingContoursProcessing (const bool activate); + void activateContoursProcessing (const bool activate); + }; + + // Allocators + cv::Ptr createRetina (Size inputSize); + cv::Ptr createRetina (Size inputSize, const bool colorMode, RETINA_COLORSAMPLINGMETHOD colorSamplingMethod=RETINA_COLOR_BAYER, const bool useRetinaLogSampling=false, const double reductionFactor=1.0, const double samplingStrenght=10.0); + }} // cv and bioinspired namespaces end + +.. Sample code:: + + * An example on retina tone mapping can be found at opencv_source_code/samples/cpp/OpenEXRimages_HDR_Retina_toneMapping.cpp + * An example on retina tone mapping on video input can be found at opencv_source_code/samples/cpp/OpenEXRimages_HDR_Retina_toneMapping.cpp + * A complete example illustrating the retina interface can be found at opencv_source_code/samples/cpp/retinaDemo.cpp + +Description ++++++++++++ + +Class which allows the `Gipsa `_ (preliminary work) / `Listic `_ (code maintainer and user) labs retina model to be used. This class allows human retina spatio-temporal image processing to be applied on still images, images sequences and video sequences. Briefly, here are the main human retina model properties: + +* spectral whithening (mid-frequency details enhancement) + +* high frequency spatio-temporal noise reduction (temporal noise and high frequency spatial noise are minimized) + +* low frequency luminance reduction (luminance range compression) : high luminance regions do not hide details in darker regions anymore + +* local logarithmic luminance compression allows details to be enhanced even in low light conditions + +Use : this model can be used basically for spatio-temporal video effects but also in the aim of : + +* performing texture analysis with enhanced signal to noise ratio and enhanced details robust against input images luminance ranges (check out the parvocellular retina channel output, by using the provided **getParvo** methods) + +* performing motion analysis also taking benefit of the previously cited properties (check out the magnocellular retina channel output, by using the provided **getMagno** methods) + +* general image/video sequence description using either one or both channels. An example of the use of Retina in a Bag of Words approach is given in [Strat2013]_. + +Literature +========== +For more information, refer to the following papers : + +* Model description : + +.. [Benoit2010] Benoit A., Caplier A., Durette B., Herault, J., "Using Human Visual System Modeling For Bio-Inspired Low Level Image Processing", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773. DOI + +* Model use in a Bag of Words approach : + +.. [Strat2013] Strat S., Benoit A., Lambert P., "Retina enhanced SIFT descriptors for video indexing", CBMI2013, Veszprém, Hungary, 2013. + +* Please have a look at the reference work of Jeanny Herault that you can read in his book : + +.. [Herault2010] Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. + +This retina filter code includes the research contributions of phd/research collegues from which code has been redrawn by the author : + +* take a look at the *retinacolor.hpp* module to discover Brice Chaix de Lavarene phD color mosaicing/demosaicing and his reference paper: + +.. [Chaix2007] B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 + +* take a look at *imagelogpolprojection.hpp* to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. More informations in the above cited Jeanny Heraults's book. + +* Meylan&al work on HDR tone mapping that is implemented as a specific method within the model : + +.. [Meylan2007] L. Meylan , D. Alleysson, S. Susstrunk, "A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images", Journal of Optical Society of America, A, Vol. 24, N 9, September, 1st, 2007, pp. 2807-2816 + +Demos and experiments ! +======================= + +**NOTE : Complementary to the following examples, have a look at the Retina tutorial in the tutorial/contrib section for complementary explanations.** + +Take a look at the provided C++ examples provided with OpenCV : + +* **samples/cpp/retinademo.cpp** shows how to use the retina module for details enhancement (Parvo channel output) and transient maps observation (Magno channel output). You can play with images, video sequences and webcam video. + Typical uses are (provided your OpenCV installation is situated in folder *OpenCVReleaseFolder*) + + * image processing : **OpenCVReleaseFolder/bin/retinademo -image myPicture.jpg** + + * video processing : **OpenCVReleaseFolder/bin/retinademo -video myMovie.avi** + + * webcam processing: **OpenCVReleaseFolder/bin/retinademo -video** + + **Note :** This demo generates the file *RetinaDefaultParameters.xml* which contains the default parameters of the retina. Then, rename this as *RetinaSpecificParameters.xml*, adjust the parameters the way you want and reload the program to check the effect. + + +* **samples/cpp/OpenEXRimages_HDR_Retina_toneMapping.cpp** shows how to use the retina to perform High Dynamic Range (HDR) luminance compression + + Then, take a HDR image using bracketing with your camera and generate an OpenEXR image and then process it using the demo. + + Typical use, supposing that you have the OpenEXR image such as *memorial.exr* (present in the samples/cpp/ folder) + + **OpenCVReleaseFolder/bin/OpenEXRimages_HDR_Retina_toneMapping memorial.exr [optional: 'fast']** + + Note that some sliders are made available to allow you to play with luminance compression. + + If not using the 'fast' option, then, tone mapping is performed using the full retina model [Benoit2010]_. It includes spectral whitening that allows luminance energy to be reduced. When using the 'fast' option, then, a simpler method is used, it is an adaptation of the algorithm presented in [Meylan2007]_. This method gives also good results and is faster to process but it sometimes requires some more parameters adjustement. + + +Methods description +=================== + +Here are detailled the main methods to control the retina model + +Ptr::createRetina ++++++++++++++++++++++++++ + +.. ocv:function:: Ptr createRetina(Size inputSize) +.. ocv:function:: Ptr createRetina(Size inputSize, const bool colorMode, cv::bioinspired::RETINA_COLORSAMPLINGMETHOD colorSamplingMethod = cv::bioinspired::RETINA_COLOR_BAYER, const bool useRetinaLogSampling = false, const double reductionFactor = 1.0, const double samplingStrenght = 10.0 ) + + Constructors from standardized interfaces : retreive a smart pointer to a Retina instance + + :param inputSize: the input frame size + :param colorMode: the chosen processing mode : with or without color processing + :param colorSamplingMethod: specifies which kind of color sampling will be used : + + * cv::bioinspired::RETINA_COLOR_RANDOM: each pixel position is either R, G or B in a random choice + + * cv::bioinspired::RETINA_COLOR_DIAGONAL: color sampling is RGBRGBRGB..., line 2 BRGBRGBRG..., line 3, GBRGBRGBR... + + * cv::bioinspired::RETINA_COLOR_BAYER: standard bayer sampling + + :param useRetinaLogSampling: activate retina log sampling, if true, the 2 following parameters can be used + :param reductionFactor: only usefull if param useRetinaLogSampling=true, specifies the reduction factor of the output frame (as the center (fovea) is high resolution and corners can be underscaled, then a reduction of the output is allowed without precision leak + :param samplingStrenght: only usefull if param useRetinaLogSampling=true, specifies the strenght of the log scale that is applied + +Retina::activateContoursProcessing +++++++++++++++++++++++++++++++++++ + +.. ocv:function:: void Retina::activateContoursProcessing(const bool activate) + + Activate/desactivate the Parvocellular pathway processing (contours information extraction), by default, it is activated + + :param activate: true if Parvocellular (contours information extraction) output should be activated, false if not... if activated, the Parvocellular output can be retrieved using the **getParvo** methods + +Retina::activateMovingContoursProcessing +++++++++++++++++++++++++++++++++++++++++ + +.. ocv:function:: void Retina::activateMovingContoursProcessing(const bool activate) + + Activate/desactivate the Magnocellular pathway processing (motion information extraction), by default, it is activated + + :param activate: true if Magnocellular output should be activated, false if not... if activated, the Magnocellular output can be retrieved using the **getMagno** methods + +Retina::clearBuffers +++++++++++++++++++++ + +.. ocv:function:: void Retina::clearBuffers() + + Clears all retina buffers (equivalent to opening the eyes after a long period of eye close ;o) whatchout the temporal transition occuring just after this method call. + +Retina::getParvo +++++++++++++++++ + +.. ocv:function:: void Retina::getParvo( OutputArray retinaOutput_parvo ) +.. ocv:function:: void Retina::getParvoRAW( OutputArray retinaOutput_parvo ) +.. ocv:function:: const Mat Retina::getParvoRAW() const + + Accessor of the details channel of the retina (models foveal vision). Warning, getParvoRAW methods return buffers that are not rescaled within range [0;255] while the non RAW method allows a normalized matrix to be retrieved. + + :param retinaOutput_parvo: the output buffer (reallocated if necessary), format can be : + + * a Mat, this output is rescaled for standard 8bits image processing use in OpenCV + + * RAW methods actually return a 1D matrix (encoding is R1, R2, ... Rn, G1, G2, ..., Gn, B1, B2, ...Bn), this output is the original retina filter model output, without any quantification or rescaling. + +Retina::getMagno +++++++++++++++++ + +.. ocv:function:: void Retina::getMagno( OutputArray retinaOutput_magno ) +.. ocv:function:: void Retina::getMagnoRAW( OutputArray retinaOutput_magno ) +.. ocv:function:: const Mat Retina::getMagnoRAW() const + + Accessor of the motion channel of the retina (models peripheral vision). Warning, getMagnoRAW methods return buffers that are not rescaled within range [0;255] while the non RAW method allows a normalized matrix to be retrieved. + + :param retinaOutput_magno: the output buffer (reallocated if necessary), format can be : + + * a Mat, this output is rescaled for standard 8bits image processing use in OpenCV + + * RAW methods actually return a 1D matrix (encoding is M1, M2,... Mn), this output is the original retina filter model output, without any quantification or rescaling. + +Retina::getInputSize +++++++++++++++++++++ + +.. ocv:function:: Size Retina::getInputSize() + + Retreive retina input buffer size + + :return: the retina input buffer size + +Retina::getOutputSize ++++++++++++++++++++++ + +.. ocv:function:: Size Retina::getOutputSize() + + Retreive retina output buffer size that can be different from the input if a spatial log transformation is applied + + :return: the retina output buffer size + +Retina::printSetup +++++++++++++++++++ + +.. ocv:function:: const String Retina::printSetup() + + Outputs a string showing the used parameters setup + + :return: a string which contains formated parameters information + +Retina::run ++++++++++++ + +.. ocv:function:: void Retina::run(InputArray inputImage) + + Method which allows retina to be applied on an input image, after run, encapsulated retina module is ready to deliver its outputs using dedicated acccessors, see getParvo and getMagno methods + + :param inputImage: the input Mat image to be processed, can be gray level or BGR coded in any format (from 8bit to 16bits) + +Retina::applyFastToneMapping +++++++++++++++++++++++++++++ + +.. ocv:function:: void Retina::applyFastToneMapping(InputArray inputImage, OutputArray outputToneMappedImage) + + Method which processes an image in the aim to correct its luminance : correct backlight problems, enhance details in shadows. This method is designed to perform High Dynamic Range image tone mapping (compress >8bit/pixel images to 8bit/pixel). This is a simplified version of the Retina Parvocellular model (simplified version of the run/getParvo methods call) since it does not include the spatio-temporal filter modelling the Outer Plexiform Layer of the retina that performs spectral whitening and many other stuff. However, it works great for tone mapping and in a faster way. + + Check the demos and experiments section to see examples and the way to perform tone mapping using the original retina model and the method. + + :param inputImage: the input image to process (should be coded in float format : CV_32F, CV_32FC1, CV_32F_C3, CV_32F_C4, the 4th channel won't be considered). + :param outputToneMappedImage: the output 8bit/channel tone mapped image (CV_8U or CV_8UC3 format). + +Retina::setColorSaturation +++++++++++++++++++++++++++ + +.. ocv:function:: void Retina::setColorSaturation(const bool saturateColors = true, const float colorSaturationValue = 4.0 ) + + Activate color saturation as the final step of the color demultiplexing process -> this saturation is a sigmoide function applied to each channel of the demultiplexed image. + + :param saturateColors: boolean that activates color saturation (if true) or desactivate (if false) + :param colorSaturationValue: the saturation factor : a simple factor applied on the chrominance buffers + + +Retina::setup ++++++++++++++ + +.. ocv:function:: void Retina::setup(String retinaParameterFile = "", const bool applyDefaultSetupOnFailure = true ) +.. ocv:function:: void Retina::setup(FileStorage & fs, const bool applyDefaultSetupOnFailure = true ) +.. ocv:function:: void Retina::setup(RetinaParameters newParameters) + + Try to open an XML retina parameters file to adjust current retina instance setup => if the xml file does not exist, then default setup is applied => warning, Exceptions are thrown if read XML file is not valid + + :param retinaParameterFile: the parameters filename + :param applyDefaultSetupOnFailure: set to true if an error must be thrown on error + :param fs: the open Filestorage which contains retina parameters + :param newParameters: a parameters structures updated with the new target configuration. You can retreive the current parameers structure using method *Retina::RetinaParameters Retina::getParameters()* and update it before running method *setup*. + +Retina::write ++++++++++++++ + +.. ocv:function:: void Retina::write( String fs ) const +.. ocv:function:: void Retina::write( FileStorage& fs ) const + + Write xml/yml formated parameters information + + :param fs: the filename of the xml file that will be open and writen with formatted parameters information + +Retina::setupIPLMagnoChannel +++++++++++++++++++++++++++++ + +.. ocv:function:: void Retina::setupIPLMagnoChannel(const bool normaliseOutput = true, const float parasolCells_beta = 0, const float parasolCells_tau = 0, const float parasolCells_k = 7, const float amacrinCellsTemporalCutFrequency = 1.2, const float V0CompressionParameter = 0.95, const float localAdaptintegration_tau = 0, const float localAdaptintegration_k = 7 ) + + Set parameters values for the Inner Plexiform Layer (IPL) magnocellular channel this channel processes signals output from OPL processing stage in peripheral vision, it allows motion information enhancement. It is decorrelated from the details channel. See reference papers for more details. + + :param normaliseOutput: specifies if (true) output is rescaled between 0 and 255 of not (false) + :param parasolCells_beta: the low pass filter gain used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), typical value is 0 + :param parasolCells_tau: the low pass filter time constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is frame, typical value is 0 (immediate response) + :param parasolCells_k: the low pass filter spatial constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is pixels, typical value is 5 + :param amacrinCellsTemporalCutFrequency: the time constant of the first order high pass fiter of the magnocellular way (motion information channel), unit is frames, typical value is 1.2 + :param V0CompressionParameter: the compression strengh of the ganglion cells local adaptation output, set a value between 0.6 and 1 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 0.95 + :param localAdaptintegration_tau: specifies the temporal constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + :param localAdaptintegration_k: specifies the spatial constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + +Retina::setupOPLandIPLParvoChannel +++++++++++++++++++++++++++++++++++ + +.. ocv:function:: void Retina::setupOPLandIPLParvoChannel(const bool colorMode = true, const bool normaliseOutput = true, const float photoreceptorsLocalAdaptationSensitivity = 0.7, const float photoreceptorsTemporalConstant = 0.5, const float photoreceptorsSpatialConstant = 0.53, const float horizontalCellsGain = 0, const float HcellsTemporalConstant = 1, const float HcellsSpatialConstant = 7, const float ganglionCellsSensitivity = 0.7 ) + + Setup the OPL and IPL parvo channels (see biologocal model) OPL is referred as Outer Plexiform Layer of the retina, it allows the spatio-temporal filtering which withens the spectrum and reduces spatio-temporal noise while attenuating global luminance (low frequency energy) IPL parvo is the OPL next processing stage, it refers to a part of the Inner Plexiform layer of the retina, it allows high contours sensitivity in foveal vision. See reference papers for more informations. + + :param colorMode: specifies if (true) color is processed of not (false) to then processing gray level image + :param normaliseOutput: specifies if (true) output is rescaled between 0 and 255 of not (false) + :param photoreceptorsLocalAdaptationSensitivity: the photoreceptors sensitivity renage is 0-1 (more log compression effect when value increases) + :param photoreceptorsTemporalConstant: the time constant of the first order low pass filter of the photoreceptors, use it to cut high temporal frequencies (noise or fast motion), unit is frames, typical value is 1 frame + :param photoreceptorsSpatialConstant: the spatial constant of the first order low pass filter of the photoreceptors, use it to cut high spatial frequencies (noise or thick contours), unit is pixels, typical value is 1 pixel + :param horizontalCellsGain: gain of the horizontal cells network, if 0, then the mean value of the output is zero, if the parameter is near 1, then, the luminance is not filtered and is still reachable at the output, typicall value is 0 + :param HcellsTemporalConstant: the time constant of the first order low pass filter of the horizontal cells, use it to cut low temporal frequencies (local luminance variations), unit is frames, typical value is 1 frame, as the photoreceptors + :param HcellsSpatialConstant: the spatial constant of the first order low pass filter of the horizontal cells, use it to cut low spatial frequencies (local luminance), unit is pixels, typical value is 5 pixel, this value is also used for local contrast computing when computing the local contrast adaptation at the ganglion cells level (Inner Plexiform Layer parvocellular channel model) + :param ganglionCellsSensitivity: the compression strengh of the ganglion cells local adaptation output, set a value between 0.6 and 1 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 0.7 + + +Retina::RetinaParameters +======================== + +.. ocv:struct:: Retina::RetinaParameters + + This structure merges all the parameters that can be adjusted threw the **Retina::setup()**, **Retina::setupOPLandIPLParvoChannel** and **Retina::setupIPLMagnoChannel** setup methods + Parameters structure for better clarity, check explenations on the comments of methods : setupOPLandIPLParvoChannel and setupIPLMagnoChannel. :: + + class RetinaParameters{ + struct OPLandIplParvoParameters{ // Outer Plexiform Layer (OPL) and Inner Plexiform Layer Parvocellular (IplParvo) parameters + OPLandIplParvoParameters():colorMode(true), + normaliseOutput(true), // specifies if (true) output is rescaled between 0 and 255 of not (false) + photoreceptorsLocalAdaptationSensitivity(0.7f), // the photoreceptors sensitivity renage is 0-1 (more log compression effect when value increases) + photoreceptorsTemporalConstant(0.5f),// the time constant of the first order low pass filter of the photoreceptors, use it to cut high temporal frequencies (noise or fast motion), unit is frames, typical value is 1 frame + photoreceptorsSpatialConstant(0.53f),// the spatial constant of the first order low pass filter of the photoreceptors, use it to cut high spatial frequencies (noise or thick contours), unit is pixels, typical value is 1 pixel + horizontalCellsGain(0.0f),//gain of the horizontal cells network, if 0, then the mean value of the output is zero, if the parameter is near 1, then, the luminance is not filtered and is still reachable at the output, typicall value is 0 + hcellsTemporalConstant(1.f),// the time constant of the first order low pass filter of the horizontal cells, use it to cut low temporal frequencies (local luminance variations), unit is frames, typical value is 1 frame, as the photoreceptors. Reduce to 0.5 to limit retina after effects. + hcellsSpatialConstant(7.f),//the spatial constant of the first order low pass filter of the horizontal cells, use it to cut low spatial frequencies (local luminance), unit is pixels, typical value is 5 pixel, this value is also used for local contrast computing when computing the local contrast adaptation at the ganglion cells level (Inner Plexiform Layer parvocellular channel model) + ganglionCellsSensitivity(0.7f)//the compression strengh of the ganglion cells local adaptation output, set a value between 0.6 and 1 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 0.7 + {};// default setup + bool colorMode, normaliseOutput; + float photoreceptorsLocalAdaptationSensitivity, photoreceptorsTemporalConstant, photoreceptorsSpatialConstant, horizontalCellsGain, hcellsTemporalConstant, hcellsSpatialConstant, ganglionCellsSensitivity; + }; + struct IplMagnoParameters{ // Inner Plexiform Layer Magnocellular channel (IplMagno) + IplMagnoParameters(): + normaliseOutput(true), //specifies if (true) output is rescaled between 0 and 255 of not (false) + parasolCells_beta(0.f), // the low pass filter gain used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), typical value is 0 + parasolCells_tau(0.f), //the low pass filter time constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is frame, typical value is 0 (immediate response) + parasolCells_k(7.f), //the low pass filter spatial constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is pixels, typical value is 5 + amacrinCellsTemporalCutFrequency(1.2f), //the time constant of the first order high pass fiter of the magnocellular way (motion information channel), unit is frames, typical value is 1.2 + V0CompressionParameter(0.95f), the compression strengh of the ganglion cells local adaptation output, set a value between 0.6 and 1 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 0.95 + localAdaptintegration_tau(0.f), // specifies the temporal constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + localAdaptintegration_k(7.f) // specifies the spatial constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + {};// default setup + bool normaliseOutput; + float parasolCells_beta, parasolCells_tau, parasolCells_k, amacrinCellsTemporalCutFrequency, V0CompressionParameter, localAdaptintegration_tau, localAdaptintegration_k; + }; + struct OPLandIplParvoParameters OPLandIplParvo; + struct IplMagnoParameters IplMagno; + }; + +Retina parameters files examples +++++++++++++++++++++++++++++++++ + +Here is the default configuration file of the retina module. It gives results such as the first retina output shown on the top of this page. + +.. code-block:: cpp + + + + + 1 + 1 + 7.5e-01 + 9.0e-01 + 5.3e-01 + 0.01 + 0.5 + 7. + 7.5e-01 + + 1 + 0. + 0. + 7. + 2.0e+00 + 9.5e-01 + 0. + 7. + + +Here is the 'realistic" setup used to obtain the second retina output shown on the top of this page. + +.. code-block:: cpp + + + + + 1 + 1 + 8.9e-01 + 9.0e-01 + 5.3e-01 + 0.3 + 0.5 + 7. + 8.9e-01 + + 1 + 0. + 0. + 7. + 2.0e+00 + 9.5e-01 + 0. + 7. + diff --git a/modules/bioinspired/include/opencv2/bioinspired.hpp b/modules/bioinspired/include/opencv2/bioinspired.hpp new file mode 100644 index 00000000000..5f2f8644d2d --- /dev/null +++ b/modules/bioinspired/include/opencv2/bioinspired.hpp @@ -0,0 +1,50 @@ +/*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. +// 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 __OPENCV_BIOINSPIRED_HPP__ +#define __OPENCV_BIOINSPIRED_HPP__ + +#include "opencv2/core.hpp" +#include "opencv2/bioinspired/retina.hpp" +#include "opencv2/bioinspired/retinafasttonemapping.hpp" + +#endif diff --git a/modules/bioinspired/include/opencv2/bioinspired/bioinspired.hpp b/modules/bioinspired/include/opencv2/bioinspired/bioinspired.hpp new file mode 100644 index 00000000000..40be2854eb9 --- /dev/null +++ b/modules/bioinspired/include/opencv2/bioinspired/bioinspired.hpp @@ -0,0 +1,48 @@ +/*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*/ + +#ifdef __OPENCV_BUILD +#error this is a compatibility header which should not be used inside the OpenCV library +#endif + +#include "opencv2/bioinspired.hpp" diff --git a/modules/bioinspired/include/opencv2/bioinspired/retina.hpp b/modules/bioinspired/include/opencv2/bioinspired/retina.hpp new file mode 100644 index 00000000000..c9655c4a99b --- /dev/null +++ b/modules/bioinspired/include/opencv2/bioinspired/retina.hpp @@ -0,0 +1,311 @@ +/*#****************************************************************************** + ** 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. + ** + ** + ** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. + ** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. + ** + ** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) + ** + ** Creation - enhancement process 2007-2013 + ** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France + ** + ** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). + ** Refer to the following research paper for more information: + ** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 + ** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: + ** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. + ** + ** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : + ** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: + ** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 + ** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. + ** ====> more informations in the above cited Jeanny Heraults's book. + ** + ** License Agreement + ** For Open Source Computer Vision Library + ** + ** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. + ** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. + ** + ** For Human Visual System tools (bioinspired) + ** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: + ** + ** * Redistributions of source code must retain the above copyright notice, + ** this list of conditions and the following disclaimer. + ** + ** * Redistributions 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. + *******************************************************************************/ + +#ifndef __OPENCV_BIOINSPIRED_RETINA_HPP__ +#define __OPENCV_BIOINSPIRED_RETINA_HPP__ + +/* + * Retina.hpp + * + * Created on: Jul 19, 2011 + * Author: Alexandre Benoit + */ + +#include "opencv2/core.hpp" // for all OpenCV core functionalities access, including cv::Exception support + + +namespace cv{ +namespace bioinspired{ + +enum { + RETINA_COLOR_RANDOM, //!< each pixel position is either R, G or B in a random choice + RETINA_COLOR_DIAGONAL,//!< color sampling is RGBRGBRGB..., line 2 BRGBRGBRG..., line 3, GBRGBRGBR... + RETINA_COLOR_BAYER//!< standard bayer sampling +}; + +/** + * @class Retina a wrapper class which allows the Gipsa/Listic Labs model to be used with OpenCV. + * This retina model allows spatio-temporal image processing (applied on still images, video sequences). + * As a summary, these are the retina model properties: + * => It applies a spectral whithening (mid-frequency details enhancement) + * => high frequency spatio-temporal noise reduction + * => low frequency luminance to be reduced (luminance range compression) + * => local logarithmic luminance compression allows details to be enhanced in low light conditions + * + * USE : this model can be used basically for spatio-temporal video effects but also for : + * _using the getParvo method output matrix : texture analysiswith enhanced signal to noise ratio and enhanced details robust against input images luminance ranges + * _using the getMagno method output matrix : motion analysis also with the previously cited properties + * + * for more information, reer to the following papers : + * Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 + * Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. + * + * The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : + * _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: + * ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 + * _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. + * ====> more informations in the above cited Jeanny Heraults's book. + */ +class CV_EXPORTS Retina : public Algorithm { + +public: + + // parameters structure for better clarity, check explenations on the comments of methods : setupOPLandIPLParvoChannel and setupIPLMagnoChannel + struct RetinaParameters{ + struct OPLandIplParvoParameters{ // Outer Plexiform Layer (OPL) and Inner Plexiform Layer Parvocellular (IplParvo) parameters + OPLandIplParvoParameters():colorMode(true), + normaliseOutput(true), + photoreceptorsLocalAdaptationSensitivity(0.75f), + photoreceptorsTemporalConstant(0.9f), + photoreceptorsSpatialConstant(0.53f), + horizontalCellsGain(0.01f), + hcellsTemporalConstant(0.5f), + hcellsSpatialConstant(7.f), + ganglionCellsSensitivity(0.75f) { } // default setup + bool colorMode, normaliseOutput; + float photoreceptorsLocalAdaptationSensitivity, photoreceptorsTemporalConstant, photoreceptorsSpatialConstant, horizontalCellsGain, hcellsTemporalConstant, hcellsSpatialConstant, ganglionCellsSensitivity; + }; + struct IplMagnoParameters{ // Inner Plexiform Layer Magnocellular channel (IplMagno) + IplMagnoParameters(): + normaliseOutput(true), + parasolCells_beta(0.f), + parasolCells_tau(0.f), + parasolCells_k(7.f), + amacrinCellsTemporalCutFrequency(2.0f), + V0CompressionParameter(0.95f), + localAdaptintegration_tau(0.f), + localAdaptintegration_k(7.f) { } // default setup + bool normaliseOutput; + float parasolCells_beta, parasolCells_tau, parasolCells_k, amacrinCellsTemporalCutFrequency, V0CompressionParameter, localAdaptintegration_tau, localAdaptintegration_k; + }; + struct OPLandIplParvoParameters OPLandIplParvo; + struct IplMagnoParameters IplMagno; + }; + + /** + * retreive retina input buffer size + */ + virtual Size getInputSize()=0; + + /** + * retreive retina output buffer size + */ + virtual Size getOutputSize()=0; + + /** + * try to open an XML retina parameters file to adjust current retina instance setup + * => if the xml file does not exist, then default setup is applied + * => warning, Exceptions are thrown if read XML file is not valid + * @param retinaParameterFile : the parameters filename + * @param applyDefaultSetupOnFailure : set to true if an error must be thrown on error + */ + virtual void setup(String retinaParameterFile="", const bool applyDefaultSetupOnFailure=true)=0; + + /** + * try to open an XML retina parameters file to adjust current retina instance setup + * => if the xml file does not exist, then default setup is applied + * => warning, Exceptions are thrown if read XML file is not valid + * @param fs : the open Filestorage which contains retina parameters + * @param applyDefaultSetupOnFailure : set to true if an error must be thrown on error + */ + virtual void setup(cv::FileStorage &fs, const bool applyDefaultSetupOnFailure=true)=0; + + /** + * try to open an XML retina parameters file to adjust current retina instance setup + * => if the xml file does not exist, then default setup is applied + * => warning, Exceptions are thrown if read XML file is not valid + * @param newParameters : a parameters structures updated with the new target configuration + * @param applyDefaultSetupOnFailure : set to true if an error must be thrown on error + */ + virtual void setup(RetinaParameters newParameters)=0; + + /** + * @return the current parameters setup + */ + virtual struct Retina::RetinaParameters getParameters()=0; + + /** + * parameters setup display method + * @return a string which contains formatted parameters information + */ + virtual const String printSetup()=0; + + /** + * write xml/yml formated parameters information + * @rparam fs : the filename of the xml file that will be open and writen with formatted parameters information + */ + virtual void write( String fs ) const=0; + + /** + * write xml/yml formated parameters information + * @param fs : a cv::Filestorage object ready to be filled + */ + virtual void write( FileStorage& fs ) const=0; + + /** + * setup the OPL and IPL parvo channels (see biologocal model) + * OPL is referred as Outer Plexiform Layer of the retina, it allows the spatio-temporal filtering which withens the spectrum and reduces spatio-temporal noise while attenuating global luminance (low frequency energy) + * IPL parvo is the OPL next processing stage, it refers to Inner Plexiform layer of the retina, it allows high contours sensitivity in foveal vision. + * for more informations, please have a look at the paper Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 + * @param colorMode : specifies if (true) color is processed of not (false) to then processing gray level image + * @param normaliseOutput : specifies if (true) output is rescaled between 0 and 255 of not (false) + * @param photoreceptorsLocalAdaptationSensitivity: the photoreceptors sensitivity renage is 0-1 (more log compression effect when value increases) + * @param photoreceptorsTemporalConstant: the time constant of the first order low pass filter of the photoreceptors, use it to cut high temporal frequencies (noise or fast motion), unit is frames, typical value is 1 frame + * @param photoreceptorsSpatialConstant: the spatial constant of the first order low pass filter of the photoreceptors, use it to cut high spatial frequencies (noise or thick contours), unit is pixels, typical value is 1 pixel + * @param horizontalCellsGain: gain of the horizontal cells network, if 0, then the mean value of the output is zero, if the parameter is near 1, then, the luminance is not filtered and is still reachable at the output, typicall value is 0 + * @param HcellsTemporalConstant: the time constant of the first order low pass filter of the horizontal cells, use it to cut low temporal frequencies (local luminance variations), unit is frames, typical value is 1 frame, as the photoreceptors + * @param HcellsSpatialConstant: the spatial constant of the first order low pass filter of the horizontal cells, use it to cut low spatial frequencies (local luminance), unit is pixels, typical value is 5 pixel, this value is also used for local contrast computing when computing the local contrast adaptation at the ganglion cells level (Inner Plexiform Layer parvocellular channel model) + * @param ganglionCellsSensitivity: the compression strengh of the ganglion cells local adaptation output, set a value between 160 and 250 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 230 + */ + virtual void setupOPLandIPLParvoChannel(const bool colorMode=true, const bool normaliseOutput = true, const float photoreceptorsLocalAdaptationSensitivity=0.7, const float photoreceptorsTemporalConstant=0.5, const float photoreceptorsSpatialConstant=0.53, const float horizontalCellsGain=0, const float HcellsTemporalConstant=1, const float HcellsSpatialConstant=7, const float ganglionCellsSensitivity=0.7)=0; + + /** + * set parameters values for the Inner Plexiform Layer (IPL) magnocellular channel + * this channel processes signals outpint from OPL processing stage in peripheral vision, it allows motion information enhancement. It is decorrelated from the details channel. See reference paper for more details. + * @param normaliseOutput : specifies if (true) output is rescaled between 0 and 255 of not (false) + * @param parasolCells_beta: the low pass filter gain used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), typical value is 0 + * @param parasolCells_tau: the low pass filter time constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is frame, typical value is 0 (immediate response) + * @param parasolCells_k: the low pass filter spatial constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is pixels, typical value is 5 + * @param amacrinCellsTemporalCutFrequency: the time constant of the first order high pass fiter of the magnocellular way (motion information channel), unit is frames, tipicall value is 5 + * @param V0CompressionParameter: the compression strengh of the ganglion cells local adaptation output, set a value between 160 and 250 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 200 + * @param localAdaptintegration_tau: specifies the temporal constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + * @param localAdaptintegration_k: specifies the spatial constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + */ + virtual void setupIPLMagnoChannel(const bool normaliseOutput = true, const float parasolCells_beta=0, const float parasolCells_tau=0, const float parasolCells_k=7, const float amacrinCellsTemporalCutFrequency=1.2, const float V0CompressionParameter=0.95, const float localAdaptintegration_tau=0, const float localAdaptintegration_k=7)=0; + + /** + * method which allows retina to be applied on an input image, after run, encapsulated retina module is ready to deliver its outputs using dedicated acccessors, see getParvo and getMagno methods + * @param inputImage : the input cv::Mat image to be processed, can be gray level or BGR coded in any format (from 8bit to 16bits) + */ + virtual void run(InputArray inputImage)=0; + + /** + * method that applies a luminance correction (initially High Dynamic Range (HDR) tone mapping) using only the 2 local adaptation stages of the retina parvo channel : photoreceptors level and ganlion cells level. Spatio temporal filtering is applied but limited to temporal smoothing and eventually high frequencies attenuation. This is a lighter method than the one available using the regular run method. It is then faster but it does not include complete temporal filtering nor retina spectral whitening. Then, it can have a more limited effect on images with a very high dynamic range. This is an adptation of the original still image HDR tone mapping algorithm of David Alleyson, Sabine Susstruck and Laurence Meylan's work, please cite: + * -> Meylan L., Alleysson D., and Susstrunk S., A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images, Journal of Optical Society of America, A, Vol. 24, N 9, September, 1st, 2007, pp. 2807-2816 + @param inputImage the input image to process RGB or gray levels + @param outputToneMappedImage the output tone mapped image + */ + virtual void applyFastToneMapping(InputArray inputImage, OutputArray outputToneMappedImage)=0; + + /** + * accessor of the details channel of the retina (models foveal vision) + * @param retinaOutput_parvo : the output buffer (reallocated if necessary), this output is rescaled for standard 8bits image processing use in OpenCV + */ + virtual void getParvo(OutputArray retinaOutput_parvo)=0; + + /** + * accessor of the details channel of the retina (models foveal vision) + * @param retinaOutput_parvo : a cv::Mat header filled with the internal parvo buffer of the retina module. This output is the original retina filter model output, without any quantification or rescaling + */ + virtual void getParvoRAW(OutputArray retinaOutput_parvo)=0; + + /** + * accessor of the motion channel of the retina (models peripheral vision) + * @param retinaOutput_magno : the output buffer (reallocated if necessary), this output is rescaled for standard 8bits image processing use in OpenCV + */ + virtual void getMagno(OutputArray retinaOutput_magno)=0; + + /** + * accessor of the motion channel of the retina (models peripheral vision) + * @param retinaOutput_magno : a cv::Mat header filled with the internal retina magno buffer of the retina module. This output is the original retina filter model output, without any quantification or rescaling + */ + virtual void getMagnoRAW(OutputArray retinaOutput_magno)=0; + + // original API level data accessors : get buffers addresses from a Mat header, similar to getParvoRAW and getMagnoRAW... + virtual const Mat getMagnoRAW() const=0; + virtual const Mat getParvoRAW() const=0; + + /** + * activate color saturation as the final step of the color demultiplexing process + * -> this saturation is a sigmoide function applied to each channel of the demultiplexed image. + * @param saturateColors: boolean that activates color saturation (if true) or desactivate (if false) + * @param colorSaturationValue: the saturation factor + */ + virtual void setColorSaturation(const bool saturateColors=true, const float colorSaturationValue=4.0)=0; + + /** + * clear all retina buffers (equivalent to opening the eyes after a long period of eye close ;o) + */ + virtual void clearBuffers()=0; + + /** + * Activate/desactivate the Magnocellular pathway processing (motion information extraction), by default, it is activated + * @param activate: true if Magnocellular output should be activated, false if not + */ + virtual void activateMovingContoursProcessing(const bool activate)=0; + + /** + * Activate/desactivate the Parvocellular pathway processing (contours information extraction), by default, it is activated + * @param activate: true if Parvocellular (contours information extraction) output should be activated, false if not + */ + virtual void activateContoursProcessing(const bool activate)=0; +}; +CV_EXPORTS Ptr createRetina(Size inputSize); +CV_EXPORTS Ptr createRetina(Size inputSize, const bool colorMode, int colorSamplingMethod=RETINA_COLOR_BAYER, const bool useRetinaLogSampling=false, const double reductionFactor=1.0, const double samplingStrenght=10.0); + +CV_EXPORTS Ptr createRetina_OCL(Size inputSize); +CV_EXPORTS Ptr createRetina_OCL(Size inputSize, const bool colorMode, int colorSamplingMethod=RETINA_COLOR_BAYER, const bool useRetinaLogSampling=false, const double reductionFactor=1.0, const double samplingStrenght=10.0); +} +} +#endif /* __OPENCV_BIOINSPIRED_RETINA_HPP__ */ diff --git a/modules/bioinspired/include/opencv2/bioinspired/retinafasttonemapping.hpp b/modules/bioinspired/include/opencv2/bioinspired/retinafasttonemapping.hpp new file mode 100644 index 00000000000..6c83f885c8a --- /dev/null +++ b/modules/bioinspired/include/opencv2/bioinspired/retinafasttonemapping.hpp @@ -0,0 +1,121 @@ + +/*#****************************************************************************** + ** 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. + ** + ** + ** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. + ** + ** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) + ** + ** Creation - enhancement process 2007-2013 + ** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France + ** + ** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). + ** Refer to the following research paper for more information: + ** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 + ** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: + ** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. + ** + ** + ** + ** + ** + ** This class is based on image processing tools of the author and already used within the Retina class (this is the same code as method retina::applyFastToneMapping, but in an independent class, it is ligth from a memory requirement point of view). It implements an adaptation of the efficient tone mapping algorithm propose by David Alleyson, Sabine Susstruck and Laurence Meylan's work, please cite: + ** -> Meylan L., Alleysson D., and Susstrunk S., A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images, Journal of Optical Society of America, A, Vol. 24, N 9, September, 1st, 2007, pp. 2807-2816 + ** + ** + ** License Agreement + ** For Open Source Computer Vision Library + ** + ** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. + ** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. + ** + ** For Human Visual System tools (bioinspired) + ** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: + ** + ** * Redistributions of source code must retain the above copyright notice, + ** this list of conditions and the following disclaimer. + ** + ** * Redistributions 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. + *******************************************************************************/ + +#ifndef __OPENCV_BIOINSPIRED_RETINAFASTTONEMAPPING_HPP__ +#define __OPENCV_BIOINSPIRED_RETINAFASTTONEMAPPING_HPP__ + +/* + * retinafasttonemapping.hpp + * + * Created on: May 26, 2013 + * Author: Alexandre Benoit + */ + +#include "opencv2/core.hpp" // for all OpenCV core functionalities access, including cv::Exception support + +namespace cv{ +namespace bioinspired{ + +/** + * @class RetinaFastToneMappingImpl a wrapper class which allows the tone mapping algorithm of Meylan&al(2007) to be used with OpenCV. + * This algorithm is already implemented in thre Retina class (retina::applyFastToneMapping) but used it does not require all the retina model to be allocated. This allows a light memory use for low memory devices (smartphones, etc. + * As a summary, these are the model properties: + * => 2 stages of local luminance adaptation with a different local neighborhood for each. + * => first stage models the retina photorecetors local luminance adaptation + * => second stage models th ganglion cells local information adaptation + * => compared to the initial publication, this class uses spatio-temporal low pass filters instead of spatial only filters. + * ====> this can help noise robustness and temporal stability for video sequence use cases. + * for more information, read to the following papers : + * Meylan L., Alleysson D., and Susstrunk S., A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images, Journal of Optical Society of America, A, Vol. 24, N 9, September, 1st, 2007, pp. 2807-2816Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 + * regarding spatio-temporal filter and the bigger retina model : + * Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. + */ +class CV_EXPORTS RetinaFastToneMapping : public Algorithm +{ +public: + + /** + * method that applies a luminance correction (initially High Dynamic Range (HDR) tone mapping) using only the 2 local adaptation stages of the retina parvocellular channel : photoreceptors level and ganlion cells level. Spatio temporal filtering is applied but limited to temporal smoothing and eventually high frequencies attenuation. This is a lighter method than the one available using the regular retina::run method. It is then faster but it does not include complete temporal filtering nor retina spectral whitening. Then, it can have a more limited effect on images with a very high dynamic range. This is an adptation of the original still image HDR tone mapping algorithm of David Alleyson, Sabine Susstruck and Laurence Meylan's work, please cite: + * -> Meylan L., Alleysson D., and Susstrunk S., A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images, Journal of Optical Society of America, A, Vol. 24, N 9, September, 1st, 2007, pp. 2807-2816 + @param inputImage the input image to process RGB or gray levels + @param outputToneMappedImage the output tone mapped image + */ + virtual void applyFastToneMapping(InputArray inputImage, OutputArray outputToneMappedImage)=0; + + /** + * setup method that updates tone mapping behaviors by adjusing the local luminance computation area + * @param photoreceptorsNeighborhoodRadius the first stage local adaptation area + * @param ganglioncellsNeighborhoodRadius the second stage local adaptation area + * @param meanLuminanceModulatorK the factor applied to modulate the meanLuminance information (default is 1, see reference paper) + */ + virtual void setup(const float photoreceptorsNeighborhoodRadius=3.f, const float ganglioncellsNeighborhoodRadius=1.f, const float meanLuminanceModulatorK=1.f)=0; +}; + +CV_EXPORTS Ptr createRetinaFastToneMapping(Size inputSize); + +} +} +#endif /* __OPENCV_BIOINSPIRED_RETINAFASTTONEMAPPING_HPP__ */ diff --git a/modules/bioinspired/samples/cpp/OpenEXRimages_HDR_Retina_toneMapping.cpp b/modules/bioinspired/samples/cpp/OpenEXRimages_HDR_Retina_toneMapping.cpp new file mode 100644 index 00000000000..66cae77198b --- /dev/null +++ b/modules/bioinspired/samples/cpp/OpenEXRimages_HDR_Retina_toneMapping.cpp @@ -0,0 +1,304 @@ + +//============================================================================ +// Name : OpenEXRimages_HDR_Retina_toneMapping.cpp +// Author : Alexandre Benoit (benoit.alexandre.vision@gmail.com) +// Version : 0.1 +// Copyright : Alexandre Benoit, LISTIC Lab, july 2011 +// Description : HighDynamicRange retina tone mapping with the help of the Gipsa/Listic's retina in C++, Ansi-style +//============================================================================ + +#include +#include + +#include "opencv2/bioinspired.hpp" // retina based algorithms +#include "opencv2/imgproc.hpp" // cvCvtcolor function +#include "opencv2/highgui.hpp" // display + +static void help(std::string errorMessage) +{ + std::cout<<"Program init error : "<(i))), + cv::Scalar::all(0), -1, 8, 0 ); + rectangle( displayedCurveImage, cv::Point(0, 0), + cv::Point((lowerLimit)*binW, 200), + cv::Scalar::all(128), -1, 8, 0 ); + rectangle( displayedCurveImage, cv::Point(displayedCurveImage.cols, 0), + cv::Point((upperLimit)*binW, 200), + cv::Scalar::all(128), -1, 8, 0 ); + + cv::imshow(figureTitle, displayedCurveImage); +} +/* + * objective : get the gray level map of the input image and rescale it to the range [0-255] + */ + static void rescaleGrayLevelMat(const cv::Mat &inputMat, cv::Mat &outputMat, const float histogramClippingLimit) + { + + // adjust output matrix wrt the input size but single channel + std::cout<<"Input image rescaling with histogram edges cutting (in order to eliminate bad pixels created during the HDR image creation) :"< image size (h,w,channels) = "< pixel coding (nbchannel, bytes per channel) = "<(0)=normalizedHist.at(0); + int histLowerLimit=0, histUpperLimit=0; + for (int i=1;i(i)=denseProb.at(i-1)+normalizedHist.at(i); + //std::cout<(i)<<", "<(i)<(i)(i)<1-histogramClippingLimit) + histUpperLimit=i; + } + // deduce min and max admitted gray levels + float minInputValue = (float)histLowerLimit/histSize*255; + float maxInputValue = (float)histUpperLimit/histSize*255; + + std::cout<<"=> Histogram limits " + <<"\n\t"< normalizedHist value = "<(histLowerLimit)<<" => input gray level = "< normalizedHist value = "<(histUpperLimit)<<" => input gray level = "< the main application is tone mapping of HDR images (i.e. see on a 8bit display a more than 8bits coded (up to 16bits) image with details in high and low luminance ranges"< It applies a spectral whithening (mid-frequency details enhancement)"< high frequency spatio-temporal noise reduction"< low frequency luminance to be reduced (luminance range compression)"< local logarithmic luminance compression allows details to be enhanced in low light conditions\n"< reports comments/remarks at benoit.alexandre.vision@gmail.com"< more informations and papers at : http://sites.google.com/site/benoitalexandrevision/"< 1. take a set of photos from the same viewpoint using bracketing ***"< 2. generate an OpenEXR image with tools like qtpfsgui.sourceforge.net ***"< 3. apply tone mapping with this program ***"< image size (h,w) = "<8bits linear rescaling ", inputImage); + imshow("EXR image with basic processing : 16bits=>8bits with gamma correction", gammaTransformedImage); + if (inputImage.empty()) + { + help("Input image could not be loaded, aborting"); + return -1; + } + + ////////////////////////////////////////////////////////////////////////////// + // Program start in a try/catch safety context (Retina may throw errors) + try + { + /* create a retina instance with default parameters setup, uncomment the initialisation you wanna test + * -> if the last parameter is 'log', then activate log sampling (favour foveal vision and subsamples peripheral vision) + */ + if (useLogSampling) + { + retina = cv::bioinspired::createRetina(inputImage.size(),true, cv::bioinspired::RETINA_COLOR_BAYER, true, 2.0, 10.0); + } + else// -> else allocate "classical" retina : + retina = cv::bioinspired::createRetina(inputImage.size()); + + // create a fast retina tone mapper (Meyla&al algorithm) + std::cout<<"Allocating fast tone mapper..."< fastToneMapper=createRetinaFastToneMapping(inputImage.size()); + std::cout<<"Fast tone mapper allocated"<write("RetinaDefaultParameters.xml"); + + // desactivate Magnocellular pathway processing (motion information extraction) since it is not usefull here + retina->activateMovingContoursProcessing(false); + + // declare retina output buffers + cv::Mat retinaOutput_parvo; + + ///////////////////////////////////////////// + // prepare displays and interactions + histogramClippingValue=0; // default value... updated with interface slider + //inputRescaleMat = inputImage; + //outputRescaleMat = imageInputRescaled; + cv::namedWindow("Processing configuration",1); + cv::createTrackbar("histogram edges clipping limit", "Processing configuration",&histogramClippingValue,50,callBack_rescaleGrayLevelMat); + + colorSaturationFactor=3; + cv::createTrackbar("Color saturation", "Processing configuration", &colorSaturationFactor,5,callback_saturateColors); + + retinaHcellsGain=40; + cv::createTrackbar("Hcells gain", "Processing configuration",&retinaHcellsGain,100,callBack_updateRetinaParams); + + localAdaptation_photoreceptors=197; + localAdaptation_Gcells=190; + cv::createTrackbar("Ph sensitivity", "Processing configuration", &localAdaptation_photoreceptors,199,callBack_updateRetinaParams); + cv::createTrackbar("Gcells sensitivity", "Processing configuration", &localAdaptation_Gcells,199,callBack_updateRetinaParams); + + + ///////////////////////////////////////////// + // apply default parameters of user interaction variables + rescaleGrayLevelMat(inputImage, imageInputRescaled, (float)histogramClippingValue/100); + retina->setColorSaturation(true,(float)colorSaturationFactor); + callBack_updateRetinaParams(1,NULL); // first call for default parameters setup + + // processing loop with stop condition + bool continueProcessing=true; + while(continueProcessing) + { + // run retina filter + if (!chosenMethod) + { + retina->run(imageInputRescaled); + // Retrieve and display retina output + retina->getParvo(retinaOutput_parvo); + cv::imshow("Retina input image (with cut edges histogram for basic pixels error avoidance)", imageInputRescaled/255.0); + cv::imshow("Retina Parvocellular pathway output : 16bit=>8bit image retina tonemapping", retinaOutput_parvo); + cv::imwrite("HDRinput.jpg",imageInputRescaled/255.0); + cv::imwrite("RetinaToneMapping.jpg",retinaOutput_parvo); + } + else + { + // apply the simplified hdr tone mapping method + cv::Mat fastToneMappingOutput; + retina->applyFastToneMapping(imageInputRescaled, fastToneMappingOutput); + cv::imshow("Retina fast tone mapping output : 16bit=>8bit image retina tonemapping", fastToneMappingOutput); + } + /*cv::Mat fastToneMappingOutput_specificObject; + fastToneMapper->setup(3.f, 1.5f, 1.f); + fastToneMapper->applyFastToneMapping(imageInputRescaled, fastToneMappingOutput_specificObject); + cv::imshow("### Retina fast tone mapping output : 16bit=>8bit image retina tonemapping", fastToneMappingOutput_specificObject); +*/ + cv::waitKey(10); + } + }catch(cv::Exception e) + { + std::cerr<<"Error using Retina : "< a simple method consists of cutting histogram edges (a slider for this on the UI is provided) +// => however, in image sequences, this histogramm cut must be done in an elegant way from frame to frame... still not done... +//============================================================================ + +#include +#include +#include + +#include "opencv2/bioinspired.hpp" // retina based algorithms +#include "opencv2/imgproc.hpp" // cvCvtcolor function +#include "opencv2/highgui.hpp" // display + +#ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +#endif + +static void help(std::string errorMessage) +{ + std::cout<<"Program init error : "< WARNING : image index number of digits cannot exceed 10"< to process images from memorial020d.exr to memorial045d.exr"<(i))), + cv::Scalar::all(0), -1, 8, 0 ); + rectangle( displayedCurveImage, cv::Point(0, 0), + cv::Point((lowerLimit)*binW, 200), + cv::Scalar::all(128), -1, 8, 0 ); + rectangle( displayedCurveImage, cv::Point(displayedCurveImage.cols, 0), + cv::Point((upperLimit)*binW, 200), + cv::Scalar::all(128), -1, 8, 0 ); + + cv::imshow(figureTitle, displayedCurveImage); +} + +/* + * objective : get the gray level map of the input image and rescale it to the range [0-255] if rescale0_255=TRUE, simply trunks else + */ +static void rescaleGrayLevelMat(const cv::Mat &inputMat, cv::Mat &outputMat, const float histogramClippingLimit, const bool rescale0_255) + { + // adjust output matrix wrt the input size but single channel + std::cout<<"Input image rescaling with histogram edges cutting (in order to eliminate bad pixels created during the HDR image creation) :"< image size (h,w,channels) = "< pixel coding (nbchannel, bytes per channel) = "< scale, offset = "<(0)=normalizedHist.at(0); + int histLowerLimit=0, histUpperLimit=0; + for (int i=1;i(i)=denseProb.at(i-1)+normalizedHist.at(i); + //std::cout<(i)<<", "<(i)<(i)(i)<1.f-histogramClippingLimit) + histUpperLimit=i; + } + // deduce min and max admitted gray levels + float minInputValue = (float)histLowerLimit/histSize*255.f; + float maxInputValue = (float)histUpperLimit/histSize*255.f; + + std::cout<<"=> Histogram limits " + <<"\n\t"< normalizedHist value = "<(histLowerLimit)<<" => input gray level = "< normalizedHist value = "<(histUpperLimit)<<" => input gray level = "< Input Hist clipping values (max,min) = "< image size (h,w) = "< actually using a little less in order to let some more flexibility in range evolves... + */ + double maxInput1, minInput1; + minMaxLoc(inputImage, &minInput1, &maxInput1); + std::cout<<"FIRST IMAGE pixels values range (max,min) : "< the main application is tone mapping of HDR images (i.e. see on a 8bit display a more than 8bits coded (up to 16bits) image with details in high and low luminance ranges"< It applies a spectral whithening (mid-frequency details enhancement)"< high frequency spatio-temporal noise reduction"< low frequency luminance to be reduced (luminance range compression)"< local logarithmic luminance compression allows details to be enhanced in low light conditions\n"< reports comments/remarks at benoit.alexandre.vision@gmail.com"< more informations and papers at : http://sites.google.com/site/benoitalexandrevision/"< 1. take a set of photos from the same viewpoint using bracketing ***"< 2. generate an OpenEXR image with tools like qtpfsgui.sourceforge.net ***"< 3. apply tone mapping with this program ***"< if the last parameter is 'log', then activate log sampling (favour foveal vision and subsamples peripheral vision) + */ + if (useLogSampling) + { + retina = cv::bioinspired::createRetina(inputImage.size(),true, cv::bioinspired::RETINA_COLOR_BAYER, true, 2.0, 10.0); + } + else// -> else allocate "classical" retina : + retina = cv::bioinspired::createRetina(inputImage.size()); + + // save default retina parameters file in order to let you see this and maybe modify it and reload using method "setup" + retina->write("RetinaDefaultParameters.xml"); + + // desactivate Magnocellular pathway processing (motion information extraction) since it is not usefull here + retina->activateMovingContoursProcessing(false); + + // declare retina output buffers + cv::Mat retinaOutput_parvo; + + ///////////////////////////////////////////// + // prepare displays and interactions + histogramClippingValue=0; // default value... updated with interface slider + + std::string retinaInputCorrected("Retina input image (with cut edges histogram for basic pixels error avoidance)"); + cv::namedWindow(retinaInputCorrected,1); + cv::createTrackbar("histogram edges clipping limit", "Retina input image (with cut edges histogram for basic pixels error avoidance)",&histogramClippingValue,50,callBack_rescaleGrayLevelMat); + + std::string RetinaParvoWindow("Retina Parvocellular pathway output : 16bit=>8bit image retina tonemapping"); + cv::namedWindow(RetinaParvoWindow, 1); + colorSaturationFactor=3; + cv::createTrackbar("Color saturation", "Retina Parvocellular pathway output : 16bit=>8bit image retina tonemapping", &colorSaturationFactor,5,callback_saturateColors); + + retinaHcellsGain=40; + cv::createTrackbar("Hcells gain", "Retina Parvocellular pathway output : 16bit=>8bit image retina tonemapping",&retinaHcellsGain,100,callBack_updateRetinaParams); + + localAdaptation_photoreceptors=197; + localAdaptation_Gcells=190; + cv::createTrackbar("Ph sensitivity", "Retina Parvocellular pathway output : 16bit=>8bit image retina tonemapping", &localAdaptation_photoreceptors,199,callBack_updateRetinaParams); + cv::createTrackbar("Gcells sensitivity", "Retina Parvocellular pathway output : 16bit=>8bit image retina tonemapping", &localAdaptation_Gcells,199,callBack_updateRetinaParams); + + std::string powerTransformedInput("EXR image with basic processing : 16bits=>8bits with gamma correction"); + + ///////////////////////////////////////////// + // apply default parameters of user interaction variables + callBack_updateRetinaParams(1,NULL); // first call for default parameters setup + callback_saturateColors(1, NULL); + + // processing loop with stop condition + currentFrameIndex=startFrameIndex; + while(currentFrameIndex <= endFrameIndex) + { + loadNewFrame(inputImageNamePrototype, currentFrameIndex, false); + + if (inputImage.empty()) + { + std::cout<<"Could not load new image (index = "<8bits linear rescaling ", imageInputRescaled); + cv::Mat gammaTransformedImage; + cv::pow(imageInputRescaled, 1./5, gammaTransformedImage); // apply gamma curve: img = img ** (1./5) + imshow(powerTransformedInput, gammaTransformedImage); + // run retina filter + retina->run(imageInputRescaled); + // Retrieve and display retina output + retina->getParvo(retinaOutput_parvo); + cv::imshow(retinaInputCorrected, imageInputRescaled/255.f); + cv::imshow(RetinaParvoWindow, retinaOutput_parvo); + cv::waitKey(4); + // jump to next frame + ++currentFrameIndex; + } + }catch(cv::Exception e) + { + std::cerr<<"Error using Retina : "< +#include + +#include "opencv2/bioinspired.hpp" +#include "opencv2/highgui.hpp" + +static void help(std::string errorMessage) +{ + std::cout<<"Program init error : "< It applies a spectral whithening (mid-frequency details enhancement)"< high frequency spatio-temporal noise reduction"< low frequency luminance to be reduced (luminance range compression)"< local logarithmic luminance compression allows details to be enhanced in low light conditions\n"< reports comments/remarks at benoit.alexandre.vision@gmail.com"< more informations and papers at : http://sites.google.com/site/benoitalexandrevision/"< you can use this to fine tune parameters and load them if you save to file 'RetinaSpecificParameters.xml'"<= 3) + { + std::cout<<"RetinaDemo: processing image "<>inputFrame; + }else + { + // bad command parameter + help("bad command parameter"); + return -1; + } + + if (inputFrame.empty()) + { + help("Input media could not be loaded, aborting"); + return -1; + } + + + ////////////////////////////////////////////////////////////////////////////// + // Program start in a try/catch safety context (Retina may throw errors) + try + { + // create a retina instance with default parameters setup, uncomment the initialisation you wanna test + cv::Ptr myRetina; + + // if the last parameter is 'log', then activate log sampling (favour foveal vision and subsamples peripheral vision) + if (useLogSampling) + { + myRetina = cv::bioinspired::createRetina(inputFrame.size(), true, cv::bioinspired::RETINA_COLOR_BAYER, true, 2.0, 10.0); + } + else// -> else allocate "classical" retina : + myRetina = cv::bioinspired::createRetina(inputFrame.size()); + + // save default retina parameters file in order to let you see this and maybe modify it and reload using method "setup" + myRetina->write("RetinaDefaultParameters.xml"); + + // load parameters if file exists + myRetina->setup("RetinaSpecificParameters.xml"); + myRetina->clearBuffers(); + + // declare retina output buffers + cv::Mat retinaOutput_parvo; + cv::Mat retinaOutput_magno; + + // processing loop with stop condition + bool continueProcessing=true; // FIXME : not yet managed during process... + while(continueProcessing) + { + // if using video stream, then, grabbing a new frame, else, input remains the same + if (videoCapture.isOpened()) + videoCapture>>inputFrame; + + // run retina filter + myRetina->run(inputFrame); + // Retrieve and display retina output + myRetina->getParvo(retinaOutput_parvo); + myRetina->getMagno(retinaOutput_magno); + cv::imshow("retina input", inputFrame); + cv::imshow("Retina Parvo", retinaOutput_parvo); + cv::imshow("Retina Magno", retinaOutput_magno); + + cv::waitKey(5); + } + }catch(cv::Exception e) + { + std::cerr<<"Error using Retina : "< +#include + +#include "opencv2/bioinspired.hpp" +#include "opencv2/highgui.hpp" + +static void help(std::string errorMessage) +{ + std::cout<<"Program init error : "< you can use this to fine tune parameters and load them if you save to file 'RetinaSpecificParameters.xml'"<= 3) + { + std::cout<<"RetinaDemo: processing image "<>inputFrame; + }else + { + // bad command parameter + help("bad command parameter"); + return -1; + } + + if (inputFrame.empty()) + { + help("Input media could not be loaded, aborting"); + return -1; + } + + + ////////////////////////////////////////////////////////////////////////////// + // Program start in a try/catch safety context (Retina may throw errors) + try + { + // create a retina instance with default parameters setup, uncomment the initialisation you wanna test + cv::Ptr myRetina; + + // if the last parameter is 'log', then activate log sampling (favour foveal vision and subsamples peripheral vision) + if (useLogSampling) + { + myRetina = cv::bioinspired::createRetina(inputFrame.size(), true, cv::bioinspired::RETINA_COLOR_BAYER, true, 2.0, 10.0); + } + else// -> else allocate "classical" retina : + { + myRetina = cv::bioinspired::createRetina(inputFrame.size()); + } + + // save default retina parameters file in order to let you see this and maybe modify it and reload using method "setup" + myRetina->write("RetinaDefaultParameters.xml"); + + // load parameters if file exists + myRetina->setup("RetinaSpecificParameters.xml"); + + // reset all retina buffers (imagine you close your eyes for a long time) + myRetina->clearBuffers(); + + // declare retina output buffers + cv::Mat retinaOutput_parvo; + cv::Mat retinaOutput_magno; + + // processing loop with no stop condition + for(;;) + { + // if using video stream, then, grabbing a new frame, else, input remains the same + if (videoCapture.isOpened()) + videoCapture>>inputFrame; + + // run retina filter on the loaded input frame + myRetina->run(inputFrame); + // Retrieve and display retina output + myRetina->getParvo(retinaOutput_parvo); + myRetina->getMagno(retinaOutput_magno); + cv::imshow("retina input", inputFrame); + cv::imshow("Retina Parvo", retinaOutput_parvo); + cv::imshow("Retina Magno", retinaOutput_magno); + cv::waitKey(10); + } + }catch(cv::Exception e) + { + std::cerr<<"Error using Retina or end of video sequence reached : "< +#include + +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/highgui.hpp" +#include "opencv2/ocl.hpp" +#include "opencv2/bioinspired.hpp" + +using namespace cv; +using namespace cv::ocl; +using namespace std; + +const int total_loop_count = 50; + +static void help(CommandLineParser cmd, const String& errorMessage) +{ + cout << errorMessage << endl; + cout << "Avaible options:" << endl; + cmd.printMessage(); +} + +int main(int argc, char* argv[]) +{ + //set this to save kernel compile time from second time you run + ocl::setBinaryDiskCache(); + const char* keys = + "{ h | help | false | print help message }" + "{ c | cpu | false | use cpu (original version) or gpu(OpenCL) to process the image }" + "{ i | image | cat.jpg | specify the input image }"; + + CommandLineParser cmd(argc, argv, keys); + + if(cmd.get("help")) + { + help(cmd, "Usage: ./retina_ocl [options]"); + return EXIT_FAILURE; + } + + String fname = cmd.get("i"); + bool useCPU = cmd.get("c"); + + cv::Mat input = imread(fname); + if(input.empty()) + { + help(cmd, "Error opening: " + fname); + return EXIT_FAILURE; + } + ////////////////////////////////////////////////////////////////////////////// + // Program start in a try/catch safety context (Retina may throw errors) + try + { + // create a retina instance with default parameters setup, uncomment the initialisation you wanna test + cv::Ptr oclRetina; + cv::Ptr retina; + // declare retina output buffers + cv::ocl::oclMat retina_parvo_ocl; + cv::ocl::oclMat retina_magno_ocl; + cv::Mat retina_parvo; + cv::Mat retina_magno; + + if(useCPU) + { + retina = cv::bioinspired::createRetina(input.size()); + retina->clearBuffers(); + } + else + { + oclRetina = cv::bioinspired::createRetina_OCL(input.size()); + oclRetina->clearBuffers(); + } + + int64 temp_time = 0, total_time = 0; + + int loop_counter = 0; + for(; loop_counter <= total_loop_count; ++loop_counter) + { + if(useCPU) + { + temp_time = cv::getTickCount(); + retina->run(input); + retina->getParvo(retina_parvo); + retina->getMagno(retina_magno); + } + else + { + cv::ocl::oclMat input_ocl(input); + temp_time = cv::getTickCount(); + oclRetina->run(input_ocl); + oclRetina->getParvo(retina_parvo_ocl); + oclRetina->getMagno(retina_magno_ocl); + } + // will not count the first loop, which is considered as warm-up period + if(loop_counter > 0) + { + temp_time = (cv::getTickCount() - temp_time); + total_time += temp_time; + printf("Frame id %2d: %3.4fms\n", loop_counter, (double)temp_time / cv::getTickFrequency() * 1000.0); + } + if(!useCPU) + { + retina_parvo = retina_parvo_ocl; + retina_magno = retina_magno_ocl; + } + cv::imshow("retina input", input); + cv::imshow("Retina Parvo", retina_parvo); + cv::imshow("Retina Magno", retina_magno); + cv::waitKey(10); + } + printf("Average: %.4fms\n", (double)total_time / total_loop_count / cv::getTickFrequency() * 1000.0); + } + catch(cv::Exception e) + { + std::cerr << "Error using Retina : " << e.what() << std::endl; + } + // Program end message + std::cout << "Retina demo end" << std::endl; + return EXIT_SUCCESS; +} diff --git a/modules/bioinspired/src/basicretinafilter.cpp b/modules/bioinspired/src/basicretinafilter.cpp new file mode 100644 index 00000000000..7e7b467faaf --- /dev/null +++ b/modules/bioinspired/src/basicretinafilter.cpp @@ -0,0 +1,888 @@ +/*#****************************************************************************** +** 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. +** +** +** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. +** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. +** +** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) +** +** Creation - enhancement process 2007-2011 +** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France +** +** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). +** Refer to the following research paper for more information: +** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 +** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: +** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. +** +** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : +** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: +** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#include "precomp.hpp" + +#include +#include +#include "basicretinafilter.hpp" +#include + + +namespace cv +{ +namespace bioinspired +{ +// @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC : www.listic.univ-savoie.fr Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ + +////////////////////////////////////////////////////////// +// BASIC RETINA FILTER +////////////////////////////////////////////////////////// + +// Constructor and Desctructor of the basic retina filter +BasicRetinaFilter::BasicRetinaFilter(const unsigned int NBrows, const unsigned int NBcolumns, const unsigned int parametersListSize, const bool useProgressiveFilter) +:_filterOutput(NBrows, NBcolumns), + _localBuffer(NBrows*NBcolumns), + _filteringCoeficientsTable(3*parametersListSize), + _progressiveSpatialConstant(0),// pointer to a local table containing local spatial constant (allocated with the object) + _progressiveGain(0) +{ +#ifdef T_BASIC_RETINA_ELEMENT_DEBUG + std::cout<<"BasicRetinaFilter::BasicRetinaFilter: new filter, size="<0) + { + _progressiveSpatialConstant.resize(_filterOutput.size()); + _progressiveGain.resize(_filterOutput.size()); + } + // reset buffers + clearAllBuffers(); +} + +// Change coefficients table +void BasicRetinaFilter::setLPfilterParameters(const float beta, const float tau, const float desired_k, const unsigned int filterIndex) +{ + float _beta = beta+tau; + float k=desired_k; + // check if the spatial constant is correct (avoid 0 value to avoid division by 0) + if (desired_k<=0) + { + k=0.001f; + std::cerr<<"BasicRetinaFilter::spatial constant of the low pass filter must be superior to zero !!! correcting parameter setting to 0,001"< old:"<<(1-a)*(1-a)*(1-a)*(1-a)/(1+_beta)<1.0f) + localSpatialConstantValue=1.0f; + + _progressiveSpatialConstant[_halfNBcolumns-1+idColumn+_filterOutput.getNBcolumns()*(_halfNBrows-1+idRow)]=localSpatialConstantValue; + _progressiveSpatialConstant[_halfNBcolumns-1-idColumn+_filterOutput.getNBcolumns()*(_halfNBrows-1+idRow)]=localSpatialConstantValue; + _progressiveSpatialConstant[_halfNBcolumns-1+idColumn+_filterOutput.getNBcolumns()*(_halfNBrows-1-idRow)]=localSpatialConstantValue; + _progressiveSpatialConstant[_halfNBcolumns-1-idColumn+_filterOutput.getNBcolumns()*(_halfNBrows-1-idRow)]=localSpatialConstantValue; + + // computing local gain + float localGain=(1-localSpatialConstantValue)*(1-localSpatialConstantValue)*(1-localSpatialConstantValue)*(1-localSpatialConstantValue)/(1+_beta); + _progressiveGain[_halfNBcolumns-1+idColumn+_filterOutput.getNBcolumns()*(_halfNBrows-1+idRow)]=localGain; + _progressiveGain[_halfNBcolumns-1-idColumn+_filterOutput.getNBcolumns()*(_halfNBrows-1+idRow)]=localGain; + _progressiveGain[_halfNBcolumns-1+idColumn+_filterOutput.getNBcolumns()*(_halfNBrows-1-idRow)]=localGain; + _progressiveGain[_halfNBcolumns-1-idColumn+_filterOutput.getNBcolumns()*(_halfNBrows-1-idRow)]=localGain; + + //std::cout< &accuracyMap, const unsigned int filterIndex) +{ + + if (accuracyMap.size()!=_filterOutput.size()) + { + std::cerr<<"BasicRetinaFilter::setProgressiveFilterConstants_CustomAccuracy: error: input accuracy map does not match filter size, init skept"<1) + localSpatialConstantValue=1; + + _progressiveSpatialConstant[index]=localSpatialConstantValue; + + // computing local gain + float localGain=(1.0f-localSpatialConstantValue)*(1.0f-localSpatialConstantValue)*(1.0f-localSpatialConstantValue)*(1.0f-localSpatialConstantValue)/(1.0f+_beta); + _progressiveGain[index]=localGain; + + //std::cout< &BasicRetinaFilter::runFilter_LocalAdapdation(const std::valarray &inputFrame, const std::valarray &localLuminance) +{ + _localLuminanceAdaptation(get_data(inputFrame), get_data(localLuminance), &_filterOutput[0]); + return _filterOutput; +} +// run local adaptation filter at a specific output adress +void BasicRetinaFilter::runFilter_LocalAdapdation(const std::valarray &inputFrame, const std::valarray &localLuminance, std::valarray &outputFrame) +{ + _localLuminanceAdaptation(get_data(inputFrame), get_data(localLuminance), &outputFrame[0]); +} +// run local adaptation filter and save result in _filterOutput with autonomous low pass filtering before adaptation +const std::valarray &BasicRetinaFilter::runFilter_LocalAdapdation_autonomous(const std::valarray &inputFrame) +{ + _spatiotemporalLPfilter(get_data(inputFrame), &_filterOutput[0]); + _localLuminanceAdaptation(get_data(inputFrame), &_filterOutput[0], &_filterOutput[0]); + return _filterOutput; +} +// run local adaptation filter at a specific output adress with autonomous low pass filtering before adaptation +void BasicRetinaFilter::runFilter_LocalAdapdation_autonomous(const std::valarray &inputFrame, std::valarray &outputFrame) +{ + _spatiotemporalLPfilter(get_data(inputFrame), &_filterOutput[0]); + _localLuminanceAdaptation(get_data(inputFrame), &_filterOutput[0], &outputFrame[0]); +} + +// local luminance adaptation of the input in regard of localLuminance buffer, the input is rewrited and becomes the output +void BasicRetinaFilter::_localLuminanceAdaptation(float *inputOutputFrame, const float *localLuminance) +{ + _localLuminanceAdaptation(inputOutputFrame, localLuminance, inputOutputFrame, false); + + /* const float *localLuminancePTR=localLuminance; + float *inputOutputFramePTR=inputOutputFrame; + + for (register unsigned int IDpixel=0 ; IDpixel<_filterOutput.getNBpixels() ; ++IDpixel, ++inputOutputFramePTR) + { + float X0=*(localLuminancePTR++)*_localLuminanceFactor+_localLuminanceAddon; + *(inputOutputFramePTR) = (_maxInputValue+X0)**inputOutputFramePTR/(*inputOutputFramePTR +X0+0.00000000001); + } + */ +} + +// local luminance adaptation of the input in regard of localLuminance buffer +void BasicRetinaFilter::_localLuminanceAdaptation(const float *inputFrame, const float *localLuminance, float *outputFrame, const bool updateLuminanceMean) +{ + if (updateLuminanceMean) + { float meanLuminance=0; + const float *luminancePTR=inputFrame; + for (unsigned int i=0;i<_filterOutput.getNBpixels();++i) + meanLuminance+=*(luminancePTR++); + meanLuminance/=_filterOutput.getNBpixels(); + //float tempMeanValue=meanLuminance+_meanInputValue*_tau; + updateCompressionParameter(meanLuminance); + } +#ifdef MAKE_PARALLEL + cv::parallel_for_(cv::Range(0,_filterOutput.getNBpixels()), Parallel_localAdaptation(localLuminance, inputFrame, outputFrame, _localLuminanceFactor, _localLuminanceAddon, _maxInputValue)); +#else + //std::cout< &BasicRetinaFilter::runFilter_LPfilter(const std::valarray &inputFrame, const unsigned int filterIndex) +{ + _spatiotemporalLPfilter(get_data(inputFrame), &_filterOutput[0], filterIndex); + return _filterOutput; +} + +// run LP filter for a new frame input and save result at a specific output adress +void BasicRetinaFilter::runFilter_LPfilter(const std::valarray &inputFrame, std::valarray &outputFrame, const unsigned int filterIndex) +{ + _spatiotemporalLPfilter(get_data(inputFrame), &outputFrame[0], filterIndex); +} + +// run LP filter on the input data and rewrite it +void BasicRetinaFilter::runFilter_LPfilter_Autonomous(std::valarray &inputOutputFrame, const unsigned int filterIndex) +{ + unsigned int coefTableOffset=filterIndex*3; + + /**********/ + _a=_filteringCoeficientsTable[coefTableOffset]; + _gain=_filteringCoeficientsTable[1+coefTableOffset]; + _tau=_filteringCoeficientsTable[2+coefTableOffset]; + + // launch the serie of 1D directional filters in order to compute the 2D low pass filter + _horizontalCausalFilter(&inputOutputFrame[0], 0, _filterOutput.getNBrows()); + _horizontalAnticausalFilter(&inputOutputFrame[0], 0, _filterOutput.getNBrows()); + _verticalCausalFilter(&inputOutputFrame[0], 0, _filterOutput.getNBcolumns()); + _verticalAnticausalFilter_multGain(&inputOutputFrame[0], 0, _filterOutput.getNBcolumns()); + +} +// run LP filter for a new frame input and save result at a specific output adress +void BasicRetinaFilter::_spatiotemporalLPfilter(const float *inputFrame, float *outputFrame, const unsigned int filterIndex) +{ + unsigned int coefTableOffset=filterIndex*3; + /**********/ + _a=_filteringCoeficientsTable[coefTableOffset]; + _gain=_filteringCoeficientsTable[1+coefTableOffset]; + _tau=_filteringCoeficientsTable[2+coefTableOffset]; + + // launch the serie of 1D directional filters in order to compute the 2D low pass filter + _horizontalCausalFilter_addInput(inputFrame, outputFrame, 0,_filterOutput.getNBrows()); + _horizontalAnticausalFilter(outputFrame, 0, _filterOutput.getNBrows()); + _verticalCausalFilter(outputFrame, 0, _filterOutput.getNBcolumns()); + _verticalAnticausalFilter_multGain(outputFrame, 0, _filterOutput.getNBcolumns()); + +} + +// run SQUARING LP filter for a new frame input and save result at a specific output adress +float BasicRetinaFilter::_squaringSpatiotemporalLPfilter(const float *inputFrame, float *outputFrame, const unsigned int filterIndex) +{ + unsigned int coefTableOffset=filterIndex*3; + /**********/ + _a=_filteringCoeficientsTable[coefTableOffset]; + _gain=_filteringCoeficientsTable[1+coefTableOffset]; + _tau=_filteringCoeficientsTable[2+coefTableOffset]; + + // launch the serie of 1D directional filters in order to compute the 2D low pass filter + + _squaringHorizontalCausalFilter(inputFrame, outputFrame, 0, _filterOutput.getNBrows()); + _horizontalAnticausalFilter(outputFrame, 0, _filterOutput.getNBrows()); + _verticalCausalFilter(outputFrame, 0, _filterOutput.getNBcolumns()); + return _verticalAnticausalFilter_returnMeanValue(outputFrame, 0, _filterOutput.getNBcolumns()); +} + +///////////////////////////////////////////////// +// standard version of the 1D low pass filters + +// horizontal causal filter which adds the input inside +void BasicRetinaFilter::_horizontalCausalFilter(float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd) +{ + + + //#pragma omp parallel for + for (unsigned int IDrow=IDrowStart; IDrow squaring horizontal causal filter +void BasicRetinaFilter::_squaringHorizontalCausalFilter(const float *inputFrame, float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd) +{ + register float* outputPTR=outputFrame+IDrowStart*_filterOutput.getNBcolumns(); + register const float* inputPTR=inputFrame+IDrowStart*_filterOutput.getNBcolumns(); + for (unsigned int IDrow=IDrowStart; IDrow USE IRREGULAR SPATIAL CONSTANT + +// irregular filter computed from a buffer and rewrites it +void BasicRetinaFilter::_spatiotemporalLPfilter_Irregular(float *inputOutputFrame, const unsigned int filterIndex) +{ + if (_progressiveGain.size()==0) + { + std::cerr<<"BasicRetinaFilter::runProgressiveFilter: cannot perform filtering, no progressive filter settled up"< B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#ifndef BASICRETINAELEMENT_HPP_ +#define BASICRETINAELEMENT_HPP_ + +#include + + +/** +* @class BasicRetinaFilter +* @brief Brief overview, this class provides tools for low level image processing: +* --> this class is able to perform: +* -> first order Low pass optimized filtering +* -> local luminance adaptation (able to correct back light problems and contrast enhancement) +* -> progressive low pass filter filtering (higher filtering on the borders than on the center) +* -> image data between 0 and 255 resampling with different options, linear rescaling, sigmoide) +* +* NOTE : initially the retina model was based on double format scalar values but +* a good memory/precision compromise is float... +* also the double format precision does not make so much sense from a biological point of view (neurons value coding is not so precise) +* +* TYPICAL USE: +* +* // create object at a specified picture size +* BasicRetinaFilter *_photoreceptorsPrefilter; +* _photoreceptorsPrefilter =new BasicRetinaFilter(sizeRows, sizeWindows); +* +* // init gain, spatial and temporal parameters: +* _photoreceptorsPrefilter->setCoefficientsTable(gain,temporalConstant, spatialConstant); +* +* // during program execution, call the filter for local luminance correction or low pass filtering for an input picture called "FrameBuffer": +* _photoreceptorsPrefilter->runFilter_LocalAdapdation(FrameBuffer); +* // or (Low pass first order filter) +* _photoreceptorsPrefilter->runFilter_LPfilter(FrameBuffer); +* // get output frame and its size: +* const unsigned int output_nbRows=_photoreceptorsPrefilter->getNBrows(); +* const unsigned int output_nbColumns=_photoreceptorsPrefilter->getNBcolumns(); +* const double *outputFrame=_photoreceptorsPrefilter->getOutput(); +* +* // at the end of the program, destroy object: +* delete _photoreceptorsPrefilter; + +* @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC : www.listic.univ-savoie.fr, Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ +* Creation date 2007 +* synthesis of the work described in Alexandre BENOIT thesis: "Le systeme visuel humain au secours de la vision par ordinateur" +*/ + +#include +#include "templatebuffer.hpp" + +//#define __BASIC_RETINA_ELEMENT_DEBUG + +namespace cv +{ +namespace bioinspired +{ + class BasicRetinaFilter + { + public: + + /** + * constructor of the base bio-inspired toolbox, parameters are only linked to imae input size and number of filtering capabilities of the object + * @param NBrows: number of rows of the input image + * @param NBcolumns: number of columns of the input image + * @param parametersListSize: specifies the number of parameters set (each parameters set represents a specific low pass spatio-temporal filter) + * @param useProgressiveFilter: specifies if the filter has irreguar (progressive) filtering capabilities (this can be activated later using setProgressiveFilterConstants_xxx methods) + */ + BasicRetinaFilter(const unsigned int NBrows, const unsigned int NBcolumns, const unsigned int parametersListSize=1, const bool useProgressiveFilter=false); + + /** + * standrad destructore + */ + ~BasicRetinaFilter(); + + /** + * function which clears the output buffer of the object + */ + inline void clearOutputBuffer() { _filterOutput = 0; } + + /** + * function which clears the secondary buffer of the object + */ + inline void clearSecondaryBuffer() { _localBuffer = 0; } + + /** + * function which clears the output and the secondary buffer of the object + */ + inline void clearAllBuffers() { clearOutputBuffer(); clearSecondaryBuffer(); } + + /** + * resize basic retina filter object (resize all allocated buffers + * @param NBrows: the new height size + * @param NBcolumns: the new width size + */ + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + + /** + * forbiden method inherited from parent std::valarray + * prefer not to use this method since the filter matrix become vectors + */ + void resize(const unsigned int) { std::cerr<<"error, not accessible method"< &runFilter_LPfilter(const std::valarray &inputFrame, const unsigned int filterIndex=0); // run the LP filter for a new frame input and save result in _filterOutput + + /** + * low pass filter call and run (models the homogeneous cells network at the retina level, for example horizontal cells or photoreceptors) + * @param inputFrame: the input image to be processed + * @param outputFrame: the output buffer in which the result is writed + * @param filterIndex: the offset which specifies the parameter set that should be used for the filtering + */ + void runFilter_LPfilter(const std::valarray &inputFrame, std::valarray &outputFrame, const unsigned int filterIndex=0); // run LP filter on a specific output adress + + /** + * low pass filter call and run (models the homogeneous cells network at the retina level, for example horizontal cells or photoreceptors) + * @param inputOutputFrame: the input image to be processed on which the result is rewrited + * @param filterIndex: the offset which specifies the parameter set that should be used for the filtering + */ + void runFilter_LPfilter_Autonomous(std::valarray &inputOutputFrame, const unsigned int filterIndex=0);// run LP filter on the input data and rewrite it + + /** + * local luminance adaptation call and run (contrast enhancement property of the photoreceptors) + * @param inputOutputFrame: the input image to be processed + * @param localLuminance: an image which represents the local luminance of the inputFrame parameter, in general, it is its low pass spatial filtering + * @return the processed image, the output is reachable later by using function getOutput() + */ + const std::valarray &runFilter_LocalAdapdation(const std::valarray &inputOutputFrame, const std::valarray &localLuminance);// run local adaptation filter and save result in _filterOutput + + /** + * local luminance adaptation call and run (contrast enhancement property of the photoreceptors) + * @param inputFrame: the input image to be processed + * @param localLuminance: an image which represents the local luminance of the inputFrame parameter, in general, it is its low pass spatial filtering + * @param outputFrame: the output buffer in which the result is writed + */ + void runFilter_LocalAdapdation(const std::valarray &inputFrame, const std::valarray &localLuminance, std::valarray &outputFrame); // run local adaptation filter on a specific output adress + + /** + * local luminance adaptation call and run (contrast enhancement property of the photoreceptors) + * @param inputFrame: the input image to be processed + * @return the processed image, the output is reachable later by using function getOutput() + */ + const std::valarray &runFilter_LocalAdapdation_autonomous(const std::valarray &inputFrame);// run local adaptation filter and save result in _filterOutput + + /** + * local luminance adaptation call and run (contrast enhancement property of the photoreceptors) + * @param inputFrame: the input image to be processed + * @param outputFrame: the output buffer in which the result is writen + */ + void runFilter_LocalAdapdation_autonomous(const std::valarray &inputFrame, std::valarray &outputFrame); // run local adaptation filter on a specific output adress + + /** + * run low pass filtering with progressive parameters (models the retina log sampling of the photoreceptors and its low pass filtering effect consequence: more powerfull low pass filtering effect on the corners) + * @param inputFrame: the input image to be processed + * @param filterIndex: the index which specifies the parameter set that should be used for the filtering + * @return the processed image, the output is reachable later by using function getOutput() if outputFrame is NULL + */ + inline void runProgressiveFilter(std::valarray &inputFrame, const unsigned int filterIndex=0) { _spatiotemporalLPfilter_Irregular(&inputFrame[0], filterIndex); } + + /** + * run low pass filtering with progressive parameters (models the retina log sampling of the photoreceptors and its low pass filtering effect consequence: more powerfull low pass filtering effect on the corners) + * @param inputFrame: the input image to be processed + * @param outputFrame: the output buffer in which the result is writen + * @param filterIndex: the index which specifies the parameter set that should be used for the filtering + */ + inline void runProgressiveFilter(const std::valarray &inputFrame, + std::valarray &outputFrame, + const unsigned int filterIndex=0) + {_spatiotemporalLPfilter_Irregular(get_data(inputFrame), &outputFrame[0], filterIndex); } + + /** + * first order spatio-temporal low pass filter setup function + * @param beta: gain of the filter (generally set to zero) + * @param tau: time constant of the filter (unit is frame for video processing) + * @param k: spatial constant of the filter (unit is pixels) + * @param filterIndex: the index which specifies the parameter set that should be used for the filtering + */ + void setLPfilterParameters(const float beta, const float tau, const float k, const unsigned int filterIndex=0); // change the parameters of the filter + + /** + * first order spatio-temporal low pass filter setup function + * @param beta: gain of the filter (generally set to zero) + * @param tau: time constant of the filter (unit is frame for video processing) + * @param alpha0: spatial constant of the filter (unit is pixels) on the border of the image + * @param filterIndex: the index which specifies the parameter set that should be used for the filtering + */ + void setProgressiveFilterConstants_CentredAccuracy(const float beta, const float tau, const float alpha0, const unsigned int filterIndex=0); + + /** + * first order spatio-temporal low pass filter setup function + * @param beta: gain of the filter (generally set to zero) + * @param tau: time constant of the filter (unit is frame for video processing) + * @param alpha0: spatial constant of the filter (unit is pixels) on the border of the image + * @param accuracyMap an image (float format) which values range is between 0 and 1, where 0 means, apply no filtering and 1 means apply the filtering as specified in the parameters set, intermediate values allow to smooth variations of the filtering strenght + * @param filterIndex: the index which specifies the parameter set that should be used for the filtering + */ + void setProgressiveFilterConstants_CustomAccuracy(const float beta, const float tau, const float alpha0, const std::valarray &accuracyMap, const unsigned int filterIndex=0); + + /** + * local luminance adaptation setup, this function should be applied for normal local adaptation (not for tone mapping operation) + * @param v0: compression effect for the local luminance adaptation processing, set a value between 0.6 and 0.9 for best results, a high value yields to a high compression effect + * @param maxInputValue: the maximum amplitude value measured after local adaptation processing (c.f. function runFilter_LocalAdapdation & runFilter_LocalAdapdation_autonomous) + * @param meanLuminance: the a priori meann luminance of the input data (should be 128 for 8bits images but can vary greatly in case of High Dynamic Range Images (HDRI) + */ + void setV0CompressionParameter(const float v0, const float maxInputValue, const float) + { + _v0=v0*maxInputValue; + _localLuminanceFactor=v0; + _localLuminanceAddon=maxInputValue*(1.0f-v0); + _maxInputValue=maxInputValue; + } + + /** + * update local luminance adaptation setup, initial maxInputValue is kept. This function should be applied for normal local adaptation (not for tone mapping operation) + * @param v0: compression effect for the local luminance adaptation processing, set a value between 0.6 and 0.9 for best results, a high value yields to a high compression effect + * @param meanLuminance: the a priori meann luminance of the input data (should be 128 for 8bits images but can vary greatly in case of High Dynamic Range Images (HDRI) + */ + void setV0CompressionParameter(const float v0, const float meanLuminance) { this->setV0CompressionParameter(v0, _maxInputValue, meanLuminance); } + + /** + * local luminance adaptation setup, this function should be applied for normal local adaptation (not for tone mapping operation) + * @param v0: compression effect for the local luminance adaptation processing, set a value between 0.6 and 0.9 for best results, a high value yields to a high compression effect + */ + void setV0CompressionParameter(const float v0) + { + _v0=v0*_maxInputValue; + _localLuminanceFactor=v0; + _localLuminanceAddon=_maxInputValue*(1.0f-v0); + } + + /** + * local luminance adaptation setup, this function should be applied for local adaptation applied to tone mapping operation + * @param v0: compression effect for the local luminance adaptation processing, set a value between 0.6 and 0.9 for best results, a high value yields to a high compression effect + * @param maxInputValue: the maximum amplitude value measured after local adaptation processing (c.f. function runFilter_LocalAdapdation & runFilter_LocalAdapdation_autonomous) + * @param meanLuminance: the a priori meann luminance of the input data (should be 128 for 8bits images but can vary greatly in case of High Dynamic Range Images (HDRI) + */ + void setV0CompressionParameterToneMapping(const float v0, const float maxInputValue, const float meanLuminance=128.0f) + { + _v0=v0*maxInputValue; + _localLuminanceFactor=1.0f; + _localLuminanceAddon=meanLuminance*v0; + _maxInputValue=maxInputValue; + } + + /** + * update compression parameters while keeping v0 parameter value + * @param meanLuminance the input frame mean luminance + */ + inline void updateCompressionParameter(const float meanLuminance) + { + _localLuminanceFactor=1; + _localLuminanceAddon=meanLuminance*_v0; + } + + /** + * @return the v0 compression parameter used to compute the local adaptation + */ + float getV0CompressionParameter() { return _v0/_maxInputValue; } + + /** + * @return the output result of the object + */ + inline const std::valarray &getOutput() const { return _filterOutput; } + + /** + * @return number of rows of the filter + */ + inline unsigned int getNBrows() { return _filterOutput.getNBrows(); } + + /** + * @return number of columns of the filter + */ + inline unsigned int getNBcolumns() { return _filterOutput.getNBcolumns(); } + + /** + * @return number of pixels of the filter + */ + inline unsigned int getNBpixels() { return _filterOutput.getNBpixels(); } + + /** + * force filter output to be normalized between 0 and maxValue + * @param maxValue: the maximum output value that is required + */ + inline void normalizeGrayOutput_0_maxOutputValue(const float maxValue) { _filterOutput.normalizeGrayOutput_0_maxOutputValue(maxValue); } + + /** + * force filter output to be normalized around 0 and rescaled with a sigmoide effect (extrem values saturation) + * @param maxValue: the maximum output value that is required + */ + inline void normalizeGrayOutputCentredSigmoide() { _filterOutput.normalizeGrayOutputCentredSigmoide(); } + + /** + * force filter output to be normalized : data centering and std normalisation + * @param maxValue: the maximum output value that is required + */ + inline void centerReductImageLuminance() { _filterOutput.centerReductImageLuminance(); } + + /** + * @return the maximum input buffer value + */ + inline float getMaxInputValue() { return _maxInputValue; } + + /** + * @return the maximum input buffer value + */ + inline void setMaxInputValue(const float newMaxInputValue) { this->_maxInputValue=newMaxInputValue; } + + protected: + + ///////////////////////// + // data buffers + TemplateBuffer _filterOutput; // primary buffer (contains processing outputs) + std::valarray _localBuffer; // local secondary buffer + ///////////////////////// + // PARAMETERS + unsigned int _halfNBrows; + unsigned int _halfNBcolumns; + + // parameters buffers + std::valarray _filteringCoeficientsTable; + std::valarray _progressiveSpatialConstant;// pointer to a local table containing local spatial constant (allocated with the object) + std::valarray _progressiveGain;// pointer to a local table containing local spatial constant (allocated with the object) + + // local adaptation filtering parameters + float _v0; //value used for local luminance adaptation function + float _maxInputValue; + float _meanInputValue; + float _localLuminanceFactor; + float _localLuminanceAddon; + + // protected data related to standard low pass filters parameters + float _a; + float _tau; + float _gain; + + ///////////////////////// + // FILTERS METHODS + + // Basic low pass spation temporal low pass filter used by each retina filters + void _spatiotemporalLPfilter(const float *inputFrame, float *LPfilterOutput, const unsigned int coefTableOffset=0); + float _squaringSpatiotemporalLPfilter(const float *inputFrame, float *outputFrame, const unsigned int filterIndex=0); + + // LP filter with an irregular spatial filtering + + // -> rewrites the input buffer + void _spatiotemporalLPfilter_Irregular(float *inputOutputFrame, const unsigned int filterIndex=0); + // writes the output on another buffer + void _spatiotemporalLPfilter_Irregular(const float *inputFrame, float *outputFrame, const unsigned int filterIndex=0); + // LP filter that squares the input and computes the output ONLY on the areas where the integrationAreas map are TRUE + void _localSquaringSpatioTemporalLPfilter(const float *inputFrame, float *LPfilterOutput, const unsigned int *integrationAreas, const unsigned int filterIndex=0); + + // local luminance adaptation of the input in regard of localLuminance buffer + void _localLuminanceAdaptation(const float *inputFrame, const float *localLuminance, float *outputFrame, const bool updateLuminanceMean=true); + // local luminance adaptation of the input in regard of localLuminance buffer, the input is rewrited and becomes the output + void _localLuminanceAdaptation(float *inputOutputFrame, const float *localLuminance); + // local adaptation applied on a range of values which can be positive and negative + void _localLuminanceAdaptationPosNegValues(const float *inputFrame, const float *localLuminance, float *outputFrame); + + + ////////////////////////////////////////////////////////////// + // 1D directional filters used for the 2D low pass filtering + + // 1D filters with image input + void _horizontalCausalFilter_addInput(const float *inputFrame, float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd); + // 1D filters with image input that is squared in the function // parallelized with TBB + void _squaringHorizontalCausalFilter(const float *inputFrame, float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd); + // vertical anticausal filter that returns the mean value of its result + float _verticalAnticausalFilter_returnMeanValue(float *outputFrame, unsigned int IDcolumnStart, unsigned int IDcolumnEnd); + + // most simple functions: only perform 1D filtering with output=input (no add on) + void _horizontalCausalFilter(float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd); + void _horizontalAnticausalFilter(float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd); // parallelized with TBB + void _verticalCausalFilter(float *outputFrame, unsigned int IDcolumnStart, unsigned int IDcolumnEnd); // parallelized with TBB + void _verticalAnticausalFilter(float *outputFrame, unsigned int IDcolumnStart, unsigned int IDcolumnEnd); + + // perform 1D filtering with output with varrying spatial coefficient + void _horizontalCausalFilter_Irregular(float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd); + void _horizontalCausalFilter_Irregular_addInput(const float *inputFrame, float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd); + void _horizontalAnticausalFilter_Irregular(float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd, const float *spatialConstantBuffer); // parallelized with TBB + void _verticalCausalFilter_Irregular(float *outputFrame, unsigned int IDcolumnStart, unsigned int IDcolumnEnd, const float *spatialConstantBuffer); // parallelized with TBB + void _verticalAnticausalFilter_Irregular_multGain(float *outputFrame, unsigned int IDcolumnStart, unsigned int IDcolumnEnd); + + + // 1D filters in which the output is multiplied by _gain + void _verticalAnticausalFilter_multGain(float *outputFrame, unsigned int IDcolumnStart, unsigned int IDcolumnEnd); // this functions affects _gain at the output // parallelized with TBB + void _horizontalAnticausalFilter_multGain(float *outputFrame, unsigned int IDcolumnStart, unsigned int IDcolumnEnd); // this functions affects _gain at the output + + // LP filter on specific parts of the picture instead of all the image + // same functions (some of them) but take a binary flag to allow integration, false flag means, 0 at the output... + void _local_squaringHorizontalCausalFilter(const float *inputFrame, float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd, const unsigned int *integrationAreas); + void _local_horizontalAnticausalFilter(float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd, const unsigned int *integrationAreas); + void _local_verticalCausalFilter(float *outputFrame, unsigned int IDcolumnStart, unsigned int IDcolumnEnd, const unsigned int *integrationAreas); + void _local_verticalAnticausalFilter_multGain(float *outputFrame, unsigned int IDcolumnStart, unsigned int IDcolumnEnd, const unsigned int *integrationAreas); // this functions affects _gain at the output + +#ifdef MAKE_PARALLEL + /****************************************************** + ** IF some parallelizing thread methods are available, then, main loops are parallelized using these functors + ** ==> main idea paralellise main filters loops, then, only the most used methods are parallelized... TODO : increase the number of parallelised methods as necessary + ** ==> functors names = Parallel_$$$ where $$$= the name of the serial method that is parallelised + ** ==> functors constructors can differ from the parameters used with their related serial functions + */ + +#define _DEBUG_TBB // define DEBUG_TBB in order to display additionnal data on stdout + class Parallel_horizontalAnticausalFilter: public cv::ParallelLoopBody + { + private: + float *outputFrame; + unsigned int IDrowEnd, nbColumns; + float filterParam_a; + public: + // constructor which takes the input image pointer reference reference and limits + Parallel_horizontalAnticausalFilter(float *bufferToProcess, const unsigned int idEnd, const unsigned int nbCols, const float a ) + :outputFrame(bufferToProcess), IDrowEnd(idEnd), nbColumns(nbCols), filterParam_a(a) + { +#ifdef DEBUG_TBB + std::cout<<"Parallel_horizontalAnticausalFilter::Parallel_horizontalAnticausalFilter :" + <<"\n\t idEnd="< B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#include "precomp.hpp" +#include "imagelogpolprojection.hpp" + +#include +#include + +// @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC : www.listic.univ-savoie.fr, Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ + +namespace cv +{ +namespace bioinspired +{ +// constructor +ImageLogPolProjection::ImageLogPolProjection(const unsigned int nbRows, const unsigned int nbColumns, const PROJECTIONTYPE projection, const bool colorModeCapable) +:BasicRetinaFilter(nbRows, nbColumns), + _sampledFrame(0), + _tempBuffer(_localBuffer), + _transformTable(0), + _irregularLPfilteredFrame(_filterOutput) +{ + _inputDoubleNBpixels=nbRows*nbColumns*2; + _selectedProjection = projection; + _reductionFactor=0; + _initOK=false; + _usefullpixelIndex=0; + _colorModeCapable=colorModeCapable; +#ifdef IMAGELOGPOLPROJECTION_DEBUG + std::cout<<"ImageLogPolProjection::allocating"< private init functions dedicated to each projection +bool ImageLogPolProjection::_initLogRetinaSampling(const double reductionFactor, const double samplingStrenght) +{ + _initOK=false; + + if (_selectedProjection!=RETINALOGPROJECTION) + { + std::cerr<<"ImageLogPolProjection::initLogRetinaSampling: could not initialize logPolar projection for a log projection system\n -> you probably chose the wrong init function, use initLogPolarCortexSampling() instead"<getNBrows(), reductionFactor); + _outputNBcolumns=predictOutputSize(this->getNBcolumns(), reductionFactor); + _outputNBpixels=_outputNBrows*_outputNBcolumns; + _outputDoubleNBpixels=_outputNBrows*_outputNBcolumns*2; + +#ifdef IMAGELOGPOLPROJECTION_DEBUG + std::cout<<"ImageLogPolProjection::initLogRetinaSampling: Log resampled image resampling factor: "< ImageLogPolProjection::(u, v)="< ImageLogPolProjection::(u, v)="< "<<(halfInputRows-u)+_filterOutput.getNBrows()*(halfInputColumns+v)< you probably chose the wrong init function, use initLogRetinaSampling() instead"< radiusAxis(_outputNBcolumns); + double radiusStep=2.30/(double)_outputNBcolumns; + for (unsigned int i=0;i<_outputNBcolumns;++i) + { + radiusAxis[i]=i*radiusStep; + } + std::valarray orientationAxis(_outputNBrows); + double orientationStep=-2.0*CV_PI/(double)_outputNBrows; + for (unsigned int io=0;io<_outputNBrows;++io) + { + orientationAxis[io]=io*orientationStep; + } + // -> use a temporay transform table which is bigger than the final one, we only report pixels coordinates that are included in the sampled picture + std::valarray tempTransformTable(2*_outputNBpixels); // the structure would be: (pixelInputCoordinate n)(pixelOutputCoordinate n)(pixelInputCoordinate n+1)(pixelOutputCoordinate n+1) + _usefullpixelIndex=0; + + //std::cout<<"ImageLogPolProjection::Starting cortex projection"<0)&&(rowIndex<_filterOutput.getNBrows())&&(rowIndex>0)) + { + // set coordinate + tempTransformTable[_usefullpixelIndex++]=radiusIndex+orientationIndex*_outputNBcolumns; + tempTransformTable[_usefullpixelIndex++]= columnIndex+rowIndex*_filterOutput.getNBcolumns(); + } + } + + // (re)creating and filling the transform table + _transformTable.resize(_usefullpixelIndex); + memcpy(&_transformTable[0], &tempTransformTable[0], sizeof(unsigned int)*_usefullpixelIndex); + + // reset all buffers + clearAllBuffers(); + _initOK=true; + return true; +} + +// action function +std::valarray &ImageLogPolProjection::runProjection(const std::valarray &inputFrame, const bool colorMode) +{ + if (_colorModeCapable&&colorMode) + { + // progressive filtering and storage of the result in _tempBuffer + _spatiotemporalLPfilter_Irregular(get_data(inputFrame), &_irregularLPfilteredFrame[0]); + _spatiotemporalLPfilter_Irregular(&_irregularLPfilteredFrame[0], &_tempBuffer[0]); // warning, temporal issue may occur, if the temporal constant is not NULL !!! + + _spatiotemporalLPfilter_Irregular(get_data(inputFrame)+_filterOutput.getNBpixels(), &_irregularLPfilteredFrame[0]); + _spatiotemporalLPfilter_Irregular(&_irregularLPfilteredFrame[0], &_tempBuffer[0]+_filterOutput.getNBpixels()); + + _spatiotemporalLPfilter_Irregular(get_data(inputFrame)+_filterOutput.getNBpixels()*2, &_irregularLPfilteredFrame[0]); + _spatiotemporalLPfilter_Irregular(&_irregularLPfilteredFrame[0], &_tempBuffer[0]+_filterOutput.getNBpixels()*2); + + // applying image projection/resampling + register unsigned int *transformTablePTR=&_transformTable[0]; + for (unsigned int i=0 ; i<_usefullpixelIndex ; i+=2, transformTablePTR+=2) + { +#ifdef IMAGELOGPOLPROJECTION_DEBUG + std::cout<<"ImageLogPolProjection::i:"< B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#ifndef IMAGELOGPOLPROJECTION_H_ +#define IMAGELOGPOLPROJECTION_H_ + +/** +* @class ImageLogPolProjection +* @brief class able to perform a log sampling of an image input (models the log sampling of the photoreceptors of the retina) +* or a log polar projection which models the retina information projection on the primary visual cortex: a linear projection in the center for detail analysis and a log projection of the borders (low spatial frequency motion information in general) +* +* collaboration: Barthelemy DURETTE who experimented the retina log projection +-> "Traitement visuels Bio mimtiques pour la supplance perceptive", internal technical report, May 2005, Gipsa-lab/DIS, Grenoble, FRANCE +* +* * TYPICAL USE: +* +* // create object, here for a log sampling (keyword:RETINALOGPROJECTION): (dynamic object allocation sample) +* ImageLogPolProjection *imageSamplingTool; +* imageSamplingTool = new ImageLogPolProjection(frameSizeRows, frameSizeColumns, RETINALOGPROJECTION); +* +* // init log projection: +* imageSamplingTool->initProjection(1.0, 15.0); +* +* // during program execution, call the log transform applied to a frame called "FrameBuffer" : +* imageSamplingTool->runProjection(FrameBuffer); +* // get output frame and its size: +* const unsigned int logSampledFrame_nbRows=imageSamplingTool->getOutputNBrows(); +* const unsigned int logSampledFrame_nbColumns=imageSamplingTool->getOutputNBcolumns(); +* const double *logSampledFrame=imageSamplingTool->getSampledFrame(); +* +* // at the end of the program, destroy object: +* delete imageSamplingTool; +* +* @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC : www.listic.univ-savoie.fr, Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ +* Creation date 2007 +*/ + +//#define __IMAGELOGPOLPROJECTION_DEBUG // used for std output debug information + +#include "basicretinafilter.hpp" + + +namespace cv +{ +namespace bioinspired +{ + +class ImageLogPolProjection:public BasicRetinaFilter +{ +public: + + enum PROJECTIONTYPE{RETINALOGPROJECTION, CORTEXLOGPOLARPROJECTION}; + + /** + * constructor, just specifies the image input size and the projection type, no projection initialisation is done + * -> use initLogRetinaSampling() or initLogPolarCortexSampling() for that + * @param nbRows: number of rows of the input image + * @param nbColumns: number of columns of the input image + * @param projection: the type of projection, RETINALOGPROJECTION or CORTEXLOGPOLARPROJECTION + * @param colorMode: specifies if the projection is applied on a grayscale image (false) or color images (3 layers) (true) + */ + ImageLogPolProjection(const unsigned int nbRows, const unsigned int nbColumns, const PROJECTIONTYPE projection, const bool colorMode=false); + + /** + * standard destructor + */ + virtual ~ImageLogPolProjection(); + + /** + * function that clears all buffers of the object + */ + void clearAllBuffers(); + + /** + * resize retina color filter object (resize all allocated buffers) + * @param NBrows: the new height size + * @param NBcolumns: the new width size + */ + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + + /** + * init function depending on the projection type + * @param reductionFactor: the size reduction factor of the ouptup image in regard of the size of the input image, must be superior to 1 + * @param samplingStrenght: specifies the strenght of the log compression effect (magnifying coefficient) + * @return true if the init was performed without any errors + */ + bool initProjection(const double reductionFactor, const double samplingStrenght); + + /** + * main funtion of the class: run projection function + * @param inputFrame: the input frame to be processed + * @param colorMode: the input buffer color mode: false=gray levels, true = 3 color channels mode + * @return the output frame + */ + std::valarray &runProjection(const std::valarray &inputFrame, const bool colorMode=false); + + /** + * @return the numbers of rows (height) of the images OUTPUTS of the object + */ + inline unsigned int getOutputNBrows() { return _outputNBrows; } + + /** + * @return the numbers of columns (width) of the images OUTPUTS of the object + */ + inline unsigned int getOutputNBcolumns() { return _outputNBcolumns; } + + /** + * main funtion of the class: run projection function + * @param size: one of the input frame initial dimensions to be processed + * @return the output frame dimension + */ + inline static unsigned int predictOutputSize(const unsigned int size, const double reductionFactor){return (unsigned int)((double)size/reductionFactor); } + + /** + * @return the output of the filter which applies an irregular Low Pass spatial filter to the imag input (see function + */ + inline const std::valarray &getIrregularLPfilteredInputFrame() const { return _irregularLPfilteredFrame; } + + /** + * function which allows to retrieve the output frame which was updated after the "runProjection(...) function BasicRetinaFilter::runProgressiveFilter(...) + * @return the projection result + */ + inline const std::valarray &getSampledFrame() const { return _sampledFrame; } + + /** + * function which allows gives the tranformation table, its size is (getNBrows()*getNBcolumns()*2) + * @return the transformation matrix [outputPixIndex_i, inputPixIndex_i, outputPixIndex_i+1, inputPixIndex_i+1....] + */ + inline const std::valarray &getSamplingMap() const { return _transformTable; } + + inline double getOriginalRadiusLength(const double projectedRadiusLength) + { return _azero/(_alim-projectedRadiusLength*2.0/_minDimension); } + + // unsigned int getInputPixelIndex(const unsigned int ){ return _transformTable[index*2+1]}; + +private: + PROJECTIONTYPE _selectedProjection; + + // size of the image output + unsigned int _outputNBrows; + unsigned int _outputNBcolumns; + unsigned int _outputNBpixels; + unsigned int _outputDoubleNBpixels; + unsigned int _inputDoubleNBpixels; + + // is the object able to manage color flag + bool _colorModeCapable; + // sampling strenght factor + double _samplingStrenght; + // sampling reduction factor + double _reductionFactor; + + // log sampling parameters + double _azero; + double _alim; + double _minDimension; + + // template buffers + std::valarray_sampledFrame; + std::valarray&_tempBuffer; + std::valarray_transformTable; + + std::valarray &_irregularLPfilteredFrame; // just a reference for easier understanding + unsigned int _usefullpixelIndex; + + // init transformation tables + bool _computeLogProjection(); + bool _computeLogPolarProjection(); + + // specifies if init was done correctly + bool _initOK; + // private init projections functions called by "initProjection(...)" function + bool _initLogRetinaSampling(const double reductionFactor, const double samplingStrenght); + bool _initLogPolarCortexSampling(const double reductionFactor, const double samplingStrenght); + + ImageLogPolProjection(const ImageLogPolProjection&); + ImageLogPolProjection& operator=(const ImageLogPolProjection&); + +}; + +}// end of namespace bioinspired +}// end of namespace cv +#endif /*IMAGELOGPOLPROJECTION_H_*/ diff --git a/modules/bioinspired/src/magnoretinafilter.cpp b/modules/bioinspired/src/magnoretinafilter.cpp new file mode 100644 index 00000000000..81fdb1df54b --- /dev/null +++ b/modules/bioinspired/src/magnoretinafilter.cpp @@ -0,0 +1,212 @@ +/*#****************************************************************************** +** 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. +** +** +** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. +** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. +** +** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) +** +** Creation - enhancement process 2007-2011 +** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France +** +** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). +** Refer to the following research paper for more information: +** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 +** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: +** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. +** +** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : +** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: +** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#include "precomp.hpp" + +#include + +#include "magnoretinafilter.hpp" + +#include + +namespace cv +{ +namespace bioinspired +{ +// Constructor and Desctructor of the OPL retina filter +MagnoRetinaFilter::MagnoRetinaFilter(const unsigned int NBrows, const unsigned int NBcolumns) +:BasicRetinaFilter(NBrows, NBcolumns, 2), + _previousInput_ON(NBrows*NBcolumns), + _previousInput_OFF(NBrows*NBcolumns), + _amacrinCellsTempOutput_ON(NBrows*NBcolumns), + _amacrinCellsTempOutput_OFF(NBrows*NBcolumns), + _magnoXOutputON(NBrows*NBcolumns), + _magnoXOutputOFF(NBrows*NBcolumns), + _localProcessBufferON(NBrows*NBcolumns), + _localProcessBufferOFF(NBrows*NBcolumns) +{ + _magnoYOutput=&_filterOutput; + _magnoYsaturated=&_localBuffer; + + + clearAllBuffers(); + +#ifdef IPL_RETINA_ELEMENT_DEBUG + std::cout<<"MagnoRetinaFilter::Init IPL retina filter at specified frame size OK"<getNBpixels(); ++IDpixel) + { + + /* Compute ON and OFF amacrin cells high pass temporal filter */ + float magnoXonPixelResult = _temporalCoefficient*(*amacrinCellsTempOutput_ON_PTR+ *OPL_ON_PTR-*previousInput_ON_PTR); + *(amacrinCellsTempOutput_ON_PTR++)=((float)(magnoXonPixelResult>0))*magnoXonPixelResult; + + float magnoXoffPixelResult = _temporalCoefficient*(*amacrinCellsTempOutput_OFF_PTR+ *OPL_OFF_PTR-*previousInput_OFF_PTR); + *(amacrinCellsTempOutput_OFF_PTR++)=((float)(magnoXoffPixelResult>0))*magnoXoffPixelResult; + + /* prepare next loop */ + *(previousInput_ON_PTR++)=*(OPL_ON_PTR++); + *(previousInput_OFF_PTR++)=*(OPL_OFF_PTR++); + + } +#endif +} + +// launch filter that runs all the IPL filter +const std::valarray &MagnoRetinaFilter::runFilter(const std::valarray &OPL_ON, const std::valarray &OPL_OFF) +{ + // Compute the high pass temporal filter + _amacrineCellsComputing(get_data(OPL_ON), get_data(OPL_OFF)); + + // apply low pass filtering on ON and OFF ways after temporal high pass filtering + _spatiotemporalLPfilter(&_amacrinCellsTempOutput_ON[0], &_magnoXOutputON[0], 0); + _spatiotemporalLPfilter(&_amacrinCellsTempOutput_OFF[0], &_magnoXOutputOFF[0], 0); + + // local adaptation of the ganglion cells to the local contrast of the moving contours + _spatiotemporalLPfilter(&_magnoXOutputON[0], &_localProcessBufferON[0], 1); + _localLuminanceAdaptation(&_magnoXOutputON[0], &_localProcessBufferON[0]); + _spatiotemporalLPfilter(&_magnoXOutputOFF[0], &_localProcessBufferOFF[0], 1); + _localLuminanceAdaptation(&_magnoXOutputOFF[0], &_localProcessBufferOFF[0]); + + /* Compute MagnoY */ + register float *magnoYOutput= &(*_magnoYOutput)[0]; + register float *magnoXOutputON_PTR= &_magnoXOutputON[0]; + register float *magnoXOutputOFF_PTR= &_magnoXOutputOFF[0]; + for (register unsigned int IDpixel=0 ; IDpixel<_filterOutput.getNBpixels() ; ++IDpixel) + *(magnoYOutput++)=*(magnoXOutputON_PTR++)+*(magnoXOutputOFF_PTR++); + + return (*_magnoYOutput); +} +}// end of namespace bioinspired +}// end of namespace cv diff --git a/modules/bioinspired/src/magnoretinafilter.hpp b/modules/bioinspired/src/magnoretinafilter.hpp new file mode 100644 index 00000000000..723537de8c8 --- /dev/null +++ b/modules/bioinspired/src/magnoretinafilter.hpp @@ -0,0 +1,246 @@ +/*#****************************************************************************** +** 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. +** +** +** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. +** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. +** +** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) +** +** Creation - enhancement process 2007-2011 +** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France +** +** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). +** Refer to the following research paper for more information: +** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 +** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: +** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. +** +** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : +** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: +** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#ifndef MagnoRetinaFilter_H_ +#define MagnoRetinaFilter_H_ + +/** +* @class MagnoRetinaFilter +* @brief class which describes the magnocellular channel of the retina: +* -> performs a moving contours extraction with powerfull local data enhancement +* +* TYPICAL USE: +* +* // create object at a specified picture size +* MagnoRetinaFilter *movingContoursExtractor; +* movingContoursExtractor =new MagnoRetinaFilter(frameSizeRows, frameSizeColumns); +* +* // init gain, spatial and temporal parameters: +* movingContoursExtractor->setCoefficientsTable(0, 0.7, 5, 3); +* +* // during program execution, call the filter for contours extraction for an input picture called "FrameBuffer": +* movingContoursExtractor->runfilter(FrameBuffer); +* +* // get the output frame, check in the class description below for more outputs: +* const float *movingContours=movingContoursExtractor->getMagnoYsaturated(); +* +* // at the end of the program, destroy object: +* delete movingContoursExtractor; + +* @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC : www.listic.univ-savoie.fr, Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ +* Creation date 2007 +* Based on Alexandre BENOIT thesis: "Le système visuel humain au secours de la vision par ordinateur" +*/ + +#include "basicretinafilter.hpp" + +//#define _IPL_RETINA_ELEMENT_DEBUG + +namespace cv +{ +namespace bioinspired +{ + class MagnoRetinaFilter: public BasicRetinaFilter + { + public: + /** + * constructor parameters are only linked to image input size + * @param NBrows: number of rows of the input image + * @param NBcolumns: number of columns of the input image + */ + MagnoRetinaFilter(const unsigned int NBrows, const unsigned int NBcolumns); + + + /** + * destructor + */ + virtual ~MagnoRetinaFilter(); + + /** + * function that clears all buffers of the object + */ + void clearAllBuffers(); + + /** + * resize retina magno filter object (resize all allocated buffers) + * @param NBrows: the new height size + * @param NBcolumns: the new width size + */ + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + + /** + * set parameters values + * @param parasolCells_beta: the low pass filter gain used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), typical value is 0 + * @param parasolCells_tau: the low pass filter time constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is frame, typical value is 0 (immediate response) + * @param parasolCells_k: the low pass filter spatial constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is pixels, typical value is 5 + * @param amacrinCellsTemporalCutFrequency: the time constant of the first order high pass fiter of the magnocellular way (motion information channel), unit is frames, tipicall value is 5 + * @param localAdaptIntegration_tau: specifies the temporal constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + * @param localAdaptIntegration_k: specifies the spatial constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + */ + void setCoefficientsTable(const float parasolCells_beta, const float parasolCells_tau, const float parasolCells_k, const float amacrinCellsTemporalCutFrequency, const float localAdaptIntegration_tau, const float localAdaptIntegration_k); + + /** + * launch filter that runs all the IPL magno filter (model of the magnocellular channel of the Inner Plexiform Layer of the retina) + * @param OPL_ON: the output of the bipolar ON cells of the retina (available from the ParvoRetinaFilter class (getBipolarCellsON() function) + * @param OPL_OFF: the output of the bipolar OFF cells of the retina (available from the ParvoRetinaFilter class (getBipolarCellsOFF() function) + * @return the processed result without post-processing + */ + const std::valarray &runFilter(const std::valarray &OPL_ON, const std::valarray &OPL_OFF); + + /** + * @return the Magnocellular ON channel filtering output + */ + inline const std::valarray &getMagnoON() const { return _magnoXOutputON; } + + /** + * @return the Magnocellular OFF channel filtering output + */ + inline const std::valarray &getMagnoOFF() const { return _magnoXOutputOFF; } + + /** + * @return the Magnocellular Y (sum of the ON and OFF magno channels) filtering output + */ + inline const std::valarray &getMagnoYsaturated() const { return *_magnoYsaturated; } + + /** + * applies an image normalization which saturates the high output values by the use of an assymetric sigmoide + */ + inline void normalizeGrayOutputNearZeroCentreredSigmoide() + { _filterOutput.normalizeGrayOutputNearZeroCentreredSigmoide(&(*_magnoYOutput)[0], &(*_magnoYsaturated)[0]); } + + /** + * @return the horizontal cells' temporal constant + */ + inline float getTemporalConstant() { return _filteringCoeficientsTable[2]; } + + private: + + // related pointers to these buffers + std::valarray _previousInput_ON; + std::valarray _previousInput_OFF; + std::valarray _amacrinCellsTempOutput_ON; + std::valarray _amacrinCellsTempOutput_OFF; + std::valarray _magnoXOutputON; + std::valarray _magnoXOutputOFF; + std::valarray _localProcessBufferON; + std::valarray _localProcessBufferOFF; + // reference to parent buffers and allow better readability + TemplateBuffer *_magnoYOutput; + std::valarray *_magnoYsaturated; + + // varialbles + float _temporalCoefficient; + + // amacrine cells filter : high pass temporal filter + void _amacrineCellsComputing(const float *ONinput, const float *OFFinput); +#ifdef MAKE_PARALLEL + /****************************************************** + ** IF some parallelizing thread methods are available, then, main loops are parallelized using these functors + ** ==> main idea paralellise main filters loops, then, only the most used methods are parallelized... TODO : increase the number of parallelised methods as necessary + ** ==> functors names = Parallel_$$$ where $$$= the name of the serial method that is parallelised + ** ==> functors constructors can differ from the parameters used with their related serial functions + */ + class Parallel_amacrineCellsComputing: public cv::ParallelLoopBody + { + private: + const float *OPL_ON, *OPL_OFF; + float *previousInput_ON, *previousInput_OFF, *amacrinCellsTempOutput_ON, *amacrinCellsTempOutput_OFF; + float temporalCoefficient; + public: + Parallel_amacrineCellsComputing(const float *OPL_ON_PTR, const float *OPL_OFF_PTR, float *previousInput_ON_PTR, float *previousInput_OFF_PTR, float *amacrinCellsTempOutput_ON_PTR, float *amacrinCellsTempOutput_OFF_PTR, float temporalCoefficientVal) + :OPL_ON(OPL_ON_PTR), OPL_OFF(OPL_OFF_PTR), previousInput_ON(previousInput_ON_PTR), previousInput_OFF(previousInput_OFF_PTR), amacrinCellsTempOutput_ON(amacrinCellsTempOutput_ON_PTR), amacrinCellsTempOutput_OFF(amacrinCellsTempOutput_OFF_PTR), temporalCoefficient(temporalCoefficientVal) {} + + virtual void operator()( const Range& r ) const { + register const float *OPL_ON_PTR=OPL_ON+r.start; + register const float *OPL_OFF_PTR=OPL_OFF+r.start; + register float *previousInput_ON_PTR= previousInput_ON+r.start; + register float *previousInput_OFF_PTR= previousInput_OFF+r.start; + register float *amacrinCellsTempOutput_ON_PTR= amacrinCellsTempOutput_ON+r.start; + register float *amacrinCellsTempOutput_OFF_PTR= amacrinCellsTempOutput_OFF+r.start; + + for (int IDpixel=r.start ; IDpixel!=r.end; ++IDpixel) + { + + /* Compute ON and OFF amacrin cells high pass temporal filter */ + float magnoXonPixelResult = temporalCoefficient*(*amacrinCellsTempOutput_ON_PTR+ *OPL_ON_PTR-*previousInput_ON_PTR); + *(amacrinCellsTempOutput_ON_PTR++)=((float)(magnoXonPixelResult>0))*magnoXonPixelResult; + + float magnoXoffPixelResult = temporalCoefficient*(*amacrinCellsTempOutput_OFF_PTR+ *OPL_OFF_PTR-*previousInput_OFF_PTR); + *(amacrinCellsTempOutput_OFF_PTR++)=((float)(magnoXoffPixelResult>0))*magnoXoffPixelResult; + + /* prepare next loop */ + *(previousInput_ON_PTR++)=*(OPL_ON_PTR++); + *(previousInput_OFF_PTR++)=*(OPL_OFF_PTR++); + + } + } + + }; +#endif + }; + +}// end of namespace bioinspired +}// end of namespace cv + +#endif /*MagnoRetinaFilter_H_*/ diff --git a/modules/bioinspired/src/opencl/retina_kernel.cl b/modules/bioinspired/src/opencl/retina_kernel.cl new file mode 100644 index 00000000000..169be4d2707 --- /dev/null +++ b/modules/bioinspired/src/opencl/retina_kernel.cl @@ -0,0 +1,779 @@ +/*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) 2010-2013, Multicoreware, Inc., all rights reserved. +// Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// @Authors +// Peng Xiao, pengxiao@multicorewareinc.com +// +// 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 oclMaterials 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*/ + +//data (which is float) is aligend in 32 bytes +#define WIDTH_MULTIPLE (32 >> 2) + +///////////////////////////////////////////////////////// +//******************************************************* +// basicretinafilter +//////////////// _spatiotemporalLPfilter //////////////// +//_horizontalCausalFilter_addInput +kernel void horizontalCausalFilter_addInput( + global const float * input, + global float * output, + const int cols, + const int rows, + const int elements_per_row, + const int in_offset, + const int out_offset, + const float _tau, + const float _a +) +{ + int gid = get_global_id(0); + if(gid >= rows) + { + return; + } + + global const float * iptr = + input + mad24(gid, elements_per_row, in_offset / 4); + global float * optr = + output + mad24(gid, elements_per_row, out_offset / 4); + + float res; + float4 in_v4, out_v4, res_v4 = (float4)(0); + //vectorize to increase throughput + for(int i = 0; i < cols / 4; ++i, iptr += 4, optr += 4) + { + in_v4 = vload4(0, iptr); + out_v4 = vload4(0, optr); + + res_v4.x = in_v4.x + _tau * out_v4.x + _a * res_v4.w; + res_v4.y = in_v4.y + _tau * out_v4.y + _a * res_v4.x; + res_v4.z = in_v4.z + _tau * out_v4.z + _a * res_v4.y; + res_v4.w = in_v4.w + _tau * out_v4.w + _a * res_v4.z; + + vstore4(res_v4, 0, optr); + } + res = res_v4.w; + // there may be left some + for(int i = 0; i < cols % 4; ++i, ++iptr, ++optr) + { + res = *iptr + _tau * *optr + _a * res; + *optr = res; + } +} + +//_horizontalAnticausalFilter +kernel void horizontalAnticausalFilter( + global float * output, + const int cols, + const int rows, + const int elements_per_row, + const int out_offset, + const float _a +) +{ + int gid = get_global_id(0); + if(gid >= rows) + { + return; + } + + global float * optr = output + + mad24(gid + 1, elements_per_row, - 1 + out_offset / 4); + + float4 result_v4 = (float4)(0), out_v4; + float result = 0; + // we assume elements_per_row is multple of WIDTH_MULTIPLE + for(int i = 0; i < WIDTH_MULTIPLE; ++ i, -- optr) + { + if(i >= elements_per_row - cols) + { + result = *optr + _a * result; + } + *optr = result; + } + result_v4.x = result; + optr -= 3; + for(int i = WIDTH_MULTIPLE / 4; i < elements_per_row / 4; ++i, optr -= 4) + { + // shift left, `offset` is type `size_t` so it cannot be negative + out_v4 = vload4(0, optr); + + result_v4.w = out_v4.w + _a * result_v4.x; + result_v4.z = out_v4.z + _a * result_v4.w; + result_v4.y = out_v4.y + _a * result_v4.z; + result_v4.x = out_v4.x + _a * result_v4.y; + + vstore4(result_v4, 0, optr); + } +} + +//_verticalCausalFilter +kernel void verticalCausalFilter( + global float * output, + const int cols, + const int rows, + const int elements_per_row, + const int out_offset, + const float _a +) +{ + int gid = get_global_id(0); + if(gid >= cols) + { + return; + } + + global float * optr = output + gid + out_offset / 4; + float result = 0; + for(int i = 0; i < rows; ++i, optr += elements_per_row) + { + result = *optr + _a * result; + *optr = result; + } +} + +//_verticalCausalFilter +kernel void verticalAnticausalFilter_multGain( + global float * output, + const int cols, + const int rows, + const int elements_per_row, + const int out_offset, + const float _a, + const float _gain +) +{ + int gid = get_global_id(0); + if(gid >= cols) + { + return; + } + + global float * optr = output + (rows - 1) * elements_per_row + gid + out_offset / 4; + float result = 0; + for(int i = 0; i < rows; ++i, optr -= elements_per_row) + { + result = *optr + _a * result; + *optr = _gain * result; + } +} +// +// end of _spatiotemporalLPfilter +///////////////////////////////////////////////////////////////////// + +//////////////// horizontalAnticausalFilter_Irregular //////////////// +kernel void horizontalAnticausalFilter_Irregular( + global float * output, + global float * buffer, + const int cols, + const int rows, + const int elements_per_row, + const int out_offset, + const int buffer_offset +) +{ + int gid = get_global_id(0); + if(gid >= rows) + { + return; + } + + global float * optr = + output + mad24(rows - gid, elements_per_row, -1 + out_offset / 4); + global float * bptr = + buffer + mad24(rows - gid, elements_per_row, -1 + buffer_offset / 4); + + float4 buf_v4, out_v4, res_v4 = (float4)(0); + float result = 0; + // we assume elements_per_row is multple of WIDTH_MULTIPLE + for(int i = 0; i < WIDTH_MULTIPLE; ++ i, -- optr, -- bptr) + { + if(i >= elements_per_row - cols) + { + result = *optr + *bptr * result; + } + *optr = result; + } + res_v4.x = result; + optr -= 3; + bptr -= 3; + for(int i = WIDTH_MULTIPLE / 4; i < elements_per_row / 4; ++i, optr -= 4, bptr -= 4) + { + buf_v4 = vload4(0, bptr); + out_v4 = vload4(0, optr); + + res_v4.w = out_v4.w + buf_v4.w * res_v4.x; + res_v4.z = out_v4.z + buf_v4.z * res_v4.w; + res_v4.y = out_v4.y + buf_v4.y * res_v4.z; + res_v4.x = out_v4.x + buf_v4.x * res_v4.y; + + vstore4(res_v4, 0, optr); + } +} + +//////////////// verticalCausalFilter_Irregular //////////////// +kernel void verticalCausalFilter_Irregular( + global float * output, + global float * buffer, + const int cols, + const int rows, + const int elements_per_row, + const int out_offset, + const int buffer_offset +) +{ + int gid = get_global_id(0); + if(gid >= cols) + { + return; + } + + global float * optr = output + gid + out_offset / 4; + global float * bptr = buffer + gid + buffer_offset / 4; + float result = 0; + for(int i = 0; i < rows; ++i, optr += elements_per_row, bptr += elements_per_row) + { + result = *optr + *bptr * result; + *optr = result; + } +} + +//////////////// _adaptiveHorizontalCausalFilter_addInput //////////////// +kernel void adaptiveHorizontalCausalFilter_addInput( + global const float * input, + global const float * gradient, + global float * output, + const int cols, + const int rows, + const int elements_per_row, + const int in_offset, + const int grad_offset, + const int out_offset +) +{ + int gid = get_global_id(0); + if(gid >= rows) + { + return; + } + + global const float * iptr = + input + mad24(gid, elements_per_row, in_offset / 4); + global const float * gptr = + gradient + mad24(gid, elements_per_row, grad_offset / 4); + global float * optr = + output + mad24(gid, elements_per_row, out_offset / 4); + + float4 in_v4, grad_v4, out_v4, res_v4 = (float4)(0); + for(int i = 0; i < cols / 4; ++i, iptr += 4, gptr += 4, optr += 4) + { + in_v4 = vload4(0, iptr); + grad_v4 = vload4(0, gptr); + + res_v4.x = in_v4.x + grad_v4.x * res_v4.w; + res_v4.y = in_v4.y + grad_v4.y * res_v4.x; + res_v4.z = in_v4.z + grad_v4.z * res_v4.y; + res_v4.w = in_v4.w + grad_v4.w * res_v4.z; + + vstore4(res_v4, 0, optr); + } + for(int i = 0; i < cols % 4; ++i, ++iptr, ++gptr, ++optr) + { + res_v4.w = *iptr + *gptr * res_v4.w; + *optr = res_v4.w; + } +} + +//////////////// _adaptiveVerticalAnticausalFilter_multGain //////////////// +kernel void adaptiveVerticalAnticausalFilter_multGain( + global const float * gradient, + global float * output, + const int cols, + const int rows, + const int elements_per_row, + const int grad_offset, + const int out_offset, + const float gain +) +{ + int gid = get_global_id(0); + if(gid >= cols) + { + return; + } + + int start_idx = mad24(rows - 1, elements_per_row, gid); + + global const float * gptr = gradient + start_idx + grad_offset / 4; + global float * optr = output + start_idx + out_offset / 4; + + float result = 0; + for(int i = 0; i < rows; ++i, gptr -= elements_per_row, optr -= elements_per_row) + { + result = *optr + *gptr * result; + *optr = gain * result; + } +} + +//////////////// _localLuminanceAdaptation //////////////// +// FIXME: +// This kernel seems to have precision problem on GPU +kernel void localLuminanceAdaptation( + global const float * luma, + global const float * input, + global float * output, + const int cols, + const int rows, + const int elements_per_row, + const float _localLuminanceAddon, + const float _localLuminanceFactor, + const float _maxInputValue +) +{ + int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + int offset = mad24(gidy, elements_per_row, gidx); + + float X0 = luma[offset] * _localLuminanceFactor + _localLuminanceAddon; + float input_val = input[offset]; + // output of the following line may be different between GPU and CPU + output[offset] = (_maxInputValue + X0) * input_val / (input_val + X0 + 0.00000000001f); +} +// end of basicretinafilter +//******************************************************* +///////////////////////////////////////////////////////// + + + +///////////////////////////////////////////////////////// +//****************************************************** +// magno +// TODO: this kernel has too many buffer accesses, better to make it +// vector read/write for fetch efficiency +kernel void amacrineCellsComputing( + global const float * opl_on, + global const float * opl_off, + global float * prev_in_on, + global float * prev_in_off, + global float * out_on, + global float * out_off, + const int cols, + const int rows, + const int elements_per_row, + const float coeff +) +{ + int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + + int offset = mad24(gidy, elements_per_row, gidx); + opl_on += offset; + opl_off += offset; + prev_in_on += offset; + prev_in_off += offset; + out_on += offset; + out_off += offset; + + float magnoXonPixelResult = coeff * (*out_on + *opl_on - *prev_in_on); + *out_on = fmax(magnoXonPixelResult, 0); + float magnoXoffPixelResult = coeff * (*out_off + *opl_off - *prev_in_off); + *out_off = fmax(magnoXoffPixelResult, 0); + + *prev_in_on = *opl_on; + *prev_in_off = *opl_off; +} + +///////////////////////////////////////////////////////// +//****************************************************** +// parvo +// TODO: this kernel has too many buffer accesses, needs optimization +kernel void OPL_OnOffWaysComputing( + global float4 * photo_out, + global float4 * horiz_out, + global float4 * bipol_on, + global float4 * bipol_off, + global float4 * parvo_on, + global float4 * parvo_off, + const int cols, + const int rows, + const int elements_per_row +) +{ + int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx * 4 >= cols || gidy >= rows) + { + return; + } + // we assume elements_per_row must be multiples of 4 + int offset = mad24(gidy, elements_per_row >> 2, gidx); + photo_out += offset; + horiz_out += offset; + bipol_on += offset; + bipol_off += offset; + parvo_on += offset; + parvo_off += offset; + + float4 diff = *photo_out - *horiz_out; + float4 isPositive;// = convert_float4(diff > (float4)(0.0f, 0.0f, 0.0f, 0.0f)); + isPositive.x = diff.x > 0.0f; + isPositive.y = diff.y > 0.0f; + isPositive.z = diff.z > 0.0f; + isPositive.w = diff.w > 0.0f; + float4 res_on = isPositive * diff; + float4 res_off = (isPositive - (float4)(1.0f)) * diff; + + *bipol_on = res_on; + *parvo_on = res_on; + + *bipol_off = res_off; + *parvo_off = res_off; +} + +///////////////////////////////////////////////////////// +//****************************************************** +// retinacolor +inline int bayerSampleOffset(int step, int rows, int x, int y) +{ + return mad24(y, step, x) + + ((y % 2) + (x % 2)) * rows * step; +} + + +/////// colorMultiplexing ////// +kernel void runColorMultiplexingBayer( + global const float * input, + global float * output, + const int cols, + const int rows, + const int elements_per_row +) +{ + int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + + int offset = mad24(gidy, elements_per_row, gidx); + output[offset] = input[bayerSampleOffset(elements_per_row, rows, gidx, gidy)]; +} + +kernel void runColorDemultiplexingBayer( + global const float * input, + global float * output, + const int cols, + const int rows, + const int elements_per_row +) +{ + int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + + int offset = mad24(gidy, elements_per_row, gidx); + output[bayerSampleOffset(elements_per_row, rows, gidx, gidy)] = input[offset]; +} + +kernel void demultiplexAssign( + global const float * input, + global float * output, + const int cols, + const int rows, + const int elements_per_row +) +{ + int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + + int offset = bayerSampleOffset(elements_per_row, rows, gidx, gidy); + output[offset] = input[offset]; +} + + +//// normalizeGrayOutputCentredSigmoide +kernel void normalizeGrayOutputCentredSigmoide( + global const float * input, + global float * output, + const int cols, + const int rows, + const int elements_per_row, + const float meanval, + const float X0 +) + +{ + int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + int offset = mad24(gidy, elements_per_row, gidx); + + float input_val = input[offset]; + output[offset] = meanval + + (meanval + X0) * (input_val - meanval) / (fabs(input_val - meanval) + X0); +} + +//// normalize by photoreceptors density +kernel void normalizePhotoDensity( + global const float * chroma, + global const float * colorDensity, + global const float * multiplex, + global float * luma, + global float * demultiplex, + const int cols, + const int rows, + const int elements_per_row, + const float pG +) +{ + const int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + const int offset = mad24(gidy, elements_per_row, gidx); + int index = offset; + + float Cr = chroma[index] * colorDensity[index]; + index += elements_per_row * rows; + float Cg = chroma[index] * colorDensity[index]; + index += elements_per_row * rows; + float Cb = chroma[index] * colorDensity[index]; + + const float luma_res = (Cr + Cg + Cb) * pG; + luma[offset] = luma_res; + demultiplex[bayerSampleOffset(elements_per_row, rows, gidx, gidy)] = + multiplex[offset] - luma_res; +} + + + +//////// computeGradient /////// +// TODO: +// this function maybe accelerated by image2d_t or lds +kernel void computeGradient( + global const float * luma, + global float * gradient, + const int cols, + const int rows, + const int elements_per_row +) +{ + int gidx = get_global_id(0) + 2, gidy = get_global_id(1) + 2; + if(gidx >= cols - 2 || gidy >= rows - 2) + { + return; + } + int offset = mad24(gidy, elements_per_row, gidx); + luma += offset; + + // horizontal and vertical local gradients + const float v_grad = fabs(luma[elements_per_row] - luma[- elements_per_row]); + const float h_grad = fabs(luma[1] - luma[-1]); + + // neighborhood horizontal and vertical gradients + const float cur_val = luma[0]; + const float v_grad_p = fabs(cur_val - luma[- 2 * elements_per_row]); + const float h_grad_p = fabs(cur_val - luma[- 2]); + const float v_grad_n = fabs(cur_val - luma[2 * elements_per_row]); + const float h_grad_n = fabs(cur_val - luma[2]); + + const float horiz_grad = 0.5f * h_grad + 0.25f * (h_grad_p + h_grad_n); + const float verti_grad = 0.5f * v_grad + 0.25f * (v_grad_p + v_grad_n); + const bool is_vertical_greater = horiz_grad < verti_grad; + + gradient[offset + elements_per_row * rows] = is_vertical_greater ? 0.06f : 0.57f; + gradient[offset ] = is_vertical_greater ? 0.57f : 0.06f; +} + + +/////// substractResidual /////// +kernel void substractResidual( + global float * input, + const int cols, + const int rows, + const int elements_per_row, + const float pR, + const float pG, + const float pB +) +{ + const int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + int indices [3] = + { + mad24(gidy, elements_per_row, gidx), + mad24(gidy + rows, elements_per_row, gidx), + mad24(gidy + 2 * rows, elements_per_row, gidx) + }; + float vals[3] = {input[indices[0]], input[indices[1]], input[indices[2]]}; + float residu = pR * vals[0] + pG * vals[1] + pB * vals[2]; + + input[indices[0]] = vals[0] - residu; + input[indices[1]] = vals[1] - residu; + input[indices[2]] = vals[2] - residu; +} + +///// clipRGBOutput_0_maxInputValue ///// +kernel void clipRGBOutput_0_maxInputValue( + global float * input, + const int cols, + const int rows, + const int elements_per_row, + const float maxVal +) +{ + const int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + const int offset = mad24(gidy, elements_per_row, gidx); + float val = input[offset]; + val = clamp(val, 0.0f, maxVal); + input[offset] = val; +} + +//// normalizeGrayOutputNearZeroCentreredSigmoide //// +kernel void normalizeGrayOutputNearZeroCentreredSigmoide( + global float * input, + global float * output, + const int cols, + const int rows, + const int elements_per_row, + const float maxVal, + const float X0cube +) +{ + const int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + const int offset = mad24(gidy, elements_per_row, gidx); + float currentCubeLuminance = input[offset]; + currentCubeLuminance = currentCubeLuminance * currentCubeLuminance * currentCubeLuminance; + output[offset] = currentCubeLuminance * X0cube / (X0cube + currentCubeLuminance); +} + +//// centerReductImageLuminance //// +kernel void centerReductImageLuminance( + global float * input, + const int cols, + const int rows, + const int elements_per_row, + const float mean, + const float std_dev +) +{ + const int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + const int offset = mad24(gidy, elements_per_row, gidx); + + float val = input[offset]; + input[offset] = (val - mean) / std_dev; +} + +//// inverseValue //// +kernel void inverseValue( + global float * input, + const int cols, + const int rows, + const int elements_per_row +) +{ + const int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + const int offset = mad24(gidy, elements_per_row, gidx); + input[offset] = 1.f / input[offset]; +} + +#define CV_PI 3.1415926535897932384626433832795 + +//// _processRetinaParvoMagnoMapping //// +kernel void processRetinaParvoMagnoMapping( + global float * parvo, + global float * magno, + global float * output, + const int cols, + const int rows, + const int halfCols, + const int halfRows, + const int elements_per_row, + const float minDistance +) +{ + const int gidx = get_global_id(0), gidy = get_global_id(1); + if(gidx >= cols || gidy >= rows) + { + return; + } + const int offset = mad24(gidy, elements_per_row, gidx); + + float distanceToCenter = + sqrt(((float)(gidy - halfRows) * (gidy - halfRows) + (gidx - halfCols) * (gidx - halfCols))); + + float a = distanceToCenter < minDistance ? + (0.5f + 0.5f * (float)cos(CV_PI * distanceToCenter / minDistance)) : 0; + float b = 1.f - a; + + output[offset] = parvo[offset] * a + magno[offset] * b; +} diff --git a/modules/bioinspired/src/parvoretinafilter.cpp b/modules/bioinspired/src/parvoretinafilter.cpp new file mode 100644 index 00000000000..a276d97a4cd --- /dev/null +++ b/modules/bioinspired/src/parvoretinafilter.cpp @@ -0,0 +1,233 @@ +/*#****************************************************************************** +** 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. +** +** +** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. +** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. +** +** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) +** +** Creation - enhancement process 2007-2011 +** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France +** +** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). +** Refer to the following research paper for more information: +** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 +** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: +** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. +** +** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : +** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: +** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#include "precomp.hpp" + +#include "parvoretinafilter.hpp" + +// @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC : www.listic.univ-savoie.fr, Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ + +#include +#include + +namespace cv +{ +namespace bioinspired +{ +////////////////////////////////////////////////////////// +// OPL RETINA FILTER +////////////////////////////////////////////////////////// + +// Constructor and Desctructor of the OPL retina filter + +ParvoRetinaFilter::ParvoRetinaFilter(const unsigned int NBrows, const unsigned int NBcolumns) +:BasicRetinaFilter(NBrows, NBcolumns, 3), + _photoreceptorsOutput(NBrows*NBcolumns), + _horizontalCellsOutput(NBrows*NBcolumns), + _parvocellularOutputON(NBrows*NBcolumns), + _parvocellularOutputOFF(NBrows*NBcolumns), + _bipolarCellsOutputON(NBrows*NBcolumns), + _bipolarCellsOutputOFF(NBrows*NBcolumns), + _localAdaptationOFF(NBrows*NBcolumns) +{ + // link to the required local parent adaptation buffers + _localAdaptationON=&_localBuffer; + _parvocellularOutputONminusOFF=&_filterOutput; + // (*_localAdaptationON)=&_localBuffer; + // (*_parvocellularOutputONminusOFF)=&(BasicRetinaFilter::TemplateBuffer); + + // init: set all the values to 0 + clearAllBuffers(); + + +#ifdef OPL_RETINA_ELEMENT_DEBUG + std::cout<<"ParvoRetinaFilter::Init OPL retina filter at specified frame size OK\n"< &ParvoRetinaFilter::runFilter(const std::valarray &inputFrame, const bool useParvoOutput) +{ + _spatiotemporalLPfilter(get_data(inputFrame), &_photoreceptorsOutput[0]); + _spatiotemporalLPfilter(&_photoreceptorsOutput[0], &_horizontalCellsOutput[0], 1); + _OPL_OnOffWaysComputing(); + + if (useParvoOutput) + { + // local adaptation processes on ON and OFF ways + _spatiotemporalLPfilter(&_bipolarCellsOutputON[0], &(*_localAdaptationON)[0], 2); + _localLuminanceAdaptation(&_parvocellularOutputON[0], &(*_localAdaptationON)[0]); + + _spatiotemporalLPfilter(&_bipolarCellsOutputOFF[0], &_localAdaptationOFF[0], 2); + _localLuminanceAdaptation(&_parvocellularOutputOFF[0], &_localAdaptationOFF[0]); + + //// Final loop that computes the main output of this filter + // + //// loop that makes the difference between photoreceptor cells output and horizontal cells + //// positive part goes on the ON way, negative pat goes on the OFF way + register float *parvocellularOutputONminusOFF_PTR=&(*_parvocellularOutputONminusOFF)[0]; + register float *parvocellularOutputON_PTR=&_parvocellularOutputON[0]; + register float *parvocellularOutputOFF_PTR=&_parvocellularOutputOFF[0]; + + for (register unsigned int IDpixel=0 ; IDpixel<_filterOutput.getNBpixels() ; ++IDpixel) + *(parvocellularOutputONminusOFF_PTR++)= (*(parvocellularOutputON_PTR++)-*(parvocellularOutputOFF_PTR++)); + } + return (*_parvocellularOutputONminusOFF); +} + +void ParvoRetinaFilter::_OPL_OnOffWaysComputing() // WARNING : this method requires many buffer accesses, parallelizing can increase bandwith & core efficacy +{ + // loop that makes the difference between photoreceptor cells output and horizontal cells + // positive part goes on the ON way, negative pat goes on the OFF way + +#ifdef MAKE_PARALLEL + cv::parallel_for_(cv::Range(0,_filterOutput.getNBpixels()), Parallel_OPL_OnOffWaysComputing(&_photoreceptorsOutput[0], &_horizontalCellsOutput[0], &_bipolarCellsOutputON[0], &_bipolarCellsOutputOFF[0], &_parvocellularOutputON[0], &_parvocellularOutputOFF[0])); +#else + float *photoreceptorsOutput_PTR= &_photoreceptorsOutput[0]; + float *horizontalCellsOutput_PTR= &_horizontalCellsOutput[0]; + float *bipolarCellsON_PTR = &_bipolarCellsOutputON[0]; + float *bipolarCellsOFF_PTR = &_bipolarCellsOutputOFF[0]; + float *parvocellularOutputON_PTR= &_parvocellularOutputON[0]; + float *parvocellularOutputOFF_PTR= &_parvocellularOutputOFF[0]; + // compute bipolar cells response equal to photoreceptors minus horizontal cells response + // and copy the result on parvo cellular outputs... keeping time before their local contrast adaptation for final result + for (register unsigned int IDpixel=0 ; IDpixel<_filterOutput.getNBpixels() ; ++IDpixel) + { + float pixelDifference = *(photoreceptorsOutput_PTR++) -*(horizontalCellsOutput_PTR++); + // test condition to allow write pixelDifference in ON or OFF buffer and 0 in the over + float isPositive=(float) (pixelDifference>0.0f); + + // ON and OFF channels writing step + *(parvocellularOutputON_PTR++)=*(bipolarCellsON_PTR++) = isPositive*pixelDifference; + *(parvocellularOutputOFF_PTR++)=*(bipolarCellsOFF_PTR++)= (isPositive-1.0f)*pixelDifference; + } +#endif +} +}// end of namespace bioinspired +}// end of namespace cv diff --git a/modules/bioinspired/src/parvoretinafilter.hpp b/modules/bioinspired/src/parvoretinafilter.hpp new file mode 100644 index 00000000000..0fc18479290 --- /dev/null +++ b/modules/bioinspired/src/parvoretinafilter.hpp @@ -0,0 +1,264 @@ +/*#****************************************************************************** +** 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. +** +** +** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. +** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. +** +** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) +** +** Creation - enhancement process 2007-2011 +** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France +** +** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). +** Refer to the following research paper for more information: +** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 +** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: +** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. +** +** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : +** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: +** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#ifndef ParvoRetinaFilter_H_ +#define ParvoRetinaFilter_H_ + +/** +* @class ParvoRetinaFilter +* @brief class which describes the OPL retina model and the Inner Plexiform Layer parvocellular channel of the retina: +* -> performs a contours extraction with powerfull local data enhancement as at the retina level +* -> spectrum whitening occurs at the OPL (Outer Plexiform Layer) of the retina: corrects the 1/f spectrum tendancy of natural images +* ---> enhances details with mid spatial frequencies, attenuates low spatial frequencies (luminance), attenuates high temporal frequencies and high spatial frequencies, etc. +* +* TYPICAL USE: +* +* // create object at a specified picture size +* ParvoRetinaFilter *contoursExtractor; +* contoursExtractor =new ParvoRetinaFilter(frameSizeRows, frameSizeColumns); +* +* // init gain, spatial and temporal parameters: +* contoursExtractor->setCoefficientsTable(0, 0.7, 1, 0, 7, 1); +* +* // during program execution, call the filter for contours extraction for an input picture called "FrameBuffer": +* contoursExtractor->runfilter(FrameBuffer); +* +* // get the output frame, check in the class description below for more outputs: +* const float *contours=contoursExtractor->getParvoONminusOFF(); +* +* // at the end of the program, destroy object: +* delete contoursExtractor; + +* @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC : www.listic.univ-savoie.fr, Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ +* Creation date 2007 +* Based on Alexandre BENOIT thesis: "Le système visuel humain au secours de la vision par ordinateur" +* +*/ + +#include "basicretinafilter.hpp" + + +//#define _OPL_RETINA_ELEMENT_DEBUG + +namespace cv +{ +namespace bioinspired +{ +//retina classes that derivate from the Basic Retrina class +class ParvoRetinaFilter: public BasicRetinaFilter +{ + +public: + /** + * constructor parameters are only linked to image input size + * @param NBrows: number of rows of the input image + * @param NBcolumns: number of columns of the input image + */ + ParvoRetinaFilter(const unsigned int NBrows=480, const unsigned int NBcolumns=640); + + /** + * standard desctructor + */ + virtual ~ParvoRetinaFilter(); + + /** + * resize method, keeps initial parameters, all buffers are flushed + * @param NBrows: number of rows of the input image + * @param NBcolumns: number of columns of the input image + */ + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + + /** + * function that clears all buffers of the object + */ + void clearAllBuffers(); + + /** + * setup the OPL and IPL parvo channels + * @param beta1: gain of the horizontal cells network, if 0, then the mean value of the output is zero, if the parameter is near 1, the amplitude is boosted but it should only be used for values rescaling... if needed + * @param tau1: the time constant of the first order low pass filter of the photoreceptors, use it to cut high temporal frequencies (noise or fast motion), unit is frames, typical value is 1 frame + * @param k1: the spatial constant of the first order low pass filter of the photoreceptors, use it to cut high spatial frequencies (noise or thick contours), unit is pixels, typical value is 1 pixel + * @param beta2: gain of the horizontal cells network, if 0, then the mean value of the output is zero, if the parameter is near 1, then, the luminance is not filtered and is still reachable at the output, typicall value is 0 + * @param tau2: the time constant of the first order low pass filter of the horizontal cells, use it to cut low temporal frequencies (local luminance variations), unit is frames, typical value is 1 frame, as the photoreceptors + * @param k2: the spatial constant of the first order low pass filter of the horizontal cells, use it to cut low spatial frequencies (local luminance), unit is pixels, typical value is 5 pixel, this value is also used for local contrast computing when computing the local contrast adaptation at the ganglion cells level (Inner Plexiform Layer parvocellular channel model) + */ + void setOPLandParvoFiltersParameters(const float beta1, const float tau1, const float k1, const float beta2, const float tau2, const float k2); + + /** + * setup more precisely the low pass filter used for the ganglion cells low pass filtering (used for local luminance adaptation) + * @param tau: time constant of the filter (unit is frame for video processing) + * @param k: spatial constant of the filter (unit is pixels) + */ + void setGanglionCellsLocalAdaptationLPfilterParameters(const float tau, const float k) + { BasicRetinaFilter::setLPfilterParameters(0, tau, k, 2); } // change the parameters of the filter + + + /** + * launch filter that runs the OPL spatiotemporal filtering and optionally finalizes IPL Pagno filter (model of the Parvocellular channel of the Inner Plexiform Layer of the retina) + * @param inputFrame: the input image to be processed, this can be the direct gray level input frame, but a better efficacy is expected if the input is preliminary processed by the photoreceptors local adaptation possible to acheive with the help of a BasicRetinaFilter object + * @param useParvoOutput: set true if the final IPL filtering step has to be computed (local contrast enhancement) + * @return the processed Parvocellular channel output (updated only if useParvoOutput is true) + * @details: in any case, after this function call, photoreceptors and horizontal cells output are updated, use getPhotoreceptorsLPfilteringOutput() and getHorizontalCellsOutput() to get them + * also, bipolar cells output are accessible (difference between photoreceptors and horizontal cells, ON output has positive values, OFF ouput has negative values), use the following access methods: getBipolarCellsON() and getBipolarCellsOFF()if useParvoOutput is true, + * if useParvoOutput is true, the complete Parvocellular channel is computed, more outputs are updated and can be accessed threw: getParvoON(), getParvoOFF() and their difference with getOutput() + */ + const std::valarray &runFilter(const std::valarray &inputFrame, const bool useParvoOutput=true); // output return is _parvocellularOutputONminusOFF + + /** + * @return the output of the photoreceptors filtering step (high cut frequency spatio-temporal low pass filter) + */ + inline const std::valarray &getPhotoreceptorsLPfilteringOutput() const { return _photoreceptorsOutput; } + + /** + * @return the output of the photoreceptors filtering step (low cut frequency spatio-temporal low pass filter) + */ + inline const std::valarray &getHorizontalCellsOutput() const { return _horizontalCellsOutput; } + + /** + * @return the output Parvocellular ON channel of the retina model + */ + inline const std::valarray &getParvoON() const { return _parvocellularOutputON; } + + /** + * @return the output Parvocellular OFF channel of the retina model + */ + inline const std::valarray &getParvoOFF() const { return _parvocellularOutputOFF; } + + /** + * @return the output of the Bipolar cells of the ON channel of the retina model same as function getParvoON() but without luminance local adaptation + */ + inline const std::valarray &getBipolarCellsON() const { return _bipolarCellsOutputON; } + + /** + * @return the output of the Bipolar cells of the OFF channel of the retina model same as function getParvoON() but without luminance local adaptation + */ + inline const std::valarray &getBipolarCellsOFF() const { return _bipolarCellsOutputOFF; } + + /** + * @return the photoreceptors's temporal constant + */ + inline float getPhotoreceptorsTemporalConstant() { return _filteringCoeficientsTable[2]; } + + /** + * @return the horizontal cells' temporal constant + */ + inline float getHcellsTemporalConstant(){return _filteringCoeficientsTable[5]; } + +private: + // template buffers + std::valarray _photoreceptorsOutput; + std::valarray _horizontalCellsOutput; + std::valarray _parvocellularOutputON; + std::valarray _parvocellularOutputOFF; + std::valarray _bipolarCellsOutputON; + std::valarray _bipolarCellsOutputOFF; + std::valarray _localAdaptationOFF; + std::valarray *_localAdaptationON; + TemplateBuffer *_parvocellularOutputONminusOFF; + // private functions + void _OPL_OnOffWaysComputing(); + +#ifdef MAKE_PARALLEL +/****************************************************** +** IF some parallelizing thread methods are available, then, main loops are parallelized using these functors +** ==> main idea paralellise main filters loops, then, only the most used methods are parallelized... TODO : increase the number of parallelised methods as necessary +** ==> functors names = Parallel_$$$ where $$$= the name of the serial method that is parallelised +** ==> functors constructors can differ from the parameters used with their related serial functions +*/ + class Parallel_OPL_OnOffWaysComputing: public cv::ParallelLoopBody + { + private: + float *photoreceptorsOutput, *horizontalCellsOutput, *bipolarCellsON, *bipolarCellsOFF, *parvocellularOutputON, *parvocellularOutputOFF; + public: + Parallel_OPL_OnOffWaysComputing(float *photoreceptorsOutput_PTR, float *horizontalCellsOutput_PTR, float *bipolarCellsON_PTR, float *bipolarCellsOFF_PTR, float *parvocellularOutputON_PTR, float *parvocellularOutputOFF_PTR) + :photoreceptorsOutput(photoreceptorsOutput_PTR), horizontalCellsOutput(horizontalCellsOutput_PTR), bipolarCellsON(bipolarCellsON_PTR), bipolarCellsOFF(bipolarCellsOFF_PTR), parvocellularOutputON(parvocellularOutputON_PTR), parvocellularOutputOFF(parvocellularOutputOFF_PTR) {} + + virtual void operator()( const Range& r ) const { + // compute bipolar cells response equal to photoreceptors minus horizontal cells response + // and copy the result on parvo cellular outputs... keeping time before their local contrast adaptation for final result + float *photoreceptorsOutput_PTR= photoreceptorsOutput+r.start; + float *horizontalCellsOutput_PTR= horizontalCellsOutput+r.start; + float *bipolarCellsON_PTR = bipolarCellsON+r.start; + float *bipolarCellsOFF_PTR = bipolarCellsOFF+r.start; + float *parvocellularOutputON_PTR= parvocellularOutputON+r.start; + float *parvocellularOutputOFF_PTR= parvocellularOutputOFF+r.start; + + for (register int IDpixel=r.start ; IDpixel!=r.end ; ++IDpixel) + { + float pixelDifference = *(photoreceptorsOutput_PTR++) -*(horizontalCellsOutput_PTR++); + // test condition to allow write pixelDifference in ON or OFF buffer and 0 in the over + float isPositive=(float) (pixelDifference>0.0f); + + // ON and OFF channels writing step + *(parvocellularOutputON_PTR++)=*(bipolarCellsON_PTR++) = isPositive*pixelDifference; + *(parvocellularOutputOFF_PTR++)=*(bipolarCellsOFF_PTR++)= (isPositive-1.0f)*pixelDifference; + } + } + }; +#endif + +}; +}// end of namespace bioinspired +}// end of namespace cv +#endif diff --git a/modules/bioinspired/src/precomp.hpp b/modules/bioinspired/src/precomp.hpp new file mode 100644 index 00000000000..61aeb5409c4 --- /dev/null +++ b/modules/bioinspired/src/precomp.hpp @@ -0,0 +1,68 @@ +/*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. +// 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 __OPENCV_PRECOMP_H__ +#define __OPENCV_PRECOMP_H__ + +#include "opencv2/opencv_modules.hpp" +#include "opencv2/bioinspired.hpp" +#include "opencv2/core/utility.hpp" +#include "opencv2/core/private.hpp" +#include "opencv2/core/ocl.hpp" + +#include + +#ifdef HAVE_OPENCV_OCL + #include "opencv2/ocl/private/util.hpp" +#endif + +namespace cv +{ + +// special function to get pointer to constant valarray elements, since +// simple &arr[0] does not compile on VS2005/VS2008. +template inline const T* get_data(const std::valarray& arr) +{ return &((std::valarray&)arr)[0]; } + +} + +#endif diff --git a/modules/bioinspired/src/retina.cpp b/modules/bioinspired/src/retina.cpp new file mode 100644 index 00000000000..e4322402871 --- /dev/null +++ b/modules/bioinspired/src/retina.cpp @@ -0,0 +1,745 @@ +/*#****************************************************************************** + ** 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. + ** + ** + ** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. + ** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. + ** + ** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) + ** + ** Creation - enhancement process 2007-2011 + ** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France + ** + ** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). + ** Refer to the following research paper for more information: + ** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 + ** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: + ** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. + ** + ** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : + ** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: + ** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 + ** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. + ** ====> more informations in the above cited Jeanny Heraults's book. + ** + ** License Agreement + ** For Open Source Computer Vision Library + ** + ** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. + ** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. + ** + ** For Human Visual System tools (bioinspired) + ** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: + ** + ** * Redistributions of source code must retain the above copyright notice, + ** this list of conditions and the following disclaimer. + ** + ** * Redistributions 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. + *******************************************************************************/ + +/* + * Retina.cpp + * + * Created on: Jul 19, 2011 + * Author: Alexandre Benoit + */ +#include "precomp.hpp" +#include "retinafilter.hpp" +#include +#include +#include + +namespace cv +{ +namespace bioinspired +{ + +class RetinaImpl : public Retina +{ +public: + /** + * Main constructor with most commun use setup : create an instance of color ready retina model + * @param inputSize : the input frame size + */ + RetinaImpl(Size inputSize); + + /** + * Complete Retina filter constructor which allows all basic structural parameters definition + * @param inputSize : the input frame size + * @param colorMode : the chosen processing mode : with or without color processing + * @param colorSamplingMethod: specifies which kind of color sampling will be used + * @param useRetinaLogSampling: activate retina log sampling, if true, the 2 following parameters can be used + * @param reductionFactor: only usefull if param useRetinaLogSampling=true, specifies the reduction factor of the output frame (as the center (fovea) is high resolution and corners can be underscaled, then a reduction of the output is allowed without precision leak + * @param samplingStrenght: only usefull if param useRetinaLogSampling=true, specifies the strenght of the log scale that is applied + */ + RetinaImpl(Size inputSize, const bool colorMode, int colorSamplingMethod=RETINA_COLOR_BAYER, const bool useRetinaLogSampling=false, const double reductionFactor=1.0, const double samplingStrenght=10.0); + + virtual ~RetinaImpl(); + /** + * retreive retina input buffer size + */ + Size getInputSize(); + + /** + * retreive retina output buffer size + */ + Size getOutputSize(); + + /** + * try to open an XML retina parameters file to adjust current retina instance setup + * => if the xml file does not exist, then default setup is applied + * => warning, Exceptions are thrown if read XML file is not valid + * @param retinaParameterFile : the parameters filename + * @param applyDefaultSetupOnFailure : set to true if an error must be thrown on error + */ + void setup(String retinaParameterFile="", const bool applyDefaultSetupOnFailure=true); + + + /** + * try to open an XML retina parameters file to adjust current retina instance setup + * => if the xml file does not exist, then default setup is applied + * => warning, Exceptions are thrown if read XML file is not valid + * @param fs : the open Filestorage which contains retina parameters + * @param applyDefaultSetupOnFailure : set to true if an error must be thrown on error + */ + void setup(cv::FileStorage &fs, const bool applyDefaultSetupOnFailure=true); + + /** + * try to open an XML retina parameters file to adjust current retina instance setup + * => if the xml file does not exist, then default setup is applied + * => warning, Exceptions are thrown if read XML file is not valid + * @param newParameters : a parameters structures updated with the new target configuration + * @param applyDefaultSetupOnFailure : set to true if an error must be thrown on error + */ + void setup(Retina::RetinaParameters newParameters); + + /** + * @return the current parameters setup + */ + struct Retina::RetinaParameters getParameters(); + + /** + * parameters setup display method + * @return a string which contains formatted parameters information + */ + const String printSetup(); + + /** + * write xml/yml formated parameters information + * @rparam fs : the filename of the xml file that will be open and writen with formatted parameters information + */ + virtual void write( String fs ) const; + + + /** + * write xml/yml formated parameters information + * @param fs : a cv::Filestorage object ready to be filled + */ + virtual void write( FileStorage& fs ) const; + + /** + * setup the OPL and IPL parvo channels (see biologocal model) + * OPL is referred as Outer Plexiform Layer of the retina, it allows the spatio-temporal filtering which withens the spectrum and reduces spatio-temporal noise while attenuating global luminance (low frequency energy) + * IPL parvo is the OPL next processing stage, it refers to Inner Plexiform layer of the retina, it allows high contours sensitivity in foveal vision. + * for more informations, please have a look at the paper Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 + * @param colorMode : specifies if (true) color is processed of not (false) to then processing gray level image + * @param normaliseOutput : specifies if (true) output is rescaled between 0 and 255 of not (false) + * @param photoreceptorsLocalAdaptationSensitivity: the photoreceptors sensitivity renage is 0-1 (more log compression effect when value increases) + * @param photoreceptorsTemporalConstant: the time constant of the first order low pass filter of the photoreceptors, use it to cut high temporal frequencies (noise or fast motion), unit is frames, typical value is 1 frame + * @param photoreceptorsSpatialConstant: the spatial constant of the first order low pass filter of the photoreceptors, use it to cut high spatial frequencies (noise or thick contours), unit is pixels, typical value is 1 pixel + * @param horizontalCellsGain: gain of the horizontal cells network, if 0, then the mean value of the output is zero, if the parameter is near 1, then, the luminance is not filtered and is still reachable at the output, typicall value is 0 + * @param HcellsTemporalConstant: the time constant of the first order low pass filter of the horizontal cells, use it to cut low temporal frequencies (local luminance variations), unit is frames, typical value is 1 frame, as the photoreceptors + * @param HcellsSpatialConstant: the spatial constant of the first order low pass filter of the horizontal cells, use it to cut low spatial frequencies (local luminance), unit is pixels, typical value is 5 pixel, this value is also used for local contrast computing when computing the local contrast adaptation at the ganglion cells level (Inner Plexiform Layer parvocellular channel model) + * @param ganglionCellsSensitivity: the compression strengh of the ganglion cells local adaptation output, set a value between 160 and 250 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 230 + */ + void setupOPLandIPLParvoChannel(const bool colorMode=true, const bool normaliseOutput = true, const float photoreceptorsLocalAdaptationSensitivity=0.7, const float photoreceptorsTemporalConstant=0.5, const float photoreceptorsSpatialConstant=0.53, const float horizontalCellsGain=0, const float HcellsTemporalConstant=1, const float HcellsSpatialConstant=7, const float ganglionCellsSensitivity=0.7); + + /** + * set parameters values for the Inner Plexiform Layer (IPL) magnocellular channel + * this channel processes signals outpint from OPL processing stage in peripheral vision, it allows motion information enhancement. It is decorrelated from the details channel. See reference paper for more details. + * @param normaliseOutput : specifies if (true) output is rescaled between 0 and 255 of not (false) + * @param parasolCells_beta: the low pass filter gain used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), typical value is 0 + * @param parasolCells_tau: the low pass filter time constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is frame, typical value is 0 (immediate response) + * @param parasolCells_k: the low pass filter spatial constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is pixels, typical value is 5 + * @param amacrinCellsTemporalCutFrequency: the time constant of the first order high pass fiter of the magnocellular way (motion information channel), unit is frames, tipicall value is 5 + * @param V0CompressionParameter: the compression strengh of the ganglion cells local adaptation output, set a value between 160 and 250 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 200 + * @param localAdaptintegration_tau: specifies the temporal constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + * @param localAdaptintegration_k: specifies the spatial constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + */ + void setupIPLMagnoChannel(const bool normaliseOutput = true, const float parasolCells_beta=0, const float parasolCells_tau=0, const float parasolCells_k=7, const float amacrinCellsTemporalCutFrequency=1.2, const float V0CompressionParameter=0.95, const float localAdaptintegration_tau=0, const float localAdaptintegration_k=7); + + /** + * method which allows retina to be applied on an input image, after run, encapsulated retina module is ready to deliver its outputs using dedicated acccessors, see getParvo and getMagno methods + * @param inputImage : the input cv::Mat image to be processed, can be gray level or BGR coded in any format (from 8bit to 16bits) + */ + void run(InputArray inputImage); + + /** + * method that applies a luminance correction (initially High Dynamic Range (HDR) tone mapping) using only the 2 local adaptation stages of the retina parvo channel : photoreceptors level and ganlion cells level. Spatio temporal filtering is applied but limited to temporal smoothing and eventually high frequencies attenuation. This is a lighter method than the one available using the regular run method. It is then faster but it does not include complete temporal filtering nor retina spectral whitening. This is an adptation of the original still image HDR tone mapping algorithm of David Alleyson, Sabine Susstruck and Laurence Meylan's work, please cite: + * -> Meylan L., Alleysson D., and Susstrunk S., A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images, Journal of Optical Society of America, A, Vol. 24, N 9, September, 1st, 2007, pp. 2807-2816 + @param inputImage the input image to process RGB or gray levels + @param outputToneMappedImage the output tone mapped image + */ + void applyFastToneMapping(InputArray inputImage, OutputArray outputToneMappedImage); + + /** + * accessor of the details channel of the retina (models foveal vision) + * @param retinaOutput_parvo : the output buffer (reallocated if necessary), this output is rescaled for standard 8bits image processing use in OpenCV + */ + void getParvo(OutputArray retinaOutput_parvo); + + /** + * accessor of the details channel of the retina (models foveal vision) + * @param retinaOutput_parvo : a cv::Mat header filled with the internal parvo buffer of the retina module. This output is the original retina filter model output, without any quantification or rescaling + */ + void getParvoRAW(OutputArray retinaOutput_parvo); + + /** + * accessor of the motion channel of the retina (models peripheral vision) + * @param retinaOutput_magno : the output buffer (reallocated if necessary), this output is rescaled for standard 8bits image processing use in OpenCV + */ + void getMagno(OutputArray retinaOutput_magno); + + /** + * accessor of the motion channel of the retina (models peripheral vision) + * @param retinaOutput_magno : a cv::Mat header filled with the internal retina magno buffer of the retina module. This output is the original retina filter model output, without any quantification or rescaling + */ + void getMagnoRAW(OutputArray retinaOutput_magno); + + // original API level data accessors : get buffers addresses from a Mat header, similar to getParvoRAW and getMagnoRAW... + const Mat getMagnoRAW() const; + const Mat getParvoRAW() const; + + /** + * activate color saturation as the final step of the color demultiplexing process + * -> this saturation is a sigmoide function applied to each channel of the demultiplexed image. + * @param saturateColors: boolean that activates color saturation (if true) or desactivate (if false) + * @param colorSaturationValue: the saturation factor + */ + void setColorSaturation(const bool saturateColors=true, const float colorSaturationValue=4.0); + + /** + * clear all retina buffers (equivalent to opening the eyes after a long period of eye close ;o) + */ + void clearBuffers(); + + /** + * Activate/desactivate the Magnocellular pathway processing (motion information extraction), by default, it is activated + * @param activate: true if Magnocellular output should be activated, false if not + */ + void activateMovingContoursProcessing(const bool activate); + + /** + * Activate/desactivate the Parvocellular pathway processing (contours information extraction), by default, it is activated + * @param activate: true if Parvocellular (contours information extraction) output should be activated, false if not + */ + void activateContoursProcessing(const bool activate); +private: + + // Parameteres setup members + RetinaParameters _retinaParameters; // structure of parameters + + // Retina model related modules + std::valarray _inputBuffer; //!< buffer used to convert input cv::Mat to internal retina buffers format (valarrays) + + // pointer to retina model + RetinaFilter* _retinaFilter; //!< the pointer to the retina module, allocated with instance construction + + //! private method called by constructors, gathers their parameters and use them in a unified way + void _init(const Size inputSize, const bool colorMode, int colorSamplingMethod=RETINA_COLOR_BAYER, const bool useRetinaLogSampling=false, const double reductionFactor=1.0, const double samplingStrenght=10.0); + + /** + * exports a valarray buffer outing from bioinspired objects to a cv::Mat in CV_8UC1 (gray level picture) or CV_8UC3 (color) format + * @param grayMatrixToConvert the valarray to export to OpenCV + * @param nbRows : the number of rows of the valarray flatten matrix + * @param nbColumns : the number of rows of the valarray flatten matrix + * @param colorMode : a flag which mentions if matrix is color (true) or graylevel (false) + * @param outBuffer : the output matrix which is reallocated to satisfy Retina output buffer dimensions + */ + void _convertValarrayBuffer2cvMat(const std::valarray &grayMatrixToConvert, const unsigned int nbRows, const unsigned int nbColumns, const bool colorMode, OutputArray outBuffer); + + /** + * convert a cv::Mat to a valarray buffer in float format + * @param inputMatToConvert : the OpenCV cv::Mat that has to be converted to gray or RGB valarray buffer that will be processed by the retina model + * @param outputValarrayMatrix : the output valarray + * @return the input image color mode (color=true, gray levels=false) + */ + bool _convertCvMat2ValarrayBuffer(InputArray inputMatToConvert, std::valarray &outputValarrayMatrix); + + +}; + +// smart pointers allocation : +Ptr createRetina(Size inputSize){ return makePtr(inputSize); } +Ptr createRetina(Size inputSize, const bool colorMode, int colorSamplingMethod, const bool useRetinaLogSampling, const double reductionFactor, const double samplingStrenght){ + return makePtr(inputSize, colorMode, colorSamplingMethod, useRetinaLogSampling, reductionFactor, samplingStrenght); +} + + +// RetinaImpl code +RetinaImpl::RetinaImpl(const cv::Size inputSz) +{ + _retinaFilter = 0; + _init(inputSz, true, RETINA_COLOR_BAYER, false); +} + +RetinaImpl::RetinaImpl(const cv::Size inputSz, const bool colorMode, int colorSamplingMethod, const bool useRetinaLogSampling, const double reductionFactor, const double samplingStrenght) +{ + _retinaFilter = 0; + _init(inputSz, colorMode, colorSamplingMethod, useRetinaLogSampling, reductionFactor, samplingStrenght); +} + +RetinaImpl::~RetinaImpl() +{ + if (_retinaFilter) + delete _retinaFilter; +} + +/** +* retreive retina input buffer size +*/ +Size RetinaImpl::getInputSize(){return cv::Size(_retinaFilter->getInputNBcolumns(), _retinaFilter->getInputNBrows());} + +/** +* retreive retina output buffer size +*/ +Size RetinaImpl::getOutputSize(){return cv::Size(_retinaFilter->getOutputNBcolumns(), _retinaFilter->getOutputNBrows());} + + +void RetinaImpl::setColorSaturation(const bool saturateColors, const float colorSaturationValue) +{ + _retinaFilter->setColorSaturation(saturateColors, colorSaturationValue); +} + +struct Retina::RetinaParameters RetinaImpl::getParameters(){return _retinaParameters;} + +void RetinaImpl::setup(String retinaParameterFile, const bool applyDefaultSetupOnFailure) +{ + try + { + // opening retinaParameterFile in read mode + cv::FileStorage fs(retinaParameterFile, cv::FileStorage::READ); + setup(fs, applyDefaultSetupOnFailure); + } + catch(Exception &e) + { + printf("Retina::setup: wrong/unappropriate xml parameter file : error report :`n=>%s\n", e.what()); + if (applyDefaultSetupOnFailure) + { + printf("Retina::setup: resetting retina with default parameters\n"); + setupOPLandIPLParvoChannel(); + setupIPLMagnoChannel(); + } + else + { + printf("=> keeping current parameters\n"); + } + } +} + +void RetinaImpl::setup(cv::FileStorage &fs, const bool applyDefaultSetupOnFailure) +{ + try + { + // read parameters file if it exists or apply default setup if asked for + if (!fs.isOpened()) + { + printf("Retina::setup: provided parameters file could not be open... skeeping configuration\n"); + return; + // implicit else case : retinaParameterFile could be open (it exists at least) + } + // OPL and Parvo init first... update at the same time the parameters structure and the retina core + cv::FileNode rootFn = fs.root(), currFn=rootFn["OPLandIPLparvo"]; + currFn["colorMode"]>>_retinaParameters.OPLandIplParvo.colorMode; + currFn["normaliseOutput"]>>_retinaParameters.OPLandIplParvo.normaliseOutput; + currFn["photoreceptorsLocalAdaptationSensitivity"]>>_retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity; + currFn["photoreceptorsTemporalConstant"]>>_retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant; + currFn["photoreceptorsSpatialConstant"]>>_retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant; + currFn["horizontalCellsGain"]>>_retinaParameters.OPLandIplParvo.horizontalCellsGain; + currFn["hcellsTemporalConstant"]>>_retinaParameters.OPLandIplParvo.hcellsTemporalConstant; + currFn["hcellsSpatialConstant"]>>_retinaParameters.OPLandIplParvo.hcellsSpatialConstant; + currFn["ganglionCellsSensitivity"]>>_retinaParameters.OPLandIplParvo.ganglionCellsSensitivity; + setupOPLandIPLParvoChannel(_retinaParameters.OPLandIplParvo.colorMode, _retinaParameters.OPLandIplParvo.normaliseOutput, _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity, _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant, _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant, _retinaParameters.OPLandIplParvo.horizontalCellsGain, _retinaParameters.OPLandIplParvo.hcellsTemporalConstant, _retinaParameters.OPLandIplParvo.hcellsSpatialConstant, _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity); + + // init retina IPL magno setup... update at the same time the parameters structure and the retina core + currFn=rootFn["IPLmagno"]; + currFn["normaliseOutput"]>>_retinaParameters.IplMagno.normaliseOutput; + currFn["parasolCells_beta"]>>_retinaParameters.IplMagno.parasolCells_beta; + currFn["parasolCells_tau"]>>_retinaParameters.IplMagno.parasolCells_tau; + currFn["parasolCells_k"]>>_retinaParameters.IplMagno.parasolCells_k; + currFn["amacrinCellsTemporalCutFrequency"]>>_retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency; + currFn["V0CompressionParameter"]>>_retinaParameters.IplMagno.V0CompressionParameter; + currFn["localAdaptintegration_tau"]>>_retinaParameters.IplMagno.localAdaptintegration_tau; + currFn["localAdaptintegration_k"]>>_retinaParameters.IplMagno.localAdaptintegration_k; + + setupIPLMagnoChannel(_retinaParameters.IplMagno.normaliseOutput, _retinaParameters.IplMagno.parasolCells_beta, _retinaParameters.IplMagno.parasolCells_tau, _retinaParameters.IplMagno.parasolCells_k, _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency,_retinaParameters.IplMagno.V0CompressionParameter, _retinaParameters.IplMagno.localAdaptintegration_tau, _retinaParameters.IplMagno.localAdaptintegration_k); + + } + catch(Exception &e) + { + printf("RetinaImpl::setup: resetting retina with default parameters\n"); + if (applyDefaultSetupOnFailure) + { + setupOPLandIPLParvoChannel(); + setupIPLMagnoChannel(); + } + printf("Retina::setup: wrong/unappropriate xml parameter file : error report :`n=>%s\n", e.what()); + printf("=> keeping current parameters\n"); + } + + // report current configuration + printf("%s\n", printSetup().c_str()); +} + +void RetinaImpl::setup(Retina::RetinaParameters newConfiguration) +{ + // simply copy structures + memcpy(&_retinaParameters, &newConfiguration, sizeof(Retina::RetinaParameters)); + // apply setup + setupOPLandIPLParvoChannel(_retinaParameters.OPLandIplParvo.colorMode, _retinaParameters.OPLandIplParvo.normaliseOutput, _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity, _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant, _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant, _retinaParameters.OPLandIplParvo.horizontalCellsGain, _retinaParameters.OPLandIplParvo.hcellsTemporalConstant, _retinaParameters.OPLandIplParvo.hcellsSpatialConstant, _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity); + setupIPLMagnoChannel(_retinaParameters.IplMagno.normaliseOutput, _retinaParameters.IplMagno.parasolCells_beta, _retinaParameters.IplMagno.parasolCells_tau, _retinaParameters.IplMagno.parasolCells_k, _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency,_retinaParameters.IplMagno.V0CompressionParameter, _retinaParameters.IplMagno.localAdaptintegration_tau, _retinaParameters.IplMagno.localAdaptintegration_k); + +} + +const String RetinaImpl::printSetup() +{ + std::stringstream outmessage; + + // displaying OPL and IPL parvo setup + outmessage<<"Current Retina instance setup :" + <<"\nOPLandIPLparvo"<<"{" + << "\n\t colorMode : " << _retinaParameters.OPLandIplParvo.colorMode + << "\n\t normalizeParvoOutput :" << _retinaParameters.OPLandIplParvo.normaliseOutput + << "\n\t photoreceptorsLocalAdaptationSensitivity : " << _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity + << "\n\t photoreceptorsTemporalConstant : " << _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant + << "\n\t photoreceptorsSpatialConstant : " << _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant + << "\n\t horizontalCellsGain : " << _retinaParameters.OPLandIplParvo.horizontalCellsGain + << "\n\t hcellsTemporalConstant : " << _retinaParameters.OPLandIplParvo.hcellsTemporalConstant + << "\n\t hcellsSpatialConstant : " << _retinaParameters.OPLandIplParvo.hcellsSpatialConstant + << "\n\t parvoGanglionCellsSensitivity : " << _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity + <<"}\n"; + + // displaying IPL magno setup + outmessage<<"Current Retina instance setup :" + <<"\nIPLmagno"<<"{" + << "\n\t normaliseOutput : " << _retinaParameters.IplMagno.normaliseOutput + << "\n\t parasolCells_beta : " << _retinaParameters.IplMagno.parasolCells_beta + << "\n\t parasolCells_tau : " << _retinaParameters.IplMagno.parasolCells_tau + << "\n\t parasolCells_k : " << _retinaParameters.IplMagno.parasolCells_k + << "\n\t amacrinCellsTemporalCutFrequency : " << _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency + << "\n\t V0CompressionParameter : " << _retinaParameters.IplMagno.V0CompressionParameter + << "\n\t localAdaptintegration_tau : " << _retinaParameters.IplMagno.localAdaptintegration_tau + << "\n\t localAdaptintegration_k : " << _retinaParameters.IplMagno.localAdaptintegration_k + <<"}"; + return outmessage.str().c_str(); +} + +void RetinaImpl::write( String fs ) const +{ + FileStorage parametersSaveFile(fs, cv::FileStorage::WRITE ); + write(parametersSaveFile); +} + +void RetinaImpl::write( FileStorage& fs ) const +{ + if (!fs.isOpened()) + return; // basic error case + fs<<"OPLandIPLparvo"<<"{"; + fs << "colorMode" << _retinaParameters.OPLandIplParvo.colorMode; + fs << "normaliseOutput" << _retinaParameters.OPLandIplParvo.normaliseOutput; + fs << "photoreceptorsLocalAdaptationSensitivity" << _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity; + fs << "photoreceptorsTemporalConstant" << _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant; + fs << "photoreceptorsSpatialConstant" << _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant; + fs << "horizontalCellsGain" << _retinaParameters.OPLandIplParvo.horizontalCellsGain; + fs << "hcellsTemporalConstant" << _retinaParameters.OPLandIplParvo.hcellsTemporalConstant; + fs << "hcellsSpatialConstant" << _retinaParameters.OPLandIplParvo.hcellsSpatialConstant; + fs << "ganglionCellsSensitivity" << _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity; + fs << "}"; + fs<<"IPLmagno"<<"{"; + fs << "normaliseOutput" << _retinaParameters.IplMagno.normaliseOutput; + fs << "parasolCells_beta" << _retinaParameters.IplMagno.parasolCells_beta; + fs << "parasolCells_tau" << _retinaParameters.IplMagno.parasolCells_tau; + fs << "parasolCells_k" << _retinaParameters.IplMagno.parasolCells_k; + fs << "amacrinCellsTemporalCutFrequency" << _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency; + fs << "V0CompressionParameter" << _retinaParameters.IplMagno.V0CompressionParameter; + fs << "localAdaptintegration_tau" << _retinaParameters.IplMagno.localAdaptintegration_tau; + fs << "localAdaptintegration_k" << _retinaParameters.IplMagno.localAdaptintegration_k; + fs<<"}"; +} + +void RetinaImpl::setupOPLandIPLParvoChannel(const bool colorMode, const bool normaliseOutput, const float photoreceptorsLocalAdaptationSensitivity, const float photoreceptorsTemporalConstant, const float photoreceptorsSpatialConstant, const float horizontalCellsGain, const float HcellsTemporalConstant, const float HcellsSpatialConstant, const float ganglionCellsSensitivity) +{ + // retina core parameters setup + _retinaFilter->setColorMode(colorMode); + _retinaFilter->setPhotoreceptorsLocalAdaptationSensitivity(photoreceptorsLocalAdaptationSensitivity); + _retinaFilter->setOPLandParvoParameters(0, photoreceptorsTemporalConstant, photoreceptorsSpatialConstant, horizontalCellsGain, HcellsTemporalConstant, HcellsSpatialConstant, ganglionCellsSensitivity); + _retinaFilter->setParvoGanglionCellsLocalAdaptationSensitivity(ganglionCellsSensitivity); + _retinaFilter->activateNormalizeParvoOutput_0_maxOutputValue(normaliseOutput); + + // update parameters struture + + _retinaParameters.OPLandIplParvo.colorMode = colorMode; + _retinaParameters.OPLandIplParvo.normaliseOutput = normaliseOutput; + _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity = photoreceptorsLocalAdaptationSensitivity; + _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant = photoreceptorsTemporalConstant; + _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant = photoreceptorsSpatialConstant; + _retinaParameters.OPLandIplParvo.horizontalCellsGain = horizontalCellsGain; + _retinaParameters.OPLandIplParvo.hcellsTemporalConstant = HcellsTemporalConstant; + _retinaParameters.OPLandIplParvo.hcellsSpatialConstant = HcellsSpatialConstant; + _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity = ganglionCellsSensitivity; + +} + +void RetinaImpl::setupIPLMagnoChannel(const bool normaliseOutput, const float parasolCells_beta, const float parasolCells_tau, const float parasolCells_k, const float amacrinCellsTemporalCutFrequency, const float V0CompressionParameter, const float localAdaptintegration_tau, const float localAdaptintegration_k) +{ + + _retinaFilter->setMagnoCoefficientsTable(parasolCells_beta, parasolCells_tau, parasolCells_k, amacrinCellsTemporalCutFrequency, V0CompressionParameter, localAdaptintegration_tau, localAdaptintegration_k); + _retinaFilter->activateNormalizeMagnoOutput_0_maxOutputValue(normaliseOutput); + + // update parameters struture + _retinaParameters.IplMagno.normaliseOutput = normaliseOutput; + _retinaParameters.IplMagno.parasolCells_beta = parasolCells_beta; + _retinaParameters.IplMagno.parasolCells_tau = parasolCells_tau; + _retinaParameters.IplMagno.parasolCells_k = parasolCells_k; + _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency = amacrinCellsTemporalCutFrequency; + _retinaParameters.IplMagno.V0CompressionParameter = V0CompressionParameter; + _retinaParameters.IplMagno.localAdaptintegration_tau = localAdaptintegration_tau; + _retinaParameters.IplMagno.localAdaptintegration_k = localAdaptintegration_k; +} + +void RetinaImpl::run(InputArray inputMatToConvert) +{ + // first convert input image to the compatible format : std::valarray + const bool colorMode = _convertCvMat2ValarrayBuffer(inputMatToConvert.getMat(), _inputBuffer); + // process the retina + if (!_retinaFilter->runFilter(_inputBuffer, colorMode, false, _retinaParameters.OPLandIplParvo.colorMode && colorMode, false)) + throw cv::Exception(-1, "RetinaImpl cannot be applied, wrong input buffer size", "RetinaImpl::run", "RetinaImpl.h", 0); +} + +void RetinaImpl::applyFastToneMapping(InputArray inputImage, OutputArray outputToneMappedImage) +{ + // first convert input image to the compatible format : + const bool colorMode = _convertCvMat2ValarrayBuffer(inputImage.getMat(), _inputBuffer); + const unsigned int nbPixels=_retinaFilter->getOutputNBrows()*_retinaFilter->getOutputNBcolumns(); + + // process tone mapping + if (colorMode) + { + std::valarray imageOutput(nbPixels*3); + _retinaFilter->runRGBToneMapping(_inputBuffer, imageOutput, true, _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity, _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity); + _convertValarrayBuffer2cvMat(imageOutput, _retinaFilter->getOutputNBrows(), _retinaFilter->getOutputNBcolumns(), true, outputToneMappedImage); + }else + { + std::valarray imageOutput(nbPixels); + _retinaFilter->runGrayToneMapping(_inputBuffer, imageOutput, _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity, _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity); + _convertValarrayBuffer2cvMat(imageOutput, _retinaFilter->getOutputNBrows(), _retinaFilter->getOutputNBcolumns(), false, outputToneMappedImage); + } + +} + +void RetinaImpl::getParvo(OutputArray retinaOutput_parvo) +{ + if (_retinaFilter->getColorMode()) + { + // reallocate output buffer (if necessary) + _convertValarrayBuffer2cvMat(_retinaFilter->getColorOutput(), _retinaFilter->getOutputNBrows(), _retinaFilter->getOutputNBcolumns(), true, retinaOutput_parvo); + }else + { + // reallocate output buffer (if necessary) + _convertValarrayBuffer2cvMat(_retinaFilter->getContours(), _retinaFilter->getOutputNBrows(), _retinaFilter->getOutputNBcolumns(), false, retinaOutput_parvo); + } + //retinaOutput_parvo/=255.0; +} +void RetinaImpl::getMagno(OutputArray retinaOutput_magno) +{ + // reallocate output buffer (if necessary) + _convertValarrayBuffer2cvMat(_retinaFilter->getMovingContours(), _retinaFilter->getOutputNBrows(), _retinaFilter->getOutputNBcolumns(), false, retinaOutput_magno); + //retinaOutput_magno/=255.0; +} + +// original API level data accessors : copy buffers if size matches, reallocate if required +void RetinaImpl::getMagnoRAW(OutputArray magnoOutputBufferCopy){ + // get magno channel header + const cv::Mat magnoChannel=cv::Mat(getMagnoRAW()); + // copy data + magnoChannel.copyTo(magnoOutputBufferCopy); +} + +void RetinaImpl::getParvoRAW(OutputArray parvoOutputBufferCopy){ + // get parvo channel header + const cv::Mat parvoChannel=cv::Mat(getMagnoRAW()); + // copy data + parvoChannel.copyTo(parvoOutputBufferCopy); +} + +// original API level data accessors : get buffers addresses... +const Mat RetinaImpl::getMagnoRAW() const { + // create a cv::Mat header for the valarray + return Mat((int)_retinaFilter->getMovingContours().size(),1, CV_32F, (void*)get_data(_retinaFilter->getMovingContours())); + +} + +const Mat RetinaImpl::getParvoRAW() const { + if (_retinaFilter->getColorMode()) // check if color mode is enabled + { + // create a cv::Mat table (for RGB planes as a single vector) + return Mat((int)_retinaFilter->getColorOutput().size(), 1, CV_32F, (void*)get_data(_retinaFilter->getColorOutput())); + } + // otherwise, output is gray level + // create a cv::Mat header for the valarray + return Mat((int)_retinaFilter->getContours().size(), 1, CV_32F, (void*)get_data(_retinaFilter->getContours())); +} + +// private method called by constructirs +void RetinaImpl::_init(const cv::Size inputSz, const bool colorMode, int colorSamplingMethod, const bool useRetinaLogSampling, const double reductionFactor, const double samplingStrenght) +{ + // basic error check + if (inputSz.height*inputSz.width <= 0) + throw cv::Exception(-1, "Bad retina size setup : size height and with must be superior to zero", "RetinaImpl::setup", "Retina.cpp", 0); + + unsigned int nbPixels=inputSz.height*inputSz.width; + // resize buffers if size does not match + _inputBuffer.resize(nbPixels*3); // buffer supports gray images but also 3 channels color buffers... (larger is better...) + + // allocate the retina model + if (_retinaFilter) + delete _retinaFilter; + _retinaFilter = new RetinaFilter(inputSz.height, inputSz.width, colorMode, colorSamplingMethod, useRetinaLogSampling, reductionFactor, samplingStrenght); + + _retinaParameters.OPLandIplParvo.colorMode = colorMode; + // prepare the default parameter XML file with default setup + setup(_retinaParameters); + + // init retina + _retinaFilter->clearAllBuffers(); + + // report current configuration + printf("%s\n", printSetup().c_str()); +} + +void RetinaImpl::_convertValarrayBuffer2cvMat(const std::valarray &grayMatrixToConvert, const unsigned int nbRows, const unsigned int nbColumns, const bool colorMode, OutputArray outBuffer) +{ + // fill output buffer with the valarray buffer + const float *valarrayPTR=get_data(grayMatrixToConvert); + if (!colorMode) + { + outBuffer.create(cv::Size(nbColumns, nbRows), CV_8U); + Mat outMat = outBuffer.getMat(); + for (unsigned int i=0;i(pixel)=(unsigned char)*(valarrayPTR++); + } + } + } + else + { + const unsigned int nbPixels=nbColumns*nbRows; + const unsigned int doubleNBpixels=nbColumns*nbRows*2; + outBuffer.create(cv::Size(nbColumns, nbRows), CV_8UC3); + Mat outMat = outBuffer.getMat(); + for (unsigned int i=0;i(pixel)=pixelValues; + } + } + } +} + +bool RetinaImpl::_convertCvMat2ValarrayBuffer(InputArray inputMat, std::valarray &outputValarrayMatrix) +{ + const Mat inputMatToConvert=inputMat.getMat(); + // first check input consistency + if (inputMatToConvert.empty()) + throw cv::Exception(-1, "RetinaImpl cannot be applied, input buffer is empty", "RetinaImpl::run", "RetinaImpl.h", 0); + + // retreive color mode from image input + int imageNumberOfChannels = inputMatToConvert.channels(); + + // convert to float AND fill the valarray buffer + typedef float T; // define here the target pixel format, here, float + const int dsttype = DataType::depth; // output buffer is float format + + const unsigned int nbPixels=inputMat.getMat().rows*inputMat.getMat().cols; + const unsigned int doubleNBpixels=inputMat.getMat().rows*inputMat.getMat().cols*2; + + if(imageNumberOfChannels==4) + { + // create a cv::Mat table (for RGBA planes) + cv::Mat planes[4] = + { + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[doubleNBpixels]), + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[nbPixels]), + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[0]) + }; + planes[3] = cv::Mat(inputMatToConvert.size(), dsttype); // last channel (alpha) does not point on the valarray (not usefull in our case) + // split color cv::Mat in 4 planes... it fills valarray directely + cv::split(Mat_ >(inputMatToConvert), planes); + } + else if (imageNumberOfChannels==3) + { + // create a cv::Mat table (for RGB planes) + cv::Mat planes[] = + { + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[doubleNBpixels]), + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[nbPixels]), + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[0]) + }; + // split color cv::Mat in 3 planes... it fills valarray directely + cv::split(cv::Mat_ >(inputMatToConvert), planes); + } + else if(imageNumberOfChannels==1) + { + // create a cv::Mat header for the valarray + cv::Mat dst(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[0]); + inputMatToConvert.convertTo(dst, dsttype); + } + else + CV_Error(Error::StsUnsupportedFormat, "input image must be single channel (gray levels), bgr format (color) or bgra (color with transparency which won't be considered"); + + return imageNumberOfChannels>1; // return bool : false for gray level image processing, true for color mode +} + +void RetinaImpl::clearBuffers() { _retinaFilter->clearAllBuffers(); } + +void RetinaImpl::activateMovingContoursProcessing(const bool activate) { _retinaFilter->activateMovingContoursProcessing(activate); } + +void RetinaImpl::activateContoursProcessing(const bool activate) { _retinaFilter->activateContoursProcessing(activate); } + +}// end of namespace bioinspired +}// end of namespace cv diff --git a/modules/bioinspired/src/retina_ocl.cpp b/modules/bioinspired/src/retina_ocl.cpp new file mode 100644 index 00000000000..51f43f38bf2 --- /dev/null +++ b/modules/bioinspired/src/retina_ocl.cpp @@ -0,0 +1,1643 @@ +/*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) 2010-2013, Multicoreware, Inc., all rights reserved. +// Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// @Authors +// Peng Xiao, pengxiao@multicorewareinc.com +// +// 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 oclMaterials 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*/ + +#include "precomp.hpp" +#include "retina_ocl.hpp" +#include +#include + +#ifdef HAVE_OPENCV_OCL + +#include "opencl_kernels.hpp" + +#define NOT_IMPLEMENTED CV_Error(cv::Error::StsNotImplemented, "Not implemented") + +namespace cv +{ +static ocl::ProgramEntry retina_kernel = ocl::bioinspired::retina_kernel; + +namespace bioinspired +{ +namespace ocl +{ +using namespace cv::ocl; + +class RetinaOCLImpl : public Retina +{ +public: + RetinaOCLImpl(Size getInputSize); + RetinaOCLImpl(Size getInputSize, const bool colorMode, int colorSamplingMethod = RETINA_COLOR_BAYER, const bool useRetinaLogSampling = false, const double reductionFactor = 1.0, const double samplingStrenght = 10.0); + virtual ~RetinaOCLImpl(); + + Size getInputSize(); + Size getOutputSize(); + + void setup(String retinaParameterFile = "", const bool applyDefaultSetupOnFailure = true); + void setup(cv::FileStorage &fs, const bool applyDefaultSetupOnFailure = true); + void setup(RetinaParameters newParameters); + + RetinaOCLImpl::RetinaParameters getParameters(); + + const String printSetup(); + virtual void write( String fs ) const; + virtual void write( FileStorage& fs ) const; + + void setupOPLandIPLParvoChannel(const bool colorMode = true, const bool normaliseOutput = true, const float photoreceptorsLocalAdaptationSensitivity = 0.7, const float photoreceptorsTemporalConstant = 0.5, const float photoreceptorsSpatialConstant = 0.53, const float horizontalCellsGain = 0, const float HcellsTemporalConstant = 1, const float HcellsSpatialConstant = 7, const float ganglionCellsSensitivity = 0.7); + void setupIPLMagnoChannel(const bool normaliseOutput = true, const float parasolCells_beta = 0, const float parasolCells_tau = 0, const float parasolCells_k = 7, const float amacrinCellsTemporalCutFrequency = 1.2, const float V0CompressionParameter = 0.95, const float localAdaptintegration_tau = 0, const float localAdaptintegration_k = 7); + + void run(InputArray inputImage); + void getParvo(OutputArray retinaOutput_parvo); + void getMagno(OutputArray retinaOutput_magno); + + void setColorSaturation(const bool saturateColors = true, const float colorSaturationValue = 4.0); + void clearBuffers(); + void activateMovingContoursProcessing(const bool activate); + void activateContoursProcessing(const bool activate); + + // unimplemented interfaces: + void applyFastToneMapping(InputArray /*inputImage*/, OutputArray /*outputToneMappedImage*/) { NOT_IMPLEMENTED; } + void getParvoRAW(OutputArray /*retinaOutput_parvo*/) { NOT_IMPLEMENTED; } + void getMagnoRAW(OutputArray /*retinaOutput_magno*/) { NOT_IMPLEMENTED; } + const Mat getMagnoRAW() const { NOT_IMPLEMENTED; return Mat(); } + const Mat getParvoRAW() const { NOT_IMPLEMENTED; return Mat(); } + +protected: + RetinaParameters _retinaParameters; + cv::ocl::oclMat _inputBuffer; + RetinaFilter* _retinaFilter; + bool convertToColorPlanes(const cv::ocl::oclMat& input, cv::ocl::oclMat &output); + void convertToInterleaved(const cv::ocl::oclMat& input, bool colorMode, cv::ocl::oclMat &output); + void _init(const Size getInputSize, const bool colorMode, int colorSamplingMethod = RETINA_COLOR_BAYER, const bool useRetinaLogSampling = false, const double reductionFactor = 1.0, const double samplingStrenght = 10.0); +}; + +RetinaOCLImpl::RetinaOCLImpl(const cv::Size inputSz) +{ + _retinaFilter = 0; + _init(inputSz, true, RETINA_COLOR_BAYER, false); +} + +RetinaOCLImpl::RetinaOCLImpl(const cv::Size inputSz, const bool colorMode, int colorSamplingMethod, const bool useRetinaLogSampling, const double reductionFactor, const double samplingStrenght) +{ + _retinaFilter = 0; + _init(inputSz, colorMode, colorSamplingMethod, useRetinaLogSampling, reductionFactor, samplingStrenght); +} + +RetinaOCLImpl::~RetinaOCLImpl() +{ + if (_retinaFilter) + { + delete _retinaFilter; + } +} + +/** +* retreive retina input buffer size +*/ +Size RetinaOCLImpl::getInputSize() +{ + return cv::Size(_retinaFilter->getInputNBcolumns(), _retinaFilter->getInputNBrows()); +} + +/** +* retreive retina output buffer size +*/ +Size RetinaOCLImpl::getOutputSize() +{ + return cv::Size(_retinaFilter->getOutputNBcolumns(), _retinaFilter->getOutputNBrows()); +} + + +void RetinaOCLImpl::setColorSaturation(const bool saturateColors, const float colorSaturationValue) +{ + _retinaFilter->setColorSaturation(saturateColors, colorSaturationValue); +} + +struct RetinaOCLImpl::RetinaParameters RetinaOCLImpl::getParameters() +{ + return _retinaParameters; +} + + +void RetinaOCLImpl::setup(String retinaParameterFile, const bool applyDefaultSetupOnFailure) +{ + try + { + // opening retinaParameterFile in read mode + cv::FileStorage fs(retinaParameterFile, cv::FileStorage::READ); + setup(fs, applyDefaultSetupOnFailure); + } + catch(Exception &e) + { + std::cout << "RetinaOCLImpl::setup: wrong/unappropriate xml parameter file : error report :`n=>" << e.what() << std::endl; + if (applyDefaultSetupOnFailure) + { + std::cout << "RetinaOCLImpl::setup: resetting retina with default parameters" << std::endl; + setupOPLandIPLParvoChannel(); + setupIPLMagnoChannel(); + } + else + { + std::cout << "=> keeping current parameters" << std::endl; + } + } +} + +void RetinaOCLImpl::setup(cv::FileStorage &fs, const bool applyDefaultSetupOnFailure) +{ + try + { + // read parameters file if it exists or apply default setup if asked for + if (!fs.isOpened()) + { + std::cout << "RetinaOCLImpl::setup: provided parameters file could not be open... skeeping configuration" << std::endl; + return; + // implicit else case : retinaParameterFile could be open (it exists at least) + } + // OPL and Parvo init first... update at the same time the parameters structure and the retina core + cv::FileNode rootFn = fs.root(), currFn = rootFn["OPLandIPLparvo"]; + currFn["colorMode"] >> _retinaParameters.OPLandIplParvo.colorMode; + currFn["normaliseOutput"] >> _retinaParameters.OPLandIplParvo.normaliseOutput; + currFn["photoreceptorsLocalAdaptationSensitivity"] >> _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity; + currFn["photoreceptorsTemporalConstant"] >> _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant; + currFn["photoreceptorsSpatialConstant"] >> _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant; + currFn["horizontalCellsGain"] >> _retinaParameters.OPLandIplParvo.horizontalCellsGain; + currFn["hcellsTemporalConstant"] >> _retinaParameters.OPLandIplParvo.hcellsTemporalConstant; + currFn["hcellsSpatialConstant"] >> _retinaParameters.OPLandIplParvo.hcellsSpatialConstant; + currFn["ganglionCellsSensitivity"] >> _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity; + setupOPLandIPLParvoChannel(_retinaParameters.OPLandIplParvo.colorMode, _retinaParameters.OPLandIplParvo.normaliseOutput, _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity, _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant, _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant, _retinaParameters.OPLandIplParvo.horizontalCellsGain, _retinaParameters.OPLandIplParvo.hcellsTemporalConstant, _retinaParameters.OPLandIplParvo.hcellsSpatialConstant, _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity); + + // init retina IPL magno setup... update at the same time the parameters structure and the retina core + currFn = rootFn["IPLmagno"]; + currFn["normaliseOutput"] >> _retinaParameters.IplMagno.normaliseOutput; + currFn["parasolCells_beta"] >> _retinaParameters.IplMagno.parasolCells_beta; + currFn["parasolCells_tau"] >> _retinaParameters.IplMagno.parasolCells_tau; + currFn["parasolCells_k"] >> _retinaParameters.IplMagno.parasolCells_k; + currFn["amacrinCellsTemporalCutFrequency"] >> _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency; + currFn["V0CompressionParameter"] >> _retinaParameters.IplMagno.V0CompressionParameter; + currFn["localAdaptintegration_tau"] >> _retinaParameters.IplMagno.localAdaptintegration_tau; + currFn["localAdaptintegration_k"] >> _retinaParameters.IplMagno.localAdaptintegration_k; + + setupIPLMagnoChannel(_retinaParameters.IplMagno.normaliseOutput, _retinaParameters.IplMagno.parasolCells_beta, _retinaParameters.IplMagno.parasolCells_tau, _retinaParameters.IplMagno.parasolCells_k, _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency, _retinaParameters.IplMagno.V0CompressionParameter, _retinaParameters.IplMagno.localAdaptintegration_tau, _retinaParameters.IplMagno.localAdaptintegration_k); + + } + catch(Exception &e) + { + std::cout << "RetinaOCLImpl::setup: resetting retina with default parameters" << std::endl; + if (applyDefaultSetupOnFailure) + { + setupOPLandIPLParvoChannel(); + setupIPLMagnoChannel(); + } + std::cout << "RetinaOCLImpl::setup: wrong/unappropriate xml parameter file : error report :`n=>" << e.what() << std::endl; + std::cout << "=> keeping current parameters" << std::endl; + } +} + +void RetinaOCLImpl::setup(cv::bioinspired::Retina::RetinaParameters newConfiguration) +{ + // simply copy structures + memcpy(&_retinaParameters, &newConfiguration, sizeof(cv::bioinspired::Retina::RetinaParameters)); + // apply setup + setupOPLandIPLParvoChannel(_retinaParameters.OPLandIplParvo.colorMode, _retinaParameters.OPLandIplParvo.normaliseOutput, _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity, _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant, _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant, _retinaParameters.OPLandIplParvo.horizontalCellsGain, _retinaParameters.OPLandIplParvo.hcellsTemporalConstant, _retinaParameters.OPLandIplParvo.hcellsSpatialConstant, _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity); + setupIPLMagnoChannel(_retinaParameters.IplMagno.normaliseOutput, _retinaParameters.IplMagno.parasolCells_beta, _retinaParameters.IplMagno.parasolCells_tau, _retinaParameters.IplMagno.parasolCells_k, _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency, _retinaParameters.IplMagno.V0CompressionParameter, _retinaParameters.IplMagno.localAdaptintegration_tau, _retinaParameters.IplMagno.localAdaptintegration_k); +} + +const String RetinaOCLImpl::printSetup() +{ + std::stringstream outmessage; + + // displaying OPL and IPL parvo setup + outmessage << "Current Retina instance setup :" + << "\nOPLandIPLparvo" << "{" + << "\n==> colorMode : " << _retinaParameters.OPLandIplParvo.colorMode + << "\n==> normalizeParvoOutput :" << _retinaParameters.OPLandIplParvo.normaliseOutput + << "\n==> photoreceptorsLocalAdaptationSensitivity : " << _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity + << "\n==> photoreceptorsTemporalConstant : " << _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant + << "\n==> photoreceptorsSpatialConstant : " << _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant + << "\n==> horizontalCellsGain : " << _retinaParameters.OPLandIplParvo.horizontalCellsGain + << "\n==> hcellsTemporalConstant : " << _retinaParameters.OPLandIplParvo.hcellsTemporalConstant + << "\n==> hcellsSpatialConstant : " << _retinaParameters.OPLandIplParvo.hcellsSpatialConstant + << "\n==> parvoGanglionCellsSensitivity : " << _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity + << "}\n"; + + // displaying IPL magno setup + outmessage << "Current Retina instance setup :" + << "\nIPLmagno" << "{" + << "\n==> normaliseOutput : " << _retinaParameters.IplMagno.normaliseOutput + << "\n==> parasolCells_beta : " << _retinaParameters.IplMagno.parasolCells_beta + << "\n==> parasolCells_tau : " << _retinaParameters.IplMagno.parasolCells_tau + << "\n==> parasolCells_k : " << _retinaParameters.IplMagno.parasolCells_k + << "\n==> amacrinCellsTemporalCutFrequency : " << _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency + << "\n==> V0CompressionParameter : " << _retinaParameters.IplMagno.V0CompressionParameter + << "\n==> localAdaptintegration_tau : " << _retinaParameters.IplMagno.localAdaptintegration_tau + << "\n==> localAdaptintegration_k : " << _retinaParameters.IplMagno.localAdaptintegration_k + << "}"; + return outmessage.str().c_str(); +} + +void RetinaOCLImpl::write( String fs ) const +{ + FileStorage parametersSaveFile(fs, cv::FileStorage::WRITE ); + write(parametersSaveFile); +} + +void RetinaOCLImpl::write( FileStorage& fs ) const +{ + if (!fs.isOpened()) + { + return; // basic error case + } + fs << "OPLandIPLparvo" << "{"; + fs << "colorMode" << _retinaParameters.OPLandIplParvo.colorMode; + fs << "normaliseOutput" << _retinaParameters.OPLandIplParvo.normaliseOutput; + fs << "photoreceptorsLocalAdaptationSensitivity" << _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity; + fs << "photoreceptorsTemporalConstant" << _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant; + fs << "photoreceptorsSpatialConstant" << _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant; + fs << "horizontalCellsGain" << _retinaParameters.OPLandIplParvo.horizontalCellsGain; + fs << "hcellsTemporalConstant" << _retinaParameters.OPLandIplParvo.hcellsTemporalConstant; + fs << "hcellsSpatialConstant" << _retinaParameters.OPLandIplParvo.hcellsSpatialConstant; + fs << "ganglionCellsSensitivity" << _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity; + fs << "}"; + fs << "IPLmagno" << "{"; + fs << "normaliseOutput" << _retinaParameters.IplMagno.normaliseOutput; + fs << "parasolCells_beta" << _retinaParameters.IplMagno.parasolCells_beta; + fs << "parasolCells_tau" << _retinaParameters.IplMagno.parasolCells_tau; + fs << "parasolCells_k" << _retinaParameters.IplMagno.parasolCells_k; + fs << "amacrinCellsTemporalCutFrequency" << _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency; + fs << "V0CompressionParameter" << _retinaParameters.IplMagno.V0CompressionParameter; + fs << "localAdaptintegration_tau" << _retinaParameters.IplMagno.localAdaptintegration_tau; + fs << "localAdaptintegration_k" << _retinaParameters.IplMagno.localAdaptintegration_k; + fs << "}"; +} + +void RetinaOCLImpl::setupOPLandIPLParvoChannel(const bool colorMode, const bool normaliseOutput, const float photoreceptorsLocalAdaptationSensitivity, const float photoreceptorsTemporalConstant, const float photoreceptorsSpatialConstant, const float horizontalCellsGain, const float HcellsTemporalConstant, const float HcellsSpatialConstant, const float ganglionCellsSensitivity) +{ + // retina core parameters setup + _retinaFilter->setColorMode(colorMode); + _retinaFilter->setPhotoreceptorsLocalAdaptationSensitivity(photoreceptorsLocalAdaptationSensitivity); + _retinaFilter->setOPLandParvoParameters(0, photoreceptorsTemporalConstant, photoreceptorsSpatialConstant, horizontalCellsGain, HcellsTemporalConstant, HcellsSpatialConstant, ganglionCellsSensitivity); + _retinaFilter->setParvoGanglionCellsLocalAdaptationSensitivity(ganglionCellsSensitivity); + _retinaFilter->activateNormalizeParvoOutput_0_maxOutputValue(normaliseOutput); + + // update parameters struture + + _retinaParameters.OPLandIplParvo.colorMode = colorMode; + _retinaParameters.OPLandIplParvo.normaliseOutput = normaliseOutput; + _retinaParameters.OPLandIplParvo.photoreceptorsLocalAdaptationSensitivity = photoreceptorsLocalAdaptationSensitivity; + _retinaParameters.OPLandIplParvo.photoreceptorsTemporalConstant = photoreceptorsTemporalConstant; + _retinaParameters.OPLandIplParvo.photoreceptorsSpatialConstant = photoreceptorsSpatialConstant; + _retinaParameters.OPLandIplParvo.horizontalCellsGain = horizontalCellsGain; + _retinaParameters.OPLandIplParvo.hcellsTemporalConstant = HcellsTemporalConstant; + _retinaParameters.OPLandIplParvo.hcellsSpatialConstant = HcellsSpatialConstant; + _retinaParameters.OPLandIplParvo.ganglionCellsSensitivity = ganglionCellsSensitivity; +} + +void RetinaOCLImpl::setupIPLMagnoChannel(const bool normaliseOutput, const float parasolCells_beta, const float parasolCells_tau, const float parasolCells_k, const float amacrinCellsTemporalCutFrequency, const float V0CompressionParameter, const float localAdaptintegration_tau, const float localAdaptintegration_k) +{ + + _retinaFilter->setMagnoCoefficientsTable(parasolCells_beta, parasolCells_tau, parasolCells_k, amacrinCellsTemporalCutFrequency, V0CompressionParameter, localAdaptintegration_tau, localAdaptintegration_k); + _retinaFilter->activateNormalizeMagnoOutput_0_maxOutputValue(normaliseOutput); + + // update parameters struture + _retinaParameters.IplMagno.normaliseOutput = normaliseOutput; + _retinaParameters.IplMagno.parasolCells_beta = parasolCells_beta; + _retinaParameters.IplMagno.parasolCells_tau = parasolCells_tau; + _retinaParameters.IplMagno.parasolCells_k = parasolCells_k; + _retinaParameters.IplMagno.amacrinCellsTemporalCutFrequency = amacrinCellsTemporalCutFrequency; + _retinaParameters.IplMagno.V0CompressionParameter = V0CompressionParameter; + _retinaParameters.IplMagno.localAdaptintegration_tau = localAdaptintegration_tau; + _retinaParameters.IplMagno.localAdaptintegration_k = localAdaptintegration_k; +} + +void RetinaOCLImpl::run(const InputArray input) +{ + oclMat &inputMatToConvert = getOclMatRef(input); + bool colorMode = convertToColorPlanes(inputMatToConvert, _inputBuffer); + // first convert input image to the compatible format : std::valarray + // process the retina + if (!_retinaFilter->runFilter(_inputBuffer, colorMode, false, _retinaParameters.OPLandIplParvo.colorMode && colorMode, false)) + { + throw cv::Exception(-1, "Retina cannot be applied, wrong input buffer size", "RetinaOCLImpl::run", "Retina.h", 0); + } +} + +void RetinaOCLImpl::getParvo(OutputArray output) +{ + oclMat &retinaOutput_parvo = getOclMatRef(output); + if (_retinaFilter->getColorMode()) + { + // reallocate output buffer (if necessary) + convertToInterleaved(_retinaFilter->getColorOutput(), true, retinaOutput_parvo); + } + else + { + // reallocate output buffer (if necessary) + convertToInterleaved(_retinaFilter->getContours(), false, retinaOutput_parvo); + } + //retinaOutput_parvo/=255.0; +} +void RetinaOCLImpl::getMagno(OutputArray output) +{ + oclMat &retinaOutput_magno = getOclMatRef(output); + // reallocate output buffer (if necessary) + convertToInterleaved(_retinaFilter->getMovingContours(), false, retinaOutput_magno); + //retinaOutput_magno/=255.0; +} +// private method called by constructirs +void RetinaOCLImpl::_init(const cv::Size inputSz, const bool colorMode, int colorSamplingMethod, const bool useRetinaLogSampling, const double reductionFactor, const double samplingStrenght) +{ + // basic error check + if (inputSz.height*inputSz.width <= 0) + { + throw cv::Exception(-1, "Bad retina size setup : size height and with must be superior to zero", "RetinaOCLImpl::setup", "Retina.h", 0); + } + + // allocate the retina model + if (_retinaFilter) + { + delete _retinaFilter; + } + _retinaFilter = new RetinaFilter(inputSz.height, inputSz.width, colorMode, colorSamplingMethod, useRetinaLogSampling, reductionFactor, samplingStrenght); + + // prepare the default parameter XML file with default setup + setup(_retinaParameters); + + // init retina + _retinaFilter->clearAllBuffers(); +} + +bool RetinaOCLImpl::convertToColorPlanes(const oclMat& input, oclMat &output) +{ + oclMat convert_input; + input.convertTo(convert_input, CV_32F); + if(convert_input.channels() == 3 || convert_input.channels() == 4) + { + ocl::ensureSizeIsEnough(int(_retinaFilter->getInputNBrows() * 4), + int(_retinaFilter->getInputNBcolumns()), CV_32FC1, output); + oclMat channel_splits[4] = + { + output(Rect(Point(0, _retinaFilter->getInputNBrows() * 2), getInputSize())), + output(Rect(Point(0, _retinaFilter->getInputNBrows()), getInputSize())), + output(Rect(Point(0, 0), getInputSize())), + output(Rect(Point(0, _retinaFilter->getInputNBrows() * 3), getInputSize())) + }; + ocl::split(convert_input, channel_splits); + return true; + } + else if(convert_input.channels() == 1) + { + convert_input.copyTo(output); + return false; + } + else + { + CV_Error(-1, "Retina ocl only support 1, 3, 4 channel input"); + return false; + } +} +void RetinaOCLImpl::convertToInterleaved(const oclMat& input, bool colorMode, oclMat &output) +{ + input.convertTo(output, CV_8U); + if(colorMode) + { + int numOfSplits = input.rows / getInputSize().height; + std::vector channel_splits(numOfSplits); + for(int i = 0; i < static_cast(channel_splits.size()); i ++) + { + channel_splits[i] = + output(Rect(Point(0, _retinaFilter->getInputNBrows() * (numOfSplits - i - 1)), getInputSize())); + } + merge(channel_splits, output); + } + else + { + //... + } +} + +void RetinaOCLImpl::clearBuffers() +{ + _retinaFilter->clearAllBuffers(); +} + +void RetinaOCLImpl::activateMovingContoursProcessing(const bool activate) +{ + _retinaFilter->activateMovingContoursProcessing(activate); +} + +void RetinaOCLImpl::activateContoursProcessing(const bool activate) +{ + _retinaFilter->activateContoursProcessing(activate); +} + +/////////////////////////////////////// +///////// BasicRetinaFilter /////////// +/////////////////////////////////////// +BasicRetinaFilter::BasicRetinaFilter(const unsigned int NBrows, const unsigned int NBcolumns, const unsigned int parametersListSize, const bool) + : _NBrows(NBrows), _NBcols(NBcolumns), + _filterOutput(NBrows, NBcolumns, CV_32FC1), + _localBuffer(NBrows, NBcolumns, CV_32FC1), + _filteringCoeficientsTable(3 * parametersListSize) +{ + _halfNBrows = _filterOutput.rows / 2; + _halfNBcolumns = _filterOutput.cols / 2; + + // set default values + _maxInputValue = 256.0; + + // reset all buffers + clearAllBuffers(); +} + +BasicRetinaFilter::~BasicRetinaFilter() +{ +} + +void BasicRetinaFilter::resize(const unsigned int NBrows, const unsigned int NBcolumns) +{ + // resizing buffers + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _filterOutput); + + // updating variables + _halfNBrows = _filterOutput.rows / 2; + _halfNBcolumns = _filterOutput.cols / 2; + + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _localBuffer); + // reset buffers + clearAllBuffers(); +} + +void BasicRetinaFilter::setLPfilterParameters(const float beta, const float tau, const float desired_k, const unsigned int filterIndex) +{ + float _beta = beta + tau; + float k = desired_k; + // check if the spatial constant is correct (avoid 0 value to avoid division by 0) + if (desired_k <= 0) + { + k = 0.001f; + std::cerr << "BasicRetinaFilter::spatial constant of the low pass filter must be superior to zero !!! correcting parameter setting to 0,001" << std::endl; + } + + float _alpha = k * k; + float _mu = 0.8f; + unsigned int tableOffset = filterIndex * 3; + if (k <= 0) + { + std::cerr << "BasicRetinaFilter::spatial filtering coefficient must be superior to zero, correcting value to 0.01" << std::endl; + _alpha = 0.0001f; + } + + float _temp = (1.0f + _beta) / (2.0f * _mu * _alpha); + float a = _filteringCoeficientsTable[tableOffset] = 1.0f + _temp - (float)sqrt( (1.0f + _temp) * (1.0f + _temp) - 1.0f); + _filteringCoeficientsTable[1 + tableOffset] = (1.0f - a) * (1.0f - a) * (1.0f - a) * (1.0f - a) / (1.0f + _beta); + _filteringCoeficientsTable[2 + tableOffset] = tau; +} +const oclMat &BasicRetinaFilter::runFilter_LocalAdapdation(const oclMat &inputFrame, const oclMat &localLuminance) +{ + _localLuminanceAdaptation(inputFrame, localLuminance, _filterOutput); + return _filterOutput; +} + + +void BasicRetinaFilter::runFilter_LocalAdapdation(const oclMat &inputFrame, const oclMat &localLuminance, oclMat &outputFrame) +{ + _localLuminanceAdaptation(inputFrame, localLuminance, outputFrame); +} + +const oclMat &BasicRetinaFilter::runFilter_LocalAdapdation_autonomous(const oclMat &inputFrame) +{ + _spatiotemporalLPfilter(inputFrame, _filterOutput); + _localLuminanceAdaptation(inputFrame, _filterOutput, _filterOutput); + return _filterOutput; +} +void BasicRetinaFilter::runFilter_LocalAdapdation_autonomous(const oclMat &inputFrame, oclMat &outputFrame) +{ + _spatiotemporalLPfilter(inputFrame, _filterOutput); + _localLuminanceAdaptation(inputFrame, _filterOutput, outputFrame); +} + +void BasicRetinaFilter::_localLuminanceAdaptation(oclMat &inputOutputFrame, const oclMat &localLuminance) +{ + _localLuminanceAdaptation(inputOutputFrame, localLuminance, inputOutputFrame, false); +} + +void BasicRetinaFilter::_localLuminanceAdaptation(const oclMat &inputFrame, const oclMat &localLuminance, oclMat &outputFrame, const bool updateLuminanceMean) +{ + if (updateLuminanceMean) + { + float meanLuminance = saturate_cast(ocl::sum(inputFrame)[0]) / getNBpixels(); + updateCompressionParameter(meanLuminance); + } + int elements_per_row = static_cast(inputFrame.step / inputFrame.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {_NBcols, _NBrows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &localLuminance.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &inputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &outputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBcols)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBrows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_float), &_localLuminanceAddon)); + args.push_back(std::make_pair(sizeof(cl_float), &_localLuminanceFactor)); + args.push_back(std::make_pair(sizeof(cl_float), &_maxInputValue)); + openCLExecuteKernel(ctx, &retina_kernel, "localLuminanceAdaptation", globalSize, localSize, args, -1, -1); +} + +const oclMat &BasicRetinaFilter::runFilter_LPfilter(const oclMat &inputFrame, const unsigned int filterIndex) +{ + _spatiotemporalLPfilter(inputFrame, _filterOutput, filterIndex); + return _filterOutput; +} +void BasicRetinaFilter::runFilter_LPfilter(const oclMat &inputFrame, oclMat &outputFrame, const unsigned int filterIndex) +{ + _spatiotemporalLPfilter(inputFrame, outputFrame, filterIndex); +} + +void BasicRetinaFilter::_spatiotemporalLPfilter(const oclMat &inputFrame, oclMat &LPfilterOutput, const unsigned int filterIndex) +{ + unsigned int coefTableOffset = filterIndex * 3; + + _a = _filteringCoeficientsTable[coefTableOffset]; + _gain = _filteringCoeficientsTable[1 + coefTableOffset]; + _tau = _filteringCoeficientsTable[2 + coefTableOffset]; + + _horizontalCausalFilter_addInput(inputFrame, LPfilterOutput); + _horizontalAnticausalFilter(LPfilterOutput); + _verticalCausalFilter(LPfilterOutput); + _verticalAnticausalFilter_multGain(LPfilterOutput); +} + +void BasicRetinaFilter::_horizontalCausalFilter_addInput(const oclMat &inputFrame, oclMat &outputFrame) +{ + int elements_per_row = static_cast(inputFrame.step / inputFrame.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {_NBrows, 1, 1}; + size_t localSize[] = {256, 1, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &inputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &outputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBcols)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBrows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_int), &inputFrame.offset)); + args.push_back(std::make_pair(sizeof(cl_int), &inputFrame.offset)); + args.push_back(std::make_pair(sizeof(cl_float), &_tau)); + args.push_back(std::make_pair(sizeof(cl_float), &_a)); + openCLExecuteKernel(ctx, &retina_kernel, "horizontalCausalFilter_addInput", globalSize, localSize, args, -1, -1); +} + +void BasicRetinaFilter::_horizontalAnticausalFilter(oclMat &outputFrame) +{ + int elements_per_row = static_cast(outputFrame.step / outputFrame.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {_NBrows, 1, 1}; + size_t localSize[] = {256, 1, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &outputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBcols)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBrows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.offset)); + args.push_back(std::make_pair(sizeof(cl_float), &_a)); + openCLExecuteKernel(ctx, &retina_kernel, "horizontalAnticausalFilter", globalSize, localSize, args, -1, -1); +} + +void BasicRetinaFilter::_verticalCausalFilter(oclMat &outputFrame) +{ + int elements_per_row = static_cast(outputFrame.step / outputFrame.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {_NBcols, 1, 1}; + size_t localSize[] = {256, 1, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &outputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBcols)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBrows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.offset)); + args.push_back(std::make_pair(sizeof(cl_float), &_a)); + openCLExecuteKernel(ctx, &retina_kernel, "verticalCausalFilter", globalSize, localSize, args, -1, -1); +} + +void BasicRetinaFilter::_verticalAnticausalFilter_multGain(oclMat &outputFrame) +{ + int elements_per_row = static_cast(outputFrame.step / outputFrame.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {_NBcols, 1, 1}; + size_t localSize[] = {256, 1, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &outputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBcols)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBrows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.offset)); + args.push_back(std::make_pair(sizeof(cl_float), &_a)); + args.push_back(std::make_pair(sizeof(cl_float), &_gain)); + openCLExecuteKernel(ctx, &retina_kernel, "verticalAnticausalFilter_multGain", globalSize, localSize, args, -1, -1); +} + +void BasicRetinaFilter::_horizontalAnticausalFilter_Irregular(oclMat &outputFrame, const oclMat &spatialConstantBuffer) +{ + int elements_per_row = static_cast(outputFrame.step / outputFrame.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {outputFrame.rows, 1, 1}; + size_t localSize[] = {256, 1, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &outputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &spatialConstantBuffer.data)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.offset)); + args.push_back(std::make_pair(sizeof(cl_int), &spatialConstantBuffer.offset)); + openCLExecuteKernel(ctx, &retina_kernel, "horizontalAnticausalFilter_Irregular", globalSize, localSize, args, -1, -1); +} + +// vertical anticausal filter +void BasicRetinaFilter::_verticalCausalFilter_Irregular(oclMat &outputFrame, const oclMat &spatialConstantBuffer) +{ + int elements_per_row = static_cast(outputFrame.step / outputFrame.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {outputFrame.cols, 1, 1}; + size_t localSize[] = {256, 1, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &outputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &spatialConstantBuffer.data)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.offset)); + args.push_back(std::make_pair(sizeof(cl_int), &spatialConstantBuffer.offset)); + openCLExecuteKernel(ctx, &retina_kernel, "verticalCausalFilter_Irregular", globalSize, localSize, args, -1, -1); +} + +void normalizeGrayOutput_0_maxOutputValue(oclMat &inputOutputBuffer, const float maxOutputValue) +{ + double min_val, max_val; + ocl::minMax(inputOutputBuffer, &min_val, &max_val); + float factor = maxOutputValue / static_cast(max_val - min_val); + float offset = - static_cast(min_val) * factor; + ocl::multiply(factor, inputOutputBuffer, inputOutputBuffer); + ocl::add(inputOutputBuffer, offset, inputOutputBuffer); +} + +void normalizeGrayOutputCentredSigmoide(const float meanValue, const float sensitivity, oclMat &in, oclMat &out, const float maxValue) +{ + if (sensitivity == 1.0f) + { + std::cerr << "TemplateBuffer::TemplateBuffer::normalizeGrayOutputCentredSigmoide error: 2nd parameter (sensitivity) must not equal 0, copying original data..." << std::endl; + in.copyTo(out); + return; + } + + float X0 = maxValue / (sensitivity - 1.0f); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {in.cols, out.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + int elements_per_row = static_cast(out.step / out.elemSize()); + + args.push_back(std::make_pair(sizeof(cl_mem), &in.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &out.data)); + args.push_back(std::make_pair(sizeof(cl_int), &in.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &in.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_float), &meanValue)); + args.push_back(std::make_pair(sizeof(cl_float), &X0)); + openCLExecuteKernel(ctx, &retina_kernel, "normalizeGrayOutputCentredSigmoide", globalSize, localSize, args, -1, -1); +} + +void normalizeGrayOutputNearZeroCentreredSigmoide(oclMat &inputPicture, oclMat &outputBuffer, const float sensitivity, const float maxOutputValue) +{ + float X0cube = sensitivity * sensitivity * sensitivity; + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {inputPicture.cols, inputPicture.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + int elements_per_row = static_cast(inputPicture.step / inputPicture.elemSize()); + args.push_back(std::make_pair(sizeof(cl_mem), &inputPicture.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &outputBuffer.data)); + args.push_back(std::make_pair(sizeof(cl_int), &inputPicture.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &inputPicture.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_float), &maxOutputValue)); + args.push_back(std::make_pair(sizeof(cl_float), &X0cube)); + openCLExecuteKernel(ctx, &retina_kernel, "normalizeGrayOutputNearZeroCentreredSigmoide", globalSize, localSize, args, -1, -1); +} + +void centerReductImageLuminance(oclMat &inputoutput) +{ + Scalar mean, stddev; + cv::meanStdDev((Mat)inputoutput, mean, stddev); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {inputoutput.cols, inputoutput.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + float f_mean = static_cast(mean[0]); + float f_stddev = static_cast(stddev[0]); + int elements_per_row = static_cast(inputoutput.step / inputoutput.elemSize()); + args.push_back(std::make_pair(sizeof(cl_mem), &inputoutput.data)); + args.push_back(std::make_pair(sizeof(cl_int), &inputoutput.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &inputoutput.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_float), &f_mean)); + args.push_back(std::make_pair(sizeof(cl_float), &f_stddev)); + openCLExecuteKernel(ctx, &retina_kernel, "centerReductImageLuminance", globalSize, localSize, args, -1, -1); +} + +/////////////////////////////////////// +///////// ParvoRetinaFilter /////////// +/////////////////////////////////////// +ParvoRetinaFilter::ParvoRetinaFilter(const unsigned int NBrows, const unsigned int NBcolumns) + : BasicRetinaFilter(NBrows, NBcolumns, 3), + _photoreceptorsOutput(NBrows, NBcolumns, CV_32FC1), + _horizontalCellsOutput(NBrows, NBcolumns, CV_32FC1), + _parvocellularOutputON(NBrows, NBcolumns, CV_32FC1), + _parvocellularOutputOFF(NBrows, NBcolumns, CV_32FC1), + _bipolarCellsOutputON(NBrows, NBcolumns, CV_32FC1), + _bipolarCellsOutputOFF(NBrows, NBcolumns, CV_32FC1), + _localAdaptationOFF(NBrows, NBcolumns, CV_32FC1) +{ + // link to the required local parent adaptation buffers + _localAdaptationON = _localBuffer; + _parvocellularOutputONminusOFF = _filterOutput; + + // init: set all the values to 0 + clearAllBuffers(); +} + +ParvoRetinaFilter::~ParvoRetinaFilter() +{ +} + +void ParvoRetinaFilter::clearAllBuffers() +{ + BasicRetinaFilter::clearAllBuffers(); + _photoreceptorsOutput = 0; + _horizontalCellsOutput = 0; + _parvocellularOutputON = 0; + _parvocellularOutputOFF = 0; + _bipolarCellsOutputON = 0; + _bipolarCellsOutputOFF = 0; + _localAdaptationOFF = 0; +} +void ParvoRetinaFilter::resize(const unsigned int NBrows, const unsigned int NBcolumns) +{ + BasicRetinaFilter::resize(NBrows, NBcolumns); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _photoreceptorsOutput); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _horizontalCellsOutput); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _parvocellularOutputON); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _parvocellularOutputOFF); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _bipolarCellsOutputON); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _bipolarCellsOutputOFF); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _localAdaptationOFF); + + // link to the required local parent adaptation buffers + _localAdaptationON = _localBuffer; + _parvocellularOutputONminusOFF = _filterOutput; + + // clean buffers + clearAllBuffers(); +} + +void ParvoRetinaFilter::setOPLandParvoFiltersParameters(const float beta1, const float tau1, const float k1, const float beta2, const float tau2, const float k2) +{ + // init photoreceptors low pass filter + setLPfilterParameters(beta1, tau1, k1); + // init horizontal cells low pass filter + setLPfilterParameters(beta2, tau2, k2, 1); + // init parasol ganglion cells low pass filter (default parameters) + setLPfilterParameters(0, tau1, k1, 2); + +} +const oclMat &ParvoRetinaFilter::runFilter(const oclMat &inputFrame, const bool useParvoOutput) +{ + _spatiotemporalLPfilter(inputFrame, _photoreceptorsOutput); + _spatiotemporalLPfilter(_photoreceptorsOutput, _horizontalCellsOutput, 1); + _OPL_OnOffWaysComputing(); + + if (useParvoOutput) + { + // local adaptation processes on ON and OFF ways + _spatiotemporalLPfilter(_bipolarCellsOutputON, _localAdaptationON, 2); + _localLuminanceAdaptation(_parvocellularOutputON, _localAdaptationON); + _spatiotemporalLPfilter(_bipolarCellsOutputOFF, _localAdaptationOFF, 2); + _localLuminanceAdaptation(_parvocellularOutputOFF, _localAdaptationOFF); + ocl::subtract(_parvocellularOutputON, _parvocellularOutputOFF, _parvocellularOutputONminusOFF); + } + + return _parvocellularOutputONminusOFF; +} +void ParvoRetinaFilter::_OPL_OnOffWaysComputing() +{ + int elements_per_row = static_cast(_photoreceptorsOutput.step / _photoreceptorsOutput.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {(_photoreceptorsOutput.cols + 3) / 4, _photoreceptorsOutput.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &_photoreceptorsOutput.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &_horizontalCellsOutput.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &_bipolarCellsOutputON.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &_bipolarCellsOutputOFF.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &_parvocellularOutputON.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &_parvocellularOutputOFF.data)); + args.push_back(std::make_pair(sizeof(cl_int), &_photoreceptorsOutput.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &_photoreceptorsOutput.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + openCLExecuteKernel(ctx, &retina_kernel, "OPL_OnOffWaysComputing", globalSize, localSize, args, -1, -1); +} + +/////////////////////////////////////// +//////////// MagnoFilter ////////////// +/////////////////////////////////////// +MagnoRetinaFilter::MagnoRetinaFilter(const unsigned int NBrows, const unsigned int NBcolumns) + : BasicRetinaFilter(NBrows, NBcolumns, 2), + _previousInput_ON(NBrows, NBcolumns, CV_32FC1), + _previousInput_OFF(NBrows, NBcolumns, CV_32FC1), + _amacrinCellsTempOutput_ON(NBrows, NBcolumns, CV_32FC1), + _amacrinCellsTempOutput_OFF(NBrows, NBcolumns, CV_32FC1), + _magnoXOutputON(NBrows, NBcolumns, CV_32FC1), + _magnoXOutputOFF(NBrows, NBcolumns, CV_32FC1), + _localProcessBufferON(NBrows, NBcolumns, CV_32FC1), + _localProcessBufferOFF(NBrows, NBcolumns, CV_32FC1) +{ + _magnoYOutput = _filterOutput; + _magnoYsaturated = _localBuffer; + + clearAllBuffers(); +} + +MagnoRetinaFilter::~MagnoRetinaFilter() +{ +} +void MagnoRetinaFilter::clearAllBuffers() +{ + BasicRetinaFilter::clearAllBuffers(); + _previousInput_ON = 0; + _previousInput_OFF = 0; + _amacrinCellsTempOutput_ON = 0; + _amacrinCellsTempOutput_OFF = 0; + _magnoXOutputON = 0; + _magnoXOutputOFF = 0; + _localProcessBufferON = 0; + _localProcessBufferOFF = 0; + +} +void MagnoRetinaFilter::resize(const unsigned int NBrows, const unsigned int NBcolumns) +{ + BasicRetinaFilter::resize(NBrows, NBcolumns); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _previousInput_ON); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _previousInput_OFF); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _amacrinCellsTempOutput_ON); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _amacrinCellsTempOutput_OFF); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _magnoXOutputON); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _magnoXOutputOFF); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _localProcessBufferON); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _localProcessBufferOFF); + + // to be sure, relink buffers + _magnoYOutput = _filterOutput; + _magnoYsaturated = _localBuffer; + + // reset all buffers + clearAllBuffers(); +} + +void MagnoRetinaFilter::setCoefficientsTable(const float parasolCells_beta, const float parasolCells_tau, const float parasolCells_k, const float amacrinCellsTemporalCutFrequency, const float localAdaptIntegration_tau, const float localAdaptIntegration_k ) +{ + _temporalCoefficient = (float)std::exp(-1.0f / amacrinCellsTemporalCutFrequency); + // the first set of parameters is dedicated to the low pass filtering property of the ganglion cells + BasicRetinaFilter::setLPfilterParameters(parasolCells_beta, parasolCells_tau, parasolCells_k, 0); + // the second set of parameters is dedicated to the ganglion cells output intergartion for their local adaptation property + BasicRetinaFilter::setLPfilterParameters(0, localAdaptIntegration_tau, localAdaptIntegration_k, 1); +} + +void MagnoRetinaFilter::_amacrineCellsComputing( + const oclMat &OPL_ON, + const oclMat &OPL_OFF +) +{ + int elements_per_row = static_cast(OPL_ON.step / OPL_ON.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {OPL_ON.cols, OPL_ON.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &OPL_ON.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &OPL_OFF.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &_previousInput_ON.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &_previousInput_OFF.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &_amacrinCellsTempOutput_ON.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &_amacrinCellsTempOutput_OFF.data)); + args.push_back(std::make_pair(sizeof(cl_int), &OPL_ON.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &OPL_ON.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_float), &_temporalCoefficient)); + openCLExecuteKernel(ctx, &retina_kernel, "amacrineCellsComputing", globalSize, localSize, args, -1, -1); +} + +const oclMat &MagnoRetinaFilter::runFilter(const oclMat &OPL_ON, const oclMat &OPL_OFF) +{ + // Compute the high pass temporal filter + _amacrineCellsComputing(OPL_ON, OPL_OFF); + + // apply low pass filtering on ON and OFF ways after temporal high pass filtering + _spatiotemporalLPfilter(_amacrinCellsTempOutput_ON, _magnoXOutputON, 0); + _spatiotemporalLPfilter(_amacrinCellsTempOutput_OFF, _magnoXOutputOFF, 0); + + // local adaptation of the ganglion cells to the local contrast of the moving contours + _spatiotemporalLPfilter(_magnoXOutputON, _localProcessBufferON, 1); + _localLuminanceAdaptation(_magnoXOutputON, _localProcessBufferON); + + _spatiotemporalLPfilter(_magnoXOutputOFF, _localProcessBufferOFF, 1); + _localLuminanceAdaptation(_magnoXOutputOFF, _localProcessBufferOFF); + + _magnoYOutput = _magnoXOutputON + _magnoXOutputOFF; + + return _magnoYOutput; +} + +/////////////////////////////////////// +//////////// RetinaColor ////////////// +/////////////////////////////////////// + +// define an array of ROI headers of input x +#define MAKE_OCLMAT_SLICES(x, n) \ + oclMat x##_slices[n];\ + for(int _SLICE_INDEX_ = 0; _SLICE_INDEX_ < n; _SLICE_INDEX_ ++)\ + {\ + x##_slices[_SLICE_INDEX_] = x(getROI(_SLICE_INDEX_));\ + } + +RetinaColor::RetinaColor(const unsigned int NBrows, const unsigned int NBcolumns, const int samplingMethod) + : BasicRetinaFilter(NBrows, NBcolumns, 3), + _RGBmosaic(NBrows * 3, NBcolumns, CV_32FC1), + _tempMultiplexedFrame(NBrows, NBcolumns, CV_32FC1), + _demultiplexedTempBuffer(NBrows * 3, NBcolumns, CV_32FC1), + _demultiplexedColorFrame(NBrows * 3, NBcolumns, CV_32FC1), + _chrominance(NBrows * 3, NBcolumns, CV_32FC1), + _colorLocalDensity(NBrows * 3, NBcolumns, CV_32FC1), + _imageGradient(NBrows * 3, NBcolumns, CV_32FC1) +{ + // link to parent buffers (let's recycle !) + _luminance = _filterOutput; + _multiplexedFrame = _localBuffer; + + _objectInit = false; + _samplingMethod = samplingMethod; + _saturateColors = false; + _colorSaturationValue = 4.0; + + // set default spatio-temporal filter parameters + setLPfilterParameters(0.0, 0.0, 1.5); + setLPfilterParameters(0.0, 0.0, 10.5, 1);// for the low pass filter dedicated to contours energy extraction (demultiplexing process) + setLPfilterParameters(0.f, 0.f, 0.9f, 2); + + // init default value on image Gradient + _imageGradient = 0.57f; + + // init color sampling map + _initColorSampling(); + + // flush all buffers + clearAllBuffers(); +} + +RetinaColor::~RetinaColor() +{ + +} + +void RetinaColor::clearAllBuffers() +{ + BasicRetinaFilter::clearAllBuffers(); + _tempMultiplexedFrame = 0.f; + _demultiplexedTempBuffer = 0.f; + + _demultiplexedColorFrame = 0.f; + _chrominance = 0.f; + _imageGradient = 0.57f; +} + +void RetinaColor::resize(const unsigned int NBrows, const unsigned int NBcolumns) +{ + BasicRetinaFilter::clearAllBuffers(); + ensureSizeIsEnough(NBrows, NBcolumns, CV_32FC1, _tempMultiplexedFrame); + ensureSizeIsEnough(NBrows * 2, NBcolumns, CV_32FC1, _imageGradient); + ensureSizeIsEnough(NBrows * 3, NBcolumns, CV_32FC1, _RGBmosaic); + ensureSizeIsEnough(NBrows * 3, NBcolumns, CV_32FC1, _demultiplexedTempBuffer); + ensureSizeIsEnough(NBrows * 3, NBcolumns, CV_32FC1, _demultiplexedColorFrame); + ensureSizeIsEnough(NBrows * 3, NBcolumns, CV_32FC1, _chrominance); + ensureSizeIsEnough(NBrows * 3, NBcolumns, CV_32FC1, _colorLocalDensity); + + // link to parent buffers (let's recycle !) + _luminance = _filterOutput; + _multiplexedFrame = _localBuffer; + + // init color sampling map + _initColorSampling(); + + // clean buffers + clearAllBuffers(); +} + +static void inverseValue(oclMat &input) +{ + int elements_per_row = static_cast(input.step / input.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {input.cols, input.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &input.data)); + args.push_back(std::make_pair(sizeof(cl_int), &input.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &input.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + openCLExecuteKernel(ctx, &retina_kernel, "inverseValue", globalSize, localSize, args, -1, -1); +} + +void RetinaColor::_initColorSampling() +{ + CV_Assert(_samplingMethod == RETINA_COLOR_BAYER); + _pR = _pB = 0.25; + _pG = 0.5; + // filling the mosaic buffer: + _RGBmosaic = 0; + Mat tmp_mat(_NBrows * 3, _NBcols, CV_32FC1); + float * tmp_mat_ptr = tmp_mat.ptr(); + tmp_mat.setTo(0); + for (unsigned int index = 0 ; index < getNBpixels(); ++index) + { + tmp_mat_ptr[bayerSampleOffset(index)] = 1.0; + } + _RGBmosaic.upload(tmp_mat); + // computing photoreceptors local density + MAKE_OCLMAT_SLICES(_RGBmosaic, 3); + MAKE_OCLMAT_SLICES(_colorLocalDensity, 3); + _colorLocalDensity.setTo(0); + _spatiotemporalLPfilter(_RGBmosaic_slices[0], _colorLocalDensity_slices[0]); + _spatiotemporalLPfilter(_RGBmosaic_slices[1], _colorLocalDensity_slices[1]); + _spatiotemporalLPfilter(_RGBmosaic_slices[2], _colorLocalDensity_slices[2]); + + //_colorLocalDensity = oclMat(_colorLocalDensity.size(), _colorLocalDensity.type(), 1.f) / _colorLocalDensity; + inverseValue(_colorLocalDensity); + + _objectInit = true; +} + +static void demultiplex(const oclMat &input, oclMat &ouput) +{ + int elements_per_row = static_cast(input.step / input.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {input.cols, input.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &input.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &ouput.data)); + args.push_back(std::make_pair(sizeof(cl_int), &input.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &input.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + openCLExecuteKernel(ctx, &retina_kernel, "runColorDemultiplexingBayer", globalSize, localSize, args, -1, -1); +} + +static void normalizePhotoDensity( + const oclMat &chroma, + const oclMat &colorDensity, + const oclMat &multiplex, + oclMat &ocl_luma, + oclMat &demultiplex, + const float pG +) +{ + int elements_per_row = static_cast(ocl_luma.step / ocl_luma.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {ocl_luma.cols, ocl_luma.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &chroma.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &colorDensity.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &multiplex.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &ocl_luma.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &demultiplex.data)); + args.push_back(std::make_pair(sizeof(cl_int), &ocl_luma.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &ocl_luma.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_float), &pG)); + openCLExecuteKernel(ctx, &retina_kernel, "normalizePhotoDensity", globalSize, localSize, args, -1, -1); +} + +static void substractResidual( + oclMat &colorDemultiplex, + float pR, + float pG, + float pB +) +{ + int elements_per_row = static_cast(colorDemultiplex.step / colorDemultiplex.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + int rows = colorDemultiplex.rows / 3, cols = colorDemultiplex.cols; + size_t globalSize[] = {cols, rows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &colorDemultiplex.data)); + args.push_back(std::make_pair(sizeof(cl_int), &cols)); + args.push_back(std::make_pair(sizeof(cl_int), &rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_float), &pR)); + args.push_back(std::make_pair(sizeof(cl_float), &pG)); + args.push_back(std::make_pair(sizeof(cl_float), &pB)); + openCLExecuteKernel(ctx, &retina_kernel, "substractResidual", globalSize, localSize, args, -1, -1); +} + +static void demultiplexAssign(const oclMat& input, const oclMat& output) +{ + // only supports bayer + int elements_per_row = static_cast(input.step / input.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + int rows = input.rows / 3, cols = input.cols; + size_t globalSize[] = {cols, rows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &input.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &output.data)); + args.push_back(std::make_pair(sizeof(cl_int), &cols)); + args.push_back(std::make_pair(sizeof(cl_int), &rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + openCLExecuteKernel(ctx, &retina_kernel, "demultiplexAssign", globalSize, localSize, args, -1, -1); +} + +void RetinaColor::runColorDemultiplexing( + const oclMat &ocl_multiplexed_input, + const bool adaptiveFiltering, + const float maxInputValue +) +{ + MAKE_OCLMAT_SLICES(_demultiplexedTempBuffer, 3); + MAKE_OCLMAT_SLICES(_chrominance, 3); + MAKE_OCLMAT_SLICES(_RGBmosaic, 3); + MAKE_OCLMAT_SLICES(_demultiplexedColorFrame, 3); + MAKE_OCLMAT_SLICES(_colorLocalDensity, 3); + + _demultiplexedTempBuffer.setTo(0); + demultiplex(ocl_multiplexed_input, _demultiplexedTempBuffer); + + // interpolate the demultiplexed frame depending on the color sampling method + if (!adaptiveFiltering) + { + CV_Assert(adaptiveFiltering == false); + } + + _spatiotemporalLPfilter(_demultiplexedTempBuffer_slices[0], _chrominance_slices[0]); + _spatiotemporalLPfilter(_demultiplexedTempBuffer_slices[1], _chrominance_slices[1]); + _spatiotemporalLPfilter(_demultiplexedTempBuffer_slices[2], _chrominance_slices[2]); + + if (!adaptiveFiltering)// compute the gradient on the luminance + { + // TODO: implement me! + CV_Assert(adaptiveFiltering == false); + } + else + { + normalizePhotoDensity(_chrominance, _colorLocalDensity, ocl_multiplexed_input, _luminance, _demultiplexedTempBuffer, _pG); + // compute the gradient of the luminance + _computeGradient(_luminance, _imageGradient); + + _adaptiveSpatialLPfilter(_RGBmosaic_slices[0], _imageGradient, _chrominance_slices[0]); + _adaptiveSpatialLPfilter(_RGBmosaic_slices[1], _imageGradient, _chrominance_slices[1]); + _adaptiveSpatialLPfilter(_RGBmosaic_slices[2], _imageGradient, _chrominance_slices[2]); + + _adaptiveSpatialLPfilter(_demultiplexedTempBuffer_slices[0], _imageGradient, _demultiplexedColorFrame_slices[0]); + _adaptiveSpatialLPfilter(_demultiplexedTempBuffer_slices[1], _imageGradient, _demultiplexedColorFrame_slices[1]); + _adaptiveSpatialLPfilter(_demultiplexedTempBuffer_slices[2], _imageGradient, _demultiplexedColorFrame_slices[2]); + + _demultiplexedColorFrame /= _chrominance; // per element division + substractResidual(_demultiplexedColorFrame, _pR, _pG, _pB); + runColorMultiplexing(_demultiplexedColorFrame, _tempMultiplexedFrame); + + _demultiplexedTempBuffer.setTo(0); + _luminance = ocl_multiplexed_input - _tempMultiplexedFrame; + demultiplexAssign(_demultiplexedColorFrame, _demultiplexedTempBuffer); + + for(int i = 0; i < 3; i ++) + { + _spatiotemporalLPfilter(_demultiplexedTempBuffer_slices[i], _demultiplexedTempBuffer_slices[i]); + _demultiplexedColorFrame_slices[i] = _demultiplexedTempBuffer_slices[i] * _colorLocalDensity_slices[i] + _luminance; + } + } + // eliminate saturated colors by simple clipping values to the input range + clipRGBOutput_0_maxInputValue(_demultiplexedColorFrame, maxInputValue); + + if (_saturateColors) + { + ocl::normalizeGrayOutputCentredSigmoide(128, maxInputValue, _demultiplexedColorFrame, _demultiplexedColorFrame); + } +} +void RetinaColor::runColorMultiplexing(const oclMat &demultiplexedInputFrame, oclMat &multiplexedFrame) +{ + int elements_per_row = static_cast(multiplexedFrame.step / multiplexedFrame.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {multiplexedFrame.cols, multiplexedFrame.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &demultiplexedInputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &multiplexedFrame.data)); + args.push_back(std::make_pair(sizeof(cl_int), &multiplexedFrame.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &multiplexedFrame.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + openCLExecuteKernel(ctx, &retina_kernel, "runColorMultiplexingBayer", globalSize, localSize, args, -1, -1); +} + +void RetinaColor::clipRGBOutput_0_maxInputValue(oclMat &inputOutputBuffer, const float maxInputValue) +{ + // the kernel is equivalent to: + //ocl::threshold(inputOutputBuffer, inputOutputBuffer, maxInputValue, maxInputValue, THRESH_TRUNC); + //ocl::threshold(inputOutputBuffer, inputOutputBuffer, 0, 0, THRESH_TOZERO); + int elements_per_row = static_cast(inputOutputBuffer.step / inputOutputBuffer.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {_NBcols, inputOutputBuffer.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &inputOutputBuffer.data)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBcols)); + args.push_back(std::make_pair(sizeof(cl_int), &inputOutputBuffer.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_float), &maxInputValue)); + openCLExecuteKernel(ctx, &retina_kernel, "clipRGBOutput_0_maxInputValue", globalSize, localSize, args, -1, -1); +} + +void RetinaColor::_adaptiveSpatialLPfilter(const oclMat &inputFrame, const oclMat &gradient, oclMat &outputFrame) +{ + /**********/ + _gain = (1 - 0.57f) * (1 - 0.57f) * (1 - 0.06f) * (1 - 0.06f); + + // launch the serie of 1D directional filters in order to compute the 2D low pass filter + // -> horizontal filters work with the first layer of imageGradient + _adaptiveHorizontalCausalFilter_addInput(inputFrame, gradient, outputFrame); + _horizontalAnticausalFilter_Irregular(outputFrame, gradient); + // -> horizontal filters work with the second layer of imageGradient + _verticalCausalFilter_Irregular(outputFrame, gradient(getROI(1))); + _adaptiveVerticalAnticausalFilter_multGain(gradient, outputFrame); +} + +void RetinaColor::_adaptiveHorizontalCausalFilter_addInput(const oclMat &inputFrame, const oclMat &gradient, oclMat &outputFrame) +{ + int elements_per_row = static_cast(inputFrame.step / inputFrame.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {_NBrows, 1, 1}; + size_t localSize[] = {256, 1, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &inputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &gradient.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &outputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBcols)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBrows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_int), &inputFrame.offset)); + args.push_back(std::make_pair(sizeof(cl_int), &gradient.offset)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.offset)); + openCLExecuteKernel(ctx, &retina_kernel, "adaptiveHorizontalCausalFilter_addInput", globalSize, localSize, args, -1, -1); +} + +void RetinaColor::_adaptiveVerticalAnticausalFilter_multGain(const oclMat &gradient, oclMat &outputFrame) +{ + int elements_per_row = static_cast(outputFrame.step / outputFrame.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {_NBcols, 1, 1}; + size_t localSize[] = {256, 1, 1}; + + int gradOffset = gradient.offset + static_cast(gradient.step * _NBrows); + + args.push_back(std::make_pair(sizeof(cl_mem), &gradient.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &outputFrame.data)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBcols)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBrows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_int), &gradOffset)); + args.push_back(std::make_pair(sizeof(cl_int), &outputFrame.offset)); + args.push_back(std::make_pair(sizeof(cl_float), &_gain)); + openCLExecuteKernel(ctx, &retina_kernel, "adaptiveVerticalAnticausalFilter_multGain", globalSize, localSize, args, -1, -1); +} +void RetinaColor::_computeGradient(const oclMat &luminance, oclMat &gradient) +{ + int elements_per_row = static_cast(luminance.step / luminance.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {_NBcols, _NBrows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &luminance.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &gradient.data)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBcols)); + args.push_back(std::make_pair(sizeof(cl_int), &_NBrows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + openCLExecuteKernel(ctx, &retina_kernel, "computeGradient", globalSize, localSize, args, -1, -1); +} + +/////////////////////////////////////// +//////////// RetinaFilter ///////////// +/////////////////////////////////////// +RetinaFilter::RetinaFilter(const unsigned int sizeRows, const unsigned int sizeColumns, const bool colorMode, const int samplingMethod, const bool useRetinaLogSampling, const double, const double) + : + _photoreceptorsPrefilter(sizeRows, sizeColumns, 4), + _ParvoRetinaFilter(sizeRows, sizeColumns), + _MagnoRetinaFilter(sizeRows, sizeColumns), + _colorEngine(sizeRows, sizeColumns, samplingMethod) +{ + CV_Assert(!useRetinaLogSampling); + + // set default processing activities + _useParvoOutput = true; + _useMagnoOutput = true; + + _useColorMode = colorMode; + + // set default parameters + setGlobalParameters(); + + // stability controls values init + _setInitPeriodCount(); + _globalTemporalConstant = 25; + + // reset all buffers + clearAllBuffers(); +} + +RetinaFilter::~RetinaFilter() +{ +} + +void RetinaFilter::clearAllBuffers() +{ + _photoreceptorsPrefilter.clearAllBuffers(); + _ParvoRetinaFilter.clearAllBuffers(); + _MagnoRetinaFilter.clearAllBuffers(); + _colorEngine.clearAllBuffers(); + // stability controls value init + _setInitPeriodCount(); +} + +void RetinaFilter::resize(const unsigned int NBrows, const unsigned int NBcolumns) +{ + unsigned int rows = NBrows, cols = NBcolumns; + + // resize optionnal member and adjust other modules size if required + _photoreceptorsPrefilter.resize(rows, cols); + _ParvoRetinaFilter.resize(rows, cols); + _MagnoRetinaFilter.resize(rows, cols); + _colorEngine.resize(rows, cols); + + // clean buffers + clearAllBuffers(); + +} + +void RetinaFilter::_setInitPeriodCount() +{ + // find out the maximum temporal constant value and apply a security factor + // false value (obviously too long) but appropriate for simple use + _globalTemporalConstant = (unsigned int)(_ParvoRetinaFilter.getPhotoreceptorsTemporalConstant() + _ParvoRetinaFilter.getHcellsTemporalConstant() + _MagnoRetinaFilter.getTemporalConstant()); + // reset frame counter + _ellapsedFramesSinceLastReset = 0; +} + +void RetinaFilter::setGlobalParameters(const float OPLspatialResponse1, const float OPLtemporalresponse1, const float OPLassymetryGain, const float OPLspatialResponse2, const float OPLtemporalresponse2, const float LPfilterSpatialResponse, const float LPfilterGain, const float LPfilterTemporalresponse, const float MovingContoursExtractorCoefficient, const bool normalizeParvoOutput_0_maxOutputValue, const bool normalizeMagnoOutput_0_maxOutputValue, const float maxOutputValue, const float maxInputValue, const float meanValue) +{ + _normalizeParvoOutput_0_maxOutputValue = normalizeParvoOutput_0_maxOutputValue; + _normalizeMagnoOutput_0_maxOutputValue = normalizeMagnoOutput_0_maxOutputValue; + _maxOutputValue = maxOutputValue; + _photoreceptorsPrefilter.setV0CompressionParameter(0.9f, maxInputValue, meanValue); + _photoreceptorsPrefilter.setLPfilterParameters(0, 0, 10, 3); // keeps low pass filter with low cut frequency in memory (usefull for the tone mapping function) + _ParvoRetinaFilter.setOPLandParvoFiltersParameters(0, OPLtemporalresponse1, OPLspatialResponse1, OPLassymetryGain, OPLtemporalresponse2, OPLspatialResponse2); + _ParvoRetinaFilter.setV0CompressionParameter(0.9f, maxInputValue, meanValue); + _MagnoRetinaFilter.setCoefficientsTable(LPfilterGain, LPfilterTemporalresponse, LPfilterSpatialResponse, MovingContoursExtractorCoefficient, 0, 2.0f * LPfilterSpatialResponse); + _MagnoRetinaFilter.setV0CompressionParameter(0.7f, maxInputValue, meanValue); + + // stability controls value init + _setInitPeriodCount(); +} + +bool RetinaFilter::checkInput(const oclMat &input, const bool) +{ + BasicRetinaFilter *inputTarget = &_photoreceptorsPrefilter; + + bool test = (input.rows == static_cast(inputTarget->getNBrows()) + || input.rows == static_cast(inputTarget->getNBrows()) * 3 + || input.rows == static_cast(inputTarget->getNBrows()) * 4) + && input.cols == static_cast(inputTarget->getNBcolumns()); + if (!test) + { + std::cerr << "RetinaFilter::checkInput: input buffer does not match retina buffer size, conversion aborted" << std::endl; + return false; + } + + return true; +} + +// main function that runs the filter for a given input frame +bool RetinaFilter::runFilter(const oclMat &imageInput, const bool useAdaptiveFiltering, const bool processRetinaParvoMagnoMapping, const bool useColorMode, const bool inputIsColorMultiplexed) +{ + // preliminary check + bool processSuccess = true; + if (!checkInput(imageInput, useColorMode)) + { + return false; + } + + // run the color multiplexing if needed and compute each suub filter of the retina: + // -> local adaptation + // -> contours OPL extraction + // -> moving contours extraction + + // stability controls value update + ++_ellapsedFramesSinceLastReset; + + _useColorMode = useColorMode; + + oclMat selectedPhotoreceptorsLocalAdaptationInput = imageInput; + oclMat selectedPhotoreceptorsColorInput = imageInput; + + //********** Following is input data specific photoreceptors processing + if (useColorMode && (!inputIsColorMultiplexed)) // not multiplexed color input case + { + _colorEngine.runColorMultiplexing(selectedPhotoreceptorsColorInput); + selectedPhotoreceptorsLocalAdaptationInput = _colorEngine.getMultiplexedFrame(); + } + //********** Following is generic Retina processing + + // photoreceptors local adaptation + _photoreceptorsPrefilter.runFilter_LocalAdapdation(selectedPhotoreceptorsLocalAdaptationInput, _ParvoRetinaFilter.getHorizontalCellsOutput()); + + // run parvo filter + _ParvoRetinaFilter.runFilter(_photoreceptorsPrefilter.getOutput(), _useParvoOutput); + + if (_useParvoOutput) + { + _ParvoRetinaFilter.normalizeGrayOutputCentredSigmoide(); // models the saturation of the cells, usefull for visualisation of the ON-OFF Parvo Output, Bipolar cells outputs do not change !!! + _ParvoRetinaFilter.centerReductImageLuminance(); // best for further spectrum analysis + + if (_normalizeParvoOutput_0_maxOutputValue) + { + _ParvoRetinaFilter.normalizeGrayOutput_0_maxOutputValue(_maxOutputValue); + } + } + + if (_useParvoOutput && _useMagnoOutput) + { + _MagnoRetinaFilter.runFilter(_ParvoRetinaFilter.getBipolarCellsON(), _ParvoRetinaFilter.getBipolarCellsOFF()); + if (_normalizeMagnoOutput_0_maxOutputValue) + { + _MagnoRetinaFilter.normalizeGrayOutput_0_maxOutputValue(_maxOutputValue); + } + _MagnoRetinaFilter.normalizeGrayOutputNearZeroCentreredSigmoide(); + } + + if (_useParvoOutput && _useMagnoOutput && processRetinaParvoMagnoMapping) + { + _processRetinaParvoMagnoMapping(); + if (_useColorMode) + { + _colorEngine.runColorDemultiplexing(_retinaParvoMagnoMappedFrame, useAdaptiveFiltering, _maxOutputValue); + } + return processSuccess; + } + + if (_useParvoOutput && _useColorMode) + { + _colorEngine.runColorDemultiplexing(_ParvoRetinaFilter.getOutput(), useAdaptiveFiltering, _maxOutputValue); + } + return processSuccess; +} + +const oclMat &RetinaFilter::getContours() +{ + if (_useColorMode) + { + return _colorEngine.getLuminance(); + } + else + { + return _ParvoRetinaFilter.getOutput(); + } +} +void RetinaFilter::_processRetinaParvoMagnoMapping() +{ + oclMat parvo = _ParvoRetinaFilter.getOutput(); + oclMat magno = _MagnoRetinaFilter.getOutput(); + + int halfRows = parvo.rows / 2; + int halfCols = parvo.cols / 2; + float minDistance = MIN(halfRows, halfCols) * 0.7f; + + int elements_per_row = static_cast(parvo.step / parvo.elemSize()); + + Context * ctx = Context::getContext(); + std::vector > args; + size_t globalSize[] = {parvo.cols, parvo.rows, 1}; + size_t localSize[] = {16, 16, 1}; + + args.push_back(std::make_pair(sizeof(cl_mem), &parvo.data)); + args.push_back(std::make_pair(sizeof(cl_mem), &magno.data)); + args.push_back(std::make_pair(sizeof(cl_int), &parvo.cols)); + args.push_back(std::make_pair(sizeof(cl_int), &parvo.rows)); + args.push_back(std::make_pair(sizeof(cl_int), &halfCols)); + args.push_back(std::make_pair(sizeof(cl_int), &halfRows)); + args.push_back(std::make_pair(sizeof(cl_int), &elements_per_row)); + args.push_back(std::make_pair(sizeof(cl_float), &minDistance)); + openCLExecuteKernel(ctx, &retina_kernel, "processRetinaParvoMagnoMapping", globalSize, localSize, args, -1, -1); +} +} /* namespace ocl */ + +Ptr createRetina_OCL(Size getInputSize){ return makePtr(getInputSize); } +Ptr createRetina_OCL(Size getInputSize, const bool colorMode, int colorSamplingMethod, const bool useRetinaLogSampling, const double reductionFactor, const double samplingStrenght) +{ + return makePtr(getInputSize, colorMode, colorSamplingMethod, useRetinaLogSampling, reductionFactor, samplingStrenght); +} + +} /* namespace bioinspired */ +} /* namespace cv */ + +#endif /* #ifdef HAVE_OPENCV_OCL */ diff --git a/modules/bioinspired/src/retina_ocl.hpp b/modules/bioinspired/src/retina_ocl.hpp new file mode 100644 index 00000000000..11da48b9314 --- /dev/null +++ b/modules/bioinspired/src/retina_ocl.hpp @@ -0,0 +1,634 @@ +/*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) 2010-2013, Multicoreware, Inc., all rights reserved. +// Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// @Authors +// Peng Xiao, pengxiao@multicorewareinc.com +// +// 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 oclMaterials 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 __OCL_RETINA_HPP__ +#define __OCL_RETINA_HPP__ + +#include "precomp.hpp" + +#ifdef HAVE_OPENCV_OCL + +// please refer to c++ headers for API comments +namespace cv +{ +namespace bioinspired +{ +namespace ocl +{ +void normalizeGrayOutputCentredSigmoide(const float meanValue, const float sensitivity, cv::ocl::oclMat &in, cv::ocl::oclMat &out, const float maxValue = 255.f); +void normalizeGrayOutput_0_maxOutputValue(cv::ocl::oclMat &inputOutputBuffer, const float maxOutputValue = 255.0); +void normalizeGrayOutputNearZeroCentreredSigmoide(cv::ocl::oclMat &inputPicture, cv::ocl::oclMat &outputBuffer, const float sensitivity = 40, const float maxOutputValue = 255.0f); +void centerReductImageLuminance(cv::ocl::oclMat &inputOutputBuffer); + +class BasicRetinaFilter +{ +public: + BasicRetinaFilter(const unsigned int NBrows, const unsigned int NBcolumns, const unsigned int parametersListSize = 1, const bool useProgressiveFilter = false); + ~BasicRetinaFilter(); + inline void clearOutputBuffer() + { + _filterOutput = 0; + } + inline void clearSecondaryBuffer() + { + _localBuffer = 0; + } + inline void clearAllBuffers() + { + clearOutputBuffer(); + clearSecondaryBuffer(); + } + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + const cv::ocl::oclMat &runFilter_LPfilter(const cv::ocl::oclMat &inputFrame, const unsigned int filterIndex = 0); + void runFilter_LPfilter(const cv::ocl::oclMat &inputFrame, cv::ocl::oclMat &outputFrame, const unsigned int filterIndex = 0); + void runFilter_LPfilter_Autonomous(cv::ocl::oclMat &inputOutputFrame, const unsigned int filterIndex = 0); + const cv::ocl::oclMat &runFilter_LocalAdapdation(const cv::ocl::oclMat &inputOutputFrame, const cv::ocl::oclMat &localLuminance); + void runFilter_LocalAdapdation(const cv::ocl::oclMat &inputFrame, const cv::ocl::oclMat &localLuminance, cv::ocl::oclMat &outputFrame); + const cv::ocl::oclMat &runFilter_LocalAdapdation_autonomous(const cv::ocl::oclMat &inputFrame); + void runFilter_LocalAdapdation_autonomous(const cv::ocl::oclMat &inputFrame, cv::ocl::oclMat &outputFrame); + void setLPfilterParameters(const float beta, const float tau, const float k, const unsigned int filterIndex = 0); + inline void setV0CompressionParameter(const float v0, const float maxInputValue, const float) + { + _v0 = v0 * maxInputValue; + _localLuminanceFactor = v0; + _localLuminanceAddon = maxInputValue * (1.0f - v0); + _maxInputValue = maxInputValue; + } + inline void setV0CompressionParameter(const float v0, const float meanLuminance) + { + this->setV0CompressionParameter(v0, _maxInputValue, meanLuminance); + } + inline void setV0CompressionParameter(const float v0) + { + _v0 = v0 * _maxInputValue; + _localLuminanceFactor = v0; + _localLuminanceAddon = _maxInputValue * (1.0f - v0); + } + inline void setV0CompressionParameterToneMapping(const float v0, const float maxInputValue, const float meanLuminance = 128.0f) + { + _v0 = v0 * maxInputValue; + _localLuminanceFactor = 1.0f; + _localLuminanceAddon = meanLuminance * _v0; + _maxInputValue = maxInputValue; + } + inline void updateCompressionParameter(const float meanLuminance) + { + _localLuminanceFactor = 1; + _localLuminanceAddon = meanLuminance * _v0; + } + inline float getV0CompressionParameter() + { + return _v0 / _maxInputValue; + } + inline const cv::ocl::oclMat &getOutput() const + { + return _filterOutput; + } + inline unsigned int getNBrows() + { + return _filterOutput.rows; + } + inline unsigned int getNBcolumns() + { + return _filterOutput.cols; + } + inline unsigned int getNBpixels() + { + return _filterOutput.size().area(); + } + inline void normalizeGrayOutput_0_maxOutputValue(const float maxValue) + { + ocl::normalizeGrayOutput_0_maxOutputValue(_filterOutput, maxValue); + } + inline void normalizeGrayOutputCentredSigmoide() + { + ocl::normalizeGrayOutputCentredSigmoide(0.0, 2.0, _filterOutput, _filterOutput); + } + inline void centerReductImageLuminance() + { + ocl::centerReductImageLuminance(_filterOutput); + } + inline float getMaxInputValue() + { + return this->_maxInputValue; + } + inline void setMaxInputValue(const float newMaxInputValue) + { + this->_maxInputValue = newMaxInputValue; + } + +protected: + + int _NBrows; + int _NBcols; + unsigned int _halfNBrows; + unsigned int _halfNBcolumns; + + cv::ocl::oclMat _filterOutput; + cv::ocl::oclMat _localBuffer; + + std::valarray _filteringCoeficientsTable; + float _v0; + float _maxInputValue; + float _meanInputValue; + float _localLuminanceFactor; + float _localLuminanceAddon; + + float _a; + float _tau; + float _gain; + + void _spatiotemporalLPfilter(const cv::ocl::oclMat &inputFrame, cv::ocl::oclMat &LPfilterOutput, const unsigned int coefTableOffset = 0); + float _squaringSpatiotemporalLPfilter(const cv::ocl::oclMat &inputFrame, cv::ocl::oclMat &outputFrame, const unsigned int filterIndex = 0); + void _spatiotemporalLPfilter_Irregular(const cv::ocl::oclMat &inputFrame, cv::ocl::oclMat &outputFrame, const unsigned int filterIndex = 0); + void _localSquaringSpatioTemporalLPfilter(const cv::ocl::oclMat &inputFrame, cv::ocl::oclMat &LPfilterOutput, const unsigned int *integrationAreas, const unsigned int filterIndex = 0); + void _localLuminanceAdaptation(const cv::ocl::oclMat &inputFrame, const cv::ocl::oclMat &localLuminance, cv::ocl::oclMat &outputFrame, const bool updateLuminanceMean = true); + void _localLuminanceAdaptation(cv::ocl::oclMat &inputOutputFrame, const cv::ocl::oclMat &localLuminance); + void _localLuminanceAdaptationPosNegValues(const cv::ocl::oclMat &inputFrame, const cv::ocl::oclMat &localLuminance, float *outputFrame); + void _horizontalCausalFilter_addInput(const cv::ocl::oclMat &inputFrame, cv::ocl::oclMat &outputFrame); + void _horizontalAnticausalFilter(cv::ocl::oclMat &outputFrame); + void _verticalCausalFilter(cv::ocl::oclMat &outputFrame); + void _horizontalAnticausalFilter_Irregular(cv::ocl::oclMat &outputFrame, const cv::ocl::oclMat &spatialConstantBuffer); + void _verticalCausalFilter_Irregular(cv::ocl::oclMat &outputFrame, const cv::ocl::oclMat &spatialConstantBuffer); + void _verticalAnticausalFilter_multGain(cv::ocl::oclMat &outputFrame); +}; + +class MagnoRetinaFilter: public BasicRetinaFilter +{ +public: + MagnoRetinaFilter(const unsigned int NBrows, const unsigned int NBcolumns); + virtual ~MagnoRetinaFilter(); + void clearAllBuffers(); + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + void setCoefficientsTable(const float parasolCells_beta, const float parasolCells_tau, const float parasolCells_k, const float amacrinCellsTemporalCutFrequency, const float localAdaptIntegration_tau, const float localAdaptIntegration_k); + + const cv::ocl::oclMat &runFilter(const cv::ocl::oclMat &OPL_ON, const cv::ocl::oclMat &OPL_OFF); + + inline const cv::ocl::oclMat &getMagnoON() const + { + return _magnoXOutputON; + } + inline const cv::ocl::oclMat &getMagnoOFF() const + { + return _magnoXOutputOFF; + } + inline const cv::ocl::oclMat &getMagnoYsaturated() const + { + return _magnoYsaturated; + } + inline void normalizeGrayOutputNearZeroCentreredSigmoide() + { + ocl::normalizeGrayOutputNearZeroCentreredSigmoide(_magnoYOutput, _magnoYsaturated); + } + inline float getTemporalConstant() + { + return this->_filteringCoeficientsTable[2]; + } +private: + cv::ocl::oclMat _previousInput_ON; + cv::ocl::oclMat _previousInput_OFF; + cv::ocl::oclMat _amacrinCellsTempOutput_ON; + cv::ocl::oclMat _amacrinCellsTempOutput_OFF; + cv::ocl::oclMat _magnoXOutputON; + cv::ocl::oclMat _magnoXOutputOFF; + cv::ocl::oclMat _localProcessBufferON; + cv::ocl::oclMat _localProcessBufferOFF; + cv::ocl::oclMat _magnoYOutput; + cv::ocl::oclMat _magnoYsaturated; + + float _temporalCoefficient; + void _amacrineCellsComputing(const cv::ocl::oclMat &OPL_ON, const cv::ocl::oclMat &OPL_OFF); +}; + +class ParvoRetinaFilter: public BasicRetinaFilter +{ +public: + ParvoRetinaFilter(const unsigned int NBrows = 480, const unsigned int NBcolumns = 640); + virtual ~ParvoRetinaFilter(); + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + void clearAllBuffers(); + void setOPLandParvoFiltersParameters(const float beta1, const float tau1, const float k1, const float beta2, const float tau2, const float k2); + + inline void setGanglionCellsLocalAdaptationLPfilterParameters(const float tau, const float k) + { + BasicRetinaFilter::setLPfilterParameters(0, tau, k, 2); + } + const cv::ocl::oclMat &runFilter(const cv::ocl::oclMat &inputFrame, const bool useParvoOutput = true); + + inline const cv::ocl::oclMat &getPhotoreceptorsLPfilteringOutput() const + { + return _photoreceptorsOutput; + } + + inline const cv::ocl::oclMat &getHorizontalCellsOutput() const + { + return _horizontalCellsOutput; + } + + inline const cv::ocl::oclMat &getParvoON() const + { + return _parvocellularOutputON; + } + + inline const cv::ocl::oclMat &getParvoOFF() const + { + return _parvocellularOutputOFF; + } + + inline const cv::ocl::oclMat &getBipolarCellsON() const + { + return _bipolarCellsOutputON; + } + + inline const cv::ocl::oclMat &getBipolarCellsOFF() const + { + return _bipolarCellsOutputOFF; + } + + inline float getPhotoreceptorsTemporalConstant() + { + return this->_filteringCoeficientsTable[2]; + } + + inline float getHcellsTemporalConstant() + { + return this->_filteringCoeficientsTable[5]; + } +private: + cv::ocl::oclMat _photoreceptorsOutput; + cv::ocl::oclMat _horizontalCellsOutput; + cv::ocl::oclMat _parvocellularOutputON; + cv::ocl::oclMat _parvocellularOutputOFF; + cv::ocl::oclMat _bipolarCellsOutputON; + cv::ocl::oclMat _bipolarCellsOutputOFF; + cv::ocl::oclMat _localAdaptationOFF; + cv::ocl::oclMat _localAdaptationON; + cv::ocl::oclMat _parvocellularOutputONminusOFF; + void _OPL_OnOffWaysComputing(); +}; +class RetinaColor: public BasicRetinaFilter +{ +public: + RetinaColor(const unsigned int NBrows, const unsigned int NBcolumns, const int samplingMethod = RETINA_COLOR_DIAGONAL); + virtual ~RetinaColor(); + + void clearAllBuffers(); + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + inline void runColorMultiplexing(const cv::ocl::oclMat &inputRGBFrame) + { + runColorMultiplexing(inputRGBFrame, _multiplexedFrame); + } + void runColorMultiplexing(const cv::ocl::oclMat &demultiplexedInputFrame, cv::ocl::oclMat &multiplexedFrame); + void runColorDemultiplexing(const cv::ocl::oclMat &multiplexedColorFrame, const bool adaptiveFiltering = false, const float maxInputValue = 255.0); + + void setColorSaturation(const bool saturateColors = true, const float colorSaturationValue = 4.0) + { + _saturateColors = saturateColors; + _colorSaturationValue = colorSaturationValue; + } + + void setChrominanceLPfilterParameters(const float beta, const float tau, const float k) + { + setLPfilterParameters(beta, tau, k); + } + + bool applyKrauskopfLMS2Acr1cr2Transform(cv::ocl::oclMat &result); + bool applyLMS2LabTransform(cv::ocl::oclMat &result); + inline const cv::ocl::oclMat &getMultiplexedFrame() const + { + return _multiplexedFrame; + } + + inline const cv::ocl::oclMat &getDemultiplexedColorFrame() const + { + return _demultiplexedColorFrame; + } + + inline const cv::ocl::oclMat &getLuminance() const + { + return _luminance; + } + inline const cv::ocl::oclMat &getChrominance() const + { + return _chrominance; + } + void clipRGBOutput_0_maxInputValue(cv::ocl::oclMat &inputOutputBuffer, const float maxOutputValue = 255.0); + void normalizeRGBOutput_0_maxOutputValue(const float maxOutputValue = 255.0); + inline void setDemultiplexedColorFrame(const cv::ocl::oclMat &demultiplexedImage) + { + _demultiplexedColorFrame = demultiplexedImage; + } +protected: + inline unsigned int bayerSampleOffset(unsigned int index) + { + return index + ((index / getNBcolumns()) % 2) * getNBpixels() + ((index % getNBcolumns()) % 2) * getNBpixels(); + } + inline Rect getROI(int idx) + { + return Rect(0, idx * _NBrows, _NBcols, _NBrows); + } + int _samplingMethod; + bool _saturateColors; + float _colorSaturationValue; + cv::ocl::oclMat _luminance; + cv::ocl::oclMat _multiplexedFrame; + cv::ocl::oclMat _RGBmosaic; + cv::ocl::oclMat _tempMultiplexedFrame; + cv::ocl::oclMat _demultiplexedTempBuffer; + cv::ocl::oclMat _demultiplexedColorFrame; + cv::ocl::oclMat _chrominance; + cv::ocl::oclMat _colorLocalDensity; + cv::ocl::oclMat _imageGradient; + + float _pR, _pG, _pB; + bool _objectInit; + + void _initColorSampling(); + void _adaptiveSpatialLPfilter(const cv::ocl::oclMat &inputFrame, const cv::ocl::oclMat &gradient, cv::ocl::oclMat &outputFrame); + void _adaptiveHorizontalCausalFilter_addInput(const cv::ocl::oclMat &inputFrame, const cv::ocl::oclMat &gradient, cv::ocl::oclMat &outputFrame); + void _adaptiveVerticalAnticausalFilter_multGain(const cv::ocl::oclMat &gradient, cv::ocl::oclMat &outputFrame); + void _computeGradient(const cv::ocl::oclMat &luminance, cv::ocl::oclMat &gradient); + void _normalizeOutputs_0_maxOutputValue(void); + void _applyImageColorSpaceConversion(const cv::ocl::oclMat &inputFrame, cv::ocl::oclMat &outputFrame, const float *transformTable); +}; +class RetinaFilter +{ +public: + RetinaFilter(const unsigned int sizeRows, const unsigned int sizeColumns, const bool colorMode = false, const int samplingMethod = RETINA_COLOR_BAYER, const bool useRetinaLogSampling = false, const double reductionFactor = 1.0, const double samplingStrenght = 10.0); + ~RetinaFilter(); + + void clearAllBuffers(); + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + bool checkInput(const cv::ocl::oclMat &input, const bool colorMode); + bool runFilter(const cv::ocl::oclMat &imageInput, const bool useAdaptiveFiltering = true, const bool processRetinaParvoMagnoMapping = false, const bool useColorMode = false, const bool inputIsColorMultiplexed = false); + + void setGlobalParameters(const float OPLspatialResponse1 = 0.7, const float OPLtemporalresponse1 = 1, const float OPLassymetryGain = 0, const float OPLspatialResponse2 = 5, const float OPLtemporalresponse2 = 1, const float LPfilterSpatialResponse = 5, const float LPfilterGain = 0, const float LPfilterTemporalresponse = 0, const float MovingContoursExtractorCoefficient = 5, const bool normalizeParvoOutput_0_maxOutputValue = false, const bool normalizeMagnoOutput_0_maxOutputValue = false, const float maxOutputValue = 255.0, const float maxInputValue = 255.0, const float meanValue = 128.0); + + inline void setPhotoreceptorsLocalAdaptationSensitivity(const float V0CompressionParameter) + { + _photoreceptorsPrefilter.setV0CompressionParameter(1 - V0CompressionParameter); + _setInitPeriodCount(); + } + + inline void setParvoGanglionCellsLocalAdaptationSensitivity(const float V0CompressionParameter) + { + _ParvoRetinaFilter.setV0CompressionParameter(V0CompressionParameter); + _setInitPeriodCount(); + } + + inline void setGanglionCellsLocalAdaptationLPfilterParameters(const float spatialResponse, const float temporalResponse) + { + _ParvoRetinaFilter.setGanglionCellsLocalAdaptationLPfilterParameters(temporalResponse, spatialResponse); + _setInitPeriodCount(); + }; + + inline void setMagnoGanglionCellsLocalAdaptationSensitivity(const float V0CompressionParameter) + { + _MagnoRetinaFilter.setV0CompressionParameter(V0CompressionParameter); + _setInitPeriodCount(); + } + + void setOPLandParvoParameters(const float beta1, const float tau1, const float k1, const float beta2, const float tau2, const float k2, const float V0CompressionParameter) + { + _ParvoRetinaFilter.setOPLandParvoFiltersParameters(beta1, tau1, k1, beta2, tau2, k2); + _ParvoRetinaFilter.setV0CompressionParameter(V0CompressionParameter); + _setInitPeriodCount(); + } + + void setMagnoCoefficientsTable(const float parasolCells_beta, const float parasolCells_tau, const float parasolCells_k, const float amacrinCellsTemporalCutFrequency, const float V0CompressionParameter, const float localAdaptintegration_tau, const float localAdaptintegration_k) + { + _MagnoRetinaFilter.setCoefficientsTable(parasolCells_beta, parasolCells_tau, parasolCells_k, amacrinCellsTemporalCutFrequency, localAdaptintegration_tau, localAdaptintegration_k); + _MagnoRetinaFilter.setV0CompressionParameter(V0CompressionParameter); + _setInitPeriodCount(); + } + + inline void activateNormalizeParvoOutput_0_maxOutputValue(const bool normalizeParvoOutput_0_maxOutputValue) + { + _normalizeParvoOutput_0_maxOutputValue = normalizeParvoOutput_0_maxOutputValue; + } + + inline void activateNormalizeMagnoOutput_0_maxOutputValue(const bool normalizeMagnoOutput_0_maxOutputValue) + { + _normalizeMagnoOutput_0_maxOutputValue = normalizeMagnoOutput_0_maxOutputValue; + } + + inline void setMaxOutputValue(const float maxOutputValue) + { + _maxOutputValue = maxOutputValue; + } + + void setColorMode(const bool desiredColorMode) + { + _useColorMode = desiredColorMode; + } + inline void setColorSaturation(const bool saturateColors = true, const float colorSaturationValue = 4.0) + { + _colorEngine.setColorSaturation(saturateColors, colorSaturationValue); + } + inline const cv::ocl::oclMat &getLocalAdaptation() const + { + return _photoreceptorsPrefilter.getOutput(); + } + inline const cv::ocl::oclMat &getPhotoreceptors() const + { + return _ParvoRetinaFilter.getPhotoreceptorsLPfilteringOutput(); + } + + inline const cv::ocl::oclMat &getHorizontalCells() const + { + return _ParvoRetinaFilter.getHorizontalCellsOutput(); + } + inline bool areContoursProcessed() + { + return _useParvoOutput; + } + bool getParvoFoveaResponse(cv::ocl::oclMat &parvoFovealResponse); + inline void activateContoursProcessing(const bool useParvoOutput) + { + _useParvoOutput = useParvoOutput; + } + + const cv::ocl::oclMat &getContours(); + + inline const cv::ocl::oclMat &getContoursON() const + { + return _ParvoRetinaFilter.getParvoON(); + } + + inline const cv::ocl::oclMat &getContoursOFF() const + { + return _ParvoRetinaFilter.getParvoOFF(); + } + + inline bool areMovingContoursProcessed() + { + return _useMagnoOutput; + } + + inline void activateMovingContoursProcessing(const bool useMagnoOutput) + { + _useMagnoOutput = useMagnoOutput; + } + + inline const cv::ocl::oclMat &getMovingContours() const + { + return _MagnoRetinaFilter.getOutput(); + } + + inline const cv::ocl::oclMat &getMovingContoursSaturated() const + { + return _MagnoRetinaFilter.getMagnoYsaturated(); + } + + inline const cv::ocl::oclMat &getMovingContoursON() const + { + return _MagnoRetinaFilter.getMagnoON(); + } + + inline const cv::ocl::oclMat &getMovingContoursOFF() const + { + return _MagnoRetinaFilter.getMagnoOFF(); + } + + inline const cv::ocl::oclMat &getRetinaParvoMagnoMappedOutput() const + { + return _retinaParvoMagnoMappedFrame; + } + + inline const cv::ocl::oclMat &getParvoContoursChannel() const + { + return _colorEngine.getLuminance(); + } + + inline const cv::ocl::oclMat &getParvoChrominance() const + { + return _colorEngine.getChrominance(); + } + inline const cv::ocl::oclMat &getColorOutput() const + { + return _colorEngine.getDemultiplexedColorFrame(); + } + + inline bool isColorMode() + { + return _useColorMode; + } + bool getColorMode() + { + return _useColorMode; + } + + inline bool isInitTransitionDone() + { + if (_ellapsedFramesSinceLastReset < _globalTemporalConstant) + { + return false; + } + return true; + } + inline float getRetinaSamplingBackProjection(const float projectedRadiusLength) + { + return projectedRadiusLength; + } + + inline unsigned int getInputNBrows() + { + return _photoreceptorsPrefilter.getNBrows(); + } + + inline unsigned int getInputNBcolumns() + { + return _photoreceptorsPrefilter.getNBcolumns(); + } + + inline unsigned int getInputNBpixels() + { + return _photoreceptorsPrefilter.getNBpixels(); + } + + inline unsigned int getOutputNBrows() + { + return _photoreceptorsPrefilter.getNBrows(); + } + + inline unsigned int getOutputNBcolumns() + { + return _photoreceptorsPrefilter.getNBcolumns(); + } + + inline unsigned int getOutputNBpixels() + { + return _photoreceptorsPrefilter.getNBpixels(); + } +private: + bool _useParvoOutput; + bool _useMagnoOutput; + + unsigned int _ellapsedFramesSinceLastReset; + unsigned int _globalTemporalConstant; + + cv::ocl::oclMat _retinaParvoMagnoMappedFrame; + BasicRetinaFilter _photoreceptorsPrefilter; + ParvoRetinaFilter _ParvoRetinaFilter; + MagnoRetinaFilter _MagnoRetinaFilter; + RetinaColor _colorEngine; + + bool _useMinimalMemoryForToneMappingONLY; + bool _normalizeParvoOutput_0_maxOutputValue; + bool _normalizeMagnoOutput_0_maxOutputValue; + float _maxOutputValue; + bool _useColorMode; + + void _setInitPeriodCount(); + void _processRetinaParvoMagnoMapping(); + void _runGrayToneMapping(const cv::ocl::oclMat &grayImageInput, cv::ocl::oclMat &grayImageOutput , const float PhotoreceptorsCompression = 0.6, const float ganglionCellsCompression = 0.6); +}; + +} /* namespace ocl */ +} /* namespace bioinspired */ +} /* namespace cv */ + +#endif /* HAVE_OPENCV_OCL */ +#endif /* __OCL_RETINA_HPP__ */ diff --git a/modules/bioinspired/src/retinacolor.cpp b/modules/bioinspired/src/retinacolor.cpp new file mode 100644 index 00000000000..3fbc553852a --- /dev/null +++ b/modules/bioinspired/src/retinacolor.cpp @@ -0,0 +1,725 @@ +/*#****************************************************************************** +** 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. +** +** +** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. +** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. +** +** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) +** +** Creation - enhancement process 2007-2011 +** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France +** +** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). +** Refer to the following research paper for more information: +** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 +** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: +** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. +** +** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : +** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: +** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#include "precomp.hpp" + +#include "retinacolor.hpp" + +// @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC : www.listic.univ-savoie.fr, Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ + +#include +#include + +namespace cv +{ +namespace bioinspired +{ +// init static values +static float _LMStoACr1Cr2[]={1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -0.5, -0.5, 1.0}; +//static double _ACr1Cr2toLMS[]={0.5, 0.5, 0.0, 0.5, -0.5, 0.0, 0.5, 0.0, 1.0}; +static float _LMStoLab[]={0.5774f, 0.5774f, 0.5774f, 0.4082f, 0.4082f, -0.8165f, 0.7071f, -0.7071f, 0.f}; + +// constructor/desctructor +RetinaColor::RetinaColor(const unsigned int NBrows, const unsigned int NBcolumns, const int samplingMethod) +:BasicRetinaFilter(NBrows, NBcolumns, 3), + _colorSampling(NBrows*NBcolumns), + _RGBmosaic(NBrows*NBcolumns*3), + _tempMultiplexedFrame(NBrows*NBcolumns), + _demultiplexedTempBuffer(NBrows*NBcolumns*3), + _demultiplexedColorFrame(NBrows*NBcolumns*3), + _chrominance(NBrows*NBcolumns*3), + _colorLocalDensity(NBrows*NBcolumns*3), + _imageGradient(NBrows*NBcolumns*2) +{ + // link to parent buffers (let's recycle !) + _luminance=&_filterOutput; + _multiplexedFrame=&_localBuffer; + + _objectInit=false; + _samplingMethod=samplingMethod; + _saturateColors=false; + _colorSaturationValue=4.0; + + // set default spatio-temporal filter parameters + setLPfilterParameters(0.0, 0.0, 1.5); + setLPfilterParameters(0.0, 0.0, 10.5, 1);// for the low pass filter dedicated to contours energy extraction (demultiplexing process) + setLPfilterParameters(0.f, 0.f, 0.9f, 2); + + // init default value on image Gradient + _imageGradient=0.57f; + + // init color sampling map + _initColorSampling(); + + // flush all buffers + clearAllBuffers(); +} + +RetinaColor::~RetinaColor() +{ + +} + +/** +* function that clears all buffers of the object +*/ +void RetinaColor::clearAllBuffers() +{ + BasicRetinaFilter::clearAllBuffers(); + _tempMultiplexedFrame=0.f; + _demultiplexedTempBuffer=0.f; + + _demultiplexedColorFrame=0.f; + _chrominance=0.f; + _imageGradient=0.57f; +} + +/** +* resize retina color filter object (resize all allocated buffers) +* @param NBrows: the new height size +* @param NBcolumns: the new width size +*/ +void RetinaColor::resize(const unsigned int NBrows, const unsigned int NBcolumns) +{ + BasicRetinaFilter::clearAllBuffers(); + _colorSampling.resize(NBrows*NBcolumns); + _RGBmosaic.resize(NBrows*NBcolumns*3); + _tempMultiplexedFrame.resize(NBrows*NBcolumns); + _demultiplexedTempBuffer.resize(NBrows*NBcolumns*3); + _demultiplexedColorFrame.resize(NBrows*NBcolumns*3); + _chrominance.resize(NBrows*NBcolumns*3); + _colorLocalDensity.resize(NBrows*NBcolumns*3); + _imageGradient.resize(NBrows*NBcolumns*2); + + // link to parent buffers (let's recycle !) + _luminance=&_filterOutput; + _multiplexedFrame=&_localBuffer; + + // init color sampling map + _initColorSampling(); + + // clean buffers + clearAllBuffers(); +} + + +void RetinaColor::_initColorSampling() +{ + + // filling the conversion table for multiplexed <=> demultiplexed frame + srand((unsigned)time(NULL)); + + // preInit cones probabilities + _pR=_pB=_pG=0; + switch (_samplingMethod) + { + case RETINA_COLOR_RANDOM: + for (unsigned int index=0 ; indexgetNBpixels(); ++index) + { + + // random RGB sampling + unsigned int colorIndex=rand()%24; + + if (colorIndex<8){ + colorIndex=0; + + ++_pR; + }else + { + if (colorIndex<21){ + colorIndex=1; + ++_pG; + }else{ + colorIndex=2; + ++_pB; + } + } + _colorSampling[index] = colorIndex*this->getNBpixels()+index; + } + _pR/=(float)this->getNBpixels(); + _pG/=(float)this->getNBpixels(); + _pB/=(float)this->getNBpixels(); + std::cout<<"Color channels proportions: pR, pG, pB= "<<_pR<<", "<<_pG<<", "<<_pB<<", "<getNBpixels(); ++index) + { + _colorSampling[index] = index+((index%3+(index%_filterOutput.getNBcolumns()))%3)*_filterOutput.getNBpixels(); + } + _pR=_pB=_pG=1.f/3; + break; + case RETINA_COLOR_BAYER: // default sets bayer sampling + for (unsigned int index=0 ; index<_filterOutput.getNBpixels(); ++index) + { + //First line: R G R G + _colorSampling[index] = index+((index/_filterOutput.getNBcolumns())%2)*_filterOutput.getNBpixels()+((index%_filterOutput.getNBcolumns())%2)*_filterOutput.getNBpixels(); + //First line: G R G R + //_colorSampling[index] = 3*index+((index/_filterOutput.getNBcolumns())%2)+((index%_filterOutput.getNBcolumns()+1)%2); + } + _pR=_pB=0.25; + _pG=0.5; + break; + default: +#ifdef RETINACOLORDEBUG + std::cerr<<"RetinaColor::No or wrong color sampling method, skeeping"< &multiplexedColorFrame, const bool adaptiveFiltering, const float maxInputValue) +{ + // demultiplex the grey frame to RGB frame + // -> first set demultiplexed frame to 0 + _demultiplexedTempBuffer=0; + // -> demultiplex process + register unsigned int *colorSamplingPRT=&_colorSampling[0]; + register const float *multiplexedColorFramePtr=get_data(multiplexedColorFrame); + for (unsigned int indexa=0; indexa<_filterOutput.getNBpixels() ; ++indexa) + _demultiplexedTempBuffer[*(colorSamplingPRT++)]=*(multiplexedColorFramePtr++); + + // interpolate the demultiplexed frame depending on the color sampling method + if (!adaptiveFiltering) + _interpolateImageDemultiplexedImage(&_demultiplexedTempBuffer[0]); + + // low pass filtering the demultiplexed frame + _spatiotemporalLPfilter(&_demultiplexedTempBuffer[0], &_chrominance[0]); + _spatiotemporalLPfilter(&_demultiplexedTempBuffer[0]+_filterOutput.getNBpixels(), &_chrominance[0]+_filterOutput.getNBpixels()); + _spatiotemporalLPfilter(&_demultiplexedTempBuffer[0]+_filterOutput.getDoubleNBpixels(), &_chrominance[0]+_filterOutput.getDoubleNBpixels()); + + /*if (_samplingMethod=BAYER) + { + _applyRIFfilter(_chrominance, _chrominance); + _applyRIFfilter(_chrominance+_filterOutput.getNBpixels(), _chrominance+_filterOutput.getNBpixels()); + _applyRIFfilter(_chrominance+_filterOutput.getDoubleNBpixels(), _chrominance+_filterOutput.getDoubleNBpixels()); + }*/ + + // normalize by the photoreceptors local density and retrieve the local luminance + register float *chrominancePTR= &_chrominance[0]; + register float *colorLocalDensityPTR= &_colorLocalDensity[0]; + register float *luminance= &(*_luminance)[0]; + if (!adaptiveFiltering)// compute the gradient on the luminance + { + if (_samplingMethod==RETINA_COLOR_RANDOM) + for (unsigned int indexc=0; indexc<_filterOutput.getNBpixels() ; ++indexc, ++chrominancePTR, ++colorLocalDensityPTR, ++luminance) + { + // normalize by photoreceptors density + float Cr=*(chrominancePTR)*_colorLocalDensity[indexc]; + float Cg=*(chrominancePTR+_filterOutput.getNBpixels())*_colorLocalDensity[indexc+_filterOutput.getNBpixels()]; + float Cb=*(chrominancePTR+_filterOutput.getDoubleNBpixels())*_colorLocalDensity[indexc+_filterOutput.getDoubleNBpixels()]; + *luminance=(Cr+Cg+Cb)*_pG; + *(chrominancePTR)=Cr-*luminance; + *(chrominancePTR+_filterOutput.getNBpixels())=Cg-*luminance; + *(chrominancePTR+_filterOutput.getDoubleNBpixels())=Cb-*luminance; + } + else + for (unsigned int indexc=0; indexc<_filterOutput.getNBpixels() ; ++indexc, ++chrominancePTR, ++colorLocalDensityPTR, ++luminance) + { + float Cr=*(chrominancePTR); + float Cg=*(chrominancePTR+_filterOutput.getNBpixels()); + float Cb=*(chrominancePTR+_filterOutput.getDoubleNBpixels()); + *luminance=_pR*Cr+_pG*Cg+_pB*Cb; + *(chrominancePTR)=Cr-*luminance; + *(chrominancePTR+_filterOutput.getNBpixels())=Cg-*luminance; + *(chrominancePTR+_filterOutput.getDoubleNBpixels())=Cb-*luminance; + } + + // in order to get the color image, each colored map needs to be added the luminance + // -> to do so, compute: multiplexedColorFrame - remultiplexed chrominances + runColorMultiplexing(_chrominance, _tempMultiplexedFrame); + //lum = 1/3((f*(ImR))/(f*mR) + (f*(ImG))/(f*mG) + (f*(ImB))/(f*mB)); + float *luminancePTR= &(*_luminance)[0]; + chrominancePTR= &_chrominance[0]; + float *demultiplexedColorFramePTR= &_demultiplexedColorFrame[0]; + for (unsigned int indexp=0; indexp<_filterOutput.getNBpixels() ; ++indexp, ++luminancePTR, ++chrominancePTR, ++demultiplexedColorFramePTR) + { + *luminancePTR=(multiplexedColorFrame[indexp]-_tempMultiplexedFrame[indexp]); + *(demultiplexedColorFramePTR)=*(chrominancePTR)+*luminancePTR; + *(demultiplexedColorFramePTR+_filterOutput.getNBpixels())=*(chrominancePTR+_filterOutput.getNBpixels())+*luminancePTR; + *(demultiplexedColorFramePTR+_filterOutput.getDoubleNBpixels())=*(chrominancePTR+_filterOutput.getDoubleNBpixels())+*luminancePTR; + } + + }else + { + register const float *multiplexedColorFramePTR= get_data(multiplexedColorFrame); + for (unsigned int indexc=0; indexc<_filterOutput.getNBpixels() ; ++indexc, ++chrominancePTR, ++colorLocalDensityPTR, ++luminance, ++multiplexedColorFramePTR) + { + // normalize by photoreceptors density + float Cr=*(chrominancePTR)*_colorLocalDensity[indexc]; + float Cg=*(chrominancePTR+_filterOutput.getNBpixels())*_colorLocalDensity[indexc+_filterOutput.getNBpixels()]; + float Cb=*(chrominancePTR+_filterOutput.getDoubleNBpixels())*_colorLocalDensity[indexc+_filterOutput.getDoubleNBpixels()]; + *luminance=(Cr+Cg+Cb)*_pG; + _demultiplexedTempBuffer[_colorSampling[indexc]] = *multiplexedColorFramePTR - *luminance; + + } + + // compute the gradient of the luminance +#ifdef MAKE_PARALLEL // call the TemplateBuffer TBB clipping method + cv::parallel_for_(cv::Range(2,_filterOutput.getNBrows()-2), Parallel_computeGradient(_filterOutput.getNBcolumns(), _filterOutput.getNBrows(), &(*_luminance)[0], &_imageGradient[0])); +#else + _computeGradient(&(*_luminance)[0]); +#endif + // adaptively filter the submosaics to get the adaptive densities, here the buffer _chrominance is used as a temp buffer + _adaptiveSpatialLPfilter(&_RGBmosaic[0], &_chrominance[0]); + _adaptiveSpatialLPfilter(&_RGBmosaic[0]+_filterOutput.getNBpixels(), &_chrominance[0]+_filterOutput.getNBpixels()); + _adaptiveSpatialLPfilter(&_RGBmosaic[0]+_filterOutput.getDoubleNBpixels(), &_chrominance[0]+_filterOutput.getDoubleNBpixels()); + + _adaptiveSpatialLPfilter(&_demultiplexedTempBuffer[0], &_demultiplexedColorFrame[0]); + _adaptiveSpatialLPfilter(&_demultiplexedTempBuffer[0]+_filterOutput.getNBpixels(), &_demultiplexedColorFrame[0]+_filterOutput.getNBpixels()); + _adaptiveSpatialLPfilter(&_demultiplexedTempBuffer[0]+_filterOutput.getDoubleNBpixels(), &_demultiplexedColorFrame[0]+_filterOutput.getDoubleNBpixels()); + +/* for (unsigned int index=0; index<_filterOutput.getNBpixels()*3 ; ++index) // cette boucle pourrait �tre supprimee en passant la densit� � la fonction de filtrage + _demultiplexedColorFrame[index] /= _chrominance[index];*/ + _demultiplexedColorFrame/=_chrominance; // more optimal ;o) + + // compute and substract the residual luminance + for (unsigned int index=0; index<_filterOutput.getNBpixels() ; ++index) + { + float residu = _pR*_demultiplexedColorFrame[index] + _pG*_demultiplexedColorFrame[index+_filterOutput.getNBpixels()] + _pB*_demultiplexedColorFrame[index+_filterOutput.getDoubleNBpixels()]; + _demultiplexedColorFrame[index] = _demultiplexedColorFrame[index] - residu; + _demultiplexedColorFrame[index+_filterOutput.getNBpixels()] = _demultiplexedColorFrame[index+_filterOutput.getNBpixels()] - residu; + _demultiplexedColorFrame[index+_filterOutput.getDoubleNBpixels()] = _demultiplexedColorFrame[index+_filterOutput.getDoubleNBpixels()] - residu; + } + + // multiplex the obtained chrominance + runColorMultiplexing(_demultiplexedColorFrame, _tempMultiplexedFrame); + _demultiplexedTempBuffer=0; + + // get the luminance, et and add it to each chrominance + for (unsigned int index=0; index<_filterOutput.getNBpixels() ; ++index) + { + (*_luminance)[index]=multiplexedColorFrame[index]-_tempMultiplexedFrame[index]; + _demultiplexedTempBuffer[_colorSampling[index]] = _demultiplexedColorFrame[_colorSampling[index]];//multiplexedColorFrame[index] - (*_luminance)[index]; + } + + _spatiotemporalLPfilter(&_demultiplexedTempBuffer[0], &_demultiplexedTempBuffer[0]); + _spatiotemporalLPfilter(&_demultiplexedTempBuffer[0]+_filterOutput.getNBpixels(), &_demultiplexedTempBuffer[0]+_filterOutput.getNBpixels()); + _spatiotemporalLPfilter(&_demultiplexedTempBuffer[0]+_filterOutput.getDoubleNBpixels(), &_demultiplexedTempBuffer[0]+_filterOutput.getDoubleNBpixels()); + + // get the luminance and add it to each chrominance + for (unsigned int index=0; index<_filterOutput.getNBpixels() ; ++index) + { + _demultiplexedColorFrame[index] = _demultiplexedTempBuffer[index]*_colorLocalDensity[index]+ (*_luminance)[index]; + _demultiplexedColorFrame[index+_filterOutput.getNBpixels()] = _demultiplexedTempBuffer[index+_filterOutput.getNBpixels()]*_colorLocalDensity[index+_filterOutput.getNBpixels()]+ (*_luminance)[index]; + _demultiplexedColorFrame[index+_filterOutput.getDoubleNBpixels()] = _demultiplexedTempBuffer[index+_filterOutput.getDoubleNBpixels()]*_colorLocalDensity[index+_filterOutput.getDoubleNBpixels()]+ (*_luminance)[index]; + } + } + + // eliminate saturated colors by simple clipping values to the input range + clipRGBOutput_0_maxInputValue(NULL, maxInputValue); + + /* transfert image gradient in order to check validity + memcpy((*_luminance), _imageGradient, sizeof(float)*_filterOutput.getNBpixels()); + memcpy(_demultiplexedColorFrame, _imageGradient+_filterOutput.getNBpixels(), sizeof(float)*_filterOutput.getNBpixels()); + memcpy(_demultiplexedColorFrame+_filterOutput.getNBpixels(), _imageGradient+_filterOutput.getNBpixels(), sizeof(float)*_filterOutput.getNBpixels()); + memcpy(_demultiplexedColorFrame+2*_filterOutput.getNBpixels(), _imageGradient+_filterOutput.getNBpixels(), sizeof(float)*_filterOutput.getNBpixels()); + */ + + if (_saturateColors) + { + TemplateBuffer::normalizeGrayOutputCentredSigmoide(128, _colorSaturationValue, maxInputValue, &_demultiplexedColorFrame[0], &_demultiplexedColorFrame[0], _filterOutput.getNBpixels()); + TemplateBuffer::normalizeGrayOutputCentredSigmoide(128, _colorSaturationValue, maxInputValue, &_demultiplexedColorFrame[0]+_filterOutput.getNBpixels(), &_demultiplexedColorFrame[0]+_filterOutput.getNBpixels(), _filterOutput.getNBpixels()); + TemplateBuffer::normalizeGrayOutputCentredSigmoide(128, _colorSaturationValue, maxInputValue, &_demultiplexedColorFrame[0]+_filterOutput.getNBpixels()*2, &_demultiplexedColorFrame[0]+_filterOutput.getNBpixels()*2, _filterOutput.getNBpixels()); + } +} + +// color multiplexing: input frame size=_NBrows*_filterOutput.getNBcolumns()*3, multiplexedFrame output size=_NBrows*_filterOutput.getNBcolumns() +void RetinaColor::runColorMultiplexing(const std::valarray &demultiplexedInputFrame, std::valarray &multiplexedFrame) +{ + // multiply each color layer by its bayer mask + register unsigned int *colorSamplingPTR= &_colorSampling[0]; + register float *multiplexedFramePTR= &multiplexedFrame[0]; + for (unsigned int indexp=0; indexp<_filterOutput.getNBpixels(); ++indexp) + *(multiplexedFramePTR++)=demultiplexedInputFrame[*(colorSamplingPTR++)]; +} + +void RetinaColor::normalizeRGBOutput_0_maxOutputValue(const float maxOutputValue) +{ + //normalizeGrayOutputCentredSigmoide(0.0, 2, _chrominance); + TemplateBuffer::normalizeGrayOutput_0_maxOutputValue(&_demultiplexedColorFrame[0], 3*_filterOutput.getNBpixels(), maxOutputValue); + //normalizeGrayOutputCentredSigmoide(0.0, 2, _chrominance+_filterOutput.getNBpixels()); + //normalizeGrayOutput_0_maxOutputValue(_demultiplexedColorFrame+_filterOutput.getNBpixels(), _filterOutput.getNBpixels(), maxOutputValue); + //normalizeGrayOutputCentredSigmoide(0.0, 2, _chrominance+2*_filterOutput.getNBpixels()); + //normalizeGrayOutput_0_maxOutputValue(_demultiplexedColorFrame+_filterOutput.getDoubleNBpixels(), _filterOutput.getNBpixels(), maxOutputValue); + TemplateBuffer::normalizeGrayOutput_0_maxOutputValue(&(*_luminance)[0], _filterOutput.getNBpixels(), maxOutputValue); +} + +/// normalize output between 0 and maxOutputValue; +void RetinaColor::clipRGBOutput_0_maxInputValue(float *inputOutputBuffer, const float maxInputValue) +{ + //std::cout<<"RetinaColor::normalizing RGB frame..."<(inputOutputBuffer, 0, maxInputValue)); +#else + register float *inputOutputBufferPTR=inputOutputBuffer; + for (register unsigned int jf = 0; jf < _filterOutput.getNBpixels()*3; ++jf, ++inputOutputBufferPTR) + { + if (*inputOutputBufferPTR>maxInputValue) + *inputOutputBufferPTR=maxInputValue; + else if (*inputOutputBufferPTR<0) + *inputOutputBufferPTR=0; + } +#endif + //std::cout<<"RetinaColor::...normalizing RGB frame OK"<maxValue) + maxValue=outputFrame[index]; + } + } + normalisationFactor=1.f/maxValue; + // normalisation [0, 1] + for (unsigned int indexp=1 ; indexp<_filterOutput.getNBrows()-1; ++indexp) + outputFrame[indexp]=outputFrame[indexp]*normalisationFactor; +} + +////////////////////////////////////////////////////////// +// ADAPTIVE BASIC RETINA FILTER +////////////////////////////////////////////////////////// +// run LP filter for a new frame input and save result at a specific output adress +void RetinaColor::_adaptiveSpatialLPfilter(const float *inputFrame, float *outputFrame) +{ + + /**********/ + _gain = (1-0.57f)*(1-0.57f)*(1-0.06f)*(1-0.06f); + + // launch the serie of 1D directional filters in order to compute the 2D low pass filter + // -> horizontal filters work with the first layer of imageGradient + _adaptiveHorizontalCausalFilter_addInput(inputFrame, outputFrame, 0, _filterOutput.getNBrows()); + _horizontalAnticausalFilter_Irregular(outputFrame, 0, _filterOutput.getNBrows(), &_imageGradient[0]); + // -> horizontal filters work with the second layer of imageGradient + _verticalCausalFilter_Irregular(outputFrame, 0, _filterOutput.getNBcolumns(), &_imageGradient[0]+_filterOutput.getNBpixels()); + _adaptiveVerticalAnticausalFilter_multGain(outputFrame, 0, _filterOutput.getNBcolumns()); +} + +// horizontal causal filter which adds the input inside... replaces the parent _horizontalCausalFilter_Irregular_addInput by avoiding a product for each pixel +void RetinaColor::_adaptiveHorizontalCausalFilter_addInput(const float *inputFrame, float *outputFrame, unsigned int IDrowStart, unsigned int IDrowEnd) +{ +#ifdef MAKE_PARALLEL + cv::parallel_for_(cv::Range(IDrowStart,IDrowEnd), Parallel_adaptiveHorizontalCausalFilter_addInput(inputFrame, outputFrame, &_imageGradient[0], _filterOutput.getNBcolumns())); +#else + register float* outputPTR=outputFrame+IDrowStart*_filterOutput.getNBcolumns(); + register const float* inputPTR=inputFrame+IDrowStart*_filterOutput.getNBcolumns(); + register const float *imageGradientPTR= &_imageGradient[0]+IDrowStart*_filterOutput.getNBcolumns(); + for (unsigned int IDrow=IDrowStart; IDrow &result) +{ + bool processSuccess=true; + // basic preliminary error check + if (result.size()!=_demultiplexedColorFrame.size()) + { + std::cerr<<"RetinaColor::applyKrauskopfLMS2Acr1cr2Transform: input buffer does not match retina buffer size, conversion aborted"< &result) +{ + bool processSuccess=true; + // basic preliminary error check + if (result.size()!=_demultiplexedColorFrame.size()) + { + std::cerr<<"RetinaColor::applyKrauskopfLMS2Acr1cr2Transform: input buffer does not match retina buffer size, conversion aborted"< &inputFrameBuffer, std::valarray &outputFrameBuffer, const float *transformTable) +{ + // two step methods in order to allow inputFrame and outputFrame to be the same + unsigned int nbPixels=(unsigned int)(inputFrameBuffer.size()/3), dbpixels=(unsigned int)(2*inputFrameBuffer.size()/3); + + const float *inputFrame=get_data(inputFrameBuffer); + float *outputFrame= &outputFrameBuffer[0]; + + for (unsigned int dataIndex=0; dataIndex B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +/** +* @class RetinaColor a color multilexing/demultiplexing (demosaicing) based on a human vision inspiration. Different mosaicing strategies can be used, included random sampling ! +* => please take a look at the nice and efficient demosaicing strategy introduced by B.Chaix de Lavarene, take a look at the cited paper for more mathematical details +* @brief Retina color sampling model which allows classical bayer sampling, random and potentially several other method ! Low color errors on corners ! +* -> Based on the research of: +* .Brice Chaix Lavarene (chaix@lis.inpg.fr) +* .Jeanny Herault (herault@lis.inpg.fr) +* .David Alleyson (david.alleyson@upmf-grenoble.fr) +* .collaboration: alexandre benoit (benoit.alexandre.vision@gmail.com or benoit@lis.inpg.fr) +* Please cite: B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +* @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC / Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ +* Creation date 2007 +*/ + +#ifndef RETINACOLOR_HPP_ +#define RETINACOLOR_HPP_ + +#include "basicretinafilter.hpp" + +//#define __RETINACOLORDEBUG //define RETINACOLORDEBUG in order to display debug data + +namespace cv +{ +namespace bioinspired +{ + class RetinaColor: public BasicRetinaFilter + { + public: + /** + * @typedef which allows to select the type of photoreceptors color sampling + */ + + /** + * constructor of the retina color processing model + * @param NBrows: number of rows of the input image + * @param NBcolumns: number of columns of the input image + * @param samplingMethod: the chosen color sampling method + */ + RetinaColor(const unsigned int NBrows, const unsigned int NBcolumns, const int samplingMethod=RETINA_COLOR_BAYER); + + /** + * standard destructor + */ + virtual ~RetinaColor(); + + /** + * function that clears all buffers of the object + */ + void clearAllBuffers(); + + /** + * resize retina color filter object (resize all allocated buffers) + * @param NBrows: the new height size + * @param NBcolumns: the new width size + */ + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + + + /** + * color multiplexing function: a demultiplexed RGB frame of size M*N*3 is transformed into a multiplexed M*N*1 pixels frame where each pixel is either Red, or Green or Blue + * @param inputRGBFrame: the input RGB frame to be processed + * @return, nothing but the multiplexed frame is available by the use of the getMultiplexedFrame() function + */ + inline void runColorMultiplexing(const std::valarray &inputRGBFrame) { runColorMultiplexing(inputRGBFrame, *_multiplexedFrame); } + + /** + * color multiplexing function: a demultipleed RGB frame of size M*N*3 is transformed into a multiplexed M*N*1 pixels frame where each pixel is either Red, or Green or Blue if using RGB images + * @param demultiplexedInputFrame: the demultiplexed input frame to be processed of size M*N*3 + * @param multiplexedFrame: the resulting multiplexed frame + */ + void runColorMultiplexing(const std::valarray &demultiplexedInputFrame, std::valarray &multiplexedFrame); + + /** + * color demultiplexing function: a multiplexed frame of size M*N*1 pixels is transformed into a RGB demultiplexed M*N*3 pixels frame + * @param multiplexedColorFrame: the input multiplexed frame to be processed + * @param adaptiveFiltering: specifies if an adaptive filtering has to be perform rather than standard filtering (adaptive filtering allows a better rendering) + * @param maxInputValue: the maximum input data value (should be 255 for 8 bits images but it can change in the case of High Dynamic Range Images (HDRI) + * @return, nothing but the output demultiplexed frame is available by the use of the getDemultiplexedColorFrame() function, also use getLuminance() and getChrominance() in order to retreive either luminance or chrominance + */ + void runColorDemultiplexing(const std::valarray &multiplexedColorFrame, const bool adaptiveFiltering=false, const float maxInputValue=255.0); + + /** + * activate color saturation as the final step of the color demultiplexing process + * -> this saturation is a sigmoide function applied to each channel of the demultiplexed image. + * @param saturateColors: boolean that activates color saturation (if true) or desactivate (if false) + * @param colorSaturationValue: the saturation factor + * */ + void setColorSaturation(const bool saturateColors=true, const float colorSaturationValue=4.0) { _saturateColors=saturateColors; _colorSaturationValue=colorSaturationValue; } + + /** + * set parameters of the low pass spatio-temporal filter used to retreive the low chrominance + * @param beta: gain of the filter (generally set to zero) + * @param tau: time constant of the filter (unit is frame for video processing), typically 0 when considering static processing, 1 or more if a temporal smoothing effect is required + * @param k: spatial constant of the filter (unit is pixels), typical value is 2.5 + */ + void setChrominanceLPfilterParameters(const float beta, const float tau, const float k) { setLPfilterParameters(beta, tau, k); } + + /** + * apply to the retina color output the Krauskopf transformation which leads to an opponent color system: output colorspace if Acr1cr2 if input of the retina was LMS color space + * @param result: the input buffer to fill with the transformed colorspace retina output + * @return true if process ended successfully + */ + bool applyKrauskopfLMS2Acr1cr2Transform(std::valarray &result); + + /** + * apply to the retina color output the CIE Lab color transformation + * @param result: the input buffer to fill with the transformed colorspace retina output + * @return true if process ended successfully + */ + bool applyLMS2LabTransform(std::valarray &result); + + /** + * @return the multiplexed frame result (use this after function runColorMultiplexing) + */ + inline const std::valarray &getMultiplexedFrame() const { return *_multiplexedFrame; } + + /** + * @return the demultiplexed frame result (use this after function runColorDemultiplexing) + */ + inline const std::valarray &getDemultiplexedColorFrame() const { return _demultiplexedColorFrame; } + + /** + * @return the luminance of the processed frame (use this after function runColorDemultiplexing) + */ + inline const std::valarray &getLuminance() const { return *_luminance; } + + /** + * @return the chrominance of the processed frame (use this after function runColorDemultiplexing) + */ + inline const std::valarray &getChrominance() const { return _chrominance; } + + /** + * standard 0 to 255 image clipping function appled to RGB images (of size M*N*3 pixels) + * @param inputOutputBuffer: the image to be normalized (rewrites the input), if no parameter, then, the built in buffer reachable by getOutput() function is normalized + * @param maxOutputValue: the maximum value allowed at the output (values superior to it would be clipped + */ + void clipRGBOutput_0_maxInputValue(float *inputOutputBuffer, const float maxOutputValue=255.0); + + /** + * standard 0 to 255 image normalization function appled to RGB images (of size M*N*3 pixels) + * @param maxOutputValue: the maximum value allowed at the output (values superior to it would be clipped + */ + void normalizeRGBOutput_0_maxOutputValue(const float maxOutputValue=255.0); + + /** + * return the color sampling map: a Nrows*Mcolumns image in which each pixel value is the ofsset adress which gives the adress of the sampled pixel on an Nrows*Mcolumns*3 color image ordered by layers: layer1, layer2, layer3 + */ + inline const std::valarray &getSamplingMap() const { return _colorSampling; } + + /** + * function used (to bypass processing) to manually set the color output + * @param demultiplexedImage: the color image (luminance+chrominance) which has to be written in the object buffer + */ + inline void setDemultiplexedColorFrame(const std::valarray &demultiplexedImage) { _demultiplexedColorFrame=demultiplexedImage; } + + protected: + + // private functions + int _samplingMethod; + bool _saturateColors; + float _colorSaturationValue; + // links to parent buffers (more convienient names + TemplateBuffer *_luminance; + std::valarray *_multiplexedFrame; + // instance buffers + std::valarray _colorSampling; // table (size (_nbRows*_nbColumns) which specifies the color of each pixel + std::valarray _RGBmosaic; + std::valarray _tempMultiplexedFrame; + std::valarray _demultiplexedTempBuffer; + std::valarray _demultiplexedColorFrame; + std::valarray _chrominance; + std::valarray _colorLocalDensity;// buffer which contains the local density of the R, G and B photoreceptors for a normalization use + std::valarray _imageGradient; + + // variables + float _pR, _pG, _pB; // probabilities of color R, G and B + bool _objectInit; + + // protected functions + void _initColorSampling(); + void _interpolateImageDemultiplexedImage(float *inputOutputBuffer); + void _interpolateSingleChannelImage111(float *inputOutputBuffer); + void _interpolateBayerRGBchannels(float *inputOutputBuffer); + void _applyRIFfilter(const float *sourceBuffer, float *destinationBuffer); + void _getNormalizedContoursImage(const float *inputFrame, float *outputFrame); + // -> special adaptive filters dedicated to low pass filtering on the chrominance (skeeps filtering on the edges) + void _adaptiveSpatialLPfilter(const float *inputFrame, float *outputFrame); + void _adaptiveHorizontalCausalFilter_addInput(const float *inputFrame, float *outputFrame, const unsigned int IDrowStart, const unsigned int IDrowEnd); // TBB parallelized + void _adaptiveVerticalAnticausalFilter_multGain(float *outputFrame, const unsigned int IDcolumnStart, const unsigned int IDcolumnEnd); + void _computeGradient(const float *luminance); + void _normalizeOutputs_0_maxOutputValue(void); + + // color space transform + void _applyImageColorSpaceConversion(const std::valarray &inputFrame, std::valarray &outputFrame, const float *transformTable); + +#ifdef MAKE_PARALLEL + /****************************************************** + ** IF some parallelizing thread methods are available, then, main loops are parallelized using these functors + ** ==> main idea paralellise main filters loops, then, only the most used methods are parallelized... TODO : increase the number of parallelised methods as necessary + ** ==> functors names = Parallel_$$$ where $$$= the name of the serial method that is parallelised + ** ==> functors constructors can differ from the parameters used with their related serial functions + */ + + /* Template : + class Parallel_ : public cv::ParallelLoopBody + { + private: + + public: + Parallel_() + : {} + + virtual void operator()( const cv::Range& r ) const { + + } + }: + */ + class Parallel_adaptiveHorizontalCausalFilter_addInput: public cv::ParallelLoopBody + { + private: + float *outputFrame; + const float *inputFrame, *imageGradient; + unsigned int nbColumns; + public: + Parallel_adaptiveHorizontalCausalFilter_addInput(const float *inputImg, float *bufferToProcess, const float *imageGrad, const unsigned int nbCols) + :outputFrame(bufferToProcess), inputFrame(inputImg), imageGradient(imageGrad), nbColumns(nbCols) { } + + virtual void operator()( const Range& r ) const + { + register float* outputPTR=outputFrame+r.start*nbColumns; + register const float* inputPTR=inputFrame+r.start*nbColumns; + register const float *imageGradientPTR= imageGradient+r.start*nbColumns; + for (int IDrow=r.start; IDrow!=r.end; ++IDrow) + { + register float result=0; + for (unsigned int index=0; index Meylan L., Alleysson D., and Susstrunk S., A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images, Journal of Optical Society of America, A, Vol. 24, N 9, September, 1st, 2007, pp. 2807-2816 + ** + ** + ** License Agreement + ** For Open Source Computer Vision Library + ** + ** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. + ** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. + ** + ** For Human Visual System tools (bioinspired) + ** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: + ** + ** * Redistributions of source code must retain the above copyright notice, + ** this list of conditions and the following disclaimer. + ** + ** * Redistributions 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. + *******************************************************************************/ + +/* + * retinafasttonemapping.cpp + * + * Created on: May 26, 2013 + * Author: Alexandre Benoit + */ + +#include "precomp.hpp" +#include "basicretinafilter.hpp" +#include "retinacolor.hpp" +#include +#include +#include + +namespace cv +{ +namespace bioinspired +{ +/** + * @class RetinaFastToneMappingImpl a wrapper class which allows the tone mapping algorithm of Meylan&al(2007) to be used with OpenCV. + * This algorithm is already implemented in thre Retina class (retina::applyFastToneMapping) but used it does not require all the retina model to be allocated. This allows a light memory use for low memory devices (smartphones, etc. + * As a summary, these are the model properties: + * => 2 stages of local luminance adaptation with a different local neighborhood for each. + * => first stage models the retina photorecetors local luminance adaptation + * => second stage models th ganglion cells local information adaptation + * => compared to the initial publication, this class uses spatio-temporal low pass filters instead of spatial only filters. + * ====> this can help noise robustness and temporal stability for video sequence use cases. + * for more information, read to the following papers : + * Meylan L., Alleysson D., and Susstrunk S., A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images, Journal of Optical Society of America, A, Vol. 24, N 9, September, 1st, 2007, pp. 2807-2816Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 + * regarding spatio-temporal filter and the bigger retina model : + * Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. + */ + +class RetinaFastToneMappingImpl : public RetinaFastToneMapping +{ +public: + /** + * constructor + * @param imageInput: the size of the images to process + */ + RetinaFastToneMappingImpl(Size imageInput) + { + unsigned int nbPixels=imageInput.height*imageInput.width; + + // basic error check + if (nbPixels <= 0) + throw cv::Exception(-1, "Bad retina size setup : size height and with must be superior to zero", "RetinaImpl::setup", "retinafasttonemapping.cpp", 0); + + // resize buffers + _inputBuffer.resize(nbPixels*3); // buffer supports gray images but also 3 channels color buffers... (larger is better...) + _imageOutput.resize(nbPixels*3); + _temp2.resize(nbPixels); + // allocate the main filter with 2 setup sets properties (one for each low pass filter + _multiuseFilter = makePtr(imageInput.height, imageInput.width, 2); + // allocate the color manager (multiplexer/demultiplexer + _colorEngine = makePtr(imageInput.height, imageInput.width); + // setup filter behaviors with default values + setup(); + } + + /** + * basic destructor + */ + virtual ~RetinaFastToneMappingImpl() { } + + /** + * method that applies a luminance correction (initially High Dynamic Range (HDR) tone mapping) using only the 2 local adaptation stages of the retina parvocellular channel : photoreceptors level and ganlion cells level. Spatio temporal filtering is applied but limited to temporal smoothing and eventually high frequencies attenuation. This is a lighter method than the one available using the regular retina::run method. It is then faster but it does not include complete temporal filtering nor retina spectral whitening. Then, it can have a more limited effect on images with a very high dynamic range. This is an adptation of the original still image HDR tone mapping algorithm of David Alleyson, Sabine Susstruck and Laurence Meylan's work, please cite: + * -> Meylan L., Alleysson D., and Susstrunk S., A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images, Journal of Optical Society of America, A, Vol. 24, N 9, September, 1st, 2007, pp. 2807-2816 + @param inputImage the input image to process RGB or gray levels + @param outputToneMappedImage the output tone mapped image + */ + virtual void applyFastToneMapping(InputArray inputImage, OutputArray outputToneMappedImage) + { + // first convert input image to the compatible format : + const bool colorMode = _convertCvMat2ValarrayBuffer(inputImage.getMat(), _inputBuffer); + + // process tone mapping + if (colorMode) + { + _runRGBToneMapping(_inputBuffer, _imageOutput, true); + _convertValarrayBuffer2cvMat(_imageOutput, _multiuseFilter->getNBrows(), _multiuseFilter->getNBcolumns(), true, outputToneMappedImage); + } + else + { + _runGrayToneMapping(_inputBuffer, _imageOutput); + _convertValarrayBuffer2cvMat(_imageOutput, _multiuseFilter->getNBrows(), _multiuseFilter->getNBcolumns(), false, outputToneMappedImage); + } + + } + + /** + * setup method that updates tone mapping behaviors by adjusing the local luminance computation area + * @param photoreceptorsNeighborhoodRadius the first stage local adaptation area + * @param ganglioncellsNeighborhoodRadius the second stage local adaptation area + * @param meanLuminanceModulatorK the factor applied to modulate the meanLuminance information (default is 1, see reference paper) + */ + virtual void setup(const float photoreceptorsNeighborhoodRadius=3.f, const float ganglioncellsNeighborhoodRadius=1.f, const float meanLuminanceModulatorK=1.f) + { + // setup the spatio-temporal properties of each filter + _meanLuminanceModulatorK = meanLuminanceModulatorK; + _multiuseFilter->setV0CompressionParameter(1.f, 255.f, 128.f); + _multiuseFilter->setLPfilterParameters(0.f, 0.f, photoreceptorsNeighborhoodRadius, 1); + _multiuseFilter->setLPfilterParameters(0.f, 0.f, ganglioncellsNeighborhoodRadius, 2); + } + +private: + // a filter able to perform local adaptation and low pass spatio-temporal filtering + cv::Ptr _multiuseFilter; + cv::Ptr _colorEngine; + + //!< buffer used to convert input cv::Mat to internal retina buffers format (valarrays) + std::valarray _inputBuffer; + std::valarray _imageOutput; + std::valarray _temp2; + float _meanLuminanceModulatorK; + + +void _convertValarrayBuffer2cvMat(const std::valarray &grayMatrixToConvert, const unsigned int nbRows, const unsigned int nbColumns, const bool colorMode, OutputArray outBuffer) +{ + // fill output buffer with the valarray buffer + const float *valarrayPTR=get_data(grayMatrixToConvert); + if (!colorMode) + { + outBuffer.create(cv::Size(nbColumns, nbRows), CV_8U); + Mat outMat = outBuffer.getMat(); + for (unsigned int i=0;i(pixel)=(unsigned char)*(valarrayPTR++); + } + } + } + else + { + const unsigned int nbPixels=nbColumns*nbRows; + const unsigned int doubleNBpixels=nbColumns*nbRows*2; + outBuffer.create(cv::Size(nbColumns, nbRows), CV_8UC3); + Mat outMat = outBuffer.getMat(); + for (unsigned int i=0;i(pixel)=pixelValues; + } + } + } +} + +bool _convertCvMat2ValarrayBuffer(InputArray inputMat, std::valarray &outputValarrayMatrix) +{ + const Mat inputMatToConvert=inputMat.getMat(); + // first check input consistency + if (inputMatToConvert.empty()) + throw cv::Exception(-1, "RetinaImpl cannot be applied, input buffer is empty", "RetinaImpl::run", "RetinaImpl.h", 0); + + // retreive color mode from image input + int imageNumberOfChannels = inputMatToConvert.channels(); + + // convert to float AND fill the valarray buffer + typedef float T; // define here the target pixel format, here, float + const int dsttype = DataType::depth; // output buffer is float format + + const unsigned int nbPixels=inputMat.getMat().rows*inputMat.getMat().cols; + const unsigned int doubleNBpixels=inputMat.getMat().rows*inputMat.getMat().cols*2; + + if(imageNumberOfChannels==4) + { + // create a cv::Mat table (for RGBA planes) + cv::Mat planes[4] = + { + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[doubleNBpixels]), + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[nbPixels]), + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[0]) + }; + planes[3] = cv::Mat(inputMatToConvert.size(), dsttype); // last channel (alpha) does not point on the valarray (not usefull in our case) + // split color cv::Mat in 4 planes... it fills valarray directely + cv::split(Mat_ >(inputMatToConvert), planes); + } + else if (imageNumberOfChannels==3) + { + // create a cv::Mat table (for RGB planes) + cv::Mat planes[] = + { + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[doubleNBpixels]), + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[nbPixels]), + cv::Mat(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[0]) + }; + // split color cv::Mat in 3 planes... it fills valarray directely + cv::split(cv::Mat_ >(inputMatToConvert), planes); + } + else if(imageNumberOfChannels==1) + { + // create a cv::Mat header for the valarray + cv::Mat dst(inputMatToConvert.size(), dsttype, &outputValarrayMatrix[0]); + inputMatToConvert.convertTo(dst, dsttype); + } + else + CV_Error(Error::StsUnsupportedFormat, "input image must be single channel (gray levels), bgr format (color) or bgra (color with transparency which won't be considered"); + + return imageNumberOfChannels>1; // return bool : false for gray level image processing, true for color mode +} + + +// run the initilized retina filter in order to perform gray image tone mapping, after this call all retina outputs are updated +void _runGrayToneMapping(const std::valarray &grayImageInput, std::valarray &grayImageOutput) +{ + // apply tone mapping on the multiplexed image + // -> photoreceptors local adaptation (large area adaptation) + _multiuseFilter->runFilter_LPfilter(grayImageInput, grayImageOutput, 0); // compute low pass filtering modeling the horizontal cells filtering to acess local luminance + _multiuseFilter->setV0CompressionParameterToneMapping(1.f, grayImageOutput.max(), _meanLuminanceModulatorK*grayImageOutput.sum()/(float)_multiuseFilter->getNBpixels()); + _multiuseFilter->runFilter_LocalAdapdation(grayImageInput, grayImageOutput, _temp2); // adapt contrast to local luminance + + // -> ganglion cells local adaptation (short area adaptation) + _multiuseFilter->runFilter_LPfilter(_temp2, grayImageOutput, 1); // compute low pass filtering (high cut frequency (remove spatio-temporal noise) + _multiuseFilter->setV0CompressionParameterToneMapping(1.f, _temp2.max(), _meanLuminanceModulatorK*grayImageOutput.sum()/(float)_multiuseFilter->getNBpixels()); + _multiuseFilter->runFilter_LocalAdapdation(_temp2, grayImageOutput, grayImageOutput); // adapt contrast to local luminance + +} + +// run the initilized retina filter in order to perform color tone mapping, after this call all retina outputs are updated +void _runRGBToneMapping(const std::valarray &RGBimageInput, std::valarray &RGBimageOutput, const bool useAdaptiveFiltering) +{ + // multiplex the image with the color sampling method specified in the constructor + _colorEngine->runColorMultiplexing(RGBimageInput); + + // apply tone mapping on the multiplexed image + _runGrayToneMapping(_colorEngine->getMultiplexedFrame(), RGBimageOutput); + + // demultiplex tone maped image + _colorEngine->runColorDemultiplexing(RGBimageOutput, useAdaptiveFiltering, _multiuseFilter->getMaxInputValue());//_ColorEngine->getMultiplexedFrame());//_ParvoRetinaFilter->getPhotoreceptorsLPfilteringOutput()); + + // rescaling result between 0 and 255 + _colorEngine->normalizeRGBOutput_0_maxOutputValue(255.0); + + // return the result + RGBimageOutput=_colorEngine->getDemultiplexedColorFrame(); +} + +}; + +CV_EXPORTS Ptr createRetinaFastToneMapping(Size inputSize) +{ + return makePtr(inputSize); +} + +}// end of namespace bioinspired +}// end of namespace cv diff --git a/modules/bioinspired/src/retinafilter.cpp b/modules/bioinspired/src/retinafilter.cpp new file mode 100644 index 00000000000..e1e24c89bff --- /dev/null +++ b/modules/bioinspired/src/retinafilter.cpp @@ -0,0 +1,526 @@ +/*#****************************************************************************** +** 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. +** +** +** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. +** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. +** +** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) +** +** Creation - enhancement process 2007-2011 +** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France +** +** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). +** Refer to the following research paper for more information: +** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 +** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: +** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. +** +** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : +** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: +** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#include "precomp.hpp" + +#include "retinafilter.hpp" + +// @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC : www.listic.univ-savoie.fr, Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ + +#include +#include + +namespace cv +{ +namespace bioinspired +{ + // standard constructor without any log sampling of the input frame + RetinaFilter::RetinaFilter(const unsigned int sizeRows, const unsigned int sizeColumns, const bool colorMode, const int samplingMethod, const bool useRetinaLogSampling, const double reductionFactor, const double samplingStrenght) + : + _retinaParvoMagnoMappedFrame(0), + _retinaParvoMagnoMapCoefTable(0), + _photoreceptorsPrefilter((1-(int)useRetinaLogSampling)*sizeRows+useRetinaLogSampling*ImageLogPolProjection::predictOutputSize(sizeRows, reductionFactor), (1-(int)useRetinaLogSampling)*sizeColumns+useRetinaLogSampling*ImageLogPolProjection::predictOutputSize(sizeColumns, reductionFactor), 4), + _ParvoRetinaFilter((1-(int)useRetinaLogSampling)*sizeRows+useRetinaLogSampling*ImageLogPolProjection::predictOutputSize(sizeRows, reductionFactor), (1-(int)useRetinaLogSampling)*sizeColumns+useRetinaLogSampling*ImageLogPolProjection::predictOutputSize(sizeColumns, reductionFactor)), + _MagnoRetinaFilter((1-(int)useRetinaLogSampling)*sizeRows+useRetinaLogSampling*ImageLogPolProjection::predictOutputSize(sizeRows, reductionFactor), (1-(int)useRetinaLogSampling)*sizeColumns+useRetinaLogSampling*ImageLogPolProjection::predictOutputSize(sizeColumns, reductionFactor)), + _colorEngine((1-(int)useRetinaLogSampling)*sizeRows+useRetinaLogSampling*ImageLogPolProjection::predictOutputSize(sizeRows, reductionFactor), (1-(int)useRetinaLogSampling)*sizeColumns+useRetinaLogSampling*ImageLogPolProjection::predictOutputSize(sizeColumns, reductionFactor), samplingMethod), + // configure retina photoreceptors log sampling... if necessary + _photoreceptorsLogSampling(NULL) + { + +#ifdef RETINADEBUG + std::cout<<"RetinaFilter::size( "<<_photoreceptorsPrefilter.getNBrows()<<", "<<_photoreceptorsPrefilter.getNBcolumns()<<")"<<" =? "<<_photoreceptorsPrefilter.getNBpixels()<initProjection(reductionFactor, samplingStrenght)) + { + std::cerr<<"RetinaFilter::Problem initializing photoreceptors log sampling, could not setup retina filter"<getNBrows()<<", "<<_photoreceptorsLogSampling->getNBcolumns()<<")"<<" =? "<<_photoreceptorsLogSampling->getNBpixels()<getNBrows()<<", "<getNBcolumns()<<")"<<_filterOutput.size()<<" =? "<<_filterOutput.getNBpixels()<clearAllBuffers(); + // stability controls value init + _setInitPeriodCount(); + } + + /** + * resize retina filter object (resize all allocated buffers + * @param NBrows: the new height size + * @param NBcolumns: the new width size + */ + void RetinaFilter::resize(const unsigned int NBrows, const unsigned int NBcolumns) + { + unsigned int rows=NBrows, cols=NBcolumns; + + // resize optionnal member and adjust other modules size if required + if (_photoreceptorsLogSampling) + { + _photoreceptorsLogSampling->resize(NBrows, NBcolumns); + rows=_photoreceptorsLogSampling->getOutputNBrows(); + cols=_photoreceptorsLogSampling->getOutputNBcolumns(); + } + + _photoreceptorsPrefilter.resize(rows, cols); + _ParvoRetinaFilter.resize(rows, cols); + _MagnoRetinaFilter.resize(rows, cols); + _colorEngine.resize(rows, cols); + + // reset parvo magno mapping + _createHybridTable(); + + // clean buffers + clearAllBuffers(); + + } + + // stability controls value init + void RetinaFilter::_setInitPeriodCount() + { + + // find out the maximum temporal constant value and apply a security factor + // false value (obviously too long) but appropriate for simple use + _globalTemporalConstant=(unsigned int)(_ParvoRetinaFilter.getPhotoreceptorsTemporalConstant()+_ParvoRetinaFilter.getHcellsTemporalConstant()+_MagnoRetinaFilter.getTemporalConstant()); + // reset frame counter + _ellapsedFramesSinceLastReset=0; + } + + void RetinaFilter::_createHybridTable() + { + // create hybrid output and related coefficient table + _retinaParvoMagnoMappedFrame.resize(_photoreceptorsPrefilter.getNBpixels()); + + _retinaParvoMagnoMapCoefTable.resize(_photoreceptorsPrefilter.getNBpixels()*2); + + // fill _hybridParvoMagnoCoefTable + int i, j, halfRows=_photoreceptorsPrefilter.getNBrows()/2, halfColumns=_photoreceptorsPrefilter.getNBcolumns()/2; + float *hybridParvoMagnoCoefTablePTR= &_retinaParvoMagnoMapCoefTable[0]; + float minDistance=MIN(halfRows, halfColumns)*0.7f; + for (i=0;i<(int)_photoreceptorsPrefilter.getNBrows();++i) + { + for (j=0;j<(int)_photoreceptorsPrefilter.getNBcolumns();++j) + { + float distanceToCenter=std::sqrt(((float)(i-halfRows)*(i-halfRows)+(j-halfColumns)*(j-halfColumns))); + if (distanceToCentersetV0CompressionParameter(0.6, maxInputValue, meanValue); // keeps log compression sensitivity parameter (usefull for the tone mapping function) + _ParvoRetinaFilter.setOPLandParvoFiltersParameters(0,OPLtemporalresponse1, OPLspatialResponse1, OPLassymetryGain, OPLtemporalresponse2, OPLspatialResponse2); + _ParvoRetinaFilter.setV0CompressionParameter(0.9f, maxInputValue, meanValue); + _MagnoRetinaFilter.setCoefficientsTable(LPfilterGain, LPfilterTemporalresponse, LPfilterSpatialResponse, MovingContoursExtractorCoefficient, 0, 2.0f*LPfilterSpatialResponse); + _MagnoRetinaFilter.setV0CompressionParameter(0.7f, maxInputValue, meanValue); + + // stability controls value init + _setInitPeriodCount(); + } + + bool RetinaFilter::checkInput(const std::valarray &input, const bool) + { + + BasicRetinaFilter *inputTarget=&_photoreceptorsPrefilter; + if (_photoreceptorsLogSampling) + inputTarget=_photoreceptorsLogSampling; + + bool test=input.size()==inputTarget->getNBpixels() || input.size()==(inputTarget->getNBpixels()*3) ; + if (!test) + { + std::cerr<<"RetinaFilter::checkInput: input buffer does not match retina buffer size, conversion aborted"< &bufferInput=checkInput(LMSimageInput, true); + if (!bufferInput) + return NULL; + + if (!_useColorMode) + std::cerr<<"RetinaFilter::Can not call tone mapping oeration if the retina filter was created for gray scale images"< lmsTempBuffer(LMSimageInput); + std::cout<<"RetinaFilter::--->min LMS value="<L + _spatiotemporalLPfilter(LMSimageInput, _filterOutput, 1); + setV0CompressionParameterToneMapping(PhotoreceptorsCompression, _maxInputValue, this->sum()/_NBpixels); + _localLuminanceAdaptation(LMSimageInput, _filterOutput, lmsTempBuffer.Buffer()); + // ->M + _spatiotemporalLPfilter(LMSimageInput+_NBpixels, _filterOutput, 1); + setV0CompressionParameterToneMapping(PhotoreceptorsCompression, _maxInputValue, this->sum()/_NBpixels); + _localLuminanceAdaptation(LMSimageInput+_NBpixels, _filterOutput, lmsTempBuffer.Buffer()+_NBpixels); + // ->S + _spatiotemporalLPfilter(LMSimageInput+_NBpixels*2, _filterOutput, 1); + setV0CompressionParameterToneMapping(PhotoreceptorsCompression, _maxInputValue, this->sum()/_NBpixels); + _localLuminanceAdaptation(LMSimageInput+_NBpixels*2, _filterOutput, lmsTempBuffer.Buffer()+_NBpixels*2); + + // eliminate negative values + for (unsigned int i=0;imin LMS value="< acr1cr2TempBuffer(_NBrows, _NBcolumns, 3); + memcpy(acr1cr2TempBuffer.Buffer(), lmsTempBuffer.Buffer(), sizeof(float)*_NBpixels*3); + + // compute A Cr1 Cr2 to LMS color space conversion + _applyImageColorSpaceConversion(acr1cr2TempBuffer.Buffer(), lmsTempBuffer.Buffer(), _ACr1Cr2toLMS); + + // eliminate negative values + for (unsigned int i=0;isetDemultiplexedColorFrame(lmsTempBuffer.Buffer()); + */ + } + + // return image with center Parvo and peripheral Magno channels + void RetinaFilter::_processRetinaParvoMagnoMapping() + { + register float *hybridParvoMagnoPTR= &_retinaParvoMagnoMappedFrame[0]; + register const float *parvoOutputPTR= get_data(_ParvoRetinaFilter.getOutput()); + register const float *magnoXOutputPTR= get_data(_MagnoRetinaFilter.getOutput()); + register float *hybridParvoMagnoCoefTablePTR= &_retinaParvoMagnoMapCoefTable[0]; + + for (unsigned int i=0 ; i<_photoreceptorsPrefilter.getNBpixels() ; ++i, hybridParvoMagnoCoefTablePTR+=2) + { + float hybridValue=*(parvoOutputPTR++)**(hybridParvoMagnoCoefTablePTR)+*(magnoXOutputPTR++)**(hybridParvoMagnoCoefTablePTR+1); + *(hybridParvoMagnoPTR++)=hybridValue; + } + + TemplateBuffer::normalizeGrayOutput_0_maxOutputValue(&_retinaParvoMagnoMappedFrame[0], _photoreceptorsPrefilter.getNBpixels()); + + } + + bool RetinaFilter::getParvoFoveaResponse(std::valarray &parvoFovealResponse) + { + if (!_useParvoOutput) + return false; + if (parvoFovealResponse.size() != _ParvoRetinaFilter.getNBpixels()) + return false; + + register const float *parvoOutputPTR= get_data(_ParvoRetinaFilter.getOutput()); + register float *fovealParvoResponsePTR= &parvoFovealResponse[0]; + register float *hybridParvoMagnoCoefTablePTR= &_retinaParvoMagnoMapCoefTable[0]; + + for (unsigned int i=0 ; i<_photoreceptorsPrefilter.getNBpixels() ; ++i, hybridParvoMagnoCoefTablePTR+=2) + { + *(fovealParvoResponsePTR++)=*(parvoOutputPTR++)**(hybridParvoMagnoCoefTablePTR); + } + + return true; + } + + // method to retrieve the parafoveal magnocellular pathway response (no energy motion in fovea) + bool RetinaFilter::getMagnoParaFoveaResponse(std::valarray &magnoParafovealResponse) + { + if (!_useMagnoOutput) + return false; + if (magnoParafovealResponse.size() != _MagnoRetinaFilter.getNBpixels()) + return false; + + register const float *magnoXOutputPTR= get_data(_MagnoRetinaFilter.getOutput()); + register float *parafovealMagnoResponsePTR=&magnoParafovealResponse[0]; + register float *hybridParvoMagnoCoefTablePTR=&_retinaParvoMagnoMapCoefTable[0]+1; + + for (unsigned int i=0 ; i<_photoreceptorsPrefilter.getNBpixels() ; ++i, hybridParvoMagnoCoefTablePTR+=2) + { + *(parafovealMagnoResponsePTR++)=*(magnoXOutputPTR++)**(hybridParvoMagnoCoefTablePTR); + } + + return true; + } +}// end of namespace bioinspired +}// end of namespace cv diff --git a/modules/bioinspired/src/retinafilter.hpp b/modules/bioinspired/src/retinafilter.hpp new file mode 100644 index 00000000000..5b254c8accb --- /dev/null +++ b/modules/bioinspired/src/retinafilter.hpp @@ -0,0 +1,548 @@ +/*#****************************************************************************** +** 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. +** +** +** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. +** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. +** +** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) +** +** Creation - enhancement process 2007-2011 +** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France +** +** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). +** Refer to the following research paper for more information: +** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 +** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: +** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. +** +** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : +** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: +** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +/** +* @class RetinaFilter +* @brief class which describes the retina model developped at the LIS/GIPSA-LAB www.gipsa-lab.inpg.fr: +* -> performs a contours and moving contours extraction with powerfull local data enhancement as at the retina level +* Based on Alexandre BENOIT thesis: "Le systeme visuel humain au secours de la vision par ordinateur" +* +* => various optimisations and enhancements added after 2007 such as tone mapping capabilities, see reference paper cited in the licence and : +* Benoit A.,Alleysson D., Herault J., Le Callet P. (2009), "Spatio-Temporal Tone Mapping Operator based on a Retina model", Computational Color Imaging Workshop (CCIW09),pp 12-22, Saint Etienne, France +* +* TYPICAL USE: +* +* // create object at a specified picture size +* Retina *retina; +* retina =new Retina(frameSizeRows, frameSizeColumns, RGBmode); +* +* // init gain, spatial and temporal parameters: +* retina->setParameters(0.7, 1, 0, 7, 1, 5, 0, 0, 3 , true); +* +* // during program execution, call the filter for local luminance correction, contours extraction, moving contours extraction from an input picture called "FrameBuffer": +* retina->runfilter(FrameBuffer); +* +* // get the different output frames, check in the class description below for more outputs: +* const std::valarray correctedLuminance=retina->getLocalAdaptation(); +* const std::valarray contours=retina->getContours(); +* const std::valarray movingContours=retina->getMovingContours(); +* +* // at the end of the program, destroy object: +* delete retina; +* +* @author Alexandre BENOIT, benoit.alexandre.vision@gmail.com, LISTIC / Gipsa-Lab, France: www.gipsa-lab.inpg.fr/ +* Creation date 2007 +*/ + +#ifndef RETINACLASSES_H_ +#define RETINACLASSES_H_ + +#include "basicretinafilter.hpp" +#include "parvoretinafilter.hpp" +#include "magnoretinafilter.hpp" + +// optional includes (depending on the related publications) +#include "imagelogpolprojection.hpp" + +#include "retinacolor.hpp" + +//#define __RETINADEBUG // define RETINADEBUG to display debug data +namespace cv +{ +namespace bioinspired +{ +// retina class that process the 3 outputs of the retina filtering stages +class RetinaFilter//: public BasicRetinaFilter +{ +public: + + /** + * constructor of the retina filter model with log sampling of the input frame (models the photoreceptors log sampling (central high resolution fovea and lower precision borders)) + * @param sizeRows: number of rows of the input image + * @param sizeColumns: number of columns of the input image + * @param colorMode: specifies if the retina works with color (true) of stays in grayscale processing (false), can be adjusted online by the use of setColorMode method + * @param samplingMethod: specifies which kind of color sampling will be used + * @param useRetinaLogSampling: activate retina log sampling, if true, the 2 following parameters can be used + * @param reductionFactor: only usefull if param useRetinaLogSampling=true, specifies the reduction factor of the output frame (as the center (fovea) is high resolution and corners can be underscaled, then a reduction of the output is allowed without precision leak + * @param samplingStrenght: only usefull if param useRetinaLogSampling=true, specifies the strenght of the log scale that is applied + */ + RetinaFilter(const unsigned int sizeRows, const unsigned int sizeColumns, const bool colorMode=false, const int samplingMethod=RETINA_COLOR_BAYER, const bool useRetinaLogSampling=false, const double reductionFactor=1.0, const double samplingStrenght=10.0); + + /** + * standard destructor + */ + ~RetinaFilter(); + + /** + * function that clears all buffers of the object + */ + void clearAllBuffers(); + + /** + * resize retina parvo filter object (resize all allocated buffers) + * @param NBrows: the new height size + * @param NBcolumns: the new width size + */ + void resize(const unsigned int NBrows, const unsigned int NBcolumns); + + /** + * Input buffer checker: allows to check if the passed image buffer corresponds to retina filter expectations + * @param input: the input image buffer + * @param colorMode: specifiy if the input should be considered by the retina as colored of not + * @return false if not compatible or it returns true if OK + */ + bool checkInput(const std::valarray &input, const bool colorMode); + + /** + * run the initilized retina filter, after this call all retina outputs are updated + * @param imageInput: image input buffer, can be grayscale or RGB image respecting the size specified at the constructor level + * @param useAdaptiveFiltering: set true if you want to use adaptive color demultilexing (solve some color artefact problems), see RetinaColor for citation references + * @param processRetinaParvoMagnoMapping: tels if the main outputs takes into account the mapping of the Parvo and Magno channels on the retina (centred parvo (fovea) and magno outside (parafovea)) + * @param useColorMode: color information is used if true, warning, if input is only gray level, a buffer overflow error will occur + -> note that if color mode is activated and processRetinaParvoMagnoMapping==true, then the demultiplexed color frame (accessible throw getColorOutput() will be a color contours frame in the fovea and gray level moving contours outside + @param inputIsColorMultiplexed: set trus if the input data is a multiplexed color image (using Bayer sampling for example), the color sampling method must correspond to the RETINA_COLORSAMPLINGMETHOD passed at constructor! + * @return true if process ran well, false in case of failure + */ + bool runFilter(const std::valarray &imageInput, const bool useAdaptiveFiltering=true, const bool processRetinaParvoMagnoMapping=false, const bool useColorMode=false, const bool inputIsColorMultiplexed=false); + + /** + * run the initilized retina filter in order to perform color tone mapping applied on an RGB image, after this call the color output of the retina is updated (use function getColorOutput() to grab it) + * the algorithm is based on David Alleyson, Sabine Susstruck and Laurence Meylan's work, please cite: + * -> Meylan L., Alleysson D., and S�sstrunk S., A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images, Journal of Optical Society of America, A, Vol. 24, N� 9, September, 1st, 2007, pp. 2807-2816 + * get the resulting gray frame by calling function getParvoColor() + * @param grayImageInput: RGB image input buffer respecting the size specified at the constructor level + * @param PhotoreceptorsCompression: sets the log compression parameters applied at the photoreceptors level (enhance luminance in dark areas) + * @param ganglionCellsCompression: sets the log compression applied at the gnaglion cells output (enhance contrast) + */ + void runGrayToneMapping(const std::valarray &grayImageInput, std::valarray &grayImageOutput, const float PhotoreceptorsCompression=0.6, const float ganglionCellsCompression=0.6); + + /** + * run the initilized retina filter in order to perform color tone mapping applied on an RGB image, after this call the color output of the retina is updated (use function getColorOutput() to grab it) + * the algorithm is based on David Alleyson, Sabine Susstruck and Laurence Meylan's work, please cite: + * -> Meylan L., Alleysson D., and S�sstrunk S., A Model of Retinal Local Adaptation for the Tone Mapping of Color Filter Array Images, Journal of Optical Society of America, A, Vol. 24, N� 9, September, 1st, 2007, pp. 2807-2816 + * get the resulting RGB frame by calling function getParvoColor() + * @param RGBimageInput: RGB image input buffer respecting the size specified at the constructor level + * @param useAdaptiveFiltering: set true if you want to use adaptive color demultilexing (solve some color artefact problems), see RetinaColor for citation references + * @param PhotoreceptorsCompression: sets the log compression parameters applied at the photoreceptors level (enhance luminance in dark areas) + * @param ganglionCellsCompression: sets the log compression applied at the ganglion cells output (enhance contrast) + */ + void runRGBToneMapping(const std::valarray &RGBimageInput, std::valarray &imageOutput, const bool useAdaptiveFiltering, const float PhotoreceptorsCompression=0.6, const float ganglionCellsCompression=0.6); + + /** + * run the initilized retina filter in order to perform color tone mapping applied on an RGB image, after this call the color output of the retina is updated (use function getColorOutput() to grab it) + * get the resulting RGB frame by calling function getParvoColor() + * @param LMSimageInput: RGB image input buffer respecting the size specified at the constructor level + * @param useAdaptiveFiltering: set true if you want to use adaptive color demultilexing (solve some color artefact problems), see RetinaColor for citation references + * @param PhotoreceptorsCompression: sets the log compression parameters applied at the photoreceptors level (enhance luminance in dark areas) + * @param ganglionCellsCompression: sets the log compression applied at the gnaglion cells output (enhance contrast) + */ + void runLMSToneMapping(const std::valarray &LMSimageInput, std::valarray &imageOutput, const bool useAdaptiveFiltering, const float PhotoreceptorsCompression=0.6, const float ganglionCellsCompression=0.6); + + /** + * set up function of the retina filter: all the retina is initialized at this step, some specific parameters are set by default, use setOPLandParvoCoefficientsTable() and setMagnoCoefficientsTable in order to setup the retina with more options + * @param OPLspatialResponse1: (equal to k1 in setOPLandParvoCoefficientsTable() function) the spatial constant of the first order low pass filter of the photoreceptors, use it to cut high spatial frequencies (noise or thick contours), unit is pixels, typical value is 1 pixel + * @param OPLtemporalresponse1: (equal to tau1 in setOPLandParvoCoefficientsTable() function) the time constant of the first order low pass filter of the photoreceptors, use it to cut high temporal frequencies (noise or fast motion), unit is frames, typical value is 1 frame + * @param OPLassymetryGain: (equal to beta2 in setOPLandParvoCoefficientsTable() function) gain of the horizontal cells network, if 0, then the mean value of the output is zero, if the parameter is near 1, then, the luminance is not filtered and is still reachable at the output, typicall value is 0 + * @param OPLspatialResponse2: (equal to k2 in setOPLandParvoCoefficientsTable() function) the spatial constant of the first order low pass filter of the horizontal cells, use it to cut low spatial frequencies (local luminance), unit is pixels, typical value is 5 pixel + * @param OPLtemporalresponse2: (equal to tau2 in setOPLandParvoCoefficientsTable() function) the time constant of the first order low pass filter of the horizontal cells, use it to cut low temporal frequencies (local luminance variations), unit is frames, typical value is 1 frame, as the photoreceptors + * @param LPfilterSpatialResponse: (equal to parasolCells_k in setMagnoCoefficientsTable() function) the low pass filter spatial constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is pixels, typical value is 5 + * @param LPfilterGain: (equal to parasolCells_beta in setMagnoCoefficientsTable() function) the low pass filter gain used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), typical value is 0 + * @param LPfilterTemporalresponse: (equal to parasolCells_tau in setMagnoCoefficientsTable() function) the low pass filter time constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is frame, typical value is 0 (immediate response) + * @param MovingContoursExtractorCoefficient: (equal to amacrinCellsTemporalCutFrequency in setMagnoCoefficientsTable() function)the time constant of the first order high pass fiter of the magnocellular way (motion information channel), unit is frames, tipicall value is 5 + * @param normalizeParvoOutput_0_maxOutputValue: specifies if the Parvo cellular output should be normalized between 0 and maxOutputValue (true) or not (false) in order to remain at a null mean value, true value is recommended for visualisation + * @param normalizeMagnoOutput_0_maxOutputValue: specifies if the Magno cellular output should be normalized between 0 and maxOutputValue (true) or not (false), setting true may be hazardous because it can enhace the noise response when nothing is moving + * @param maxOutputValue: the maximum amplitude value of the normalized outputs (generally 255 for 8bit per channel pictures) + * @param maxInputValue: the maximum pixel value of the input picture (generally 255 for 8bit per channel pictures), specify it in other case (for example High Dynamic Range Images) + * @param meanValue: the global mean value of the input data usefull for local adaptation setup + */ + void setGlobalParameters(const float OPLspatialResponse1=0.7, const float OPLtemporalresponse1=1, const float OPLassymetryGain=0, const float OPLspatialResponse2=5, const float OPLtemporalresponse2=1, const float LPfilterSpatialResponse=5, const float LPfilterGain=0, const float LPfilterTemporalresponse=0, const float MovingContoursExtractorCoefficient=5, const bool normalizeParvoOutput_0_maxOutputValue=false, const bool normalizeMagnoOutput_0_maxOutputValue=false, const float maxOutputValue=255.0, const float maxInputValue=255.0, const float meanValue=128.0); + + /** + * setup the local luminance adaptation capability + * @param V0CompressionParameter: the compression strengh of the photoreceptors local adaptation output, set a value between 160 and 250 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 160 + */ + inline void setPhotoreceptorsLocalAdaptationSensitivity(const float V0CompressionParameter) { _photoreceptorsPrefilter.setV0CompressionParameter(1-V0CompressionParameter);_setInitPeriodCount(); } + + /** + * setup the local luminance adaptation capability + * @param V0CompressionParameter: the compression strengh of the parvocellular pathway (details) local adaptation output, set a value between 160 and 250 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 160 + */ + inline void setParvoGanglionCellsLocalAdaptationSensitivity(const float V0CompressionParameter) { _ParvoRetinaFilter.setV0CompressionParameter(V0CompressionParameter);_setInitPeriodCount(); } + + /** + * setup the local luminance adaptation area of integration + * @param spatialResponse: the spatial constant of the low pass filter applied on the bipolar cells output in order to compute local contrast mean values + * @param temporalResponse: the spatial constant of the low pass filter applied on the bipolar cells output in order to compute local contrast mean values (generally set to zero: immediate response) + */ + inline void setGanglionCellsLocalAdaptationLPfilterParameters(const float spatialResponse, const float temporalResponse) { _ParvoRetinaFilter.setGanglionCellsLocalAdaptationLPfilterParameters(temporalResponse, spatialResponse);_setInitPeriodCount(); } + + /** + * setup the local luminance adaptation capability + * @param V0CompressionParameter: the compression strengh of the magnocellular pathway (motion) local adaptation output, set a value between 160 and 250 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 160 + */ + inline void setMagnoGanglionCellsLocalAdaptationSensitivity(const float V0CompressionParameter) { _MagnoRetinaFilter.setV0CompressionParameter(V0CompressionParameter);_setInitPeriodCount(); } + + /** + * setup the OPL and IPL parvo channels + * @param beta1: gain of the horizontal cells network, if 0, then the mean value of the output is zero (default value), if the parameter is near 1, the amplitude is boosted but it should only be used for values rescaling... if needed + * @param tau1: the time constant of the first order low pass filter of the photoreceptors, use it to cut high temporal frequencies (noise or fast motion), unit is frames, typical value is 1 frame + * @param k1: the spatial constant of the first order low pass filter of the photoreceptors, use it to cut high spatial frequencies (noise or thick contours), unit is pixels, typical value is 1 pixel + * @param beta2: gain of the horizontal cells network, if 0, then the mean value of the output is zero, if the parameter is near 1, then, the luminance is not filtered and is still reachable at the output, typicall value is 0 + * @param tau2: the time constant of the first order low pass filter of the horizontal cells, use it to cut low temporal frequencies (local luminance variations), unit is frames, typical value is 1 frame, as the photoreceptors + * @param k2: the spatial constant of the first order low pass filter of the horizontal cells, use it to cut low spatial frequencies (local luminance), unit is pixels, typical value is 5 pixel, this value is also used for local contrast computing when computing the local contrast adaptation at the ganglion cells level (Inner Plexiform Layer parvocellular channel model) + * @param V0CompressionParameter: the compression strengh of the ganglion cells local adaptation output, set a value between 160 and 250 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 230 + */ + void setOPLandParvoParameters(const float beta1, const float tau1, const float k1, const float beta2, const float tau2, const float k2, const float V0CompressionParameter) { _ParvoRetinaFilter.setOPLandParvoFiltersParameters(beta1, tau1, k1, beta2, tau2, k2);_ParvoRetinaFilter.setV0CompressionParameter(V0CompressionParameter);_setInitPeriodCount(); } + + /** + * set parameters values for the Inner Plexiform Layer (IPL) magnocellular channel + * @param parasolCells_beta: the low pass filter gain used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), typical value is 0 + * @param parasolCells_tau: the low pass filter time constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is frame, typical value is 0 (immediate response) + * @param parasolCells_k: the low pass filter spatial constant used for local contrast adaptation at the IPL level of the retina (for ganglion cells local adaptation), unit is pixels, typical value is 5 + * @param amacrinCellsTemporalCutFrequency: the time constant of the first order high pass fiter of the magnocellular way (motion information channel), unit is frames, tipicall value is 5 + * @param V0CompressionParameter: the compression strengh of the ganglion cells local adaptation output, set a value between 160 and 250 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 200 + * @param localAdaptintegration_tau: specifies the temporal constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + * @param localAdaptintegration_k: specifies the spatial constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation + */ + void setMagnoCoefficientsTable(const float parasolCells_beta, const float parasolCells_tau, const float parasolCells_k, const float amacrinCellsTemporalCutFrequency, const float V0CompressionParameter, const float localAdaptintegration_tau, const float localAdaptintegration_k) { _MagnoRetinaFilter.setCoefficientsTable(parasolCells_beta, parasolCells_tau, parasolCells_k, amacrinCellsTemporalCutFrequency, localAdaptintegration_tau, localAdaptintegration_k);_MagnoRetinaFilter.setV0CompressionParameter(V0CompressionParameter);_setInitPeriodCount(); } + + /** + * set if the parvo output should be or not normalized between 0 and 255 (for display purpose generally) + * @param normalizeParvoOutput_0_maxOutputValue: true if normalization should be done + */ + inline void activateNormalizeParvoOutput_0_maxOutputValue(const bool normalizeParvoOutput_0_maxOutputValue) { _normalizeParvoOutput_0_maxOutputValue=normalizeParvoOutput_0_maxOutputValue; } + + /** + * set if the magno output should be or not normalized between 0 and 255 (for display purpose generally), take care, if nothing is moving, then, the noise will be enanced !!! + * @param normalizeMagnoOutput_0_maxOutputValue: true if normalization should be done + */ + inline void activateNormalizeMagnoOutput_0_maxOutputValue(const bool normalizeMagnoOutput_0_maxOutputValue) { _normalizeMagnoOutput_0_maxOutputValue=normalizeMagnoOutput_0_maxOutputValue; } + + /** + * setup the maximum amplitude value of the normalized outputs (generally 255 for 8bit per channel pictures) + * @param maxOutputValue: maximum amplitude value of the normalized outputs (generally 255 for 8bit per channel pictures) + */ + inline void setMaxOutputValue(const float maxOutputValue) { _maxOutputValue=maxOutputValue; } + + /** + * sets the color mode of the frame grabber + * @param desiredColorMode: true if the user needs color information, false for graylevels + */ + void setColorMode(const bool desiredColorMode) { _useColorMode=desiredColorMode; } + + /** + * activate color saturation as the final step of the color demultiplexing process + * -> this saturation is a sigmoide function applied to each channel of the demultiplexed image. + * @param saturateColors: boolean that activates color saturation (if true) or desactivate (if false) + * @param colorSaturationValue: the saturation factor + * */ + inline void setColorSaturation(const bool saturateColors=true, const float colorSaturationValue=4.0) { _colorEngine.setColorSaturation(saturateColors, colorSaturationValue); } + + ///////////////////////////////////////////////////////////////// + // function that retrieve the main retina outputs, one by one, or all in a structure + + /** + * @return the input image sampled by the photoreceptors spatial sampling + */ + inline const std::valarray &getPhotoreceptorsSampledFrame() const + { + CV_Assert(_photoreceptorsLogSampling); + return _photoreceptorsLogSampling->getSampledFrame(); + }; + + /** + * @return photoreceptors output, locally adapted luminance only, no high frequency spatio-temporal noise reduction at the next retina processing stages, use getPhotoreceptors method to get complete photoreceptors output + */ + inline const std::valarray &getLocalAdaptation() const {return _photoreceptorsPrefilter.getOutput(); } + + /** + * @return photoreceptors output: locally adapted luminance and high frequency spatio-temporal noise reduction, high luminance is a little saturated at this stage, but this is corrected naturally at the next retina processing stages + */ + inline const std::valarray &getPhotoreceptors() const {return _ParvoRetinaFilter.getPhotoreceptorsLPfilteringOutput(); } + + /** + * @return the local luminance of the processed frame (it is the horizontal cells output) + */ + inline const std::valarray &getHorizontalCells() const {return _ParvoRetinaFilter.getHorizontalCellsOutput(); } + + ///////// CONTOURS part, PARVOCELLULAR RETINA PATHWAY + /** + * @return true if Parvocellular output is activated, false if not + */ + inline bool areContoursProcessed() { return _useParvoOutput; } + + /** + * method to retrieve the foveal parvocellular pathway response (no details energy in parafovea) + * @param parvoParafovealResponse: buffer that will be filled with the response of the magnocellular pathway in the parafoveal area + * @return true if process succeeded (if buffer exists, is its size matches retina size, if magno channel is activated and if mapping is initialized + */ + bool getParvoFoveaResponse(std::valarray &parvoFovealResponse); + + /** + * @param useParvoOutput: true if Parvocellular output should be activated, false if not + */ + inline void activateContoursProcessing(const bool useParvoOutput) { _useParvoOutput=useParvoOutput; } + + /** + * @return the parvocellular contours information (details), should be used at the fovea level + */ + const std::valarray &getContours(); // Parvocellular output + + /** + * @return the parvocellular contours ON information (details), should be used at the fovea level + */ + inline const std::valarray &getContoursON() const {return _ParvoRetinaFilter.getParvoON(); } // Parvocellular ON output + + /** + * @return the parvocellular contours OFF information (details), should be used at the fovea level + */ + inline const std::valarray &getContoursOFF() const {return _ParvoRetinaFilter.getParvoOFF(); } // Parvocellular OFF output + + ///////// MOVING CONTOURS part, MAGNOCELLULAR RETINA PATHWAY + /** + * @return true if Magnocellular output is activated, false if not + */ + inline bool areMovingContoursProcessed() { return _useMagnoOutput; } + + /** + * method to retrieve the parafoveal magnocellular pathway response (no motion energy in fovea) + * @param magnoParafovealResponse: buffer that will be filled with the response of the magnocellular pathway in the parafoveal area + * @return true if process succeeded (if buffer exists, is its size matches retina size, if magno channel is activated and if mapping is initialized + */ + bool getMagnoParaFoveaResponse(std::valarray &magnoParafovealResponse); + + /** + * @param useMagnoOutput: true if Magnoocellular output should be activated, false if not + */ + inline void activateMovingContoursProcessing(const bool useMagnoOutput) { _useMagnoOutput=useMagnoOutput; } + + /** + * @return the magnocellular moving contours information (motion), should be used at the parafovea level without post-processing + */ + inline const std::valarray &getMovingContours() const {return _MagnoRetinaFilter.getOutput(); } // Magnocellular output + + /** + * @return the magnocellular moving contours information (motion), should be used at the parafovea level with assymetric sigmoide post-processing which saturates motion information + */ + inline const std::valarray &getMovingContoursSaturated() const {return _MagnoRetinaFilter.getMagnoYsaturated(); } // Saturated Magnocellular output + + /** + * @return the magnocellular moving contours ON information (motion), should be used at the parafovea level without post-processing + */ + inline const std::valarray &getMovingContoursON() const {return _MagnoRetinaFilter.getMagnoON(); } // Magnocellular ON output + + /** + * @return the magnocellular moving contours OFF information (motion), should be used at the parafovea level without post-processing + */ + inline const std::valarray &getMovingContoursOFF() const {return _MagnoRetinaFilter.getMagnoOFF(); } // Magnocellular OFF output + + /** + * @return a gray level image with center Parvo and peripheral Magno X channels, WARNING, the result will be ok if you called previously fucntion runFilter(imageInput, processRetinaParvoMagnoMapping=true); + * -> will be accessible even if color mode is activated (but the image is color sampled so quality is poor), but get the same thing but in color by the use of function getParvoColor() + */ + inline const std::valarray &getRetinaParvoMagnoMappedOutput() const {return _retinaParvoMagnoMappedFrame; } // return image with center Parvo and peripheral Magno channels + + /** + * color processing dedicated functions + * @return the parvo channel (contours, details) of the processed frame, grayscale output + */ + inline const std::valarray &getParvoContoursChannel() const {return _colorEngine.getLuminance(); } + + /** + * color processing dedicated functions + * @return the chrominance of the processed frame (same colorspace as the input output, usually RGB) + */ + inline const std::valarray &getParvoChrominance() const {return _colorEngine.getChrominance(); } // only retreive chrominance + + /** + * color processing dedicated functions + * @return the parvo + chrominance channels of the processed frame (same colorspace as the input output, usually RGB) + */ + inline const std::valarray &getColorOutput() const {return _colorEngine.getDemultiplexedColorFrame(); } // retrieve luminance+chrominance + + /** + * apply to the retina color output the Krauskopf transformation which leads to an opponent color system: output colorspace if Acr1cr2 if input of the retina was LMS color space + * @param result: the input buffer to fill with the transformed colorspace retina output + * @return true if process ended successfully + */ + inline bool applyKrauskopfLMS2Acr1cr2Transform(std::valarray &result) { return _colorEngine.applyKrauskopfLMS2Acr1cr2Transform(result); } + + /** + * apply to the retina color output the Krauskopf transformation which leads to an opponent color system: output colorspace if Acr1cr2 if input of the retina was LMS color space + * @param result: the input buffer to fill with the transformed colorspace retina output + * @return true if process ended successfully + */ + inline bool applyLMS2LabTransform(std::valarray &result) { return _colorEngine.applyLMS2LabTransform(result); } + + /** + * color processing dedicated functions + * @return the retina initialized mode, true if color mode (RGB), false if grayscale + */ + inline bool isColorMode() { return _useColorMode; } // return true if RGB mode, false if gray level mode + + /** + * @return the irregular low pass filter ouput at the photoreceptors level + */ + inline const std::valarray &getIrregularLPfilteredInputFrame() const {return _photoreceptorsLogSampling->getIrregularLPfilteredInputFrame(); } + + /** + * @return true if color mode is activated, false if gray levels processing + */ + bool getColorMode() { return _useColorMode; } + + /** + * + * @return true if a sufficient number of processed frames has been done since the last parameters update in order to get the stable state (r�gime permanent) + */ + inline bool isInitTransitionDone() { if (_ellapsedFramesSinceLastReset<_globalTemporalConstant)return false; return true; } + + /** + * find a distance in the image input space when the distance is known in the retina log sampled space...read again if it is not clear enough....sorry, i should sleep + * @param projectedRadiusLength: the distance to image center in the retina log sampled space + * @return the distance to image center in the input image space + */ + inline float getRetinaSamplingBackProjection(const float projectedRadiusLength) + { + if (_photoreceptorsLogSampling) + return (float)_photoreceptorsLogSampling->getOriginalRadiusLength(projectedRadiusLength); + return projectedRadiusLength; + } + + /////////////////: + // retina dimensions getters + + /** + * @return number of rows of the filter + */ + inline unsigned int getInputNBrows() { if (_photoreceptorsLogSampling) return _photoreceptorsLogSampling->getNBrows();else return _photoreceptorsPrefilter.getNBrows(); } + + /** + * @return number of columns of the filter + */ + inline unsigned int getInputNBcolumns() { if (_photoreceptorsLogSampling) return _photoreceptorsLogSampling->getNBcolumns();else return _photoreceptorsPrefilter.getNBcolumns(); } + + /** + * @return number of pixels of the filter + */ + inline unsigned int getInputNBpixels() { if (_photoreceptorsLogSampling) return _photoreceptorsLogSampling->getNBpixels();else return _photoreceptorsPrefilter.getNBpixels(); } + + /** + * @return the height of the frame output + */ + inline unsigned int getOutputNBrows() { return _photoreceptorsPrefilter.getNBrows(); } + + /** + * @return the width of the frame output + */ + inline unsigned int getOutputNBcolumns() { return _photoreceptorsPrefilter.getNBcolumns(); } + + /** + * @return the numbers of output pixels (width*height) of the images used by the object + */ + inline unsigned int getOutputNBpixels() { return _photoreceptorsPrefilter.getNBpixels(); } + + +private: + + // processing activation flags + bool _useParvoOutput; + bool _useMagnoOutput; + + + // filter stability controls + unsigned int _ellapsedFramesSinceLastReset; + unsigned int _globalTemporalConstant; + + // private template buffers and related access pointers + std::valarray _retinaParvoMagnoMappedFrame; + std::valarray _retinaParvoMagnoMapCoefTable; + // private objects of the class + BasicRetinaFilter _photoreceptorsPrefilter; + ParvoRetinaFilter _ParvoRetinaFilter; + MagnoRetinaFilter _MagnoRetinaFilter; + RetinaColor _colorEngine; + ImageLogPolProjection *_photoreceptorsLogSampling; + + bool _useMinimalMemoryForToneMappingONLY; + + bool _normalizeParvoOutput_0_maxOutputValue; + bool _normalizeMagnoOutput_0_maxOutputValue; + float _maxOutputValue; + bool _useColorMode; + + + + // private functions + void _setInitPeriodCount(); + void _createHybridTable(); + void _processRetinaParvoMagnoMapping(); + void _runGrayToneMapping(const std::valarray &grayImageInput, std::valarray &grayImageOutput ,const float PhotoreceptorsCompression=0.6, const float ganglionCellsCompression=0.6); + + +}; + +}// end of namespace bioinspired +}// end of namespace cv + +#endif /*RETINACLASSES_H_*/ diff --git a/modules/bioinspired/src/templatebuffer.hpp b/modules/bioinspired/src/templatebuffer.hpp new file mode 100644 index 00000000000..a95102d5c60 --- /dev/null +++ b/modules/bioinspired/src/templatebuffer.hpp @@ -0,0 +1,555 @@ +/*#****************************************************************************** +** 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. +** +** +** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab. +** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping. +** +** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications) +** +** Creation - enhancement process 2007-2011 +** Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France +** +** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr). +** Refer to the following research paper for more information: +** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011 +** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book: +** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. +** +** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author : +** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper: +** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007 +** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. +** ====> more informations in the above cited Jeanny Heraults's book. +** +** License Agreement +** For Open Source Computer Vision Library +** +** Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved. +** +** For Human Visual System tools (bioinspired) +** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions 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. +*******************************************************************************/ + +#ifndef __TEMPLATEBUFFER_HPP__ +#define __TEMPLATEBUFFER_HPP__ + +#include +#include +#include +#include + + +//#define __TEMPLATEBUFFERDEBUG //define TEMPLATEBUFFERDEBUG in order to display debug information + +namespace cv +{ +namespace bioinspired +{ +//// If a parallelization method is available then, you should define MAKE_PARALLEL, in the other case, the classical serial code will be used +#define MAKE_PARALLEL +// ==> then include required includes +#ifdef MAKE_PARALLEL + +// ==> declare usefull generic tools +template +class Parallel_clipBufferValues: public cv::ParallelLoopBody +{ +private: + type *bufferToClip; + type minValue, maxValue; + +public: + Parallel_clipBufferValues(type* bufferToProcess, const type min, const type max) + : bufferToClip(bufferToProcess), minValue(min), maxValue(max) { } + + virtual void operator()( const cv::Range &r ) const { + register type *inputOutputBufferPTR=bufferToClip+r.start; + for (register int jf = r.start; jf != r.end; ++jf, ++inputOutputBufferPTR) + { + if (*inputOutputBufferPTR>maxValue) + *inputOutputBufferPTR=maxValue; + else if (*inputOutputBufferPTR class TemplateBuffer : public std::valarray + { + public: + + /** + * constructor for monodimensional array + * @param dim: the size of the vector + */ + TemplateBuffer(const size_t dim=0) + : std::valarray((type)0, dim) + { + _NBrows=1; + _NBcolumns=dim; + _NBdepths=1; + _NBpixels=dim; + _doubleNBpixels=2*dim; + } + + /** + * constructor by copy for monodimensional array + * @param pVal: the pointer to a buffer to copy + * @param dim: the size of the vector + */ + TemplateBuffer(const type* pVal, const size_t dim) + : std::valarray(pVal, dim) + { + _NBrows=1; + _NBcolumns=dim; + _NBdepths=1; + _NBpixels=dim; + _doubleNBpixels=2*dim; + } + + /** + * constructor for bidimensional array + * @param dimRows: the size of the vector + * @param dimColumns: the size of the vector + * @param depth: the number of layers of the buffer in its third dimension (3 of color images, 1 for gray images. + */ + TemplateBuffer(const size_t dimRows, const size_t dimColumns, const size_t depth=1) + : std::valarray((type)0, dimRows*dimColumns*depth) + { +#ifdef TEMPLATEBUFFERDEBUG + std::cout<<"TemplateBuffer::TemplateBuffer: new buffer, size="<(toCopy) + { + memcpy(Buffer(), toCopy.Buffer(), this->size()); + }*/ + /** + * destructor + */ + virtual ~TemplateBuffer() + { +#ifdef TEMPLATEBUFFERDEBUG + std::cout<<"~TemplateBuffer"<::operator=(0); } //memset(Buffer(), 0, sizeof(type)*_NBpixels); } + + /** + * @return the numbers of rows (height) of the images used by the object + */ + inline unsigned int getNBrows() { return (unsigned int)_NBrows; } + + /** + * @return the numbers of columns (width) of the images used by the object + */ + inline unsigned int getNBcolumns() { return (unsigned int)_NBcolumns; } + + /** + * @return the numbers of pixels (width*height) of the images used by the object + */ + inline unsigned int getNBpixels() { return (unsigned int)_NBpixels; } + + /** + * @return the numbers of pixels (width*height) of the images used by the object + */ + inline unsigned int getDoubleNBpixels() { return (unsigned int)_doubleNBpixels; } + + /** + * @return the numbers of depths (3rd dimension: 1 for gray images, 3 for rgb images) of the images used by the object + */ + inline unsigned int getDepthSize() { return (unsigned int)_NBdepths; } + + /** + * resize the buffer and recompute table index etc. + */ + void resizeBuffer(const size_t dimRows, const size_t dimColumns, const size_t depth=1) + { + this->resize(dimRows*dimColumns*depth); + _NBrows=dimRows; + _NBcolumns=dimColumns; + _NBdepths=depth; + _NBpixels=dimRows*dimColumns; + _doubleNBpixels=2*dimRows*dimColumns; + } + + inline TemplateBuffer & operator=(const std::valarray &b) + { + //std::cout<<"TemplateBuffer & operator= affect vector: "<::operator=(b); + return *this; + } + + inline TemplateBuffer & operator=(const type &b) + { + //std::cout<<"TemplateBuffer & operator= affect value: "<::operator=(b); + return *this; + } + + /* inline const type &operator[](const unsigned int &b) + { + return (*this)[b]; + } + */ + /** + * @return the buffer adress in non const mode + */ + inline type* Buffer() { return &(*this)[0]; } + + /////////////////////////////////////////////////////// + // Standard Image manipulation functions + + /** + * standard 0 to 255 image normalization function + * @param inputOutputBuffer: the image to be normalized (rewrites the input), if no parameter, then, the built in buffer reachable by getOutput() function is normalized + * @param nbPixels: specifies the number of pixel on which the normalization should be performed, if 0, then all pixels specified in the constructor are processed + * @param maxOutputValue: the maximum output value + */ + static void normalizeGrayOutput_0_maxOutputValue(type *inputOutputBuffer, const size_t nbPixels, const type maxOutputValue=(type)255.0); + + /** + * standard 0 to 255 image normalization function + * @param inputOutputBuffer: the image to be normalized (rewrites the input), if no parameter, then, the built in buffer reachable by getOutput() function is normalized + * @param nbPixels: specifies the number of pixel on which the normalization should be performed, if 0, then all pixels specified in the constructor are processed + * @param maxOutputValue: the maximum output value + */ + void normalizeGrayOutput_0_maxOutputValue(const type maxOutputValue=(type)255.0) { normalizeGrayOutput_0_maxOutputValue(this->Buffer(), this->size(), maxOutputValue); } + + /** + * sigmoide image normalization function (saturates min and max values) + * @param meanValue: specifies the mean value of th pixels to be processed + * @param sensitivity: strenght of the sigmoide + * @param inputPicture: the image to be normalized if no parameter, then, the built in buffer reachable by getOutput() function is normalized + * @param outputBuffer: the ouput buffer on which the result is writed, if no parameter, then, the built in buffer reachable by getOutput() function is normalized + * @param maxOutputValue: the maximum output value + */ + static void normalizeGrayOutputCentredSigmoide(const type meanValue, const type sensitivity, const type maxOutputValue, type *inputPicture, type *outputBuffer, const unsigned int nbPixels); + + /** + * sigmoide image normalization function on the current buffer (saturates min and max values) + * @param meanValue: specifies the mean value of th pixels to be processed + * @param sensitivity: strenght of the sigmoide + * @param maxOutputValue: the maximum output value + */ + inline void normalizeGrayOutputCentredSigmoide(const type meanValue=(type)0.0, const type sensitivity=(type)2.0, const type maxOutputValue=(type)255.0) { (void)maxOutputValue; normalizeGrayOutputCentredSigmoide(meanValue, sensitivity, 255.0, this->Buffer(), this->Buffer(), this->getNBpixels()); } + + /** + * sigmoide image normalization function (saturates min and max values), in this function, the sigmoide is centered on low values (high saturation of the medium and high values + * @param inputPicture: the image to be normalized if no parameter, then, the built in buffer reachable by getOutput() function is normalized + * @param outputBuffer: the ouput buffer on which the result is writed, if no parameter, then, the built in buffer reachable by getOutput() function is normalized + * @param sensitivity: strenght of the sigmoide + * @param maxOutputValue: the maximum output value + */ + void normalizeGrayOutputNearZeroCentreredSigmoide(type *inputPicture=(type*)NULL, type *outputBuffer=(type*)NULL, const type sensitivity=(type)40, const type maxOutputValue=(type)255.0); + + /** + * center and reduct the image (image-mean)/std + * @param inputOutputBuffer: the image to be normalized if no parameter, the result is rewrited on it + */ + void centerReductImageLuminance(type *inputOutputBuffer=(type*)NULL); + + /** + * @return standard deviation of the buffer + */ + double getStandardDeviation() + { + double standardDeviation=0; + double meanValue=getMean(); + + type *bufferPTR=Buffer(); + for (unsigned int i=0;isize();++i) + { + double diff=(*(bufferPTR++)-meanValue); + standardDeviation+=diff*diff; + } + return std::sqrt(standardDeviation/this->size()); + } + + /** + * Clip buffer histogram + * @param minRatio: the minimum ratio of the lower pixel values, range=[0,1] and lower than maxRatio + * @param maxRatio: the aximum ratio of the higher pixel values, range=[0,1] and higher than minRatio + */ + void clipHistogram(double minRatio, double maxRatio, double maxOutputValue) + { + + if (minRatio>=maxRatio) + { + std::cerr<<"TemplateBuffer::clipHistogram: minRatio must be inferior to maxRatio, buffer unchanged"<max()*maxRatio; + const double minThreshold=(this->max()-this->min())*minRatio+this->min(); + + type *bufferPTR=this->Buffer(); + + double deltaH=maxThreshold; + double deltaL=maxThreshold; + + double updatedHighValue=maxThreshold; + double updatedLowValue=maxThreshold; + + for (unsigned int i=0;isize();++i) + { + double curentValue=(double)*(bufferPTR++); + + // updating "closest to the high threshold" pixel value + double highValueTest=maxThreshold-curentValue; + if (highValueTest>0) + { + if (deltaH>highValueTest) + { + deltaH=highValueTest; + updatedHighValue=curentValue; + } + } + + // updating "closest to the low threshold" pixel value + double lowValueTest=curentValue-minThreshold; + if (lowValueTest>0) + { + if (deltaL>lowValueTest) + { + deltaL=lowValueTest; + updatedLowValue=curentValue; + } + } + } + + std::cout<<"Tdebug"<max()"<max()<<"maxThreshold="<min()"<min()<<"minThreshold="<Buffer(); +#ifdef MAKE_PARALLEL // call the TemplateBuffer multitreaded clipping method + parallel_for_(cv::Range(0,this->size()), Parallel_clipBufferValues(bufferPTR, updatedLowValue, updatedHighValue)); +#else + + for (unsigned int i=0;isize();++i, ++bufferPTR) + { + if (*bufferPTRupdatedHighValue) + *bufferPTR=updatedHighValue; + } +#endif + normalizeGrayOutput_0_maxOutputValue(this->Buffer(), this->size(), maxOutputValue); + + } + + /** + * @return the mean value of the vector + */ + inline double getMean() { return this->sum()/this->size(); } + + protected: + size_t _NBrows; + size_t _NBcolumns; + size_t _NBdepths; + size_t _NBpixels; + size_t _doubleNBpixels; + // utilities + static type _abs(const type x); + + }; + + /////////////////////////////////////////////////////////////////////// + /// normalize output between 0 and 255, can be applied on images of different size that the declared size if nbPixels parameters is setted up; + template + void TemplateBuffer::normalizeGrayOutput_0_maxOutputValue(type *inputOutputBuffer, const size_t processedPixels, const type maxOutputValue) + { + type maxValue=inputOutputBuffer[0], minValue=inputOutputBuffer[0]; + + // get the min and max value + register type *inputOutputBufferPTR=inputOutputBuffer; + for (register size_t j = 0; j pixValue) + minValue = pixValue; + } + // change the range of the data to 0->255 + + type factor = maxOutputValue/(maxValue-minValue); + type offset = (type)(-minValue*factor); + + inputOutputBufferPTR=inputOutputBuffer; + for (register size_t j = 0; j < processedPixels; ++j, ++inputOutputBufferPTR) + *inputOutputBufferPTR=*(inputOutputBufferPTR)*factor+offset; + + } + // normalize data with a sigmoide close to 0 (saturates values for those superior to 0) + template + void TemplateBuffer::normalizeGrayOutputNearZeroCentreredSigmoide(type *inputBuffer, type *outputBuffer, const type sensitivity, const type maxOutputValue) + { + if (inputBuffer==NULL) + inputBuffer=Buffer(); + if (outputBuffer==NULL) + outputBuffer=Buffer(); + + type X0cube=sensitivity*sensitivity*sensitivity; + + register type *inputBufferPTR=inputBuffer; + register type *outputBufferPTR=outputBuffer; + + for (register size_t j = 0; j < _NBpixels; ++j, ++inputBufferPTR) + { + + type currentCubeLuminance=*inputBufferPTR**inputBufferPTR**inputBufferPTR; + *(outputBufferPTR++)=maxOutputValue*currentCubeLuminance/(currentCubeLuminance+X0cube); + } + } + + // normalize and adjust luminance with a centered to 128 sigmode + template + void TemplateBuffer::normalizeGrayOutputCentredSigmoide(const type meanValue, const type sensitivity, const type maxOutputValue, type *inputBuffer, type *outputBuffer, const unsigned int nbPixels) + { + + if (sensitivity==1.0) + { + std::cerr<<"TemplateBuffer::TemplateBuffer::normalizeGrayOutputCentredSigmoide error: 2nd parameter (sensitivity) must not equal 0, copying original data..."< + void TemplateBuffer::centerReductImageLuminance(type *inputOutputBuffer) + { + // if outputBuffer unsassigned, the rewrite the buffer + if (inputOutputBuffer==NULL) + inputOutputBuffer=Buffer(); + type meanValue=0, stdValue=0; + + // compute mean value + for (register size_t j = 0; j < _NBpixels; ++j) + meanValue+=inputOutputBuffer[j]; + meanValue/=((type)_NBpixels); + + // compute std value + register type *inputOutputBufferPTR=inputOutputBuffer; + for (size_t index=0;index<_NBpixels;++index) + { + type inputMinusMean=*(inputOutputBufferPTR++)-meanValue; + stdValue+=inputMinusMean*inputMinusMean; + } + + stdValue=std::sqrt(stdValue/((type)_NBpixels)); + // adjust luminance in regard of mean and std value; + inputOutputBufferPTR=inputOutputBuffer; + for (size_t index=0;index<_NBpixels;++index, ++inputOutputBufferPTR) + *inputOutputBufferPTR=(*(inputOutputBufferPTR)-meanValue)/stdValue; + } + + + template + type TemplateBuffer::_abs(const type x) + { + + if (x>0) + return x; + else + return -x; + } + + template < > + inline int TemplateBuffer::_abs(const int x) + { + return std::abs(x); + } + template < > + inline double TemplateBuffer::_abs(const double x) + { + return std::fabs(x); + } + + template < > + inline float TemplateBuffer::_abs(const float x) + { + return std::fabs(x); + } + +}// end of namespace bioinspired +}// end of namespace cv +#endif diff --git a/modules/bioinspired/test/test_main.cpp b/modules/bioinspired/test/test_main.cpp new file mode 100644 index 00000000000..6b249934475 --- /dev/null +++ b/modules/bioinspired/test/test_main.cpp @@ -0,0 +1,3 @@ +#include "test_precomp.hpp" + +CV_TEST_MAIN("cv") diff --git a/modules/bioinspired/test/test_precomp.hpp b/modules/bioinspired/test/test_precomp.hpp new file mode 100644 index 00000000000..b1672149ab5 --- /dev/null +++ b/modules/bioinspired/test/test_precomp.hpp @@ -0,0 +1,16 @@ +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wmissing-declarations" +# if defined __clang__ || defined __APPLE__ +# pragma GCC diagnostic ignored "-Wmissing-prototypes" +# pragma GCC diagnostic ignored "-Wextra" +# endif +#endif + +#ifndef __OPENCV_TEST_PRECOMP_HPP__ +#define __OPENCV_TEST_PRECOMP_HPP__ + +#include "opencv2/ts.hpp" +#include "opencv2/bioinspired.hpp" +#include + +#endif diff --git a/modules/bioinspired/test/test_retina_ocl.cpp b/modules/bioinspired/test/test_retina_ocl.cpp new file mode 100644 index 00000000000..bfccdd5577f --- /dev/null +++ b/modules/bioinspired/test/test_retina_ocl.cpp @@ -0,0 +1,164 @@ +/*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) 2010-2013, Multicoreware, Inc., all rights reserved. +// Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// @Authors +// Peng Xiao, pengxiao@multicorewareinc.com +// +// 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 oclMaterials 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*/ + +#include "test_precomp.hpp" +#include "opencv2/opencv_modules.hpp" +#include "opencv2/bioinspired.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/highgui.hpp" + +#include "opencv2/core/ocl.hpp" // cv::ocl::haveOpenCL + +#if defined(HAVE_OPENCV_OCL) + +#include "opencv2/ocl.hpp" +#define RETINA_ITERATIONS 5 + +static double checkNear(const cv::Mat &m1, const cv::Mat &m2) +{ + return cv::norm(m1, m2, cv::NORM_INF); +} + +#define PARAM_TEST_CASE(name, ...) struct name : testing::TestWithParam< std::tr1::tuple< __VA_ARGS__ > > +#define GET_PARAM(k) std::tr1::get< k >(GetParam()) + +static int oclInit = false; +static int oclAvailable = false; + +PARAM_TEST_CASE(Retina_OCL, bool, int, bool, double, double) +{ + bool colorMode; + int colorSamplingMethod; + bool useLogSampling; + double reductionFactor; + double samplingStrength; + + virtual void SetUp() + { + colorMode = GET_PARAM(0); + colorSamplingMethod = GET_PARAM(1); + useLogSampling = GET_PARAM(2); + reductionFactor = GET_PARAM(3); + samplingStrength = GET_PARAM(4); + + if (!oclInit) + { + if (cv::ocl::haveOpenCL()) + { + try + { + const cv::ocl::DeviceInfo& dev = cv::ocl::Context::getContext()->getDeviceInfo(); + std::cout << "Device name:" << dev.deviceName << std::endl; + oclAvailable = true; + } + catch (...) + { + std::cout << "Device name: N/A" << std::endl; + } + } + oclInit = true; + } + } +}; + +TEST_P(Retina_OCL, Accuracy) +{ + if (!oclAvailable) + { + std::cout << "SKIP test" << std::endl; + return; + } + + using namespace cv; + Mat input = imread(cvtest::TS::ptr()->get_data_path() + "shared/lena.png", colorMode); + CV_Assert(!input.empty()); + ocl::oclMat ocl_input(input); + + Ptr ocl_retina = bioinspired::createRetina_OCL( + input.size(), + colorMode, + colorSamplingMethod, + useLogSampling, + reductionFactor, + samplingStrength); + + Ptr gold_retina = bioinspired::createRetina( + input.size(), + colorMode, + colorSamplingMethod, + useLogSampling, + reductionFactor, + samplingStrength); + + Mat gold_parvo; + Mat gold_magno; + ocl::oclMat ocl_parvo; + ocl::oclMat ocl_magno; + + for(int i = 0; i < RETINA_ITERATIONS; i ++) + { + ocl_retina->run(ocl_input); + gold_retina->run(input); + + gold_retina->getParvo(gold_parvo); + gold_retina->getMagno(gold_magno); + + ocl_retina->getParvo(ocl_parvo); + ocl_retina->getMagno(ocl_magno); + + int eps = colorMode ? 2 : 1; + + EXPECT_LE(checkNear(gold_parvo, (Mat)ocl_parvo), eps); + EXPECT_LE(checkNear(gold_magno, (Mat)ocl_magno), eps); + } +} + +INSTANTIATE_TEST_CASE_P(Contrib, Retina_OCL, testing::Combine( + testing::Bool(), + testing::Values((int)cv::bioinspired::RETINA_COLOR_BAYER), + testing::Values(false/*,true*/), + testing::Values(1.0, 0.5), + testing::Values(10.0, 5.0))); +#endif