From d27d1e26273ddeaa1a8e34efe2f6e00b5351717a Mon Sep 17 00:00:00 2001 From: Andrews Sobral Date: Sat, 18 Mar 2017 18:40:18 +0100 Subject: [PATCH] BGSLibrary 2.0.0 --- .gitignore | 6 +- CMakeLists.txt | 103 +- Demo.cpp | 158 +- Demo.py | 84 ++ Demo2.cpp | 139 +- FrameProcessor.cpp | 452 +++--- FrameProcessor.h | 207 +-- Main.cpp | 2 +- PreProcessor.cpp | 2 +- PreProcessor.h | 1 - README.md | 9 +- VideoAnalysis.cpp | 76 +- VideoCapture.cpp | 58 +- VideoCapture.h | 5 +- config/FrameProcessor.xml | 39 +- dataset/demo.avi | Bin 0 -> 355840 bytes {frames => dataset/frames}/1.png | Bin {frames => dataset/frames}/10.png | Bin {frames => dataset/frames}/11.png | Bin {frames => dataset/frames}/12.png | Bin {frames => dataset/frames}/13.png | Bin {frames => dataset/frames}/14.png | Bin {frames => dataset/frames}/15.png | Bin {frames => dataset/frames}/16.png | Bin {frames => dataset/frames}/17.png | Bin {frames => dataset/frames}/18.png | Bin {frames => dataset/frames}/19.png | Bin {frames => dataset/frames}/2.png | Bin {frames => dataset/frames}/20.png | Bin {frames => dataset/frames}/21.png | Bin {frames => dataset/frames}/22.png | Bin {frames => dataset/frames}/23.png | Bin {frames => dataset/frames}/24.png | Bin {frames => dataset/frames}/25.png | Bin {frames => dataset/frames}/26.png | Bin {frames => dataset/frames}/27.png | Bin {frames => dataset/frames}/28.png | Bin {frames => dataset/frames}/29.png | Bin {frames => dataset/frames}/3.png | Bin {frames => dataset/frames}/30.png | Bin {frames => dataset/frames}/31.png | Bin {frames => dataset/frames}/32.png | Bin {frames => dataset/frames}/33.png | Bin {frames => dataset/frames}/34.png | Bin {frames => dataset/frames}/35.png | Bin {frames => dataset/frames}/36.png | Bin {frames => dataset/frames}/37.png | Bin {frames => dataset/frames}/38.png | Bin {frames => dataset/frames}/39.png | Bin {frames => dataset/frames}/4.png | Bin {frames => dataset/frames}/40.png | Bin {frames => dataset/frames}/41.png | Bin {frames => dataset/frames}/42.png | Bin {frames => dataset/frames}/43.png | Bin {frames => dataset/frames}/44.png | Bin {frames => dataset/frames}/45.png | Bin {frames => dataset/frames}/46.png | Bin {frames => dataset/frames}/47.png | Bin {frames => dataset/frames}/48.png | Bin {frames => dataset/frames}/49.png | Bin {frames => dataset/frames}/5.png | Bin {frames => dataset/frames}/50.png | Bin {frames => dataset/frames}/51.png | Bin {frames => dataset/frames}/6.png | Bin {frames => dataset/frames}/7.png | Bin {frames => dataset/frames}/8.png | Bin {frames => dataset/frames}/9.png | Bin demos/DemoFrameDifferenceBGS.cpp | 49 - demos/DemoMultiLayerBGS.cpp | 49 - demos/linux_ubuntu/.gitignore | 8 + .../linux_ubuntu}/CMakeLists.txt | 7 +- .../linux_ubuntu}/FrameDifferenceTest.cpp | 6 +- {example_linux => demos/linux_ubuntu}/README.txt | 0 demos/linux_ubuntu/config/.gitignore | 4 + demos/macosx/.gitignore | 8 + {example_macosx => demos/macosx}/CMakeLists.txt | 7 +- .../macosx}/FrameDifferenceTest.cpp | 6 +- {example_macosx => demos/macosx}/README.txt | 0 demos/macosx/config/.gitignore | 4 + .../README_CMAKE_USERS_OPENCV2.txt | 39 +- docs/README_CMAKE_USERS_OPENCV3.txt | 77 + .../README_LINUX_USERS.txt | 5 +- .../README_VS2010_OPENCV2.txt | 13 +- example_linux/config/KEEP_THIS_FOLDER | 1 - example_macosx/config/KEEP_THIS_FOLDER | 1 - {java_gui => gui_java}/README.txt | 0 {java_gui => gui_java}/_COPY_bgslibrary.exe_HERE_ | 0 {java_gui => gui_java}/bgslibrary_gui.jar | Bin {java_gui => gui_java}/bgslibrary_gui.properties | 0 {java_gui => gui_java}/build.xml | 0 {java_gui => gui_java}/config/.gitignore | 0 {java_gui => gui_java}/config/FrameProcessor.xml | 0 {java_gui => gui_java}/config/PreProcessor.xml | 0 {java_gui => gui_java}/config/VideoCapture.xml | 0 .../images/bgslibrary_gui_screen01.png | Bin .../images/bgslibrary_gui_screen02.png | Bin .../images/bgslibrary_gui_screen03.png | Bin .../images/bgslibrary_gui_screen04.png | Bin {java_gui => gui_java}/images/logo.jpg | Bin .../lib/commons-configuration-1.8.jar | Bin {java_gui => gui_java}/lib/commons-io-2.3.jar | Bin {java_gui => gui_java}/lib/commons-lang-2.6.jar | Bin .../lib/commons-logging-1.1.1.jar | Bin {java_gui => gui_java}/lib/swingx-all-1.6.3.jar | Bin .../lib/swingx-beaninfo-1.6.3.jar | Bin {java_gui => gui_java}/manifest.mf | 0 {java_gui => gui_java}/nbproject/build-impl.xml | 0 .../nbproject/genfiles.properties | 0 .../nbproject/private/config.properties | 0 .../nbproject/private/private.properties | 0 .../nbproject/private/private.xml | 0 .../nbproject/project.properties | 0 {java_gui => gui_java}/nbproject/project.xml | 0 {java_gui => gui_java}/run_camera.bat | 0 {java_gui => gui_java}/run_java_gui.bat | 0 .../run_java_gui_with_console.bat | 0 {java_gui => gui_java}/run_video.bat | 0 .../src/br/com/bgslibrary/Main.java | 0 .../src/br/com/bgslibrary/entity/Command.java | 0 .../br/com/bgslibrary/entity/Configuration.java | 0 .../src/br/com/bgslibrary/gui/AboutDialog.form | 0 .../src/br/com/bgslibrary/gui/AboutDialog.java | 0 .../src/br/com/bgslibrary/gui/MainFrame.form | 0 .../src/br/com/bgslibrary/gui/MainFrame.java | 0 .../src/br/com/bgslibrary/resources/logo.jpg | Bin {vs2010mfc => gui_mfc}/.gitignore | 0 {vs2013mfc => gui_mfc}/ReadMe.txt | 0 .../config/AdaptiveBackgroundLearning.xml | 0 .../config/AdaptiveSelectiveBackgroundLearning.xml | 0 .../config/DPAdaptiveMedianBGS.xml | 0 .../config/DPEigenbackgroundBGS.xml | 0 {vs2010mfc => gui_mfc}/config/DPGrimsonGMMBGS.xml | 0 {vs2010mfc => gui_mfc}/config/DPMeanBGS.xml | 0 {vs2010mfc => gui_mfc}/config/DPPratiMediodBGS.xml | 0 {vs2010mfc => gui_mfc}/config/DPTextureBGS.xml | 0 {vs2010mfc => gui_mfc}/config/DPWrenGABGS.xml | 0 .../config/DPZivkovicAGMMBGS.xml | 0 .../config/FrameDifferenceBGS.xml | 0 .../config/FuzzyChoquetIntegral.xml | 0 .../config/FuzzySugenoIntegral.xml | 0 {vs2010mfc => gui_mfc}/config/GMG.xml | 0 .../config/IndependentMultimodalBGS.xml | 0 {vs2010mfc => gui_mfc}/config/KDE.xml | 0 {vs2010mfc => gui_mfc}/config/LBAdaptiveSOM.xml | 0 .../config/LBFuzzyAdaptiveSOM.xml | 0 {vs2010mfc => gui_mfc}/config/LBFuzzyGaussian.xml | 0 .../config/LBMixtureOfGaussians.xml | 0 {vs2010mfc => gui_mfc}/config/LBSimpleGaussian.xml | 0 {vs2010mfc => gui_mfc}/config/LOBSTERBGS.xml | 0 .../config/MixtureOfGaussianV1BGS.xml | 0 .../config/MixtureOfGaussianV2BGS.xml | 0 {vs2010mfc => gui_mfc}/config/MultiCueBGS.xml | 0 {vs2010mfc => gui_mfc}/config/MultiLayerBGS.xml | 0 {vs2010mfc => gui_mfc}/config/SigmaDeltaBGS.xml | 0 .../config/StaticFrameDifferenceBGS.xml | 0 {vs2010mfc => gui_mfc}/config/SuBSENSEBGS.xml | 0 {vs2010mfc => gui_mfc}/config/T2FGMM_UM.xml | 0 {vs2010mfc => gui_mfc}/config/T2FGMM_UV.xml | 0 {vs2010mfc => gui_mfc}/config/T2FMRF_UM.xml | 0 {vs2010mfc => gui_mfc}/config/T2FMRF_UV.xml | 0 {vs2010mfc => gui_mfc}/config/VuMeter.xml | 0 .../config/WeightedMovingMeanBGS.xml | 0 .../config/WeightedMovingVarianceBGS.xml | 0 {vs2010mfc => gui_mfc}/dataset/video.avi | Bin .../outputs/background/.gitignore | 0 .../outputs/foreground/.gitignore | 0 {vs2013mfc => gui_mfc}/outputs/input/.gitignore | 0 {vs2010mfc => gui_mfc}/src/.gitignore | 0 {vs2010mfc => gui_mfc}/src/App.cpp | 0 {vs2010mfc => gui_mfc}/src/App.h | 0 {vs2010mfc => gui_mfc}/src/Dlg.cpp | 0 {vs2010mfc => gui_mfc}/src/Dlg.h | 0 {vs2013mfc => gui_mfc}/src/ReadMe.txt | 0 .../src/bgslibrary_vs2013_mfc.rc | Bin .../src/bgslibrary_vs2013_mfc.sln | 0 .../src/bgslibrary_vs2013_mfc.vcxproj | 0 .../src/bgslibrary_vs2013_mfc.vcxproj.filters | 0 .../src/bgslibrary_vs2013_mfc.vcxproj.user | 0 .../src/res/bgslibrary_vs2013_mfc.ico | Bin .../src/res/bgslibrary_vs2013_mfc.rc2 | Bin {vs2010mfc => gui_mfc}/src/resource.h | Bin {vs2010mfc => gui_mfc}/src/stdafx.cpp | 0 {vs2010mfc => gui_mfc}/src/stdafx.h | 0 {vs2010mfc => gui_mfc}/src/targetver.h | 0 gui_qt/.gitignore | 9 + gui_qt/CMakeLists.txt | 52 + gui_qt/README.txt | 17 + gui_qt/application.qrc | 10 + gui_qt/bgslibrary_gui.cpp | 38 + gui_qt/bgslibrary_gui.pro | 248 ++++ gui_qt/build/.gitignore | 4 + gui_qt/figs/copy.png | Bin 0 -> 1338 bytes gui_qt/figs/cut.png | Bin 0 -> 1323 bytes gui_qt/figs/new.png | Bin 0 -> 852 bytes gui_qt/figs/open.png | Bin 0 -> 2073 bytes gui_qt/figs/paste.png | Bin 0 -> 1645 bytes gui_qt/figs/save.png | Bin 0 -> 1187 bytes gui_qt/mainwindow.cpp | 571 +++++++ gui_qt/mainwindow.h | 110 ++ gui_qt/mainwindow.ui | 631 ++++++++ gui_qt/qt_utils.cpp | 74 + gui_qt/qt_utils.h | 99 ++ gui_qt/texteditor.cpp | 320 ++++ gui_qt/texteditor.h | 64 + gui_qt/ui_mainwindow.h | 306 ++++ output/bg/.gitignore | 4 + output/fg/.gitignore | 4 + output/in/.gitignore | 4 + .../tb => package_analysis}/PerformanceUtils.cpp | 259 ++-- .../tb => package_analysis}/PerformanceUtils.h | 16 +- package_analysis/PixelUtils.cpp | 351 +++++ {package_bgs/tb => package_analysis}/PixelUtils.h | 18 +- package_bgs/AdaptiveBackgroundLearning.cpp | 72 +- package_bgs/AdaptiveBackgroundLearning.h | 55 +- .../AdaptiveSelectiveBackgroundLearning.cpp | 53 +- package_bgs/AdaptiveSelectiveBackgroundLearning.h | 54 +- package_bgs/DPAdaptiveMedian.cpp | 110 ++ package_bgs/DPAdaptiveMedian.h | 53 + ...igenbackgroundBGS.cpp => DPEigenbackground.cpp} | 67 +- package_bgs/DPEigenbackground.h | 55 + .../{dp/DPGrimsonGMMBGS.cpp => DPGrimsonGMM.cpp} | 69 +- .../{dp/DPEigenbackgroundBGS.h => DPGrimsonGMM.h} | 67 +- package_bgs/{dp/DPMeanBGS.cpp => DPMean.cpp} | 67 +- package_bgs/{dp/DPZivkovicAGMMBGS.h => DPMean.h} | 67 +- .../{dp/DPPratiMediodBGS.cpp => DPPratiMediod.cpp} | 75 +- package_bgs/{dp/DPMeanBGS.h => DPPratiMediod.h} | 68 +- package_bgs/{dp/DPTextureBGS.cpp => DPTexture.cpp} | 91 +- package_bgs/DPTexture.h | 59 + package_bgs/{dp/DPWrenGABGS.cpp => DPWrenGA.cpp} | 70 +- package_bgs/{dp/DPWrenGABGS.h => DPWrenGA.h} | 67 +- .../DPZivkovicAGMMBGS.cpp => DPZivkovicAGMM.cpp} | 69 +- .../{dp/DPGrimsonGMMBGS.h => DPZivkovicAGMM.h} | 67 +- package_bgs/FrameDifference.cpp | 84 ++ .../{FrameDifferenceBGS.h => FrameDifference.h} | 45 +- package_bgs/FrameDifferenceBGS.cpp | 83 -- package_bgs/{tb => }/FuzzyChoquetIntegral.cpp | 127 +- package_bgs/FuzzyChoquetIntegral.h | 53 + package_bgs/{tb => }/FuzzySugenoIntegral.cpp | 133 +- package_bgs/FuzzySugenoIntegral.h | 53 + package_bgs/GMG.cpp | 49 +- package_bgs/GMG.h | 50 +- package_bgs/IBGS.h | 63 +- package_bgs/{db/imbs.cpp => IMBS/IMBS.cpp} | 254 ++-- package_bgs/{db/imbs.hpp => IMBS/IMBS.hpp} | 62 +- package_bgs/IndependentMultimodal.cpp | 74 + ...rameDifferenceBGS.h => IndependentMultimodal.h} | 46 +- package_bgs/{ae => }/KDE.cpp | 83 +- package_bgs/KDE.h | 57 + package_bgs/{ae => KDE}/KernelTable.cpp | 48 +- package_bgs/{ae => KDE}/KernelTable.h | 22 +- package_bgs/KDE/NPBGSubtractor.cpp | 1160 +++++++++++++++ package_bgs/{ae => KDE}/NPBGSubtractor.h | 26 +- package_bgs/{ae => KDE}/NPBGmodel.cpp | 42 +- package_bgs/{ae => KDE}/NPBGmodel.h | 28 +- package_bgs/KNN.cpp | 111 ++ package_bgs/KNN.h | 57 + package_bgs/{lb => }/LBAdaptiveSOM.cpp | 67 +- package_bgs/{lb => }/LBAdaptiveSOM.h | 60 +- package_bgs/{lb => }/LBFuzzyAdaptiveSOM.cpp | 69 +- package_bgs/{lb => }/LBFuzzyAdaptiveSOM.h | 60 +- package_bgs/{lb => }/LBFuzzyGaussian.cpp | 66 +- package_bgs/{lb => }/LBFuzzyGaussian.h | 58 +- package_bgs/{lb => }/LBMixtureOfGaussians.cpp | 68 +- package_bgs/{lb => }/LBMixtureOfGaussians.h | 59 +- package_bgs/{ck/LbpMrf.cpp => LBP_MRF.cpp} | 55 +- package_bgs/{WeightedMovingMeanBGS.h => LBP_MRF.h} | 48 +- package_bgs/LBP_MRF/MEDefs.cpp | 57 + package_bgs/{ck => LBP_MRF}/MEDefs.hpp | 29 +- package_bgs/{ck => LBP_MRF}/MEHistogram.cpp | 305 ++-- package_bgs/{ck => LBP_MRF}/MEHistogram.hpp | 30 +- package_bgs/{ck => LBP_MRF}/MEImage.cpp | 963 ++++++------ package_bgs/{ck => LBP_MRF}/MEImage.hpp | 38 +- package_bgs/{ck => LBP_MRF}/MotionDetection.cpp | 126 +- package_bgs/{ck => LBP_MRF}/MotionDetection.hpp | 32 +- package_bgs/{ck => LBP_MRF}/block.h | 268 ++-- package_bgs/{ck => LBP_MRF}/graph.cpp | 18 +- package_bgs/{ck => LBP_MRF}/graph.h | 105 +- package_bgs/{ck => LBP_MRF}/maxflow.cpp | 260 ++-- package_bgs/LBSP/BackgroundSubtractorLBSP.cpp | 85 ++ package_bgs/LBSP/BackgroundSubtractorLBSP.h | 101 ++ package_bgs/LBSP/BackgroundSubtractorLBSP_.cpp | 79 + package_bgs/LBSP/BackgroundSubtractorLBSP_.h | 107 ++ package_bgs/LBSP/BackgroundSubtractorLOBSTER.cpp | 342 +++++ package_bgs/LBSP/BackgroundSubtractorLOBSTER.h | 83 ++ package_bgs/LBSP/BackgroundSubtractorPAWCS.cpp | 1349 +++++++++++++++++ package_bgs/LBSP/BackgroundSubtractorPAWCS.h | 169 +++ package_bgs/LBSP/BackgroundSubtractorSuBSENSE.cpp | 753 ++++++++++ package_bgs/LBSP/BackgroundSubtractorSuBSENSE.h | 129 ++ package_bgs/LBSP/DistanceUtils.h | 332 +++++ package_bgs/LBSP/LBSP.cpp | 334 +++++ package_bgs/LBSP/LBSP.h | 134 ++ package_bgs/LBSP/LBSP_.cpp | 334 +++++ package_bgs/LBSP/LBSP_.h | 134 ++ package_bgs/{pl => LBSP}/LBSP_16bits_dbcross_1ch.i | 0 .../{pl => LBSP}/LBSP_16bits_dbcross_3ch1t.i | 0 .../{pl => LBSP}/LBSP_16bits_dbcross_3ch3t.i | 0 .../{pl => LBSP}/LBSP_16bits_dbcross_s3ch.i | 0 package_bgs/LBSP/RandUtils.h | 112 ++ package_bgs/{lb => }/LBSimpleGaussian.cpp | 62 +- package_bgs/{lb => }/LBSimpleGaussian.h | 56 +- package_bgs/LOBSTER.cpp | 98 ++ package_bgs/LOBSTER.h | 49 + ...OfGaussianV1BGS.cpp => MixtureOfGaussianV1.cpp} | 69 +- ...tureOfGaussianV2BGS.h => MixtureOfGaussianV1.h} | 50 +- ...OfGaussianV2BGS.cpp => MixtureOfGaussianV2.cpp} | 73 +- ...tureOfGaussianV1BGS.h => MixtureOfGaussianV2.h} | 49 +- .../{sjn/SJN_MultiCueBGS.cpp => MultiCue.cpp} | 587 ++++---- package_bgs/MultiCue.h | 254 ++++ .../{jmo/MultiLayerBGS.cpp => MultiLayer.cpp} | 133 +- package_bgs/MultiLayer.h | 99 ++ package_bgs/{jmo => MultiLayer}/BGS.h | 8 +- .../{jmo => MultiLayer}/BackgroundSubtractionAPI.h | 39 +- package_bgs/{jmo => MultiLayer}/BlobExtraction.cpp | 194 +-- package_bgs/{jmo => MultiLayer}/BlobExtraction.h | 8 +- .../{jmo => MultiLayer}/BlobLibraryConfiguration.h | 5 +- package_bgs/MultiLayer/BlobResult.cpp | 847 +++++++++++ package_bgs/{jmo => MultiLayer}/BlobResult.h | 60 +- package_bgs/{jmo => MultiLayer}/CMultiLayerBGS.cpp | 28 +- package_bgs/{jmo => MultiLayer}/CMultiLayerBGS.h | 27 +- .../{jmo => MultiLayer}/LocalBinaryPattern.cpp | 16 +- .../{jmo => MultiLayer}/LocalBinaryPattern.h | 9 +- .../{jmo => MultiLayer}/OpenCvDataConversion.h | 9 +- package_bgs/MultiLayer/OpenCvLegacyIncludes.h | 50 + package_bgs/MultiLayer/blob.cpp | 1148 +++++++++++++++ package_bgs/{jmo => MultiLayer}/blob.h | 120 +- package_bgs/PAWCS.cpp | 93 ++ package_bgs/PAWCS.h | 48 + package_bgs/PBAS/PBAS.cpp | 585 ++++++++ package_bgs/PBAS/PBAS.h | 207 +++ package_bgs/PixelBasedAdaptiveSegmenter.cpp | 126 ++ package_bgs/PixelBasedAdaptiveSegmenter.h | 58 + package_bgs/SigmaDelta.cpp | 101 ++ package_bgs/SigmaDelta.h | 49 + package_bgs/{bl => SigmaDelta}/sdLaMa091.cpp | 84 +- package_bgs/{bl => SigmaDelta}/sdLaMa091.h | 6 +- ...DifferenceBGS.cpp => StaticFrameDifference.cpp} | 52 +- package_bgs/StaticFrameDifference.h | 42 + package_bgs/SuBSENSE.cpp | 96 ++ package_bgs/SuBSENSE.h | 49 + package_bgs/T2F/FuzzyUtils.cpp | 512 +++++++ package_bgs/{tb => T2F}/FuzzyUtils.h | 15 +- package_bgs/{tb => T2F}/MRF.cpp | 156 +- package_bgs/{tb => T2F}/MRF.h | 11 +- package_bgs/{tb => T2F}/T2FGMM.cpp | 134 +- package_bgs/{tb => T2F}/T2FGMM.h | 26 +- package_bgs/{tb => T2F}/T2FMRF.cpp | 154 +- package_bgs/{tb => T2F}/T2FMRF.h | 30 +- package_bgs/{tb => }/T2FGMM_UM.cpp | 60 +- package_bgs/{tb => }/T2FGMM_UM.h | 71 +- package_bgs/{tb => }/T2FGMM_UV.cpp | 60 +- package_bgs/{tb => }/T2FGMM_UV.h | 71 +- package_bgs/{tb => }/T2FMRF_UM.cpp | 91 +- package_bgs/T2FMRF_UM.h | 64 + package_bgs/{tb => }/T2FMRF_UV.cpp | 91 +- package_bgs/T2FMRF_UV.h | 64 + package_bgs/TwoPoints.cpp | 112 ++ package_bgs/TwoPoints.h | 46 + package_bgs/TwoPoints/two_points.cpp | 394 +++++ package_bgs/TwoPoints/two_points.h | 50 + package_bgs/ViBe.cpp | 98 ++ package_bgs/ViBe.h | 52 + package_bgs/ViBe/LICENSE | 44 + package_bgs/ViBe/vibe-background-sequential.cpp | 929 ++++++++++++ package_bgs/ViBe/vibe-background-sequential.h | 293 ++++ package_bgs/{av => }/VuMeter.cpp | 79 +- package_bgs/VuMeter.h | 52 + package_bgs/{av => VuMeter}/TBackground.cpp | 36 +- package_bgs/{av => VuMeter}/TBackground.h | 1 - package_bgs/{av => VuMeter}/TBackgroundVuMeter.cpp | 132 +- package_bgs/{av => VuMeter}/TBackgroundVuMeter.h | 6 +- ...tedMovingMeanBGS.cpp => WeightedMovingMean.cpp} | 85 +- package_bgs/WeightedMovingMean.h | 45 + ...gVarianceBGS.cpp => WeightedMovingVariance.cpp} | 99 +- package_bgs/WeightedMovingVariance.h | 46 + package_bgs/WeightedMovingVarianceBGS.h | 48 - package_bgs/_template_/Amber.cpp | 112 ++ package_bgs/_template_/Amber.h | 45 + package_bgs/_template_/MyBGS.cpp | 44 + package_bgs/{ck/LbpMrf.h => _template_/MyBGS.h} | 41 +- package_bgs/_template_/amber/amber.c | 80 + package_bgs/_template_/amber/amber.h | 64 + package_bgs/ae/KDE.h | 58 - package_bgs/ae/NPBGSubtractor.cpp | 1160 --------------- package_bgs/av/VuMeter.h | 53 - package_bgs/bgslibrary.h | 65 + package_bgs/bl/SigmaDeltaBGS.cpp | 85 -- package_bgs/bl/SigmaDeltaBGS.h | 42 - package_bgs/bl/stdbool.h | 16 - package_bgs/ck/MEDefs.cpp | 40 - package_bgs/ck/README.TXT | 135 -- package_bgs/db/IndependentMultimodalBGS.cpp | 54 - package_bgs/db/IndependentMultimodalBGS.h | 29 - package_bgs/dp/AdaptiveMedianBGS.cpp | 148 +- package_bgs/dp/AdaptiveMedianBGS.h | 89 +- package_bgs/dp/Bgs.h | 54 +- package_bgs/dp/BgsParams.h | 54 +- package_bgs/dp/DPAdaptiveMedianBGS.cpp | 113 -- package_bgs/dp/DPAdaptiveMedianBGS.h | 56 - package_bgs/dp/DPPratiMediodBGS.h | 57 - package_bgs/dp/DPTextureBGS.h | 60 - package_bgs/dp/Eigenbackground.cpp | 260 ++-- package_bgs/dp/Eigenbackground.h | 18 +- package_bgs/dp/Error.h | 7 +- package_bgs/dp/GrimsonGMM.cpp | 486 +++--- package_bgs/dp/GrimsonGMM.h | 206 ++- package_bgs/dp/Image.cpp | 42 +- package_bgs/dp/Image.h | 126 +- package_bgs/dp/MeanBGS.cpp | 150 +- package_bgs/dp/MeanBGS.h | 22 +- package_bgs/dp/PratiMediodBGS.cpp | 380 ++--- package_bgs/dp/PratiMediodBGS.h | 36 +- package_bgs/dp/TextureBGS.cpp | 96 +- package_bgs/dp/TextureBGS.h | 12 +- package_bgs/dp/WrenGA.cpp | 210 +-- package_bgs/dp/WrenGA.h | 30 +- package_bgs/dp/ZivkovicAGMM.cpp | 656 ++++----- package_bgs/dp/ZivkovicAGMM.h | 46 +- package_bgs/jmo/BlobResult.cpp | 847 ----------- package_bgs/jmo/MultiLayerBGS.h | 101 -- package_bgs/jmo/blob.cpp | 1149 --------------- package_bgs/lb/BGModel.cpp | 24 +- package_bgs/lb/BGModel.h | 16 +- package_bgs/lb/BGModelFuzzyGauss.cpp | 60 +- package_bgs/lb/BGModelFuzzyGauss.h | 8 +- package_bgs/lb/BGModelFuzzySom.cpp | 112 +- package_bgs/lb/BGModelFuzzySom.h | 14 +- package_bgs/lb/BGModelGauss.cpp | 64 +- package_bgs/lb/BGModelGauss.h | 8 +- package_bgs/lb/BGModelMog.cpp | 94 +- package_bgs/lb/BGModelMog.h | 8 +- package_bgs/lb/BGModelSom.cpp | 120 +- package_bgs/lb/BGModelSom.h | 14 +- package_bgs/lb/Types.h | 88 +- package_bgs/my/MyBGS.cpp | 26 - package_bgs/my/MyBGS.h | 22 - package_bgs/pl/BackgroundSubtractorLBSP.cpp | 69 - package_bgs/pl/BackgroundSubtractorLBSP.h | 85 -- package_bgs/pl/BackgroundSubtractorLOBSTER.cpp | 326 ---- package_bgs/pl/BackgroundSubtractorLOBSTER.h | 67 - package_bgs/pl/BackgroundSubtractorSuBSENSE.cpp | 737 ---------- package_bgs/pl/BackgroundSubtractorSuBSENSE.h | 113 -- package_bgs/pl/DistanceUtils.h | 316 ---- package_bgs/pl/LBSP.cpp | 318 ---- package_bgs/pl/LBSP.h | 118 -- package_bgs/pl/LOBSTER.cpp | 75 - package_bgs/pl/LOBSTER.h | 33 - package_bgs/pl/RandUtils.h | 96 -- package_bgs/pl/SuBSENSE.cpp | 75 - package_bgs/pl/SuBSENSE.h | 32 - package_bgs/sjn/SJN_MultiCueBGS.h | 248 ---- package_bgs/tb/FuzzyChoquetIntegral.h | 55 - package_bgs/tb/FuzzySugenoIntegral.h | 55 - package_bgs/tb/FuzzyUtils.cpp | 512 ------- package_bgs/tb/PixelUtils.cpp | 351 ----- package_bgs/tb/T2FMRF_UM.h | 64 - package_bgs/tb/T2FMRF_UV.h | 64 - vs2010/.gitignore | 16 - vs2010/README.txt | 15 - vs2010/bgslibrary.sln | 27 - vs2010/bgslibrary.suo | Bin 11776 -> 0 bytes vs2010/bgslibrary.vcxproj | 279 ---- vs2010/bgslibrary.vcxproj.filters | 644 -------- vs2010/bgslibrary.vcxproj.user | 8 - vs2010/bgslibrary_demo.vcxproj | 241 --- vs2010/bgslibrary_demo.vcxproj.filters | 596 -------- vs2010/bgslibrary_demo.vcxproj.user | 4 - vs2010/bgslibrary_demo2.vcxproj | 241 --- vs2010/bgslibrary_demo2.vcxproj.filters | 596 -------- vs2010/bgslibrary_demo2.vcxproj.user | 4 - vs2010mfc/outputs/background/KEEP_THIS_FOLDER | 0 vs2010mfc/outputs/foreground/KEEP_THIS_FOLDER | 0 vs2010mfc/outputs/input/KEEP_THIS_FOLDER | 0 vs2010mfc/src/ReadMe.txt | 100 -- vs2010mfc/src/bgslibrary_vs2010_mfc.rc | Bin 17930 -> 0 bytes vs2010mfc/src/bgslibrary_vs2010_mfc.sln | 20 - vs2010mfc/src/bgslibrary_vs2010_mfc.v12.suo | Bin 29696 -> 0 bytes vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj | 296 ---- .../src/bgslibrary_vs2010_mfc.vcxproj.filters | 581 -------- vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj.user | 7 - vs2010mfc/src/res/bgslibrary_vs2010_mfc.ico | Bin 67777 -> 0 bytes vs2010mfc/src/res/bgslibrary_vs2010_mfc.rc2 | Bin 826 -> 0 bytes vs2013/.gitignore | 10 - vs2013/README.txt | 8 - vs2013/bgslibrary.sln | 40 - vs2013/bgslibrary.suo | Bin 11776 -> 0 bytes vs2013/bgslibrary.vcxproj | 600 -------- vs2013/bgslibrary.vcxproj.filters | 644 -------- vs2013/bgslibrary.vcxproj.user | 34 - vs2013mfc/.gitignore | 3 - vs2013mfc/config/AdaptiveBackgroundLearning.xml | 9 - .../config/AdaptiveSelectiveBackgroundLearning.xml | 8 - vs2013mfc/config/DPAdaptiveMedianBGS.xml | 7 - vs2013mfc/config/DPEigenbackgroundBGS.xml | 7 - vs2013mfc/config/DPGrimsonGMMBGS.xml | 7 - vs2013mfc/config/DPMeanBGS.xml | 7 - vs2013mfc/config/DPPratiMediodBGS.xml | 8 - vs2013mfc/config/DPTextureBGS.xml | 4 - vs2013mfc/config/DPWrenGABGS.xml | 7 - vs2013mfc/config/DPZivkovicAGMMBGS.xml | 7 - vs2013mfc/config/FrameDifferenceBGS.xml | 6 - vs2013mfc/config/FuzzyChoquetIntegral.xml | 11 - vs2013mfc/config/FuzzySugenoIntegral.xml | 11 - vs2013mfc/config/GMG.xml | 6 - vs2013mfc/config/IndependentMultimodalBGS.xml | 4 - vs2013mfc/config/KDE.xml | 11 - vs2013mfc/config/LBAdaptiveSOM.xml | 9 - vs2013mfc/config/LBFuzzyAdaptiveSOM.xml | 9 - vs2013mfc/config/LBFuzzyGaussian.xml | 8 - vs2013mfc/config/LBMixtureOfGaussians.xml | 8 - vs2013mfc/config/LBSimpleGaussian.xml | 7 - vs2013mfc/config/LOBSTERBGS.xml | 10 - vs2013mfc/config/MixtureOfGaussianV1BGS.xml | 7 - vs2013mfc/config/MixtureOfGaussianV2BGS.xml | 7 - vs2013mfc/config/MultiCueBGS.xml | 4 - vs2013mfc/config/MultiLayerBGS.xml | 31 - vs2013mfc/config/SigmaDeltaBGS.xml | 7 - vs2013mfc/config/StaticFrameDifferenceBGS.xml | 6 - vs2013mfc/config/SuBSENSEBGS.xml | 10 - vs2013mfc/config/T2FGMM_UM.xml | 9 - vs2013mfc/config/T2FGMM_UV.xml | 9 - vs2013mfc/config/T2FMRF_UM.xml | 9 - vs2013mfc/config/T2FMRF_UV.xml | 9 - vs2013mfc/config/VuMeter.xml | 8 - vs2013mfc/config/WeightedMovingMeanBGS.xml | 8 - vs2013mfc/config/WeightedMovingVarianceBGS.xml | 7 - vs2013mfc/dataset/video.avi | Bin 1049784 -> 0 bytes vs2013mfc/src/.gitignore | 6 - vs2013mfc/src/App.cpp | 94 -- vs2013mfc/src/App.h | 32 - vs2013mfc/src/Dlg.cpp | 709 --------- vs2013mfc/src/Dlg.h | 92 -- vs2013mfc/src/resource.h | Bin 4240 -> 0 bytes vs2013mfc/src/stdafx.cpp | 8 - vs2013mfc/src/stdafx.h | 104 -- vs2013mfc/src/targetver.h | 8 - wrapper_matlab/.gitignore | 1 + wrapper_matlab/backgroundSubtractor.m | 81 + wrapper_matlab/backgroundSubtractor_wrapper.cpp | 395 +++++ wrapper_matlab/compile.m | 94 ++ wrapper_matlab/config/.gitignore | 4 + wrapper_matlab/demo.m | 65 + wrapper_matlab/mxarray.h | 1555 ++++++++++++++++++++ wrapper_matlab/mxtypes.h | 345 +++++ wrapper_matlab/run_demo.m | 49 + wrapper_python/bgslibrary_module.cpp | 265 ++++ wrapper_python/np_opencv_converter.cpp | 103 ++ wrapper_python/np_opencv_converter.h | 122 ++ wrapper_python/utils/container.h | 208 +++ wrapper_python/utils/conversion.cpp | 349 +++++ wrapper_python/utils/conversion.h | 75 + wrapper_python/utils/template.h | 44 + 551 files changed, 27350 insertions(+), 21312 deletions(-) create mode 100644 Demo.py create mode 100644 dataset/demo.avi rename {frames => dataset/frames}/1.png (100%) rename {frames => dataset/frames}/10.png (100%) rename {frames => dataset/frames}/11.png (100%) rename {frames => dataset/frames}/12.png (100%) rename {frames => dataset/frames}/13.png (100%) rename {frames => dataset/frames}/14.png (100%) rename {frames => dataset/frames}/15.png (100%) rename {frames => dataset/frames}/16.png (100%) rename {frames => dataset/frames}/17.png (100%) rename {frames => dataset/frames}/18.png (100%) rename {frames => dataset/frames}/19.png (100%) rename {frames => dataset/frames}/2.png (100%) rename {frames => dataset/frames}/20.png (100%) rename {frames => dataset/frames}/21.png (100%) rename {frames => dataset/frames}/22.png (100%) rename {frames => dataset/frames}/23.png (100%) rename {frames => dataset/frames}/24.png (100%) rename {frames => dataset/frames}/25.png (100%) rename {frames => dataset/frames}/26.png (100%) rename {frames => dataset/frames}/27.png (100%) rename {frames => dataset/frames}/28.png (100%) rename {frames => dataset/frames}/29.png (100%) rename {frames => dataset/frames}/3.png (100%) rename {frames => dataset/frames}/30.png (100%) rename {frames => dataset/frames}/31.png (100%) rename {frames => dataset/frames}/32.png (100%) rename {frames => dataset/frames}/33.png (100%) rename {frames => dataset/frames}/34.png (100%) rename {frames => dataset/frames}/35.png (100%) rename {frames => dataset/frames}/36.png (100%) rename {frames => dataset/frames}/37.png (100%) rename {frames => dataset/frames}/38.png (100%) rename {frames => dataset/frames}/39.png (100%) rename {frames => dataset/frames}/4.png (100%) rename {frames => dataset/frames}/40.png (100%) rename {frames => dataset/frames}/41.png (100%) rename {frames => dataset/frames}/42.png (100%) rename {frames => dataset/frames}/43.png (100%) rename {frames => dataset/frames}/44.png (100%) rename {frames => dataset/frames}/45.png (100%) rename {frames => dataset/frames}/46.png (100%) rename {frames => dataset/frames}/47.png (100%) rename {frames => dataset/frames}/48.png (100%) rename {frames => dataset/frames}/49.png (100%) rename {frames => dataset/frames}/5.png (100%) rename {frames => dataset/frames}/50.png (100%) rename {frames => dataset/frames}/51.png (100%) rename {frames => dataset/frames}/6.png (100%) rename {frames => dataset/frames}/7.png (100%) rename {frames => dataset/frames}/8.png (100%) rename {frames => dataset/frames}/9.png (100%) delete mode 100644 demos/DemoFrameDifferenceBGS.cpp delete mode 100644 demos/DemoMultiLayerBGS.cpp create mode 100644 demos/linux_ubuntu/.gitignore rename {example_linux => demos/linux_ubuntu}/CMakeLists.txt (66%) rename {example_macosx => demos/linux_ubuntu}/FrameDifferenceTest.cpp (87%) rename {example_linux => demos/linux_ubuntu}/README.txt (100%) create mode 100644 demos/linux_ubuntu/config/.gitignore create mode 100644 demos/macosx/.gitignore rename {example_macosx => demos/macosx}/CMakeLists.txt (79%) rename {example_linux => demos/macosx}/FrameDifferenceTest.cpp (87%) rename {example_macosx => demos/macosx}/README.txt (100%) create mode 100644 demos/macosx/config/.gitignore rename README_CMAKE_USERS.txt => docs/README_CMAKE_USERS_OPENCV2.txt (70%) create mode 100644 docs/README_CMAKE_USERS_OPENCV3.txt rename README_LINUX_USERS.txt => docs/README_LINUX_USERS.txt (85%) rename README_VISUAL_STUDIO_USERS.txt => docs/README_VS2010_OPENCV2.txt (81%) delete mode 100644 example_linux/config/KEEP_THIS_FOLDER delete mode 100644 example_macosx/config/KEEP_THIS_FOLDER rename {java_gui => gui_java}/README.txt (100%) rename {java_gui => gui_java}/_COPY_bgslibrary.exe_HERE_ (100%) rename {java_gui => gui_java}/bgslibrary_gui.jar (100%) rename {java_gui => gui_java}/bgslibrary_gui.properties (100%) rename {java_gui => gui_java}/build.xml (100%) rename {java_gui => gui_java}/config/.gitignore (100%) rename {java_gui => gui_java}/config/FrameProcessor.xml (100%) rename {java_gui => gui_java}/config/PreProcessor.xml (100%) rename {java_gui => gui_java}/config/VideoCapture.xml (100%) rename {java_gui => gui_java}/images/bgslibrary_gui_screen01.png (100%) rename {java_gui => gui_java}/images/bgslibrary_gui_screen02.png (100%) rename {java_gui => gui_java}/images/bgslibrary_gui_screen03.png (100%) rename {java_gui => gui_java}/images/bgslibrary_gui_screen04.png (100%) rename {java_gui => gui_java}/images/logo.jpg (100%) rename {java_gui => gui_java}/lib/commons-configuration-1.8.jar (100%) rename {java_gui => gui_java}/lib/commons-io-2.3.jar (100%) rename {java_gui => gui_java}/lib/commons-lang-2.6.jar (100%) rename {java_gui => gui_java}/lib/commons-logging-1.1.1.jar (100%) rename {java_gui => gui_java}/lib/swingx-all-1.6.3.jar (100%) rename {java_gui => gui_java}/lib/swingx-beaninfo-1.6.3.jar (100%) rename {java_gui => gui_java}/manifest.mf (100%) rename {java_gui => gui_java}/nbproject/build-impl.xml (100%) rename {java_gui => gui_java}/nbproject/genfiles.properties (100%) rename {java_gui => gui_java}/nbproject/private/config.properties (100%) rename {java_gui => gui_java}/nbproject/private/private.properties (100%) rename {java_gui => gui_java}/nbproject/private/private.xml (100%) rename {java_gui => gui_java}/nbproject/project.properties (100%) rename {java_gui => gui_java}/nbproject/project.xml (100%) rename {java_gui => gui_java}/run_camera.bat (100%) rename {java_gui => gui_java}/run_java_gui.bat (100%) rename {java_gui => gui_java}/run_java_gui_with_console.bat (100%) rename {java_gui => gui_java}/run_video.bat (100%) rename {java_gui => gui_java}/src/br/com/bgslibrary/Main.java (100%) rename {java_gui => gui_java}/src/br/com/bgslibrary/entity/Command.java (100%) rename {java_gui => gui_java}/src/br/com/bgslibrary/entity/Configuration.java (100%) rename {java_gui => gui_java}/src/br/com/bgslibrary/gui/AboutDialog.form (100%) rename {java_gui => gui_java}/src/br/com/bgslibrary/gui/AboutDialog.java (100%) rename {java_gui => gui_java}/src/br/com/bgslibrary/gui/MainFrame.form (100%) rename {java_gui => gui_java}/src/br/com/bgslibrary/gui/MainFrame.java (100%) rename {java_gui => gui_java}/src/br/com/bgslibrary/resources/logo.jpg (100%) rename {vs2010mfc => gui_mfc}/.gitignore (100%) rename {vs2013mfc => gui_mfc}/ReadMe.txt (100%) rename {vs2010mfc => gui_mfc}/config/AdaptiveBackgroundLearning.xml (100%) rename {vs2010mfc => gui_mfc}/config/AdaptiveSelectiveBackgroundLearning.xml (100%) rename {vs2010mfc => gui_mfc}/config/DPAdaptiveMedianBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/DPEigenbackgroundBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/DPGrimsonGMMBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/DPMeanBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/DPPratiMediodBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/DPTextureBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/DPWrenGABGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/DPZivkovicAGMMBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/FrameDifferenceBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/FuzzyChoquetIntegral.xml (100%) rename {vs2010mfc => gui_mfc}/config/FuzzySugenoIntegral.xml (100%) rename {vs2010mfc => gui_mfc}/config/GMG.xml (100%) rename {vs2010mfc => gui_mfc}/config/IndependentMultimodalBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/KDE.xml (100%) rename {vs2010mfc => gui_mfc}/config/LBAdaptiveSOM.xml (100%) rename {vs2010mfc => gui_mfc}/config/LBFuzzyAdaptiveSOM.xml (100%) rename {vs2010mfc => gui_mfc}/config/LBFuzzyGaussian.xml (100%) rename {vs2010mfc => gui_mfc}/config/LBMixtureOfGaussians.xml (100%) rename {vs2010mfc => gui_mfc}/config/LBSimpleGaussian.xml (100%) rename {vs2010mfc => gui_mfc}/config/LOBSTERBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/MixtureOfGaussianV1BGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/MixtureOfGaussianV2BGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/MultiCueBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/MultiLayerBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/SigmaDeltaBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/StaticFrameDifferenceBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/SuBSENSEBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/T2FGMM_UM.xml (100%) rename {vs2010mfc => gui_mfc}/config/T2FGMM_UV.xml (100%) rename {vs2010mfc => gui_mfc}/config/T2FMRF_UM.xml (100%) rename {vs2010mfc => gui_mfc}/config/T2FMRF_UV.xml (100%) rename {vs2010mfc => gui_mfc}/config/VuMeter.xml (100%) rename {vs2010mfc => gui_mfc}/config/WeightedMovingMeanBGS.xml (100%) rename {vs2010mfc => gui_mfc}/config/WeightedMovingVarianceBGS.xml (100%) rename {vs2010mfc => gui_mfc}/dataset/video.avi (100%) rename {vs2013mfc => gui_mfc}/outputs/background/.gitignore (100%) rename {vs2013mfc => gui_mfc}/outputs/foreground/.gitignore (100%) rename {vs2013mfc => gui_mfc}/outputs/input/.gitignore (100%) rename {vs2010mfc => gui_mfc}/src/.gitignore (100%) rename {vs2010mfc => gui_mfc}/src/App.cpp (100%) rename {vs2010mfc => gui_mfc}/src/App.h (100%) rename {vs2010mfc => gui_mfc}/src/Dlg.cpp (100%) rename {vs2010mfc => gui_mfc}/src/Dlg.h (100%) rename {vs2013mfc => gui_mfc}/src/ReadMe.txt (100%) rename {vs2013mfc => gui_mfc}/src/bgslibrary_vs2013_mfc.rc (100%) rename {vs2013mfc => gui_mfc}/src/bgslibrary_vs2013_mfc.sln (100%) rename {vs2013mfc => gui_mfc}/src/bgslibrary_vs2013_mfc.vcxproj (100%) rename {vs2013mfc => gui_mfc}/src/bgslibrary_vs2013_mfc.vcxproj.filters (100%) rename {vs2013mfc => gui_mfc}/src/bgslibrary_vs2013_mfc.vcxproj.user (100%) rename {vs2013mfc => gui_mfc}/src/res/bgslibrary_vs2013_mfc.ico (100%) rename {vs2013mfc => gui_mfc}/src/res/bgslibrary_vs2013_mfc.rc2 (100%) rename {vs2010mfc => gui_mfc}/src/resource.h (100%) rename {vs2010mfc => gui_mfc}/src/stdafx.cpp (100%) rename {vs2010mfc => gui_mfc}/src/stdafx.h (100%) rename {vs2010mfc => gui_mfc}/src/targetver.h (100%) create mode 100644 gui_qt/.gitignore create mode 100644 gui_qt/CMakeLists.txt create mode 100644 gui_qt/README.txt create mode 100644 gui_qt/application.qrc create mode 100644 gui_qt/bgslibrary_gui.cpp create mode 100644 gui_qt/bgslibrary_gui.pro create mode 100644 gui_qt/build/.gitignore create mode 100644 gui_qt/figs/copy.png create mode 100644 gui_qt/figs/cut.png create mode 100644 gui_qt/figs/new.png create mode 100644 gui_qt/figs/open.png create mode 100644 gui_qt/figs/paste.png create mode 100644 gui_qt/figs/save.png create mode 100644 gui_qt/mainwindow.cpp create mode 100644 gui_qt/mainwindow.h create mode 100644 gui_qt/mainwindow.ui create mode 100644 gui_qt/qt_utils.cpp create mode 100644 gui_qt/qt_utils.h create mode 100644 gui_qt/texteditor.cpp create mode 100644 gui_qt/texteditor.h create mode 100644 gui_qt/ui_mainwindow.h create mode 100644 output/bg/.gitignore create mode 100644 output/fg/.gitignore create mode 100644 output/in/.gitignore rename {package_bgs/tb => package_analysis}/PerformanceUtils.cpp (59%) rename {package_bgs/tb => package_analysis}/PerformanceUtils.h (82%) create mode 100644 package_analysis/PixelUtils.cpp rename {package_bgs/tb => package_analysis}/PixelUtils.h (90%) create mode 100644 package_bgs/DPAdaptiveMedian.cpp create mode 100644 package_bgs/DPAdaptiveMedian.h rename package_bgs/{dp/DPEigenbackgroundBGS.cpp => DPEigenbackground.cpp} (53%) create mode 100644 package_bgs/DPEigenbackground.h rename package_bgs/{dp/DPGrimsonGMMBGS.cpp => DPGrimsonGMM.cpp} (54%) rename package_bgs/{dp/DPEigenbackgroundBGS.h => DPGrimsonGMM.h} (53%) rename package_bgs/{dp/DPMeanBGS.cpp => DPMean.cpp} (55%) rename package_bgs/{dp/DPZivkovicAGMMBGS.h => DPMean.h} (56%) rename package_bgs/{dp/DPPratiMediodBGS.cpp => DPPratiMediod.cpp} (53%) rename package_bgs/{dp/DPMeanBGS.h => DPPratiMediod.h} (52%) rename package_bgs/{dp/DPTextureBGS.cpp => DPTexture.cpp} (65%) create mode 100644 package_bgs/DPTexture.h rename package_bgs/{dp/DPWrenGABGS.cpp => DPWrenGA.cpp} (54%) rename package_bgs/{dp/DPWrenGABGS.h => DPWrenGA.h} (54%) rename package_bgs/{dp/DPZivkovicAGMMBGS.cpp => DPZivkovicAGMM.cpp} (53%) rename package_bgs/{dp/DPGrimsonGMMBGS.h => DPZivkovicAGMM.h} (53%) create mode 100644 package_bgs/FrameDifference.cpp rename package_bgs/{FrameDifferenceBGS.h => FrameDifference.h} (61%) delete mode 100644 package_bgs/FrameDifferenceBGS.cpp rename package_bgs/{tb => }/FuzzyChoquetIntegral.cpp (62%) create mode 100644 package_bgs/FuzzyChoquetIntegral.h rename package_bgs/{tb => }/FuzzySugenoIntegral.cpp (62%) create mode 100644 package_bgs/FuzzySugenoIntegral.h rename package_bgs/{db/imbs.cpp => IMBS/IMBS.cpp} (74%) rename package_bgs/{db/imbs.hpp => IMBS/IMBS.hpp} (77%) create mode 100644 package_bgs/IndependentMultimodal.cpp rename package_bgs/{StaticFrameDifferenceBGS.h => IndependentMultimodal.h} (60%) rename package_bgs/{ae => }/KDE.cpp (56%) create mode 100644 package_bgs/KDE.h rename package_bgs/{ae => KDE}/KernelTable.cpp (75%) rename package_bgs/{ae => KDE}/KernelTable.h (86%) create mode 100644 package_bgs/KDE/NPBGSubtractor.cpp rename package_bgs/{ae => KDE}/NPBGSubtractor.h (87%) rename package_bgs/{ae => KDE}/NPBGmodel.cpp (78%) rename package_bgs/{ae => KDE}/NPBGmodel.h (84%) create mode 100644 package_bgs/KNN.cpp create mode 100644 package_bgs/KNN.h rename package_bgs/{lb => }/LBAdaptiveSOM.cpp (61%) rename package_bgs/{lb => }/LBAdaptiveSOM.h (55%) rename package_bgs/{lb => }/LBFuzzyAdaptiveSOM.cpp (59%) rename package_bgs/{lb => }/LBFuzzyAdaptiveSOM.h (55%) rename package_bgs/{lb => }/LBFuzzyGaussian.cpp (59%) rename package_bgs/{lb => }/LBFuzzyGaussian.h (56%) rename package_bgs/{lb => }/LBMixtureOfGaussians.cpp (59%) rename package_bgs/{lb => }/LBMixtureOfGaussians.h (56%) rename package_bgs/{ck/LbpMrf.cpp => LBP_MRF.cpp} (57%) rename package_bgs/{WeightedMovingMeanBGS.h => LBP_MRF.h} (58%) create mode 100644 package_bgs/LBP_MRF/MEDefs.cpp rename package_bgs/{ck => LBP_MRF}/MEDefs.hpp (73%) rename package_bgs/{ck => LBP_MRF}/MEHistogram.cpp (54%) rename package_bgs/{ck => LBP_MRF}/MEHistogram.hpp (93%) rename package_bgs/{ck => LBP_MRF}/MEImage.cpp (50%) rename package_bgs/{ck => LBP_MRF}/MEImage.hpp (97%) rename package_bgs/{ck => LBP_MRF}/MotionDetection.cpp (91%) rename package_bgs/{ck => LBP_MRF}/MotionDetection.hpp (93%) rename package_bgs/{ck => LBP_MRF}/block.h (51%) rename package_bgs/{ck => LBP_MRF}/graph.cpp (78%) rename package_bgs/{ck => LBP_MRF}/graph.h (60%) rename package_bgs/{ck => LBP_MRF}/maxflow.cpp (67%) create mode 100644 package_bgs/LBSP/BackgroundSubtractorLBSP.cpp create mode 100644 package_bgs/LBSP/BackgroundSubtractorLBSP.h create mode 100644 package_bgs/LBSP/BackgroundSubtractorLBSP_.cpp create mode 100644 package_bgs/LBSP/BackgroundSubtractorLBSP_.h create mode 100644 package_bgs/LBSP/BackgroundSubtractorLOBSTER.cpp create mode 100644 package_bgs/LBSP/BackgroundSubtractorLOBSTER.h create mode 100644 package_bgs/LBSP/BackgroundSubtractorPAWCS.cpp create mode 100644 package_bgs/LBSP/BackgroundSubtractorPAWCS.h create mode 100644 package_bgs/LBSP/BackgroundSubtractorSuBSENSE.cpp create mode 100644 package_bgs/LBSP/BackgroundSubtractorSuBSENSE.h create mode 100644 package_bgs/LBSP/DistanceUtils.h create mode 100644 package_bgs/LBSP/LBSP.cpp create mode 100644 package_bgs/LBSP/LBSP.h create mode 100644 package_bgs/LBSP/LBSP_.cpp create mode 100644 package_bgs/LBSP/LBSP_.h rename package_bgs/{pl => LBSP}/LBSP_16bits_dbcross_1ch.i (100%) rename package_bgs/{pl => LBSP}/LBSP_16bits_dbcross_3ch1t.i (100%) rename package_bgs/{pl => LBSP}/LBSP_16bits_dbcross_3ch3t.i (100%) rename package_bgs/{pl => LBSP}/LBSP_16bits_dbcross_s3ch.i (100%) create mode 100644 package_bgs/LBSP/RandUtils.h rename package_bgs/{lb => }/LBSimpleGaussian.cpp (61%) rename package_bgs/{lb => }/LBSimpleGaussian.h (57%) create mode 100644 package_bgs/LOBSTER.cpp create mode 100644 package_bgs/LOBSTER.h rename package_bgs/{MixtureOfGaussianV1BGS.cpp => MixtureOfGaussianV1.cpp} (53%) rename package_bgs/{MixtureOfGaussianV2BGS.h => MixtureOfGaussianV1.h} (59%) rename package_bgs/{MixtureOfGaussianV2BGS.cpp => MixtureOfGaussianV2.cpp} (60%) rename package_bgs/{MixtureOfGaussianV1BGS.h => MixtureOfGaussianV2.h} (57%) rename package_bgs/{sjn/SJN_MultiCueBGS.cpp => MultiCue.cpp} (83%) create mode 100644 package_bgs/MultiCue.h rename package_bgs/{jmo/MultiLayerBGS.cpp => MultiLayer.cpp} (65%) create mode 100644 package_bgs/MultiLayer.h rename package_bgs/{jmo => MultiLayer}/BGS.h (98%) rename package_bgs/{jmo => MultiLayer}/BackgroundSubtractionAPI.h (89%) rename package_bgs/{jmo => MultiLayer}/BlobExtraction.cpp (91%) rename package_bgs/{jmo => MultiLayer}/BlobExtraction.h (96%) rename package_bgs/{jmo => MultiLayer}/BlobLibraryConfiguration.h (95%) create mode 100644 package_bgs/MultiLayer/BlobResult.cpp rename package_bgs/{jmo => MultiLayer}/BlobResult.h (80%) rename package_bgs/{jmo => MultiLayer}/CMultiLayerBGS.cpp (99%) rename package_bgs/{jmo => MultiLayer}/CMultiLayerBGS.h (96%) rename package_bgs/{jmo => MultiLayer}/LocalBinaryPattern.cpp (95%) rename package_bgs/{jmo => MultiLayer}/LocalBinaryPattern.h (96%) rename package_bgs/{jmo => MultiLayer}/OpenCvDataConversion.h (97%) create mode 100644 package_bgs/MultiLayer/OpenCvLegacyIncludes.h create mode 100644 package_bgs/MultiLayer/blob.cpp rename package_bgs/{jmo => MultiLayer}/blob.h (87%) create mode 100644 package_bgs/PAWCS.cpp create mode 100644 package_bgs/PAWCS.h create mode 100644 package_bgs/PBAS/PBAS.cpp create mode 100644 package_bgs/PBAS/PBAS.h create mode 100644 package_bgs/PixelBasedAdaptiveSegmenter.cpp create mode 100644 package_bgs/PixelBasedAdaptiveSegmenter.h create mode 100644 package_bgs/SigmaDelta.cpp create mode 100644 package_bgs/SigmaDelta.h rename package_bgs/{bl => SigmaDelta}/sdLaMa091.cpp (97%) rename package_bgs/{bl => SigmaDelta}/sdLaMa091.h (96%) rename package_bgs/{StaticFrameDifferenceBGS.cpp => StaticFrameDifference.cpp} (53%) create mode 100644 package_bgs/StaticFrameDifference.h create mode 100644 package_bgs/SuBSENSE.cpp create mode 100644 package_bgs/SuBSENSE.h create mode 100644 package_bgs/T2F/FuzzyUtils.cpp rename package_bgs/{tb => T2F}/FuzzyUtils.h (86%) rename package_bgs/{tb => T2F}/MRF.cpp (58%) rename package_bgs/{tb => T2F}/MRF.h (95%) rename package_bgs/{tb => T2F}/T2FGMM.cpp (69%) rename package_bgs/{tb => T2F}/T2FGMM.h (92%) rename package_bgs/{tb => T2F}/T2FMRF.cpp (70%) rename package_bgs/{tb => T2F}/T2FMRF.h (91%) rename package_bgs/{tb => }/T2FGMM_UM.cpp (64%) rename package_bgs/{tb => }/T2FGMM_UM.h (52%) rename package_bgs/{tb => }/T2FGMM_UV.cpp (64%) rename package_bgs/{tb => }/T2FGMM_UV.h (52%) rename package_bgs/{tb => }/T2FMRF_UM.cpp (59%) create mode 100644 package_bgs/T2FMRF_UM.h rename package_bgs/{tb => }/T2FMRF_UV.cpp (59%) create mode 100644 package_bgs/T2FMRF_UV.h create mode 100644 package_bgs/TwoPoints.cpp create mode 100644 package_bgs/TwoPoints.h create mode 100644 package_bgs/TwoPoints/two_points.cpp create mode 100644 package_bgs/TwoPoints/two_points.h create mode 100644 package_bgs/ViBe.cpp create mode 100644 package_bgs/ViBe.h create mode 100644 package_bgs/ViBe/LICENSE create mode 100644 package_bgs/ViBe/vibe-background-sequential.cpp create mode 100644 package_bgs/ViBe/vibe-background-sequential.h rename package_bgs/{av => }/VuMeter.cpp (52%) create mode 100644 package_bgs/VuMeter.h rename package_bgs/{av => VuMeter}/TBackground.cpp (77%) rename package_bgs/{av => VuMeter}/TBackground.h (99%) rename package_bgs/{av => VuMeter}/TBackgroundVuMeter.cpp (74%) rename package_bgs/{av => VuMeter}/TBackgroundVuMeter.h (95%) rename package_bgs/{WeightedMovingMeanBGS.cpp => WeightedMovingMean.cpp} (51%) create mode 100644 package_bgs/WeightedMovingMean.h rename package_bgs/{WeightedMovingVarianceBGS.cpp => WeightedMovingVariance.cpp} (62%) create mode 100644 package_bgs/WeightedMovingVariance.h delete mode 100644 package_bgs/WeightedMovingVarianceBGS.h create mode 100644 package_bgs/_template_/Amber.cpp create mode 100644 package_bgs/_template_/Amber.h create mode 100644 package_bgs/_template_/MyBGS.cpp rename package_bgs/{ck/LbpMrf.h => _template_/MyBGS.h} (65%) create mode 100644 package_bgs/_template_/amber/amber.c create mode 100644 package_bgs/_template_/amber/amber.h delete mode 100644 package_bgs/ae/KDE.h delete mode 100644 package_bgs/ae/NPBGSubtractor.cpp delete mode 100644 package_bgs/av/VuMeter.h create mode 100644 package_bgs/bgslibrary.h delete mode 100644 package_bgs/bl/SigmaDeltaBGS.cpp delete mode 100644 package_bgs/bl/SigmaDeltaBGS.h delete mode 100644 package_bgs/bl/stdbool.h delete mode 100644 package_bgs/ck/MEDefs.cpp delete mode 100644 package_bgs/ck/README.TXT delete mode 100644 package_bgs/db/IndependentMultimodalBGS.cpp delete mode 100644 package_bgs/db/IndependentMultimodalBGS.h delete mode 100644 package_bgs/dp/DPAdaptiveMedianBGS.cpp delete mode 100644 package_bgs/dp/DPAdaptiveMedianBGS.h delete mode 100644 package_bgs/dp/DPPratiMediodBGS.h delete mode 100644 package_bgs/dp/DPTextureBGS.h delete mode 100644 package_bgs/jmo/BlobResult.cpp delete mode 100644 package_bgs/jmo/MultiLayerBGS.h delete mode 100644 package_bgs/jmo/blob.cpp delete mode 100644 package_bgs/my/MyBGS.cpp delete mode 100644 package_bgs/my/MyBGS.h delete mode 100644 package_bgs/pl/BackgroundSubtractorLBSP.cpp delete mode 100644 package_bgs/pl/BackgroundSubtractorLBSP.h delete mode 100644 package_bgs/pl/BackgroundSubtractorLOBSTER.cpp delete mode 100644 package_bgs/pl/BackgroundSubtractorLOBSTER.h delete mode 100644 package_bgs/pl/BackgroundSubtractorSuBSENSE.cpp delete mode 100644 package_bgs/pl/BackgroundSubtractorSuBSENSE.h delete mode 100644 package_bgs/pl/DistanceUtils.h delete mode 100644 package_bgs/pl/LBSP.cpp delete mode 100644 package_bgs/pl/LBSP.h delete mode 100644 package_bgs/pl/LOBSTER.cpp delete mode 100644 package_bgs/pl/LOBSTER.h delete mode 100644 package_bgs/pl/RandUtils.h delete mode 100644 package_bgs/pl/SuBSENSE.cpp delete mode 100644 package_bgs/pl/SuBSENSE.h delete mode 100644 package_bgs/sjn/SJN_MultiCueBGS.h delete mode 100644 package_bgs/tb/FuzzyChoquetIntegral.h delete mode 100644 package_bgs/tb/FuzzySugenoIntegral.h delete mode 100644 package_bgs/tb/FuzzyUtils.cpp delete mode 100644 package_bgs/tb/PixelUtils.cpp delete mode 100644 package_bgs/tb/T2FMRF_UM.h delete mode 100644 package_bgs/tb/T2FMRF_UV.h delete mode 100644 vs2010/.gitignore delete mode 100644 vs2010/README.txt delete mode 100644 vs2010/bgslibrary.sln delete mode 100644 vs2010/bgslibrary.suo delete mode 100644 vs2010/bgslibrary.vcxproj delete mode 100644 vs2010/bgslibrary.vcxproj.filters delete mode 100644 vs2010/bgslibrary.vcxproj.user delete mode 100644 vs2010/bgslibrary_demo.vcxproj delete mode 100644 vs2010/bgslibrary_demo.vcxproj.filters delete mode 100644 vs2010/bgslibrary_demo.vcxproj.user delete mode 100644 vs2010/bgslibrary_demo2.vcxproj delete mode 100644 vs2010/bgslibrary_demo2.vcxproj.filters delete mode 100644 vs2010/bgslibrary_demo2.vcxproj.user delete mode 100644 vs2010mfc/outputs/background/KEEP_THIS_FOLDER delete mode 100644 vs2010mfc/outputs/foreground/KEEP_THIS_FOLDER delete mode 100644 vs2010mfc/outputs/input/KEEP_THIS_FOLDER delete mode 100644 vs2010mfc/src/ReadMe.txt delete mode 100644 vs2010mfc/src/bgslibrary_vs2010_mfc.rc delete mode 100644 vs2010mfc/src/bgslibrary_vs2010_mfc.sln delete mode 100644 vs2010mfc/src/bgslibrary_vs2010_mfc.v12.suo delete mode 100644 vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj delete mode 100644 vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj.filters delete mode 100644 vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj.user delete mode 100644 vs2010mfc/src/res/bgslibrary_vs2010_mfc.ico delete mode 100644 vs2010mfc/src/res/bgslibrary_vs2010_mfc.rc2 delete mode 100644 vs2013/.gitignore delete mode 100644 vs2013/README.txt delete mode 100644 vs2013/bgslibrary.sln delete mode 100644 vs2013/bgslibrary.suo delete mode 100644 vs2013/bgslibrary.vcxproj delete mode 100644 vs2013/bgslibrary.vcxproj.filters delete mode 100644 vs2013/bgslibrary.vcxproj.user delete mode 100644 vs2013mfc/.gitignore delete mode 100644 vs2013mfc/config/AdaptiveBackgroundLearning.xml delete mode 100644 vs2013mfc/config/AdaptiveSelectiveBackgroundLearning.xml delete mode 100644 vs2013mfc/config/DPAdaptiveMedianBGS.xml delete mode 100644 vs2013mfc/config/DPEigenbackgroundBGS.xml delete mode 100644 vs2013mfc/config/DPGrimsonGMMBGS.xml delete mode 100644 vs2013mfc/config/DPMeanBGS.xml delete mode 100644 vs2013mfc/config/DPPratiMediodBGS.xml delete mode 100644 vs2013mfc/config/DPTextureBGS.xml delete mode 100644 vs2013mfc/config/DPWrenGABGS.xml delete mode 100644 vs2013mfc/config/DPZivkovicAGMMBGS.xml delete mode 100644 vs2013mfc/config/FrameDifferenceBGS.xml delete mode 100644 vs2013mfc/config/FuzzyChoquetIntegral.xml delete mode 100644 vs2013mfc/config/FuzzySugenoIntegral.xml delete mode 100644 vs2013mfc/config/GMG.xml delete mode 100644 vs2013mfc/config/IndependentMultimodalBGS.xml delete mode 100644 vs2013mfc/config/KDE.xml delete mode 100644 vs2013mfc/config/LBAdaptiveSOM.xml delete mode 100644 vs2013mfc/config/LBFuzzyAdaptiveSOM.xml delete mode 100644 vs2013mfc/config/LBFuzzyGaussian.xml delete mode 100644 vs2013mfc/config/LBMixtureOfGaussians.xml delete mode 100644 vs2013mfc/config/LBSimpleGaussian.xml delete mode 100644 vs2013mfc/config/LOBSTERBGS.xml delete mode 100644 vs2013mfc/config/MixtureOfGaussianV1BGS.xml delete mode 100644 vs2013mfc/config/MixtureOfGaussianV2BGS.xml delete mode 100644 vs2013mfc/config/MultiCueBGS.xml delete mode 100644 vs2013mfc/config/MultiLayerBGS.xml delete mode 100644 vs2013mfc/config/SigmaDeltaBGS.xml delete mode 100644 vs2013mfc/config/StaticFrameDifferenceBGS.xml delete mode 100644 vs2013mfc/config/SuBSENSEBGS.xml delete mode 100644 vs2013mfc/config/T2FGMM_UM.xml delete mode 100644 vs2013mfc/config/T2FGMM_UV.xml delete mode 100644 vs2013mfc/config/T2FMRF_UM.xml delete mode 100644 vs2013mfc/config/T2FMRF_UV.xml delete mode 100644 vs2013mfc/config/VuMeter.xml delete mode 100644 vs2013mfc/config/WeightedMovingMeanBGS.xml delete mode 100644 vs2013mfc/config/WeightedMovingVarianceBGS.xml delete mode 100644 vs2013mfc/dataset/video.avi delete mode 100644 vs2013mfc/src/.gitignore delete mode 100644 vs2013mfc/src/App.cpp delete mode 100644 vs2013mfc/src/App.h delete mode 100644 vs2013mfc/src/Dlg.cpp delete mode 100644 vs2013mfc/src/Dlg.h delete mode 100644 vs2013mfc/src/resource.h delete mode 100644 vs2013mfc/src/stdafx.cpp delete mode 100644 vs2013mfc/src/stdafx.h delete mode 100644 vs2013mfc/src/targetver.h create mode 100644 wrapper_matlab/.gitignore create mode 100644 wrapper_matlab/backgroundSubtractor.m create mode 100644 wrapper_matlab/backgroundSubtractor_wrapper.cpp create mode 100644 wrapper_matlab/compile.m create mode 100644 wrapper_matlab/config/.gitignore create mode 100644 wrapper_matlab/demo.m create mode 100644 wrapper_matlab/mxarray.h create mode 100644 wrapper_matlab/mxtypes.h create mode 100644 wrapper_matlab/run_demo.m create mode 100644 wrapper_python/bgslibrary_module.cpp create mode 100644 wrapper_python/np_opencv_converter.cpp create mode 100644 wrapper_python/np_opencv_converter.h create mode 100644 wrapper_python/utils/container.h create mode 100644 wrapper_python/utils/conversion.cpp create mode 100644 wrapper_python/utils/conversion.h create mode 100644 wrapper_python/utils/template.h diff --git a/.gitignore b/.gitignore index 3f5e0f7..f1d9cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ etc/ build_*/ -binaries/ +dataset_*/ +binaries*/ java_gui/dist/ java_gui/build/ java_gui/bgslibrary.exe @@ -9,4 +10,5 @@ qt_gui/ fet/etc/ *.exe *.pdb -*.suo \ No newline at end of file +*.suo +*.dll diff --git a/CMakeLists.txt b/CMakeLists.txt index d8e60da..6a01cce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,24 +2,44 @@ cmake_minimum_required(VERSION 2.8) project(bgslibrary) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x") +# cmake -D BGS_PYTHON_SUPPORT=ON .. +if(NOT DEFINED BGS_PYTHON_SUPPORT) + set(BGS_PYTHON_SUPPORT OFF) +elseif() + # add_definitions(-DBGS_PYTHON_SUPPORT) +endif() +message(STATUS "BGSLIBRARY WITH PYTHON SUPPORT: ${BGS_PYTHON_SUPPORT}") + +if(UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x") +endif(UNIX) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") #set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules) -set( bgs_out_dir "." ) +# compilation mode setup +set(CMAKE_BUILD_TYPE Release) +#set(CMAKE_BUILD_TYPE Debug) + +if(WIN32) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") +endif(WIN32) + +set(bgs_out_dir ".") # First for the generic no-config case (e.g. with mingw) -set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${bgs_out_dir} ) -set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${bgs_out_dir} ) -set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${bgs_out_dir} ) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${bgs_out_dir}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${bgs_out_dir}) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${bgs_out_dir}) # Second, for multi-config builds (e.g. msvc) -foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) - string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) - set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${bgs_out_dir} ) - set( CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${bgs_out_dir} ) - set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${bgs_out_dir} ) -endforeach( OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES ) - -IF(UNIX) +foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${bgs_out_dir}) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${bgs_out_dir}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${bgs_out_dir}) +endforeach(OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES) + +if(UNIX) # add some standard warnings ADD_DEFINITIONS(-Wno-variadic-macros -Wno-long-long -Wall -Wextra -Winit-self -Woverloaded-virtual -Wsign-promo -Wno-unused-parameter -pedantic -Woverloaded-virtual -Wno-unknown-pragmas) @@ -30,6 +50,7 @@ IF(UNIX) #ADD_DEFINITIONS(-Wconversion -Wfloat-equal) endif(UNIX) +set(OpenCV_STATIC OFF) find_package(OpenCV REQUIRED) message(STATUS "OpenCV library status:") @@ -37,24 +58,50 @@ message(STATUS " version: ${OpenCV_VERSION}") message(STATUS " libraries: ${OpenCV_LIBS}") message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}") -if(${OpenCV_VERSION} VERSION_EQUAL 3 OR ${OpenCV_VERSION} VERSION_GREATER 3) - message(FATAL_ERROR "OpenCV version is not compatible: ${OpenCV_VERSION}") -endif() +# if(${OpenCV_VERSION} VERSION_EQUAL 3 OR ${OpenCV_VERSION} VERSION_GREATER 3) +# message(FATAL_ERROR "OpenCV version is not compatible: ${OpenCV_VERSION}") +# endif() if(${OpenCV_VERSION} VERSION_LESS 2.3.1) message(FATAL_ERROR "OpenCV version is not compatible: ${OpenCV_VERSION}") endif() -file(GLOB sources FrameProcessor.cpp PreProcessor.cpp VideoAnalysis.cpp VideoCapture.cpp) -file(GLOB main Main.cpp) +if(BGS_PYTHON_SUPPORT) + set(Boost_USE_STATIC_LIBS OFF) + set(Boost_USE_MULTITHREADED ON) + set(Boost_USE_STATIC_RUNTIME OFF) + + find_package(Boost REQUIRED COMPONENTS python) + find_package(PythonLibs REQUIRED) + + message(STATUS "Boost library status:") + message(STATUS " version: ${Boost_VERSION}") + message(STATUS " libraries: ${Boost_LIBRARIES}") + message(STATUS " include path: ${Boost_INCLUDE_DIRS}") + + message(STATUS "Python library status:") + message(STATUS " version: ${PYTHON_VERSION}") + message(STATUS " libraries: ${PYTHON_LIBRARIES}") + message(STATUS " include path: ${PYTHON_INCLUDE_DIRS}") +endif() + +#file(GLOB sources FrameProcessor.cpp PreProcessor.cpp VideoAnalysis.cpp VideoCapture.cpp) +file(GLOB main Main.cpp FrameProcessor.cpp PreProcessor.cpp VideoAnalysis.cpp VideoCapture.cpp) file(GLOB demo Demo.cpp) file(GLOB demo2 Demo2.cpp) # list(REMOVE_ITEM sources ${demo} ${demo2}) file(GLOB_RECURSE analysis_src package_analysis/*.cpp) -file(GLOB_RECURSE bgs_src package_bgs/*.cpp package_bgs/*.c) -file(GLOB_RECURSE bgs_include package_bgs/*.h) +if(BGS_PYTHON_SUPPORT) + file(GLOB_RECURSE bgs_src package_bgs/*.cpp package_bgs/*.c wrapper_python/*.cpp) + file(GLOB_RECURSE bgs_include package_bgs/*.h wrapper_python/*.h) + include_directories(${CMAKE_SOURCE_DIR} ${OpenCV_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) +else() + file(GLOB_RECURSE bgs_src package_bgs/*.cpp package_bgs/*.c) + file(GLOB_RECURSE bgs_include package_bgs/*.h) + include_directories(${CMAKE_SOURCE_DIR} ${OpenCV_INCLUDE_DIRS}) +endif() # GMG is not available in older OpenCV versions if(${OpenCV_VERSION} VERSION_LESS 2.4.3) @@ -62,10 +109,16 @@ if(${OpenCV_VERSION} VERSION_LESS 2.4.3) list(REMOVE_ITEM bgs_src ${gmg}) endif() -include_directories(${CMAKE_SOURCE_DIR}) - -add_library(libbgs STATIC ${sources} ${bgs_src} ${analysis_src}) -target_link_libraries(libbgs ${OpenCV_LIBS}) +if(BGS_PYTHON_SUPPORT) + #add_library(libbgs SHARED ${sources} ${bgs_src} ${analysis_src}) + add_library(libbgs SHARED ${bgs_src} ${analysis_src}) + target_link_libraries(libbgs ${OpenCV_LIBS} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) + target_compile_definitions(libbgs PRIVATE BGS_PYTHON_SUPPORT=1) +else() + #add_library(libbgs STATIC ${sources} ${bgs_src} ${analysis_src}) + add_library(libbgs STATIC ${bgs_src} ${analysis_src}) + target_link_libraries(libbgs ${OpenCV_LIBS}) +endif() set_property(TARGET libbgs PROPERTY PUBLIC_HEADER ${bgs_include}) if(WIN32) # set_property(TARGET libbgs PROPERTY SUFFIX ".lib") @@ -83,7 +136,7 @@ target_link_libraries(bgs_demo ${OpenCV_LIBS} libbgs) add_executable(bgs_demo2 ${demo2}) target_link_libraries(bgs_demo2 ${OpenCV_LIBS} libbgs) -INSTALL(TARGETS libbgs +install(TARGETS libbgs bgslibrary RUNTIME DESTINATION bin COMPONENT app LIBRARY DESTINATION lib COMPONENT runtime diff --git a/Demo.cpp b/Demo.cpp index 61561be..fec54d8 100644 --- a/Demo.cpp +++ b/Demo.cpp @@ -17,178 +17,98 @@ along with BGSLibrary. If not, see . #include #include - -#include "package_bgs/FrameDifferenceBGS.h" -#include "package_bgs/StaticFrameDifferenceBGS.h" -#include "package_bgs/WeightedMovingMeanBGS.h" -#include "package_bgs/WeightedMovingVarianceBGS.h" -#include "package_bgs/MixtureOfGaussianV1BGS.h" -#include "package_bgs/MixtureOfGaussianV2BGS.h" -#include "package_bgs/AdaptiveBackgroundLearning.h" -#include "package_bgs/AdaptiveSelectiveBackgroundLearning.h" - -#if CV_MAJOR_VERSION >= 2 && CV_MINOR_VERSION >= 4 && CV_SUBMINOR_VERSION >= 3 -#include "package_bgs/GMG.h" -#endif - -#include "package_bgs/dp/DPAdaptiveMedianBGS.h" -#include "package_bgs/dp/DPGrimsonGMMBGS.h" -#include "package_bgs/dp/DPZivkovicAGMMBGS.h" -#include "package_bgs/dp/DPMeanBGS.h" -#include "package_bgs/dp/DPWrenGABGS.h" -#include "package_bgs/dp/DPPratiMediodBGS.h" -#include "package_bgs/dp/DPEigenbackgroundBGS.h" -#include "package_bgs/dp/DPTextureBGS.h" - -#include "package_bgs/tb/T2FGMM_UM.h" -#include "package_bgs/tb/T2FGMM_UV.h" -#include "package_bgs/tb/T2FMRF_UM.h" -#include "package_bgs/tb/T2FMRF_UV.h" -#include "package_bgs/tb/FuzzySugenoIntegral.h" -#include "package_bgs/tb/FuzzyChoquetIntegral.h" - -#include "package_bgs/lb/LBSimpleGaussian.h" -#include "package_bgs/lb/LBFuzzyGaussian.h" -#include "package_bgs/lb/LBMixtureOfGaussians.h" -#include "package_bgs/lb/LBAdaptiveSOM.h" -#include "package_bgs/lb/LBFuzzyAdaptiveSOM.h" - -#include "package_bgs/ck/LbpMrf.h" -#include "package_bgs/jmo/MultiLayerBGS.h" -// The PBAS algorithm was removed from BGSLibrary because it is -// based on patented algorithm ViBE -// http://www2.ulg.ac.be/telecom/research/vibe/ -//#include "package_bgs/pt/PixelBasedAdaptiveSegmenter.h" -#include "package_bgs/av/VuMeter.h" -#include "package_bgs/ae/KDE.h" -#include "package_bgs/db/IndependentMultimodalBGS.h" -#include "package_bgs/sjn/SJN_MultiCueBGS.h" -#include "package_bgs/bl/SigmaDeltaBGS.h" - -#include "package_bgs/pl/SuBSENSE.h" -#include "package_bgs/pl/LOBSTER.h" +#include "package_bgs/bgslibrary.h" int main(int argc, char **argv) { std::cout << "Using OpenCV " << CV_MAJOR_VERSION << "." << CV_MINOR_VERSION << "." << CV_SUBMINOR_VERSION << std::endl; - CvCapture *capture = 0; - int resize_factor = 100; + VideoCapture capture; - if(argc > 1) + if (argc > 1) { std::cout << "Openning: " << argv[1] << std::endl; - capture = cvCaptureFromAVI(argv[1]); + capture.open(argv[1]); } else - { - capture = cvCaptureFromCAM(0); - resize_factor = 50; // set size = 50% of original image - } + capture.open(0); - if(!capture) + if (!capture.isOpened()) { std::cerr << "Cannot initialize video!" << std::endl; return -1; } - - IplImage *frame_aux = cvQueryFrame(capture); - IplImage *frame = cvCreateImage(cvSize((int)((frame_aux->width*resize_factor)/100) , (int)((frame_aux->height*resize_factor)/100)), frame_aux->depth, frame_aux->nChannels); - cvResize(frame_aux, frame); /* Background Subtraction Methods */ IBGS *bgs; - /*** Default Package ***/ - bgs = new FrameDifferenceBGS; - //bgs = new StaticFrameDifferenceBGS; - //bgs = new WeightedMovingMeanBGS; - //bgs = new WeightedMovingVarianceBGS; - //bgs = new MixtureOfGaussianV1BGS; - //bgs = new MixtureOfGaussianV2BGS; + bgs = new FrameDifference; + //bgs = new StaticFrameDifference; + //bgs = new WeightedMovingMean; + //bgs = new WeightedMovingVariance; + //bgs = new MixtureOfGaussianV1; // only on OpenCV 2.x + //bgs = new MixtureOfGaussianV2; //bgs = new AdaptiveBackgroundLearning; //bgs = new AdaptiveSelectiveBackgroundLearning; - //bgs = new GMG; - - /*** DP Package (thanks to Donovan Parks) ***/ - //bgs = new DPAdaptiveMedianBGS; - //bgs = new DPGrimsonGMMBGS; - //bgs = new DPZivkovicAGMMBGS; - //bgs = new DPMeanBGS; - //bgs = new DPWrenGABGS; - //bgs = new DPPratiMediodBGS; - //bgs = new DPEigenbackgroundBGS; - //bgs = new DPTextureBGS; - - /*** TB Package (thanks to Thierry Bouwmans, Fida EL BAF and Zhenjie Zhao) ***/ + //bgs = new GMG; // only on OpenCV 2.x + //bgs = new KNN; // only on OpenCV 3.x + //bgs = new DPAdaptiveMedian; + //bgs = new DPGrimsonGMM; + //bgs = new DPZivkovicAGMM; + //bgs = new DPMean; + //bgs = new DPWrenGA; + //bgs = new DPPratiMediod; + //bgs = new DPEigenbackground; + //bgs = new DPTexture; //bgs = new T2FGMM_UM; //bgs = new T2FGMM_UV; //bgs = new T2FMRF_UM; //bgs = new T2FMRF_UV; //bgs = new FuzzySugenoIntegral; //bgs = new FuzzyChoquetIntegral; - - /*** JMO Package (thanks to Jean-Marc Odobez) ***/ - //bgs = new MultiLayerBGS; - - /*** PT Package (thanks to Martin Hofmann, Philipp Tiefenbacher and Gerhard Rigoll) ***/ + //bgs = new MultiLayer; //bgs = new PixelBasedAdaptiveSegmenter; - - /*** LB Package (thanks to Laurence Bender) ***/ //bgs = new LBSimpleGaussian; //bgs = new LBFuzzyGaussian; //bgs = new LBMixtureOfGaussians; //bgs = new LBAdaptiveSOM; //bgs = new LBFuzzyAdaptiveSOM; - - /*** LBP-MRF Package (thanks to Csaba Kertész) ***/ - //bgs = new LbpMrf; - - /*** AV Package (thanks to Lionel Robinault and Antoine Vacavant) ***/ + //bgs = new LBP_MRF; //bgs = new VuMeter; - - /*** EG Package (thanks to Ahmed Elgammal) ***/ //bgs = new KDE; - - /*** DB Package (thanks to Domenico Daniele Bloisi) ***/ - //bgs = new IndependentMultimodalBGS; - - /*** SJN Package (thanks to SeungJong Noh) ***/ - //bgs = new SJN_MultiCueBGS; - - /*** BL Package (thanks to Benjamin Laugraud) ***/ - //bgs = new SigmaDeltaBGS; - - /*** PL Package (thanks to Pierre-Luc) ***/ - //bgs = new SuBSENSEBGS(); - //bgs = new LOBSTERBGS(); + //bgs = new IndependentMultimodal; + //bgs = new MultiCue; + //bgs = new SigmaDelta; + //bgs = new SuBSENSE; + //bgs = new LOBSTER; + //bgs = new PAWCS; + //bgs = new TwoPoints; + //bgs = new ViBe; int key = 0; - while(key != 'q') + cv::Mat img_input; + while (key != 'q') { - frame_aux = cvQueryFrame(capture); - if(!frame_aux) break; + capture >> img_input; + if (img_input.empty()) break; - cvResize(frame_aux, frame); - - cv::Mat img_input(frame); cv::imshow("input", img_input); cv::Mat img_mask; cv::Mat img_bkgmodel; bgs->process(img_input, img_mask, img_bkgmodel); // by default, it shows automatically the foreground mask image - + //if(!img_mask.empty()) // cv::imshow("Foreground", img_mask); // do something - + key = cvWaitKey(33); } delete bgs; + capture.release(); cvDestroyAllWindows(); - cvReleaseCapture(&capture); return 0; } diff --git a/Demo.py b/Demo.py new file mode 100644 index 0000000..71bc352 --- /dev/null +++ b/Demo.py @@ -0,0 +1,84 @@ +import numpy as np +import cv2 +import libbgs + +## BGS Library algorithms +bgs = libbgs.FrameDifference() +#bgs = libbgs.StaticFrameDifference() +#bgs = libbgs.AdaptiveBackgroundLearning() +#bgs = libbgs.AdaptiveSelectiveBackgroundLearning() +#bgs = libbgs.DPAdaptiveMedian() +#bgs = libbgs.DPEigenbackground() +#bgs = libbgs.DPGrimsonGMM() +#bgs = libbgs.DPMean() +#bgs = libbgs.DPPratiMediod() +#bgs = libbgs.DPTexture() +#bgs = libbgs.DPWrenGA() +#bgs = libbgs.DPZivkovicAGMM() +#bgs = libbgs.FuzzyChoquetIntegral() +#bgs = libbgs.FuzzySugenoIntegral() +#bgs = libbgs.GMG() # if opencv 2.x +#bgs = libbgs.IndependentMultimodal() +#bgs = libbgs.KDE() +#bgs = libbgs.KNN() # if opencv 3.x +#bgs = libbgs.LBAdaptiveSOM() +#bgs = libbgs.LBFuzzyAdaptiveSOM() +#bgs = libbgs.LBFuzzyGaussian() +#bgs = libbgs.LBMixtureOfGaussians() +#bgs = libbgs.LBSimpleGaussian() +#bgs = libbgs.LBP_MRF() +#bgs = libbgs.LOBSTER() +#bgs = libbgs.MixtureOfGaussianV1() # if opencv 2.x +#bgs = libbgs.MixtureOfGaussianV2() +#bgs = libbgs.MultiCue() +#bgs = libbgs.MultiLayer() +#bgs = libbgs.PAWCS() +#bgs = libbgs.PixelBasedAdaptiveSegmenter() +#bgs = libbgs.SigmaDelta() +#bgs = libbgs.SuBSENSE() +#bgs = libbgs.T2FGMM_UM() +#bgs = libbgs.T2FGMM_UV() +#bgs = libbgs.T2FMRF_UM() +#bgs = libbgs.T2FMRF_UV() +#bgs = libbgs.VuMeter() +#bgs = libbgs.WeightedMovingMean() +#bgs = libbgs.WeightedMovingVariance() +#bgs = libbgs.TwoPoints() +#bgs = libbgs.ViBe() + +video_file = "dataset/video.avi" + +capture = cv2.VideoCapture(video_file) +while not capture.isOpened(): + capture = cv2.VideoCapture(video_file) + cv2.waitKey(1000) + print "Wait for the header" + +pos_frame = capture.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) +while True: + flag, frame = capture.read() + + if flag: + cv2.imshow('video', frame) + pos_frame = capture.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) + #print str(pos_frame)+" frames" + + img_output = bgs.apply(frame) + img_bgmodel = bgs.getBackgroundModel(); + + cv2.imshow('img_output', img_output) + cv2.imshow('img_bgmodel', img_bgmodel) + + else: + capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, pos_frame-1) + print "frame is not ready" + cv2.waitKey(1000) + # break + + if 0xFF & cv2.waitKey(10) == 27: + break + + if capture.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) == capture.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT): + break + +cv2.destroyAllWindows() diff --git a/Demo2.cpp b/Demo2.cpp index fe95c52..22c6ffc 100644 --- a/Demo2.cpp +++ b/Demo2.cpp @@ -17,56 +17,7 @@ along with BGSLibrary. If not, see . #include #include - -#include "package_bgs/FrameDifferenceBGS.h" -#include "package_bgs/StaticFrameDifferenceBGS.h" -#include "package_bgs/WeightedMovingMeanBGS.h" -#include "package_bgs/WeightedMovingVarianceBGS.h" -#include "package_bgs/MixtureOfGaussianV1BGS.h" -#include "package_bgs/MixtureOfGaussianV2BGS.h" -#include "package_bgs/AdaptiveBackgroundLearning.h" -#include "package_bgs/AdaptiveSelectiveBackgroundLearning.h" - -#if CV_MAJOR_VERSION >= 2 && CV_MINOR_VERSION >= 4 && CV_SUBMINOR_VERSION >= 3 -#include "package_bgs/GMG.h" -#endif - -#include "package_bgs/dp/DPAdaptiveMedianBGS.h" -#include "package_bgs/dp/DPGrimsonGMMBGS.h" -#include "package_bgs/dp/DPZivkovicAGMMBGS.h" -#include "package_bgs/dp/DPMeanBGS.h" -#include "package_bgs/dp/DPWrenGABGS.h" -#include "package_bgs/dp/DPPratiMediodBGS.h" -#include "package_bgs/dp/DPEigenbackgroundBGS.h" -#include "package_bgs/dp/DPTextureBGS.h" - -#include "package_bgs/tb/T2FGMM_UM.h" -#include "package_bgs/tb/T2FGMM_UV.h" -#include "package_bgs/tb/T2FMRF_UM.h" -#include "package_bgs/tb/T2FMRF_UV.h" -#include "package_bgs/tb/FuzzySugenoIntegral.h" -#include "package_bgs/tb/FuzzyChoquetIntegral.h" - -#include "package_bgs/lb/LBSimpleGaussian.h" -#include "package_bgs/lb/LBFuzzyGaussian.h" -#include "package_bgs/lb/LBMixtureOfGaussians.h" -#include "package_bgs/lb/LBAdaptiveSOM.h" -#include "package_bgs/lb/LBFuzzyAdaptiveSOM.h" - -#include "package_bgs/ck/LbpMrf.h" -#include "package_bgs/jmo/MultiLayerBGS.h" -// The PBAS algorithm was removed from BGSLibrary because it is -// based on patented algorithm ViBE -// http://www2.ulg.ac.be/telecom/research/vibe/ -//#include "package_bgs/pt/PixelBasedAdaptiveSegmenter.h" -#include "package_bgs/av/VuMeter.h" -#include "package_bgs/ae/KDE.h" -#include "package_bgs/db/IndependentMultimodalBGS.h" -#include "package_bgs/sjn/SJN_MultiCueBGS.h" -#include "package_bgs/bl/SigmaDeltaBGS.h" - -#include "package_bgs/pl/SuBSENSE.h" -#include "package_bgs/pl/LOBSTER.h" +#include "package_bgs/bgslibrary.h" int main(int argc, char **argv) { @@ -75,81 +26,60 @@ int main(int argc, char **argv) /* Background Subtraction Methods */ IBGS *bgs; - /*** Default Package ***/ - bgs = new FrameDifferenceBGS; - //bgs = new StaticFrameDifferenceBGS; - //bgs = new WeightedMovingMeanBGS; - //bgs = new WeightedMovingVarianceBGS; - //bgs = new MixtureOfGaussianV1BGS; - //bgs = new MixtureOfGaussianV2BGS; + bgs = new FrameDifference; + //bgs = new StaticFrameDifference; + //bgs = new WeightedMovingMean; + //bgs = new WeightedMovingVariance; + //bgs = new MixtureOfGaussianV1; // only on OpenCV 2.x + //bgs = new MixtureOfGaussianV2; //bgs = new AdaptiveBackgroundLearning; //bgs = new AdaptiveSelectiveBackgroundLearning; - //bgs = new GMG; - - /*** DP Package (thanks to Donovan Parks) ***/ - //bgs = new DPAdaptiveMedianBGS; - //bgs = new DPGrimsonGMMBGS; - //bgs = new DPZivkovicAGMMBGS; - //bgs = new DPMeanBGS; - //bgs = new DPWrenGABGS; - //bgs = new DPPratiMediodBGS; - //bgs = new DPEigenbackgroundBGS; - //bgs = new DPTextureBGS; - - /*** TB Package (thanks to Thierry Bouwmans, Fida EL BAF and Zhenjie Zhao) ***/ + //bgs = new GMG; // only on OpenCV 2.x + //bgs = new KNN; // only on OpenCV 3.x + //bgs = new DPAdaptiveMedian; + //bgs = new DPGrimsonGMM; + //bgs = new DPZivkovicAGMM; + //bgs = new DPMean; + //bgs = new DPWrenGA; + //bgs = new DPPratiMediod; + //bgs = new DPEigenbackground; + //bgs = new DPTexture; //bgs = new T2FGMM_UM; //bgs = new T2FGMM_UV; //bgs = new T2FMRF_UM; //bgs = new T2FMRF_UV; //bgs = new FuzzySugenoIntegral; //bgs = new FuzzyChoquetIntegral; - - /*** JMO Package (thanks to Jean-Marc Odobez) ***/ - //bgs = new MultiLayerBGS; - - /*** PT Package (thanks to Martin Hofmann, Philipp Tiefenbacher and Gerhard Rigoll) ***/ + //bgs = new MultiLayer; //bgs = new PixelBasedAdaptiveSegmenter; - - /*** LB Package (thanks to Laurence Bender) ***/ //bgs = new LBSimpleGaussian; //bgs = new LBFuzzyGaussian; //bgs = new LBMixtureOfGaussians; //bgs = new LBAdaptiveSOM; //bgs = new LBFuzzyAdaptiveSOM; - - /*** LBP-MRF Package (thanks to Csaba Kertész) ***/ - //bgs = new LbpMrf; - - /*** AV Package (thanks to Lionel Robinault and Antoine Vacavant) ***/ + //bgs = new LBP_MRF; //bgs = new VuMeter; - - /*** EG Package (thanks to Ahmed Elgammal) ***/ //bgs = new KDE; - - /*** DB Package (thanks to Domenico Daniele Bloisi) ***/ - //bgs = new IndependentMultimodalBGS; - - /*** SJN Package (thanks to SeungJong Noh) ***/ - //bgs = new SJN_MultiCueBGS; - - /*** BL Package (thanks to Benjamin Laugraud) ***/ - //bgs = new SigmaDeltaBGS; - - /*** PL Package (thanks to Pierre-Luc) ***/ - //bgs = new SuBSENSEBGS(); - //bgs = new LOBSTERBGS(); + //bgs = new IndependentMultimodal; + //bgs = new MultiCue; + //bgs = new SigmaDelta; + //bgs = new SuBSENSE; + //bgs = new LOBSTER; + //bgs = new PAWCS; + //bgs = new TwoPoints; + //bgs = new ViBe; int frameNumber = 1; int key = 0; - while(key != 'q') + while (key != 'q') { std::stringstream ss; ss << frameNumber; - std::string fileName = "frames/" + ss.str() + ".png"; + std::string fileName = "dataset/frames/" + ss.str() + ".png"; std::cout << "reading " << fileName << std::endl; cv::Mat img_input = cv::imread(fileName, CV_LOAD_IMAGE_COLOR); - + if (img_input.empty()) break; @@ -157,18 +87,19 @@ int main(int argc, char **argv) cv::Mat img_mask; cv::Mat img_bkgmodel; - bgs->process(img_input, img_mask, img_bkgmodel); // by default, it shows automatically the foreground mask image - + bgs->process(img_input, img_mask, img_bkgmodel); + // by default, "bgs->process(.)" automatically shows the foreground mask image + // or set "bgs->setShowOutput(false)" to disable + //if(!img_mask.empty()) // cv::imshow("Foreground", img_mask); // do something - + key = cvWaitKey(33); frameNumber++; } cvWaitKey(0); delete bgs; - cvDestroyAllWindows(); return 0; diff --git a/FrameProcessor.cpp b/FrameProcessor.cpp index 0cce652..5088121 100644 --- a/FrameProcessor.cpp +++ b/FrameProcessor.cpp @@ -37,23 +37,25 @@ namespace bgslibrary if (enablePreProcessor) preProcessor = new PreProcessor; - if (enableFrameDifferenceBGS) - frameDifference = new FrameDifferenceBGS; + if (enableFrameDifference) + frameDifference = new FrameDifference; - if (enableStaticFrameDifferenceBGS) - staticFrameDifference = new StaticFrameDifferenceBGS; + if (enableStaticFrameDifference) + staticFrameDifference = new StaticFrameDifference; - if (enableWeightedMovingMeanBGS) - weightedMovingMean = new WeightedMovingMeanBGS; + if (enableWeightedMovingMean) + weightedMovingMean = new WeightedMovingMean; - if (enableWeightedMovingVarianceBGS) - weightedMovingVariance = new WeightedMovingVarianceBGS; + if (enableWeightedMovingVariance) + weightedMovingVariance = new WeightedMovingVariance; - if (enableMixtureOfGaussianV1BGS) - mixtureOfGaussianV1BGS = new MixtureOfGaussianV1BGS; +#if CV_MAJOR_VERSION == 2 + if (enableMixtureOfGaussianV1) + mixtureOfGaussianV1 = new MixtureOfGaussianV1; +#endif - if (enableMixtureOfGaussianV2BGS) - mixtureOfGaussianV2BGS = new MixtureOfGaussianV2BGS; + if (enableMixtureOfGaussianV2) + mixtureOfGaussianV2 = new MixtureOfGaussianV2; if (enableAdaptiveBackgroundLearning) adaptiveBackgroundLearning = new AdaptiveBackgroundLearning; @@ -63,29 +65,29 @@ namespace bgslibrary gmg = new GMG; #endif - if (enableDPAdaptiveMedianBGS) - adaptiveMedian = new DPAdaptiveMedianBGS; + if (enableDPAdaptiveMedian) + dpAdaptiveMedian = new DPAdaptiveMedian; - if (enableDPGrimsonGMMBGS) - grimsonGMM = new DPGrimsonGMMBGS; + if (enableDPGrimsonGMM) + dpGrimsonGMM = new DPGrimsonGMM; - if (enableDPZivkovicAGMMBGS) - zivkovicAGMM = new DPZivkovicAGMMBGS; + if (enableDPZivkovicAGMM) + dpZivkovicAGMM = new DPZivkovicAGMM; - if (enableDPMeanBGS) - temporalMean = new DPMeanBGS; + if (enableDPMean) + dpTemporalMean = new DPMean; - if (enableDPWrenGABGS) - wrenGA = new DPWrenGABGS; + if (enableDPWrenGA) + dpWrenGA = new DPWrenGA; - if (enableDPPratiMediodBGS) - pratiMediod = new DPPratiMediodBGS; + if (enableDPPratiMediod) + dpPratiMediod = new DPPratiMediod; - if (enableDPEigenbackgroundBGS) - eigenBackground = new DPEigenbackgroundBGS; + if (enableDPEigenbackground) + dpEigenBackground = new DPEigenbackground; - if (enableDPTextureBGS) - textureBGS = new DPTextureBGS; + if (enableDPTexture) + dpTexture = new DPTexture; if (enableT2FGMM_UM) type2FuzzyGMM_UM = new T2FGMM_UM; @@ -121,13 +123,15 @@ namespace bgslibrary lbFuzzyAdaptiveSOM = new LBFuzzyAdaptiveSOM; if (enableLbpMrf) - lbpMrf = new LbpMrf; + lbpMrf = new LBP_MRF; - if(enableMultiLayerBGS) - multiLayerBGS = new MultiLayerBGS; +#if CV_MAJOR_VERSION == 2 + if (enableMultiLayer) + multiLayer = new MultiLayer; +#endif - //if(enablePBAS) - // pixelBasedAdaptiveSegmenter = new PixelBasedAdaptiveSegmenter; + if (enablePBAS) + pixelBasedAdaptiveSegmenter = new PixelBasedAdaptiveSegmenter; if (enableVuMeter) vuMeter = new VuMeter; @@ -136,19 +140,19 @@ namespace bgslibrary kde = new KDE; if (enableIMBS) - imbs = new IndependentMultimodalBGS; + imbs = new IndependentMultimodal; - if (enableMultiCueBGS) - mcbgs = new SJN_MultiCueBGS; + if (enableMultiCue) + multiCue = new MultiCue; - if (enableSigmaDeltaBGS) - sdbgs = new SigmaDeltaBGS; + if (enableSigmaDelta) + sigmaDelta = new SigmaDelta; - if (enableSuBSENSEBGS) - ssbgs = new SuBSENSEBGS; + if (enableSuBSENSE) + subSENSE = new SuBSENSE; - if (enableLOBSTERBGS) - lobgs = new LOBSTERBGS; + if (enableLOBSTER) + lobster = new LOBSTER; if (enableForegroundMaskAnalysis) foregroundMaskAnalysis = new ForegroundMaskAnalysis; @@ -171,169 +175,177 @@ namespace bgslibrary frameNumber++; if (enablePreProcessor) - preProcessor->process(img_input, img_prep); + preProcessor->process(img_input, img_preProcessor); - if (enableFrameDifferenceBGS) - process("FrameDifferenceBGS", frameDifference, img_prep, img_framediff); + if (enableFrameDifference) + process("FrameDifference", frameDifference, img_preProcessor, img_frameDifference); - if (enableStaticFrameDifferenceBGS) - process("StaticFrameDifferenceBGS", staticFrameDifference, img_prep, img_staticfdiff); + if (enableStaticFrameDifference) + process("StaticFrameDifference", staticFrameDifference, img_preProcessor, img_staticFrameDifference); - if (enableWeightedMovingMeanBGS) - process("WeightedMovingMeanBGS", weightedMovingMean, img_prep, img_wmovmean); + if (enableWeightedMovingMean) + process("WeightedMovingMean", weightedMovingMean, img_preProcessor, img_weightedMovingMean); - if (enableWeightedMovingVarianceBGS) - process("WeightedMovingVarianceBGS", weightedMovingVariance, img_prep, img_movvar); + if (enableWeightedMovingVariance) + process("WeightedMovingVariance", weightedMovingVariance, img_preProcessor, img_weightedMovingVariance); - if (enableMixtureOfGaussianV1BGS) - process("MixtureOfGaussianV1BGS", mixtureOfGaussianV1BGS, img_prep, img_mog1); +#if CV_MAJOR_VERSION == 2 + if (enableMixtureOfGaussianV1) + process("MixtureOfGaussianV1", mixtureOfGaussianV1, img_preProcessor, img_mixtureOfGaussianV1); +#endif - if (enableMixtureOfGaussianV2BGS) - process("MixtureOfGaussianV2BGS", mixtureOfGaussianV2BGS, img_prep, img_mog2); + if (enableMixtureOfGaussianV2) + process("MixtureOfGaussianV2", mixtureOfGaussianV2, img_preProcessor, img_mixtureOfGaussianV2); if (enableAdaptiveBackgroundLearning) - process("AdaptiveBackgroundLearning", adaptiveBackgroundLearning, img_prep, img_bkgl_fgmask); + process("AdaptiveBackgroundLearning", adaptiveBackgroundLearning, img_preProcessor, img_adaptiveBackgroundLearning); #if CV_MAJOR_VERSION >= 2 && CV_MINOR_VERSION >= 4 && CV_SUBMINOR_VERSION >= 3 if (enableGMG) - process("GMG", gmg, img_prep, img_gmg); + process("GMG", gmg, img_preProcessor, img_gmg); #endif - if (enableDPAdaptiveMedianBGS) - process("DPAdaptiveMedianBGS", adaptiveMedian, img_prep, img_adpmed); + if (enableDPAdaptiveMedian) + process("DPAdaptiveMedian", dpAdaptiveMedian, img_preProcessor, img_dpAdaptiveMedian); - if (enableDPGrimsonGMMBGS) - process("DPGrimsonGMMBGS", grimsonGMM, img_prep, img_grigmm); + if (enableDPGrimsonGMM) + process("DPGrimsonGMM", dpGrimsonGMM, img_preProcessor, img_dpGrimsonGMM); - if (enableDPZivkovicAGMMBGS) - process("DPZivkovicAGMMBGS", zivkovicAGMM, img_prep, img_zivgmm); + if (enableDPZivkovicAGMM) + process("DPZivkovicAGMM", dpZivkovicAGMM, img_preProcessor, img_dpZivkovicAGMM); - if (enableDPMeanBGS) - process("DPMeanBGS", temporalMean, img_prep, img_tmpmean); + if (enableDPMean) + process("DPMean", dpTemporalMean, img_preProcessor, img_dpTemporalMean); - if (enableDPWrenGABGS) - process("DPWrenGABGS", wrenGA, img_prep, img_wrenga); + if (enableDPWrenGA) + process("DPWrenGA", dpWrenGA, img_preProcessor, img_dpWrenGA); - if (enableDPPratiMediodBGS) - process("DPPratiMediodBGS", pratiMediod, img_prep, img_pramed); + if (enableDPPratiMediod) + process("DPPratiMediod", dpPratiMediod, img_preProcessor, img_dpPratiMediod); - if (enableDPEigenbackgroundBGS) - process("DPEigenbackgroundBGS", eigenBackground, img_prep, img_eigbkg); + if (enableDPEigenbackground) + process("DPEigenbackground", dpEigenBackground, img_preProcessor, img_dpEigenBackground); - if (enableDPTextureBGS) - process("DPTextureBGS", textureBGS, img_prep, img_texbgs); + if (enableDPTexture) + process("DPTexture", dpTexture, img_preProcessor, img_dpTexture); if (enableT2FGMM_UM) - process("T2FGMM_UM", type2FuzzyGMM_UM, img_prep, img_t2fgmm_um); + process("T2FGMM_UM", type2FuzzyGMM_UM, img_preProcessor, img_type2FuzzyGMM_UM); if (enableT2FGMM_UV) - process("T2FGMM_UV", type2FuzzyGMM_UV, img_prep, img_t2fgmm_uv); + process("T2FGMM_UV", type2FuzzyGMM_UV, img_preProcessor, img_type2FuzzyGMM_UV); if (enableT2FMRF_UM) - process("T2FMRF_UM", type2FuzzyMRF_UM, img_prep, img_t2fmrf_um); + process("T2FMRF_UM", type2FuzzyMRF_UM, img_preProcessor, img_type2FuzzyMRF_UM); if (enableT2FMRF_UV) - process("T2FMRF_UV", type2FuzzyMRF_UV, img_prep, img_t2fmrf_uv); + process("T2FMRF_UV", type2FuzzyMRF_UV, img_preProcessor, img_type2FuzzyMRF_UV); if (enableFuzzySugenoIntegral) - process("FuzzySugenoIntegral", fuzzySugenoIntegral, img_prep, img_fsi); + process("FuzzySugenoIntegral", fuzzySugenoIntegral, img_preProcessor, img_fuzzySugenoIntegral); if (enableFuzzyChoquetIntegral) - process("FuzzyChoquetIntegral", fuzzyChoquetIntegral, img_prep, img_fci); + process("FuzzyChoquetIntegral", fuzzyChoquetIntegral, img_preProcessor, img_fuzzyChoquetIntegral); if (enableLBSimpleGaussian) - process("LBSimpleGaussian", lbSimpleGaussian, img_prep, img_lb_sg); + process("LBSimpleGaussian", lbSimpleGaussian, img_preProcessor, img_lbSimpleGaussian); if (enableLBFuzzyGaussian) - process("LBFuzzyGaussian", lbFuzzyGaussian, img_prep, img_lb_fg); + process("LBFuzzyGaussian", lbFuzzyGaussian, img_preProcessor, img_lbFuzzyGaussian); if (enableLBMixtureOfGaussians) - process("LBMixtureOfGaussians", lbMixtureOfGaussians, img_prep, img_lb_mog); + process("LBMixtureOfGaussians", lbMixtureOfGaussians, img_preProcessor, img_lbMixtureOfGaussians); if (enableLBAdaptiveSOM) - process("LBAdaptiveSOM", lbAdaptiveSOM, img_prep, img_lb_som); + process("LBAdaptiveSOM", lbAdaptiveSOM, img_preProcessor, img_lbAdaptiveSOM); if (enableLBFuzzyAdaptiveSOM) - process("LBFuzzyAdaptiveSOM", lbFuzzyAdaptiveSOM, img_prep, img_lb_fsom); + process("LBFuzzyAdaptiveSOM", lbFuzzyAdaptiveSOM, img_preProcessor, img_lbFuzzyAdaptiveSOM); if (enableLbpMrf) - process("LbpMrf", lbpMrf, img_prep, img_lbp_mrf); + process("LbpMrf", lbpMrf, img_preProcessor, img_lbpMrf); - if(enableMultiLayerBGS) +#if CV_MAJOR_VERSION == 2 + if (enableMultiLayer) { - multiLayerBGS->setStatus(MultiLayerBGS::MLBGS_LEARN); - //multiLayerBGS->setStatus(MultiLayerBGS::MLBGS_DETECT); - process("MultiLayerBGS", multiLayerBGS, img_prep, img_mlbgs); + multiLayer->setStatus(MultiLayer::MLBGS_LEARN); + //multiLayer->setStatus(MultiLayer::MLBGS_DETECT); + process("MultiLayer", multiLayer, img_preProcessor, img_multiLayer); } +#endif - //if(enablePBAS) - // process("PBAS", pixelBasedAdaptiveSegmenter, img_prep, img_pt_pbas); + if (enablePBAS) + process("PBAS", pixelBasedAdaptiveSegmenter, img_preProcessor, img_pixelBasedAdaptiveSegmenter); if (enableVuMeter) - process("VuMeter", vuMeter, img_prep, img_vumeter); + process("VuMeter", vuMeter, img_preProcessor, img_vumeter); if (enableKDE) - process("KDE", kde, img_prep, img_kde); + process("KDE", kde, img_preProcessor, img_kde); if (enableIMBS) - process("IMBS", imbs, img_prep, img_imbs); + process("IMBS", imbs, img_preProcessor, img_imbs); - if (enableMultiCueBGS) - process("MultiCueBGS", mcbgs, img_prep, img_mcbgs); + if (enableMultiCue) + process("MultiCue", multiCue, img_preProcessor, img_multiCue); - if (enableSigmaDeltaBGS) - process("SigmaDeltaBGS", sdbgs, img_prep, img_sdbgs); + if (enableSigmaDelta) + process("SigmaDelta", sigmaDelta, img_preProcessor, img_sigmaDelta); - if (enableSuBSENSEBGS) - process("SuBSENSEBGS", ssbgs, img_prep, img_ssbgs); + if (enableSuBSENSE) + process("SuBSENSE", subSENSE, img_preProcessor, img_subSENSE); - if (enableLOBSTERBGS) - process("LOBSTERBGS", lobgs, img_prep, img_lobgs); + if (enableLOBSTER) + process("LOBSTER", lobster, img_preProcessor, img_lobster); if (enableForegroundMaskAnalysis) { foregroundMaskAnalysis->stopAt = frameToStop; foregroundMaskAnalysis->img_ref_path = imgref; - foregroundMaskAnalysis->process(frameNumber, "FrameDifferenceBGS", img_framediff); - foregroundMaskAnalysis->process(frameNumber, "StaticFrameDifferenceBGS", img_staticfdiff); - foregroundMaskAnalysis->process(frameNumber, "WeightedMovingMeanBGS", img_wmovmean); - foregroundMaskAnalysis->process(frameNumber, "WeightedMovingVarianceBGS", img_movvar); - foregroundMaskAnalysis->process(frameNumber, "MixtureOfGaussianV1BGS", img_mog1); - foregroundMaskAnalysis->process(frameNumber, "MixtureOfGaussianV2BGS", img_mog2); - foregroundMaskAnalysis->process(frameNumber, "AdaptiveBackgroundLearning", img_bkgl_fgmask); + foregroundMaskAnalysis->process(frameNumber, "FrameDifference", img_frameDifference); + foregroundMaskAnalysis->process(frameNumber, "StaticFrameDifference", img_staticFrameDifference); + foregroundMaskAnalysis->process(frameNumber, "WeightedMovingMean", img_weightedMovingMean); + foregroundMaskAnalysis->process(frameNumber, "WeightedMovingVariance", img_weightedMovingVariance); +#if CV_MAJOR_VERSION == 2 + foregroundMaskAnalysis->process(frameNumber, "MixtureOfGaussianV1", img_mixtureOfGaussianV1); +#endif + foregroundMaskAnalysis->process(frameNumber, "MixtureOfGaussianV2", img_mixtureOfGaussianV2); + foregroundMaskAnalysis->process(frameNumber, "AdaptiveBackgroundLearning", img_adaptiveBackgroundLearning); #if CV_MAJOR_VERSION >= 2 && CV_MINOR_VERSION >= 4 && CV_SUBMINOR_VERSION >= 3 foregroundMaskAnalysis->process(frameNumber, "GMG", img_gmg); #endif - foregroundMaskAnalysis->process(frameNumber, "DPAdaptiveMedianBGS", img_adpmed); - foregroundMaskAnalysis->process(frameNumber, "DPGrimsonGMMBGS", img_grigmm); - foregroundMaskAnalysis->process(frameNumber, "DPZivkovicAGMMBGS", img_zivgmm); - foregroundMaskAnalysis->process(frameNumber, "DPMeanBGS", img_tmpmean); - foregroundMaskAnalysis->process(frameNumber, "DPWrenGABGS", img_wrenga); - foregroundMaskAnalysis->process(frameNumber, "DPPratiMediodBGS", img_pramed); - foregroundMaskAnalysis->process(frameNumber, "DPEigenbackgroundBGS", img_eigbkg); - foregroundMaskAnalysis->process(frameNumber, "DPTextureBGS", img_texbgs); - foregroundMaskAnalysis->process(frameNumber, "T2FGMM_UM", img_t2fgmm_um); - foregroundMaskAnalysis->process(frameNumber, "T2FGMM_UV", img_t2fgmm_uv); - foregroundMaskAnalysis->process(frameNumber, "T2FMRF_UM", img_t2fmrf_um); - foregroundMaskAnalysis->process(frameNumber, "T2FMRF_UV", img_t2fmrf_uv); - foregroundMaskAnalysis->process(frameNumber, "FuzzySugenoIntegral", img_fsi); - foregroundMaskAnalysis->process(frameNumber, "FuzzyChoquetIntegral", img_fci); - foregroundMaskAnalysis->process(frameNumber, "LBSimpleGaussian", img_lb_sg); - foregroundMaskAnalysis->process(frameNumber, "LBFuzzyGaussian", img_lb_fg); - foregroundMaskAnalysis->process(frameNumber, "LBMixtureOfGaussians", img_lb_mog); - foregroundMaskAnalysis->process(frameNumber, "LBAdaptiveSOM", img_lb_som); - foregroundMaskAnalysis->process(frameNumber, "LBFuzzyAdaptiveSOM", img_lb_fsom); - foregroundMaskAnalysis->process(frameNumber, "LbpMrf", img_lbp_mrf); - foregroundMaskAnalysis->process(frameNumber, "MultiLayerBGS", img_mlbgs); - //foregroundMaskAnalysis->process(frameNumber, "PBAS", img_pt_pbas); + foregroundMaskAnalysis->process(frameNumber, "DPAdaptiveMedian", img_dpAdaptiveMedian); + foregroundMaskAnalysis->process(frameNumber, "DPGrimsonGMM", img_dpGrimsonGMM); + foregroundMaskAnalysis->process(frameNumber, "DPZivkovicAGMM", img_dpZivkovicAGMM); + foregroundMaskAnalysis->process(frameNumber, "DPMean", img_dpTemporalMean); + foregroundMaskAnalysis->process(frameNumber, "DPWrenGA", img_dpWrenGA); + foregroundMaskAnalysis->process(frameNumber, "DPPratiMediod", img_dpPratiMediod); + foregroundMaskAnalysis->process(frameNumber, "DPEigenbackground", img_dpEigenBackground); + foregroundMaskAnalysis->process(frameNumber, "DPTexture", img_dpTexture); + foregroundMaskAnalysis->process(frameNumber, "T2FGMM_UM", img_type2FuzzyGMM_UM); + foregroundMaskAnalysis->process(frameNumber, "T2FGMM_UV", img_type2FuzzyGMM_UV); + foregroundMaskAnalysis->process(frameNumber, "T2FMRF_UM", img_type2FuzzyMRF_UM); + foregroundMaskAnalysis->process(frameNumber, "T2FMRF_UV", img_type2FuzzyMRF_UV); + foregroundMaskAnalysis->process(frameNumber, "FuzzySugenoIntegral", img_fuzzySugenoIntegral); + foregroundMaskAnalysis->process(frameNumber, "FuzzyChoquetIntegral", img_fuzzyChoquetIntegral); + foregroundMaskAnalysis->process(frameNumber, "LBSimpleGaussian", img_lbSimpleGaussian); + foregroundMaskAnalysis->process(frameNumber, "LBFuzzyGaussian", img_lbFuzzyGaussian); + foregroundMaskAnalysis->process(frameNumber, "LBMixtureOfGaussians", img_lbMixtureOfGaussians); + foregroundMaskAnalysis->process(frameNumber, "LBAdaptiveSOM", img_lbAdaptiveSOM); + foregroundMaskAnalysis->process(frameNumber, "LBFuzzyAdaptiveSOM", img_lbFuzzyAdaptiveSOM); + foregroundMaskAnalysis->process(frameNumber, "LbpMrf", img_lbpMrf); +#if CV_MAJOR_VERSION == 2 + foregroundMaskAnalysis->process(frameNumber, "MultiLayer", img_multiLayer); +#endif + foregroundMaskAnalysis->process(frameNumber, "PBAS", img_pixelBasedAdaptiveSegmenter); foregroundMaskAnalysis->process(frameNumber, "VuMeter", img_vumeter); foregroundMaskAnalysis->process(frameNumber, "KDE", img_kde); foregroundMaskAnalysis->process(frameNumber, "IMBS", img_imbs); - foregroundMaskAnalysis->process(frameNumber, "MultiCueBGS", img_mcbgs); - foregroundMaskAnalysis->process(frameNumber, "SigmaDeltaBGS", img_sdbgs); - foregroundMaskAnalysis->process(frameNumber, "SuBSENSEBGS", img_ssbgs); - foregroundMaskAnalysis->process(frameNumber, "LOBSTERBGS", img_lobgs); + foregroundMaskAnalysis->process(frameNumber, "MultiCue", img_multiCue); + foregroundMaskAnalysis->process(frameNumber, "SigmaDelta", img_sigmaDelta); + foregroundMaskAnalysis->process(frameNumber, "SuBSENSE", img_subSENSE); + foregroundMaskAnalysis->process(frameNumber, "LOBSTER", img_lobster); } firstTime = false; @@ -341,8 +353,8 @@ namespace bgslibrary void FrameProcessor::finish(void) { - /*if(enableMultiLayerBGS) - multiLayerBGS->finish(); + /*if(enableMultiLayer) + multiLayer->finish(); if(enableLBSimpleGaussian) lbSimpleGaussian->finish(); @@ -362,17 +374,17 @@ namespace bgslibrary if (enableForegroundMaskAnalysis) delete foregroundMaskAnalysis; - if (enableLOBSTERBGS) - delete lobgs; + if (enableLOBSTER) + delete lobster; - if (enableSuBSENSEBGS) - delete ssbgs; + if (enableSuBSENSE) + delete subSENSE; - if (enableSigmaDeltaBGS) - delete sdbgs; + if (enableSigmaDelta) + delete sigmaDelta; - if (enableMultiCueBGS) - delete mcbgs; + if (enableMultiCue) + delete multiCue; if (enableIMBS) delete imbs; @@ -383,11 +395,13 @@ namespace bgslibrary if (enableVuMeter) delete vuMeter; - //if(enablePBAS) - // delete pixelBasedAdaptiveSegmenter; + if (enablePBAS) + delete pixelBasedAdaptiveSegmenter; - if (enableMultiLayerBGS) - delete multiLayerBGS; +#if CV_MAJOR_VERSION == 2 + if (enableMultiLayer) + delete multiLayer; +#endif if (enableLBFuzzyAdaptiveSOM) delete lbFuzzyAdaptiveSOM; @@ -409,7 +423,7 @@ namespace bgslibrary delete lbpMrf; #endif - if(enableFuzzyChoquetIntegral) + if (enableFuzzyChoquetIntegral) delete fuzzyChoquetIntegral; if (enableFuzzySugenoIntegral) @@ -427,29 +441,29 @@ namespace bgslibrary if (enableT2FGMM_UM) delete type2FuzzyGMM_UM; - if (enableDPTextureBGS) - delete textureBGS; + if (enableDPTexture) + delete dpTexture; - if (enableDPEigenbackgroundBGS) - delete eigenBackground; + if (enableDPEigenbackground) + delete dpEigenBackground; - if (enableDPPratiMediodBGS) - delete pratiMediod; + if (enableDPPratiMediod) + delete dpPratiMediod; - if (enableDPWrenGABGS) - delete wrenGA; + if (enableDPWrenGA) + delete dpWrenGA; - if (enableDPMeanBGS) - delete temporalMean; + if (enableDPMean) + delete dpTemporalMean; - if (enableDPZivkovicAGMMBGS) - delete zivkovicAGMM; + if (enableDPZivkovicAGMM) + delete dpZivkovicAGMM; - if (enableDPGrimsonGMMBGS) - delete grimsonGMM; + if (enableDPGrimsonGMM) + delete dpGrimsonGMM; - if (enableDPAdaptiveMedianBGS) - delete adaptiveMedian; + if (enableDPAdaptiveMedian) + delete dpAdaptiveMedian; #if CV_MAJOR_VERSION >= 2 && CV_MINOR_VERSION >= 4 && CV_SUBMINOR_VERSION >= 3 if (enableGMG) @@ -459,22 +473,24 @@ namespace bgslibrary if (enableAdaptiveBackgroundLearning) delete adaptiveBackgroundLearning; - if (enableMixtureOfGaussianV2BGS) - delete mixtureOfGaussianV2BGS; + if (enableMixtureOfGaussianV2) + delete mixtureOfGaussianV2; - if (enableMixtureOfGaussianV1BGS) - delete mixtureOfGaussianV1BGS; +#if CV_MAJOR_VERSION == 2 + if (enableMixtureOfGaussianV1) + delete mixtureOfGaussianV1; +#endif - if (enableWeightedMovingVarianceBGS) + if (enableWeightedMovingVariance) delete weightedMovingVariance; - if (enableWeightedMovingMeanBGS) + if (enableWeightedMovingMean) delete weightedMovingMean; - if (enableStaticFrameDifferenceBGS) + if (enableStaticFrameDifference) delete staticFrameDifference; - if (enableFrameDifferenceBGS) + if (enableFrameDifference) delete frameDifference; if (enablePreProcessor) @@ -503,25 +519,27 @@ namespace bgslibrary cvWriteInt(fs, "enableForegroundMaskAnalysis", enableForegroundMaskAnalysis); - cvWriteInt(fs, "enableFrameDifferenceBGS", enableFrameDifferenceBGS); - cvWriteInt(fs, "enableStaticFrameDifferenceBGS", enableStaticFrameDifferenceBGS); - cvWriteInt(fs, "enableWeightedMovingMeanBGS", enableWeightedMovingMeanBGS); - cvWriteInt(fs, "enableWeightedMovingVarianceBGS", enableWeightedMovingVarianceBGS); - cvWriteInt(fs, "enableMixtureOfGaussianV1BGS", enableMixtureOfGaussianV1BGS); - cvWriteInt(fs, "enableMixtureOfGaussianV2BGS", enableMixtureOfGaussianV2BGS); + cvWriteInt(fs, "enableFrameDifference", enableFrameDifference); + cvWriteInt(fs, "enableStaticFrameDifference", enableStaticFrameDifference); + cvWriteInt(fs, "enableWeightedMovingMean", enableWeightedMovingMean); + cvWriteInt(fs, "enableWeightedMovingVariance", enableWeightedMovingVariance); +#if CV_MAJOR_VERSION == 2 + cvWriteInt(fs, "enableMixtureOfGaussianV1", enableMixtureOfGaussianV1); +#endif + cvWriteInt(fs, "enableMixtureOfGaussianV2", enableMixtureOfGaussianV2); cvWriteInt(fs, "enableAdaptiveBackgroundLearning", enableAdaptiveBackgroundLearning); #if CV_MAJOR_VERSION >= 2 && CV_MINOR_VERSION >= 4 && CV_SUBMINOR_VERSION >= 3 cvWriteInt(fs, "enableGMG", enableGMG); #endif - cvWriteInt(fs, "enableDPAdaptiveMedianBGS", enableDPAdaptiveMedianBGS); - cvWriteInt(fs, "enableDPGrimsonGMMBGS", enableDPGrimsonGMMBGS); - cvWriteInt(fs, "enableDPZivkovicAGMMBGS", enableDPZivkovicAGMMBGS); - cvWriteInt(fs, "enableDPMeanBGS", enableDPMeanBGS); - cvWriteInt(fs, "enableDPWrenGABGS", enableDPWrenGABGS); - cvWriteInt(fs, "enableDPPratiMediodBGS", enableDPPratiMediodBGS); - cvWriteInt(fs, "enableDPEigenbackgroundBGS", enableDPEigenbackgroundBGS); - cvWriteInt(fs, "enableDPTextureBGS", enableDPTextureBGS); + cvWriteInt(fs, "enableDPAdaptiveMedian", enableDPAdaptiveMedian); + cvWriteInt(fs, "enableDPGrimsonGMM", enableDPGrimsonGMM); + cvWriteInt(fs, "enableDPZivkovicAGMM", enableDPZivkovicAGMM); + cvWriteInt(fs, "enableDPMean", enableDPMean); + cvWriteInt(fs, "enableDPWrenGA", enableDPWrenGA); + cvWriteInt(fs, "enableDPPratiMediod", enableDPPratiMediod); + cvWriteInt(fs, "enableDPEigenbackground", enableDPEigenbackground); + cvWriteInt(fs, "enableDPTexture", enableDPTexture); cvWriteInt(fs, "enableT2FGMM_UM", enableT2FGMM_UM); cvWriteInt(fs, "enableT2FGMM_UV", enableT2FGMM_UV); @@ -538,15 +556,17 @@ namespace bgslibrary cvWriteInt(fs, "enableLbpMrf", enableLbpMrf); - cvWriteInt(fs, "enableMultiLayerBGS", enableMultiLayerBGS); - //cvWriteInt(fs, "enablePBAS", enablePBAS); +#if CV_MAJOR_VERSION == 2 + cvWriteInt(fs, "enableMultiLayer", enableMultiLayer); +#endif + cvWriteInt(fs, "enablePBAS", enablePBAS); cvWriteInt(fs, "enableVuMeter", enableVuMeter); cvWriteInt(fs, "enableKDE", enableKDE); cvWriteInt(fs, "enableIMBS", enableIMBS); - cvWriteInt(fs, "enableMultiCueBGS", enableMultiCueBGS); - cvWriteInt(fs, "enableSigmaDeltaBGS", enableSigmaDeltaBGS); - cvWriteInt(fs, "enableSuBSENSEBGS", enableSuBSENSEBGS); - cvWriteInt(fs, "enableLOBSTERBGS", enableLOBSTERBGS); + cvWriteInt(fs, "enableMultiCue", enableMultiCue); + cvWriteInt(fs, "enableSigmaDelta", enableSigmaDelta); + cvWriteInt(fs, "enableSuBSENSE", enableSuBSENSE); + cvWriteInt(fs, "enableLOBSTER", enableLOBSTER); cvReleaseFileStorage(&fs); } @@ -561,25 +581,27 @@ namespace bgslibrary enableForegroundMaskAnalysis = cvReadIntByName(fs, 0, "enableForegroundMaskAnalysis", false); - enableFrameDifferenceBGS = cvReadIntByName(fs, 0, "enableFrameDifferenceBGS", false); - enableStaticFrameDifferenceBGS = cvReadIntByName(fs, 0, "enableStaticFrameDifferenceBGS", false); - enableWeightedMovingMeanBGS = cvReadIntByName(fs, 0, "enableWeightedMovingMeanBGS", false); - enableWeightedMovingVarianceBGS = cvReadIntByName(fs, 0, "enableWeightedMovingVarianceBGS", false); - enableMixtureOfGaussianV1BGS = cvReadIntByName(fs, 0, "enableMixtureOfGaussianV1BGS", false); - enableMixtureOfGaussianV2BGS = cvReadIntByName(fs, 0, "enableMixtureOfGaussianV2BGS", false); + enableFrameDifference = cvReadIntByName(fs, 0, "enableFrameDifference", false); + enableStaticFrameDifference = cvReadIntByName(fs, 0, "enableStaticFrameDifference", false); + enableWeightedMovingMean = cvReadIntByName(fs, 0, "enableWeightedMovingMean", false); + enableWeightedMovingVariance = cvReadIntByName(fs, 0, "enableWeightedMovingVariance", false); +#if CV_MAJOR_VERSION == 2 + enableMixtureOfGaussianV1 = cvReadIntByName(fs, 0, "enableMixtureOfGaussianV1", false); +#endif + enableMixtureOfGaussianV2 = cvReadIntByName(fs, 0, "enableMixtureOfGaussianV2", false); enableAdaptiveBackgroundLearning = cvReadIntByName(fs, 0, "enableAdaptiveBackgroundLearning", false); #if CV_MAJOR_VERSION >= 2 && CV_MINOR_VERSION >= 4 && CV_SUBMINOR_VERSION >= 3 enableGMG = cvReadIntByName(fs, 0, "enableGMG", false); #endif - enableDPAdaptiveMedianBGS = cvReadIntByName(fs, 0, "enableDPAdaptiveMedianBGS", false); - enableDPGrimsonGMMBGS = cvReadIntByName(fs, 0, "enableDPGrimsonGMMBGS", false); - enableDPZivkovicAGMMBGS = cvReadIntByName(fs, 0, "enableDPZivkovicAGMMBGS", false); - enableDPMeanBGS = cvReadIntByName(fs, 0, "enableDPMeanBGS", false); - enableDPWrenGABGS = cvReadIntByName(fs, 0, "enableDPWrenGABGS", false); - enableDPPratiMediodBGS = cvReadIntByName(fs, 0, "enableDPPratiMediodBGS", false); - enableDPEigenbackgroundBGS = cvReadIntByName(fs, 0, "enableDPEigenbackgroundBGS", false); - enableDPTextureBGS = cvReadIntByName(fs, 0, "enableDPTextureBGS", false); + enableDPAdaptiveMedian = cvReadIntByName(fs, 0, "enableDPAdaptiveMedian", false); + enableDPGrimsonGMM = cvReadIntByName(fs, 0, "enableDPGrimsonGMM", false); + enableDPZivkovicAGMM = cvReadIntByName(fs, 0, "enableDPZivkovicAGMM", false); + enableDPMean = cvReadIntByName(fs, 0, "enableDPMean", false); + enableDPWrenGA = cvReadIntByName(fs, 0, "enableDPWrenGA", false); + enableDPPratiMediod = cvReadIntByName(fs, 0, "enableDPPratiMediod", false); + enableDPEigenbackground = cvReadIntByName(fs, 0, "enableDPEigenbackground", false); + enableDPTexture = cvReadIntByName(fs, 0, "enableDPTexture", false); enableT2FGMM_UM = cvReadIntByName(fs, 0, "enableT2FGMM_UM", false); enableT2FGMM_UV = cvReadIntByName(fs, 0, "enableT2FGMM_UV", false); @@ -596,15 +618,17 @@ namespace bgslibrary enableLbpMrf = cvReadIntByName(fs, 0, "enableLbpMrf", false); - enableMultiLayerBGS = cvReadIntByName(fs, 0, "enableMultiLayerBGS", false); - //enablePBAS = cvReadIntByName(fs, 0, "enablePBAS", false); +#if CV_MAJOR_VERSION == 2 + enableMultiLayer = cvReadIntByName(fs, 0, "enableMultiLayer", false); +#endif + enablePBAS = cvReadIntByName(fs, 0, "enablePBAS", false); enableVuMeter = cvReadIntByName(fs, 0, "enableVuMeter", false); enableKDE = cvReadIntByName(fs, 0, "enableKDE", false); enableIMBS = cvReadIntByName(fs, 0, "enableIMBS", false); - enableMultiCueBGS = cvReadIntByName(fs, 0, "enableMultiCueBGS", false); - enableSigmaDeltaBGS = cvReadIntByName(fs, 0, "enableSigmaDeltaBGS", false); - enableSuBSENSEBGS = cvReadIntByName(fs, 0, "enableSuBSENSEBGS", false); - enableLOBSTERBGS = cvReadIntByName(fs, 0, "enableLOBSTERBGS", false); + enableMultiCue = cvReadIntByName(fs, 0, "enableMultiCue", false); + enableSigmaDelta = cvReadIntByName(fs, 0, "enableSigmaDelta", false); + enableSuBSENSE = cvReadIntByName(fs, 0, "enableSuBSENSE", false); + enableLOBSTER = cvReadIntByName(fs, 0, "enableLOBSTER", false); cvReleaseFileStorage(&fs); } diff --git a/FrameProcessor.h b/FrameProcessor.h index 0bb854e..228fa4c 100644 --- a/FrameProcessor.h +++ b/FrameProcessor.h @@ -21,56 +21,7 @@ along with BGSLibrary. If not, see . #include "PreProcessor.h" #include "package_bgs/IBGS.h" - -#include "package_bgs/FrameDifferenceBGS.h" -#include "package_bgs/StaticFrameDifferenceBGS.h" -#include "package_bgs/WeightedMovingMeanBGS.h" -#include "package_bgs/WeightedMovingVarianceBGS.h" -#include "package_bgs/MixtureOfGaussianV1BGS.h" -#include "package_bgs/MixtureOfGaussianV2BGS.h" -#include "package_bgs/AdaptiveBackgroundLearning.h" -#if CV_MAJOR_VERSION >= 2 && CV_MINOR_VERSION >= 4 && CV_SUBMINOR_VERSION >= 3 -#include "package_bgs/GMG.h" -#endif - -#include "package_bgs/dp/DPAdaptiveMedianBGS.h" -#include "package_bgs/dp/DPGrimsonGMMBGS.h" -#include "package_bgs/dp/DPZivkovicAGMMBGS.h" -#include "package_bgs/dp/DPMeanBGS.h" -#include "package_bgs/dp/DPWrenGABGS.h" -#include "package_bgs/dp/DPPratiMediodBGS.h" -#include "package_bgs/dp/DPEigenbackgroundBGS.h" -#include "package_bgs/dp/DPTextureBGS.h" - -#include "package_bgs/tb/T2FGMM_UM.h" -#include "package_bgs/tb/T2FGMM_UV.h" -#include "package_bgs/tb/T2FMRF_UM.h" -#include "package_bgs/tb/T2FMRF_UV.h" -#include "package_bgs/tb/FuzzySugenoIntegral.h" -#include "package_bgs/tb/FuzzyChoquetIntegral.h" - -#include "package_bgs/lb/LBSimpleGaussian.h" -#include "package_bgs/lb/LBFuzzyGaussian.h" -#include "package_bgs/lb/LBMixtureOfGaussians.h" -#include "package_bgs/lb/LBAdaptiveSOM.h" -#include "package_bgs/lb/LBFuzzyAdaptiveSOM.h" - -#include "package_bgs/ck/LbpMrf.h" - -#include "package_bgs/jmo/MultiLayerBGS.h" -// The PBAS algorithm was removed from BGSLibrary because it is -// based on patented algorithm ViBE -// http://www2.ulg.ac.be/telecom/research/vibe/ -//#include "package_bgs/pt/PixelBasedAdaptiveSegmenter.h" -#include "package_bgs/av/VuMeter.h" -#include "package_bgs/ae/KDE.h" -#include "package_bgs/db/IndependentMultimodalBGS.h" -#include "package_bgs/sjn/SJN_MultiCueBGS.h" -#include "package_bgs/bl/SigmaDeltaBGS.h" - -#include "package_bgs/pl/SuBSENSE.h" -#include "package_bgs/pl/LOBSTER.h" - +#include "package_bgs/bgslibrary.h" #include "package_analysis/ForegroundMaskAnalysis.h" namespace bgslibrary @@ -84,35 +35,37 @@ namespace bgslibrary double duration; std::string tictoc; - cv::Mat img_prep; + cv::Mat img_preProcessor; PreProcessor* preProcessor; bool enablePreProcessor; - cv::Mat img_framediff; - FrameDifferenceBGS* frameDifference; - bool enableFrameDifferenceBGS; + cv::Mat img_frameDifference; + FrameDifference* frameDifference; + bool enableFrameDifference; - cv::Mat img_staticfdiff; - StaticFrameDifferenceBGS* staticFrameDifference; - bool enableStaticFrameDifferenceBGS; + cv::Mat img_staticFrameDifference; + StaticFrameDifference* staticFrameDifference; + bool enableStaticFrameDifference; - cv::Mat img_wmovmean; - WeightedMovingMeanBGS* weightedMovingMean; - bool enableWeightedMovingMeanBGS; + cv::Mat img_weightedMovingMean; + WeightedMovingMean* weightedMovingMean; + bool enableWeightedMovingMean; - cv::Mat img_movvar; - WeightedMovingVarianceBGS* weightedMovingVariance; - bool enableWeightedMovingVarianceBGS; + cv::Mat img_weightedMovingVariance; + WeightedMovingVariance* weightedMovingVariance; + bool enableWeightedMovingVariance; - cv::Mat img_mog1; - MixtureOfGaussianV1BGS* mixtureOfGaussianV1BGS; - bool enableMixtureOfGaussianV1BGS; +#if CV_MAJOR_VERSION == 2 + cv::Mat img_mixtureOfGaussianV1; + MixtureOfGaussianV1* mixtureOfGaussianV1; + bool enableMixtureOfGaussianV1; +#endif - cv::Mat img_mog2; - MixtureOfGaussianV2BGS* mixtureOfGaussianV2BGS; - bool enableMixtureOfGaussianV2BGS; + cv::Mat img_mixtureOfGaussianV2; + MixtureOfGaussianV2* mixtureOfGaussianV2; + bool enableMixtureOfGaussianV2; - cv::Mat img_bkgl_fgmask; + cv::Mat img_adaptiveBackgroundLearning; AdaptiveBackgroundLearning* adaptiveBackgroundLearning; bool enableAdaptiveBackgroundLearning; @@ -122,93 +75,95 @@ namespace bgslibrary bool enableGMG; #endif - cv::Mat img_adpmed; - DPAdaptiveMedianBGS* adaptiveMedian; - bool enableDPAdaptiveMedianBGS; + cv::Mat img_dpAdaptiveMedian; + DPAdaptiveMedian* dpAdaptiveMedian; + bool enableDPAdaptiveMedian; - cv::Mat img_grigmm; - DPGrimsonGMMBGS* grimsonGMM; - bool enableDPGrimsonGMMBGS; + cv::Mat img_dpGrimsonGMM; + DPGrimsonGMM* dpGrimsonGMM; + bool enableDPGrimsonGMM; - cv::Mat img_zivgmm; - DPZivkovicAGMMBGS* zivkovicAGMM; - bool enableDPZivkovicAGMMBGS; + cv::Mat img_dpZivkovicAGMM; + DPZivkovicAGMM* dpZivkovicAGMM; + bool enableDPZivkovicAGMM; - cv::Mat img_tmpmean; - DPMeanBGS* temporalMean; - bool enableDPMeanBGS; + cv::Mat img_dpTemporalMean; + DPMean* dpTemporalMean; + bool enableDPMean; - cv::Mat img_wrenga; - DPWrenGABGS* wrenGA; - bool enableDPWrenGABGS; + cv::Mat img_dpWrenGA; + DPWrenGA* dpWrenGA; + bool enableDPWrenGA; - cv::Mat img_pramed; - DPPratiMediodBGS* pratiMediod; - bool enableDPPratiMediodBGS; + cv::Mat img_dpPratiMediod; + DPPratiMediod* dpPratiMediod; + bool enableDPPratiMediod; - cv::Mat img_eigbkg; - DPEigenbackgroundBGS* eigenBackground; - bool enableDPEigenbackgroundBGS; + cv::Mat img_dpEigenBackground; + DPEigenbackground* dpEigenBackground; + bool enableDPEigenbackground; - cv::Mat img_texbgs; - DPTextureBGS* textureBGS; - bool enableDPTextureBGS; + cv::Mat img_dpTexture; + DPTexture* dpTexture; + bool enableDPTexture; - cv::Mat img_t2fgmm_um; + cv::Mat img_type2FuzzyGMM_UM; T2FGMM_UM* type2FuzzyGMM_UM; bool enableT2FGMM_UM; - cv::Mat img_t2fgmm_uv; + cv::Mat img_type2FuzzyGMM_UV; T2FGMM_UV* type2FuzzyGMM_UV; bool enableT2FGMM_UV; - cv::Mat img_t2fmrf_um; + cv::Mat img_type2FuzzyMRF_UM; T2FMRF_UM* type2FuzzyMRF_UM; bool enableT2FMRF_UM; - cv::Mat img_t2fmrf_uv; + cv::Mat img_type2FuzzyMRF_UV; T2FMRF_UV* type2FuzzyMRF_UV; bool enableT2FMRF_UV; - cv::Mat img_fsi; + cv::Mat img_fuzzySugenoIntegral; FuzzySugenoIntegral* fuzzySugenoIntegral; bool enableFuzzySugenoIntegral; - cv::Mat img_fci; + cv::Mat img_fuzzyChoquetIntegral; FuzzyChoquetIntegral* fuzzyChoquetIntegral; bool enableFuzzyChoquetIntegral; - cv::Mat img_lb_sg; + cv::Mat img_lbSimpleGaussian; LBSimpleGaussian* lbSimpleGaussian; bool enableLBSimpleGaussian; - cv::Mat img_lb_fg; + cv::Mat img_lbFuzzyGaussian; LBFuzzyGaussian* lbFuzzyGaussian; bool enableLBFuzzyGaussian; - cv::Mat img_lb_mog; + cv::Mat img_lbMixtureOfGaussians; LBMixtureOfGaussians* lbMixtureOfGaussians; bool enableLBMixtureOfGaussians; - cv::Mat img_lb_som; + cv::Mat img_lbAdaptiveSOM; LBAdaptiveSOM* lbAdaptiveSOM; bool enableLBAdaptiveSOM; - cv::Mat img_lb_fsom; + cv::Mat img_lbFuzzyAdaptiveSOM; LBFuzzyAdaptiveSOM* lbFuzzyAdaptiveSOM; bool enableLBFuzzyAdaptiveSOM; - cv::Mat img_lbp_mrf; - LbpMrf* lbpMrf; + cv::Mat img_lbpMrf; + LBP_MRF* lbpMrf; bool enableLbpMrf; - cv::Mat img_mlbgs; - MultiLayerBGS* multiLayerBGS; - bool enableMultiLayerBGS; +#if CV_MAJOR_VERSION == 2 + cv::Mat img_multiLayer; + MultiLayer* multiLayer; + bool enableMultiLayer; +#endif - //cv::Mat img_pt_pbas; - //PixelBasedAdaptiveSegmenter* pixelBasedAdaptiveSegmenter; - //bool enablePBAS; + cv::Mat img_pixelBasedAdaptiveSegmenter; + PixelBasedAdaptiveSegmenter* pixelBasedAdaptiveSegmenter; + bool enablePBAS; cv::Mat img_vumeter; VuMeter* vuMeter; @@ -219,24 +174,24 @@ namespace bgslibrary bool enableKDE; cv::Mat img_imbs; - IndependentMultimodalBGS* imbs; + IndependentMultimodal* imbs; bool enableIMBS; - cv::Mat img_mcbgs; - SJN_MultiCueBGS* mcbgs; - bool enableMultiCueBGS; + cv::Mat img_multiCue; + MultiCue* multiCue; + bool enableMultiCue; - cv::Mat img_sdbgs; - SigmaDeltaBGS* sdbgs; - bool enableSigmaDeltaBGS; + cv::Mat img_sigmaDelta; + SigmaDelta* sigmaDelta; + bool enableSigmaDelta; - cv::Mat img_ssbgs; - SuBSENSEBGS* ssbgs; - bool enableSuBSENSEBGS; + cv::Mat img_subSENSE; + SuBSENSE* subSENSE; + bool enableSuBSENSE; - cv::Mat img_lobgs; - LOBSTERBGS* lobgs; - bool enableLOBSTERBGS; + cv::Mat img_lobster; + LOBSTER* lobster; + bool enableLOBSTER; ForegroundMaskAnalysis* foregroundMaskAnalysis; bool enableForegroundMaskAnalysis; diff --git a/Main.cpp b/Main.cpp index 1d170ad..7a406d9 100644 --- a/Main.cpp +++ b/Main.cpp @@ -29,7 +29,7 @@ namespace bgslibrary static void start(int argc, const char **argv) { std::cout << "-----------------------------------------" << std::endl; - std::cout << "Background Subtraction Library v1.9.2 " << std::endl; + std::cout << "Background Subtraction Library v2.0.0 " << std::endl; std::cout << "http://code.google.com/p/bgslibrary " << std::endl; std::cout << "by: " << std::endl; std::cout << "Andrews Sobral (andrewssobral@gmail.com) " << std::endl; diff --git a/PreProcessor.cpp b/PreProcessor.cpp index 6f4ebbe..4c13f7e 100644 --- a/PreProcessor.cpp +++ b/PreProcessor.cpp @@ -95,7 +95,7 @@ namespace bgslibrary cv2DRotationMatrix(center, angle, 1.0, mapMatrix); cvWarpAffine(image, rotatedImage, mapMatrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvScalarAll(0)); - cv::Mat img_rot(rotatedImage); + cv::Mat img_rot = cv::cvarrToMat(rotatedImage); img_rot.copyTo(img_output); cvReleaseImage(&image); diff --git a/PreProcessor.h b/PreProcessor.h index da46cce..2e6e61c 100644 --- a/PreProcessor.h +++ b/PreProcessor.h @@ -19,7 +19,6 @@ along with BGSLibrary. If not, see . #include #include - namespace bgslibrary { class PreProcessor diff --git a/README.md b/README.md index a35389a..3b8a3cb 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ A Background Subtraction Library [![bgslibrary](http://i.giphy.com/5A94AZahSIVOw.gif)](https://youtu.be/_UbERwuQ0OU) -Last Page Update: **12/03/2017** +Last Page Update: **18/03/2017** -Latest Library Version: **1.9.2** (see [Release Notes](https://github.com/andrewssobral/bgslibrary/wiki/Release-notes) for more info) +Latest Library Version: **2.0.0** (see [Release Notes](https://github.com/andrewssobral/bgslibrary/wiki/Release-notes) for more info) -The **BGSLibrary** was developed by [Andrews Sobral](http://andrewssobral.wixsite.com/home) and provides an easy-to-use C++ framework based on [OpenCV](http://www.opencv.org/) to perform foreground-background separation in videos. The bgslibrary compiles under Windows, Linux, and Mac OS X. Currently the library contains **37** algorithms. The source code is available under [GNU GPLv3 license](https://www.gnu.org/licenses/gpl-3.0.en.html), the library is free and open source for academic purposes. +The **BGSLibrary** was developed by [Andrews Sobral](http://andrewssobral.wixsite.com/home) and provides an easy-to-use C++ framework based on [OpenCV](http://www.opencv.org/) to perform foreground-background separation in videos. The bgslibrary is compatible with OpenCV 2.x and 3.x, and compiles under Windows, Linux, and Mac OS X. Currently the library contains **40** algorithms. The source code is available under [GNU GPLv3 license](https://www.gnu.org/licenses/gpl-3.0.en.html), the library is free and open source for academic purposes. -Note: the BGSLibrary is based on OpenCV 2.X, if you want to use with OpenCV 3.x please check-out our [opencv3](https://github.com/andrewssobral/bgslibrary/tree/opencv3) branch. +***Note: The [opencv3](https://github.com/andrewssobral/bgslibrary/tree/opencv3) branch will be deprecated.*** * [List of available algorithms](https://github.com/andrewssobral/bgslibrary/wiki/List-of-available-algorithms) * [Algorithms benchmark](https://github.com/andrewssobral/bgslibrary/wiki/Algorithms-benchmark) @@ -77,6 +77,7 @@ Some algorithms of the BGSLibrary were used successfully in the following papers * (2013) Sobral, Andrews; Oliveira, Luciano; Schnitman, Leizer; Souza, Felippe. (**Best Paper Award**) Highway Traffic Congestion Classification Using Holistic Properties. In International Conference on Signal Processing, Pattern Recognition and Applications (SPPRA'2013), Innsbruck, Austria, Feb 2013. ([Online](http://dx.doi.org/10.2316/P.2013.798-105)) ([PDF](http://www.researchgate.net/publication/233427564_HIGHWAY_TRAFFIC_CONGESTION_CLASSIFICATION_USING_HOLISTIC_PROPERTIES)) + Videos ------ diff --git a/VideoAnalysis.cpp b/VideoAnalysis.cpp index 1e46542..9c8020a 100644 --- a/VideoAnalysis.cpp +++ b/VideoAnalysis.cpp @@ -18,7 +18,9 @@ along with BGSLibrary. If not, see . namespace bgslibrary { - VideoAnalysis::VideoAnalysis() : use_file(false), use_camera(false), cameraIndex(0), use_comp(false), frameToStop(0) + VideoAnalysis::VideoAnalysis() : + use_file(false), use_camera(false), cameraIndex(0), + use_comp(false), frameToStop(0) { std::cout << "VideoAnalysis()" << std::endl; } @@ -32,8 +34,9 @@ namespace bgslibrary { bool flag = false; +#if CV_MAJOR_VERSION == 2 const char* keys = - "{hp|help|false|Print help message}" + "{hp|help|false|Print this message}" "{uf|use_file|false|Use video file}" "{fn|filename||Specify video file}" "{uc|use_cam|false|Use camera}" @@ -42,21 +45,63 @@ namespace bgslibrary "{st|stopAt|0|Frame number to stop}" "{im|imgref||Specify image file}" ; +#elif CV_MAJOR_VERSION == 3 + const std::string keys = + "{h help ? | | Print this message }" + "{uf use_file |false| Use a video file }" + "{fn filename | | Specify a video file }" + "{uc use_cam |false| Use a webcamera }" + "{ca camera | 0 | Specify camera index }" + "{co use_comp |false| Use mask comparator }" + "{st stopAt | 0 | Frame number to stop }" + "{im imgref | | Specify a image file }" + ; +#endif + cv::CommandLineParser cmd(argc, argv, keys); +#if CV_MAJOR_VERSION == 2 if (argc <= 1 || cmd.get("help") == true) { std::cout << "Usage: " << argv[0] << " [options]" << std::endl; - std::cout << "Avaible options:" << std::endl; + std::cout << "Available options:" << std::endl; cmd.printParams(); return false; } +#elif CV_MAJOR_VERSION == 3 + if (argc <= 1 || cmd.has("help")) + { + std::cout << "Usage: " << argv[0] << " [options]" << std::endl; + std::cout << "Available options:" << std::endl; + cmd.printMessage(); + return false; + } + if (!cmd.check()) + { + cmd.printErrors(); + return false; + } +#endif + + use_file = cmd.get("uf"); //use_file + filename = cmd.get("fn"); //filename + use_camera = cmd.get("uc"); //use_cam + cameraIndex = cmd.get("ca"); //camera + use_comp = cmd.get("co"); //use_comp + frameToStop = cmd.get("st"); //stopAt + imgref = cmd.get("im"); //imgref + + std::cout << "use_file: " << use_file << std::endl; + std::cout << "filename: " << filename << std::endl; + std::cout << "use_camera: " << use_camera << std::endl; + std::cout << "cameraIndex: " << cameraIndex << std::endl; + std::cout << "use_comp: " << use_comp << std::endl; + std::cout << "frameToStop: " << frameToStop << std::endl; + std::cout << "imgref: " << imgref << std::endl; + //return false; - use_file = cmd.get("use_file"); if (use_file) { - filename = cmd.get("filename"); - if (filename.empty()) { std::cout << "Specify filename" << std::endl; @@ -66,26 +111,15 @@ namespace bgslibrary flag = true; } - use_camera = cmd.get("use_cam"); if (use_camera) - { - cameraIndex = cmd.get("camera"); flag = true; - } - if (flag == true) + if (flag && use_comp) { - use_comp = cmd.get("use_comp"); - if (use_comp) + if (imgref.empty()) { - frameToStop = cmd.get("stopAt"); - imgref = cmd.get("imgref"); - - if (imgref.empty()) - { - std::cout << "Specify image reference" << std::endl; - return false; - } + std::cout << "Specify image reference" << std::endl; + return false; } } diff --git a/VideoCapture.cpp b/VideoCapture.cpp index 4de5b90..b302a3f 100644 --- a/VideoCapture.cpp +++ b/VideoCapture.cpp @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ #include "VideoCapture.h" +#include namespace bgslibrary { @@ -74,8 +75,11 @@ namespace bgslibrary } } - VideoCapture::VideoCapture() : key(0), start_time(0), delta_time(0), freq(0), fps(0), frameNumber(0), stopAt(0), - useCamera(false), useVideo(false), input_resize_percent(100), showOutput(true), enableFlip(false) + VideoCapture::VideoCapture() : + key(0), start_time(0), delta_time(0), freq(0), + fps(0), frameNumber(0), stopAt(0), useCamera(false), + useVideo(false), input_resize_percent(100), showOutput(true), + enableFlip(false), cameraIndex(0) { std::cout << "VideoCapture()" << std::endl; } @@ -101,10 +105,10 @@ namespace bgslibrary void VideoCapture::setUpCamera() { std::cout << "Camera index:" << cameraIndex << std::endl; - capture = cvCaptureFromCAM(cameraIndex); + capture.open(cameraIndex); - if (!capture) - std::cerr << "Cannot open initialize webcam!\n" << std::endl; + if (!capture.isOpened()) + std::cerr << "Cannot initialize webcam!\n" << std::endl; } void VideoCapture::setVideo(std::string filename) @@ -117,10 +121,13 @@ namespace bgslibrary void VideoCapture::setUpVideo() { - capture = cvCaptureFromFile(videoFileName.c_str()); + std::cout << "Openning: " << videoFileName << std::endl; + capture.open(videoFileName.c_str()); - if (!capture) + if (!capture.isOpened()) std::cerr << "Cannot open video file " << videoFileName << std::endl; + else + std::cout << "OK" << std::endl; } void VideoCapture::start() @@ -129,17 +136,21 @@ namespace bgslibrary if (useCamera) setUpCamera(); if (useVideo) setUpVideo(); - if (!capture) std::cerr << "Capture error..." << std::endl; + //if (!capture) std::cerr << "Capture error..." << std::endl; - int input_fps = cvGetCaptureProperty(capture, CV_CAP_PROP_FPS); + int input_fps = capture.get(CV_CAP_PROP_FPS); std::cout << "input->fps:" << input_fps << std::endl; + /* IplImage* frame1 = cvQueryFrame(capture); - frame = cvCreateImage(cvSize((int)((frame1->width*input_resize_percent) / 100), (int)((frame1->height*input_resize_percent) / 100)), frame1->depth, frame1->nChannels); + frame = cvCreateImage(cvSize( + (int)((frame1->width*input_resize_percent) / 100), + (int)((frame1->height*input_resize_percent) / 100)), frame1->depth, frame1->nChannels); //cvCreateImage(cvSize(frame1->width/input_resize_factor, frame1->height/input_resize_factor), frame1->depth, frame1->nChannels); std::cout << "input->resize_percent:" << input_resize_percent << std::endl; std::cout << "input->width:" << frame->width << std::endl; std::cout << "input->height:" << frame->height << std::endl; + */ double loopDelay = 33.333; if (input_fps > 0) @@ -152,13 +163,14 @@ namespace bgslibrary { frameNumber++; - frame1 = cvQueryFrame(capture); - if (!frame1) break; + cv::Mat frame; + capture >> frame; + if (frame.empty()) break; - cvResize(frame1, frame); + //cvResize(frame1, frame); - if (enableFlip) - cvFlip(frame, frame, 0); + //if (enableFlip) + // cvFlip(frame, frame, 0); if (VC_ROI::use_roi == true && VC_ROI::roi_defined == false && firstTime == true) { @@ -166,7 +178,9 @@ namespace bgslibrary do { - cv::Mat img_input(frame); + //cv::Mat img_input = cv::cvarrToMat(frame); + cv::Mat img_input; + frame.copyTo(img_input); if (showOutput) { @@ -202,11 +216,13 @@ namespace bgslibrary if (VC_ROI::use_roi == true && VC_ROI::roi_defined == true) { - CvRect rect = cvRect(VC_ROI::roi_x0, VC_ROI::roi_y0, VC_ROI::roi_x1 - VC_ROI::roi_x0, VC_ROI::roi_y1 - VC_ROI::roi_y0); - cvSetImageROI(frame, rect); + cv::Rect roi(VC_ROI::roi_x0, VC_ROI::roi_y0, VC_ROI::roi_x1 - VC_ROI::roi_x0, VC_ROI::roi_y1 - VC_ROI::roi_y0); + frame = frame(roi); } - cv::Mat img_input(frame); + //cv::Mat img_input = cv::cvarrToMat(frame); + cv::Mat img_input; + frame.copyTo(img_input); if (showOutput) cv::imshow("Input", img_input); @@ -221,7 +237,7 @@ namespace bgslibrary fps = freq / delta_time; //std::cout << "FPS: " << fps << std::endl; - cvResetImageROI(frame); + //cvResetImageROI(frame); key = cvWaitKey(loopDelay); //std::cout << "key: " << key << std::endl; @@ -238,7 +254,7 @@ namespace bgslibrary firstTime = false; } while (1); - cvReleaseCapture(&capture); + capture.release(); } void VideoCapture::saveConfig() diff --git a/VideoCapture.h b/VideoCapture.h index a8ba2ff..0e88c87 100644 --- a/VideoCapture.h +++ b/VideoCapture.h @@ -18,7 +18,8 @@ along with BGSLibrary. If not, see . #include #include - +#include +#include #include "Config.h" #include "IFrameProcessor.h" @@ -29,7 +30,7 @@ namespace bgslibrary { private: IFrameProcessor* frameProcessor; - CvCapture* capture; + cv::VideoCapture capture; IplImage* frame; int key; int64 start_time; diff --git a/config/FrameProcessor.xml b/config/FrameProcessor.xml index 5155bbf..3cf398c 100644 --- a/config/FrameProcessor.xml +++ b/config/FrameProcessor.xml @@ -3,22 +3,22 @@ "" 1 0 -1 -0 -0 -0 -0 -0 +1 +0 +0 +0 +0 +0 0 0 -0 -0 -0 -0 -0 -0 -0 -0 +0 +0 +0 +0 +0 +0 +0 +0 0 0 0 @@ -31,12 +31,13 @@ 0 0 0 -0 +0 +0 0 0 0 -0 -0 -0 -0 +0 +0 +0 +0 diff --git a/dataset/demo.avi b/dataset/demo.avi new file mode 100644 index 0000000000000000000000000000000000000000..df20707af3b0a2cbcaaae127b982e1886b1d2aa7 GIT binary patch literal 355840 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�b{=)BY}_KMAMhOA?sDe?9ijT6qi@3m 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�tYH9P6A6)DnH2@D7_n*us18vf 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%0A_K-o zco0r>iLgP7eS|a@Bjv%3^XM0<7iwsQc)c&84=DY z&*pk+FjC=!)67+0Ig*ViuA&YqeYM~XW6DG)%m6@9eTksjcI~JN{0jtwqR8D*MNH9z zv#eaT2Xg2X8yKdXgv4Hs+JySyPoX^5h{C7p%MnhNI+#IPWdcUIT@r#@Fci@&uxSGc z<_4y6z@vOdEGyffk{M^l7yuLbH^q_{OQjs0@@B)w4`!?aS>sSdSB)bFQ3}BItOPo8!1JXoCnpp;c zlny=tGZ$%~I^|0GqVPi!kf@izwjFgB7l0m?pkn6KxG0ih470@f6j3gU2n+yh#1+)` z3!9Gg&EgOk?5Q=ud!qN4;j>q;?Twy2G+O}%990uq&9})o=ffg4A005PL$wn-M zDg~R4SnWm3U}IAebvP^{66nM~@!^w-&w0L)^S#{E)D(oq>D;=5+($!6x$kobGkYHd zJ&O3}GSG!L8-l!Kt&}ixE22~B>%)e5w{PSistN*tI#*3}Bq1RX#Y`a2{{GD$&+^x= zU-#O;vI2wB7{In?&mQa{N*#T0gy$c_Kgfe6&QS*vS`a0KS)v2uNPz+&N2h(5B_^G+ zXN)3EpDF|b%%JG_ag>^h%hB^coWLNHfB*Ic7?vB!K?P4QgxCv8T0-cEC2MXGqAvu& zAVjCv7$n`Pj;aaG7(W_Rys;F~)}$^}XF7)}r7rMz3;@h2MX4R-IKqQNcodOysabvM zf+#S6=R~oP&%YynxZ*IPSO&&i`8qYuQX?}FWep?`u&I$m5Ze)$qzezg2H`=J=SCww zVJn2_G>jZaX$qll9P<(s%hANvyQ3(U07w9^VO;r)gG~UAW*Jlqcv?O<(e%NvjyyuM zgiTFHJW$C(nrP91M+oqQ3#W1j$G?1yU=jnaDkYG%8U{KlWfn8cpqCk(!l@caFi1z! z5#5S5?%518uEGYPB}UIicqRk1v@W7j3rcn1WdHJqp1ZF9QASH-?T9WB*_{9iKMA3JUm0ivsG-Q>H~uuHUK2u zIx2zQLu z;fJ<;Ts4cm#t(bb!LZCnJiut=q()@~58hw{0Dg4goO4H~Z8da@89t4O=oAJzQGzN0 zNu<$_1`kVbwp>->NVe*M6qk}6)r&rLh(tx8Vg`l^g6+=e6_@+9xW-vJA{lA=n6Xlr z0V7#`J1ze8tCVq}o$3HXY4BK3ZV=`|2rbX>6N#o|05FCGSB{FpMy2#ccs|8@Sx5yVLQ^Eq%^aDk*P{aB)Y&*6ro+xF-Tx+7-;?nG6dj< zRNmkco5TU&2ey_KRK$Z}QB*KgLp0Av$oH$Wr1qB3Id{k$2KwC)=g{lVVh@a0JO%lOXUg*cdG?p=q0p!3G{+f`Ngh zLDZ?HD!^mJRoLLsLA#@dc~fL07dRO+U?vJmk>lNz*Ja)%{%!bo5xvKQ%42_gngx=j zfgiUI$MzpbmC|(J;a`-No`~=m8MTTH+AwZ8kR|5P(qh!-gfUf%uBv z7KBxU%rFCqxK5QuUTFy=IHhI_RSvq!YQj$jlIU$h*_|CTv2zg{q3d7fdo{5p`Zi?gHi%R)lgMp z9W1G)yxft2p^))=up|VYx)4%jQo$4sWc-XY2FzfTf){uOrUPlWCk*6}F6BUlOzNc{ zXi*BPg7ScPdBUlG+ZXt667^eR%bw9G7gF z`a1Sn^!K z;zgagP;sl;lHv^jNZ=90H|{7h0ts*akxm__9Gd8z5JhAjmnC8a(F81x2bRA}yx}$YJwkynV<@J$CGvRb^R60!}2T zF9at%1O|cnLI{e7--rKgky!ppjYDyz!vvAKKu z-2+L)4=|!jTsU}y7StuDl?DJd$ibMAWNgVG1Rmi4Kon+VCokTVUg8EBKx-o95SzmJ z@r^B9xzPfxCsxgKkQ_PG%1A_zH2(Qy37F8p=-7O)=c*9~c!0-NFto`V-NK*(U!{t7*$qw zNyyPSY<6IT#EEcD`JqPDXM;U+(XN@RzN(;7Qg#(os6sW8P)*s(y7Xhfv#Kb3Vg?2{<*rQlMA`^IX)psn zxA?@H5vSG{Fy;oP5aKEywyj5-o|1vVj4=T6LYf+8MO z00Vw~#S(>oRiKb6#Zuu?CJ8Zy!KsW`A`lq4+hl+@(Iuz)@YsB6#KQm{6*L|mKcF89O2eGx`jinutSvVB|soz)Qg;wZH%XrsG7@2g5q@ zph(-sk>ixG;c>(RhceZHN1B`qXM{sJ!X`(;Fd|A^v z9yA@%t!U$-!{e$E6s2cVc!q>G>eVL(ArwxsqjFRnFlNw#q~nsf22E@mhmC*Cz)yiA zGdNKuJis6*7(UZevd_#rmy-OdJs=t^dZiN$$b3FH)YLKuVrLY(N(l|fVB zNk70yja)H7SmD7;`>k-iK?sQ|Bn&p<>I+q@Yn(Hqhyx6+^r*c z_=l&gEgZ`V03elKAXVTBRLvA4T9x7r1KEoX2|OV*4vZI9gdqhUWansgz~-sm?q>>Y zrhvp)3Q~p#)nG7osx-hD5k)v))XpVTn9H-PD&;EwTmdE>NW#OXEI^XGrKCD^YZfz$ zR5}bm5*IIriMp&EnrF?NO9)3en@DxxmO*MI1R7j#PN_g_Re9 zGD4bD;rz*jkC^~VFeJu-p-h=_&Ju^3Vkw7larJRas3GwQ+H_at=!j1pOG$K#k@hsk zfZ+r>i|C;RI=3wg}pClO|)l0i&8Cf~0YxnaiQ7sTAkPVW6Q)XqR*hLe7SP z=IO|L2Aq2uv17{ub^N%54NKeo+D1*-b|j~oaB%7jtUpv5qJjrhAtVB!K%zJr;f;o= zLIX_0ze{*i&|vF`NLt_NKSPuF%)SC=LD1VlUf*1kw8Q)uD5pT1(Lf4A$eBp zc~VCFn1U;AmBY=#Q7lv_ko84@0ANE0u%yMUKI)JIr^_twP55QE-mI8?eKRoaQKtI6102m1Yz?%jh&yJ#4eMBGIT5*~E~22>8gpk!O9r6hd~B#RvzeeVRu*wj%Gs~})@ zU1Ua=3JSr0a#BJ3h()WEZ!7T86{UhA!MMJlfJGxn`mzJVjQ}&gjj_+ui8UpzpvVy& zC&)3P`Et$|L7=(>vrGCqHfL*)f2&5-Fs3?4vy)qTu;~Nr6AJKXOth-jRmCM6cr^UL z<7-<%g@ej6FA!k*A_m407}5KiDEy4y&au}RE3{cwsJ!ruJxY*!0C3WZLnFYbqb`-I z#EPZQQeGqh45?5rPSUf8;I-r{695!0G?1|Aj5NZj#a@?210H}xzABIqkprn@m{h)? zqBOlJnYvS>Ks-ngnAPvWyykiQ__5zXx~2tI$>cUG=Z(LGtCx7isDM^Wnn>fcazzK;+08FcX;}4Gft| zlOH^A>!Cx3jp@vIu8HEiQX|OW)=_HkK&UVMUJ1|+Gg;t42-I15LURZ?-EnT5a*z`( zJWI`BjR90bAORSOfak9i-~>`S8UjTDKv2<5;ZSPun9B@BLX(!w5*TEf#en3^0>+{Q z#^Q?vBS5l*ffF7A1BQADVY(`Vp%3uV(h@4fH7--Cv%ZZ8gl&LHnja%vaVw}j8<+&Z zIe7i4wL$x(zrekD^QIj_IJ1P%P{fr?rNfC4ARSEyEXl!7ilT^1NI=YW zq|crczX^Eo-~ljBRRJv?eEh1$R(;1N>dsck&TR81xBSvg#pig(BKCve&1j(M2=rM zxV8Efi~A(T{OClK_z_nivtS4h|1KGkb!@Ofjwr1u_A*i(cz_2WFnH!6ky9za`bKoD zFOXEn&m-LK+&A3c)XQxFNI!Al8N(Xz9Eu2lh7(*NSuCPe8drUJ4vZ1A81N5|iIK?| zGvJxdjAE6B&Rd?$ENy@Z!-sVU<%n%~+*RCQ)l_tOA=7l>06-3Jjw<3RehdP4w(`UesugJuJ#$sh|a%=;+g`vaq05|us)oEn(it;jP24w7=gqi zyeea!D}y?4nuTqbtYUpIctep(j_~NnE~%&;PQ=nE2R2)o{luwDRYy=$@MA%d4S2wW zT;DkVO5@29v2r&OX*wjQUciu0xtj}v=sY-z&de+w1r^sL0eDU&r8MwRuRd*HNfn@y ztd2)Q8g}XuOJG>ijPlupF%u`oV~JaQg6dSnHdq)C3k>od#dXObFz#7OmL_dDI$R|T z>y8;=;M{aa3E~HSs*|<8yx=ZMC8q^Wg6tu_!v0xZCBBCh<**mrF z;K7xx5<>S@_gv55KEV0yph2GnVPNdG&Hw<3iztwE^m*2lNwU_OZB|DL48%n*#=u~s z&jX^^SVw-HY3C7zW~2i!5-3es8Mvy+V~M2Ey&?qT-g~H0y+9v z;+6-cuWFUXCF2?-j)MBw;XdUxPhZMy+qMlq;sS#To;7&f||vGB+j7&vt_GVqbe(~hPP{;$|Z=$vxk(GY^Y^XJdo z7K}TRje5bOUdTrRsUw_>!GIqI6)a_RF(@enGN^ozr{^jKszm4n5BO06#wbT<@#BSr5har*C&~nvqr|DMxEL!IsVD@UFgC&lGy29MXKYaUP~^r$z=B$m zuBw!BKnO#SbihCe-k{=EDV-8FJdTPYO${N$EXP)vk%TIoa?_y^)d3zDv!EpyBf#hw z$00C|_yk5DlEC0W0|pgVv`e0dR0>+~P?Vk_SAh&l7BHjC=#q_>h&C}rXZPxAAKv4#yE$BTU(H1$sn$bIE6>P z72Pg25uA>l~ ziwKF(ks}In^xN)2hz(P~P$|mcnOn2;@qmrW1eGQzwRker!4=VZ;q4&}hs2{IA%sUP zcr2@GglDuEQLIX1{iYlvZ8!j9j6DNnvc~`(bX)=qIT}dD#~P^xB<#(?t+?~^^8lbl zzmnM;@MHbLgL#^ftPVW%AbovK^i_c?9i-Vngw}x*0H>%xEWq*wATSVuq(F26j1+jl zupfOZU-~fEZX*>T^0-g)5oW{wjCs>m*||UpwPz6s)HOb z3S>m4v;>KfR1FMQJXa>m*<(Bg1ck(OjG@bEU`z+kM&xs%xzdz~4*W`k4JC8;6v(<( zI|YJ}ndsQys^7S~DdSK&sPL&12{uEe4@qURnaGrLYXOHE0vI-lTfKu*%dLr9nan%q^(0fB{8D=-hKGsSm9G8!DobHEfgs74ikr zFe7~$u$rtX3tUhQ!(D|0bs9XtsEg;K9Xoc|r9F6f%s`Q=#O4Sa07@p`lmikf#1*t% z7Z|XO!)9D`%O4ED9HpiSgaiq4NEJfsAO~zV#^Dr~4FFyMd-np5cMepoa8@EhGyAN4|!HS0||U?@^MNHq8*h$!iUL??n`328cts6oOrU~*?q5)v^>E#MJIEf|Q4 z1ee$tN0Fo$gF)&ZHr!afH}fJ02HS`R7&-Z>$G;S3xlvtH3OSneohlzB64b$o z5uOFIIRJ(hNYnx<0_KMkkR0K`m}J?2N*>BV@RqQkz<8wB0P9N?kTmb~Fy2OmZuKI z8o%VxQAaFQL1^s;Fi6wt;0i^Up&zxg&`^X2C2(``tnOpJ5inGZr4+=~L1O}7DLXKf z05jg*`Lc*^{cV!Cf+9zB^-?S&8h(741FB0fr!gHjJJ>jB{mN8m1+saHOJ@42#z5gP z005#yH;znMAdLau+K6x>sBloJNPz&;7dLqYgig02>*{*_&mB+Dqv_gc4U2 zLBY_01Pqm;c93wZud=cr9$JFgn^oZ`#Lgo`dKM8p>s_ZZ0g%FhgaPbmbjm=BJ;$06 zt~fy;@~O@<6%1C8=%n7xA3xw>(pC5gTOdwM7h?5$FmEsXqxg>G;CB*9gB<*<&4ZyM zrMTO-!rU=@a`g{(J$drPC1(15(7=P=ln^zg_!161o>}}7-J2T_OlRryfn#RAmec3o zmovT?@lW=R(ajRVPmm#TYw`^@0D)oe@6a`FZHnCTX}Xt@@T33Jr%(Lw;N1f!YzV|S z*er$SiW8+{z=>G^c##Qh-+=ksuznxo^`X_to1{#XK0JN_z?(syjX=VixjGPXD5zN= z8MmI$B26!5cyLus@Z-M6CreWxp?a?1VUI~y`FC}Bc^MM_W}`GEs2Ks6nFauIK=KO` zM-!PLjZc|Mlb?7nQHPDM)YO+}cpOEsvQ0z|JhG4)Fc2E?7c#u{o&AWJ4ZvpcCOUgN z3P>z@9%<2$K0Gzip<;#vUtKC3zl@;ra06Q-4qU`29LRNL6OkHfn)@kW+{ig z3KDGK*2-g`7K~lufj1a9;UO?!$U+Fy@u153EI)qyhzdNw<7m1-Mg%efFiG=cgex_L zjfQPhGUb7ps}>n%7z1PTb}PV@xZnxl$gQgeA>;&6z&o-54CiQp*;gw-LP!-zGhz{$ zh%w9v$Bz&o>AT9w{o!Yea%W?Z{i8>Z9N9S97=S@916jwITr@Daa+H^erW*uC=#&hq zua`x!$Q={G?6;fg)3WUa*RHvZ1qBP4neA}N7{(9z80#3>{= z;jNXWQ?U$M6bA7C406ok-#Gjja|t#hI$V(og-G8vf>Opx=4R-VjgKR4+I#ozwcp#V z;YW)il7xz=7ciVi968~U3md1h7QJPT9BwTrjlH}OQb7R-jEvC5m8C?YO2av9HfEvy zD$5OFW@cu3dfE-V8wI+Up-YZLrEww!6Zz3Nhe|B{@L<3Ta%Q3fBO)H)G2#;Nl2R$z z@ncZ3RAGly2vcBFnhO^$xJ$Tq`Td7e6nVMmZtoUH-qs*Ll9DnfFkk}!$4bw+LB+z4 z1S#1FB%0`M4T2hH4RV2?(!q>0 zl>o4+&lNw3$0hZG(};`fD^p~OV$tCTB;ibnBKF*(U9r-<2SEP86M}zE7%+AUjB>c@R5&>C-&Ig?7>x+@ z3n3i(QIVg&Br4@leYs;BZ&opHU_h0DBO9FaFEz$ilO~od=#2q-EgYxH4YS-uG|rC| z=B2GMzN6uliXB@1LChsN=vVm~CW2Efuwe=U^<@pASrX(~zQCwXV2}@zxH2-R(Dr14 zG0u#!D136FV_cPs$XHyS;|*+u(+6906+~JbnoE1hN#7-6<5NWGYtlpx67U~Cs=&D; zbuqB4IM;#GH~=hxfq@t_r>661)j-G$KTaXhv9X8<00NC*4>S5mvsG$5a0P}a#wkSZ z8dUH|T;obPMm@P zl@Ys>DF#hJAOP^-%11*8-O(uAK4~}bnaSYg%a?J;Il-$k01_8bcGA)3Ss%SXT5g=n z+7yEd3IN8^Edw5aF#@F9>j;J!id2)*pt4NdJ}AmP&bZCf2IUsxA5k$yb?^f}hsfca zD;@+j0vqS>AeILatC~|~Vun4y%tC^RDxwzB^yNEB|o^%97PX3|d9M1rVt4!p95FI`lt6HZz3LOuSn9e|&ewDsq!-k)Jn*0yt+(}XD zlaQctQ5p>)KG*t4XII3SBiX1IJn97upCpZBM1cW63@WI;qDY(pw_HV(d|ib+J!624 z6re?pJsItOok=I2H^c&lx}XI~gTbY9=gzrgkc{!e5<(@DrtFkS){c0vWPmhIAwcTu zaHww(R3(EPBuFxV5ORKmb5BRUDRu)OEgX1Yz=@`VE=asBfP`CMoDw!Xj*22pjelZR zED;p~Q92Tbv%3Zx`~VCL0~p|emKiX{nF&oBW*LD^xM3;(`7$0D(cNANOmukI1EXZ= z8E-seo;OPgHu~qypkm1v2A5RaATt1r(7D0(WLzPErB%bZT0>}rP({~9T8sz^hL73^ zR8Y$bBp5Jep;_+&i2@1#mYDQuAg+v9JNn}CpPW<&RA%Bb08(**bmSR7rr@mtVFo`@ zl$PM36Kr6~dN&0yG#Y`Yy$C1pV8apf>JFiZLaUU-EmCpy1+4>&vYG;?#jZ5MNzE*M zwFpD}IO4(Mq8&g-bfWbWZLbzs0)|+?l8_|>4?PGZahb~6h$^HF#`wXK5wV~G7#{Wv zkjdT}Q4}4Q07H%jl5r8Mky=2)-YhiA(bFTY;DI5=?9!H}B?FJW9UjT*z=Ia)>vN*7 z3S8+R&2*u4-~_;_x*!%{3IQN65P}2@od6>R9x&_(45-$--4A{qOws89lKkL7HDLI6 ziVa9wIE-BqPJyZ&7-<+7eXc}d4@r2yP&-GtV?cF0E5gGJ+Zb>OMWWll18>B15^^Ne z5sxV<0ts>)VK8FhBN-v^pbHF&bxFr5NT!G)LEfmvp!8v|-ztG92w^6uhBpTG1UB%H z4e>BQvQ=glZV-_F_)!rZS%Be`a&QtnP}6Y9cFLQ4L8?qvjW2SI2nvab7(-Wh zaL$7%HX>gjz!PccwZu>@dbf4;A|BWj$k9ZcxN3?IEEK5dV1SJr4s}@aqhUrkM=8q` zHkQmZ#ZnjJQ0a>TAtY?v>>Lq`A~x`#4*#CP>~j29DKK;->xdan+>$7Py$T9fpA)G; zJE_F5pn$YkOf=40OG$yG#@Jk7c+>ZbeBYzGf%;IUrtG0YW~-Q((F-jSAe0)kHjS#! zEmx8vq>4r4J{BPp0Gz0c+&Lrw#gZ}%8jz4w4ufa}J{x}v;cp4t?7SbfV`EQH_Bvv< z(MioL6-0}ONb}H0*i@upzJ;%E3Z282V1yPFHX63NH9<9e3IQr4u4?cAOy^s+Z1Lw1 zo`T$1iNT1_5@&;d1)^#p#Dau|!RQzUHHF^@R5T-Nm`#TV82szUO!~o<&^*JbK~paV`9wC>)hWQurhlMF%4c=^p^P5=+wuRmtprYNt6h zU40>eGx3N;11=GU*9(3~1(LVjgyhWwonVPP_%TH~I?}ftVFq3LU`SaL8gKMTY%Xc) z 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 uK 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 -#include - - -#include "package_bgs/FrameDifferenceBGS.h" - -int main(int argc, char **argv) -{ - CvCapture *capture = 0; - - capture = cvCaptureFromCAM(0); - //capture = cvCaptureFromAVI("video.avi"); - - if(!capture){ - std::cerr << "Cannot open initialize webcam!" << std::endl; - return 1; - } - - IplImage *frame = cvQueryFrame(capture); - - FrameDifferenceBGS* bgs = new FrameDifferenceBGS; - - int key = 0; - while(key != 'q') - { - frame = cvQueryFrame(capture); - - if(!frame) break; - - cv::Mat img_input(frame,true); - cv::resize(img_input,img_input,cv::Size(320,240)); - cv::imshow("input", img_input); - - cv::Mat img_mask; - bgs->process(img_input, img_mask); // automatically shows the foreground mask image - - //if(!img_mask.empty()) - // do something - - key = cvWaitKey(1); - } - - delete bgs; - - cvDestroyAllWindows(); - cvReleaseCapture(&capture); - - return 0; -} diff --git a/demos/DemoMultiLayerBGS.cpp b/demos/DemoMultiLayerBGS.cpp deleted file mode 100644 index 08abe30..0000000 --- a/demos/DemoMultiLayerBGS.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include -#include - - -#include "package_bgs/jmo/MultiLayerBGS.h" - -int main(int argc, char **argv) -{ - CvCapture *capture = 0; - - capture = cvCaptureFromCAM(0); - //capture = cvCaptureFromAVI("video.avi"); - - if(!capture){ - std::cerr << "Cannot open initialize webcam!" << std::endl; - return 1; - } - - IplImage *frame = cvQueryFrame(capture); - - MultiLayerBGS* bgs = new MultiLayerBGS; - - int key = 0; - while(key != 'q') - { - frame = cvQueryFrame(capture); - - if(!frame) break; - - cv::Mat img_input(frame,true); - cv::resize(img_input,img_input,cv::Size(320,240)); - cv::imshow("input", img_input); - - cv::Mat img_mask; - bgs->process(img_input, img_mask); // automatically shows the foreground mask image - - //if(!img_mask.empty()) - // do something - - key = cvWaitKey(1); - } - - delete bgs; - - cvDestroyAllWindows(); - cvReleaseCapture(&capture); - - return 0; -} diff --git a/demos/linux_ubuntu/.gitignore b/demos/linux_ubuntu/.gitignore new file mode 100644 index 0000000..46d4288 --- /dev/null +++ b/demos/linux_ubuntu/.gitignore @@ -0,0 +1,8 @@ +# Ignore everything in this directory +* +# Except these files +!.gitignore +!CMakeLists.txt +!FrameDifferenceTest.cpp +!README.txt +!config/ diff --git a/example_linux/CMakeLists.txt b/demos/linux_ubuntu/CMakeLists.txt similarity index 66% rename from example_linux/CMakeLists.txt rename to demos/linux_ubuntu/CMakeLists.txt index 9317e55..8cc38e8 100644 --- a/example_linux/CMakeLists.txt +++ b/demos/linux_ubuntu/CMakeLists.txt @@ -9,15 +9,14 @@ find_package(OpenCV REQUIRED) file(GLOB source FrameDifferenceTest.cpp) -file(GLOB_RECURSE bgs_src ../package_bgs/*.cpp ../package_bgs/*.c) -file(GLOB_RECURSE bgs_include ../package_bgs/*.h) +file(GLOB_RECURSE bgs_src ../../package_bgs/*.cpp ../../package_bgs/*.c ../../package_analysis/*.cpp) +file(GLOB_RECURSE bgs_inc ../../package_bgs/*.h ../../package_analysis/*.h) include_directories(${CMAKE_SOURCE_DIR}) add_library(bgs SHARED ${bgs_src}) target_link_libraries(bgs ${OpenCV_LIBS}) -set_property(TARGET bgs PROPERTY PUBLIC_HEADER ${bgs_include}) +set_property(TARGET bgs PROPERTY PUBLIC_HEADER ${bgs_inc}) add_executable(FrameDifferenceTest ${source}) target_link_libraries(FrameDifferenceTest ${OpenCV_LIBS} bgs) - diff --git a/example_macosx/FrameDifferenceTest.cpp b/demos/linux_ubuntu/FrameDifferenceTest.cpp similarity index 87% rename from example_macosx/FrameDifferenceTest.cpp rename to demos/linux_ubuntu/FrameDifferenceTest.cpp index 56f1e7c..7ebe8b1 100644 --- a/example_macosx/FrameDifferenceTest.cpp +++ b/demos/linux_ubuntu/FrameDifferenceTest.cpp @@ -2,7 +2,9 @@ #include #include -#include "../package_bgs/FrameDifferenceBGS.h" +#include "../../package_bgs/FrameDifference.h" + +using namespace bgslibrary::algorithms; int main(int argc, char **argv) { @@ -15,7 +17,7 @@ int main(int argc, char **argv) } IBGS *bgs; - bgs = new FrameDifferenceBGS; + bgs = new FrameDifference; IplImage *frame; while(1) diff --git a/example_linux/README.txt b/demos/linux_ubuntu/README.txt similarity index 100% rename from example_linux/README.txt rename to demos/linux_ubuntu/README.txt diff --git a/demos/linux_ubuntu/config/.gitignore b/demos/linux_ubuntu/config/.gitignore new file mode 100644 index 0000000..8ee04a0 --- /dev/null +++ b/demos/linux_ubuntu/config/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except these files +!.gitignore \ No newline at end of file diff --git a/demos/macosx/.gitignore b/demos/macosx/.gitignore new file mode 100644 index 0000000..46d4288 --- /dev/null +++ b/demos/macosx/.gitignore @@ -0,0 +1,8 @@ +# Ignore everything in this directory +* +# Except these files +!.gitignore +!CMakeLists.txt +!FrameDifferenceTest.cpp +!README.txt +!config/ diff --git a/example_macosx/CMakeLists.txt b/demos/macosx/CMakeLists.txt similarity index 79% rename from example_macosx/CMakeLists.txt rename to demos/macosx/CMakeLists.txt index 107303f..a9775d8 100644 --- a/example_macosx/CMakeLists.txt +++ b/demos/macosx/CMakeLists.txt @@ -20,15 +20,14 @@ find_package(OpenCV REQUIRED) file(GLOB source FrameDifferenceTest.cpp) -file(GLOB_RECURSE bgs_src ../package_bgs/*.cpp ../package_bgs/*.c) -file(GLOB_RECURSE bgs_include ../package_bgs/*.h) +file(GLOB_RECURSE bgs_src ../../package_bgs/*.cpp ../../package_bgs/*.c ../../package_analysis/*.cpp) +file(GLOB_RECURSE bgs_inc ../../package_bgs/*.h ../../package_analysis/*.h) include_directories(${CMAKE_SOURCE_DIR}) add_library(bgs SHARED ${bgs_src}) target_link_libraries(bgs ${OpenCV_LIBS}) -set_property(TARGET bgs PROPERTY PUBLIC_HEADER ${bgs_include}) +set_property(TARGET bgs PROPERTY PUBLIC_HEADER ${bgs_inc}) add_executable(FrameDifferenceTest ${source}) target_link_libraries(FrameDifferenceTest ${OpenCV_LIBS} bgs) - diff --git a/example_linux/FrameDifferenceTest.cpp b/demos/macosx/FrameDifferenceTest.cpp similarity index 87% rename from example_linux/FrameDifferenceTest.cpp rename to demos/macosx/FrameDifferenceTest.cpp index 56f1e7c..7ebe8b1 100644 --- a/example_linux/FrameDifferenceTest.cpp +++ b/demos/macosx/FrameDifferenceTest.cpp @@ -2,7 +2,9 @@ #include #include -#include "../package_bgs/FrameDifferenceBGS.h" +#include "../../package_bgs/FrameDifference.h" + +using namespace bgslibrary::algorithms; int main(int argc, char **argv) { @@ -15,7 +17,7 @@ int main(int argc, char **argv) } IBGS *bgs; - bgs = new FrameDifferenceBGS; + bgs = new FrameDifference; IplImage *frame; while(1) diff --git a/example_macosx/README.txt b/demos/macosx/README.txt similarity index 100% rename from example_macosx/README.txt rename to demos/macosx/README.txt diff --git a/demos/macosx/config/.gitignore b/demos/macosx/config/.gitignore new file mode 100644 index 0000000..8ee04a0 --- /dev/null +++ b/demos/macosx/config/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except these files +!.gitignore \ No newline at end of file diff --git a/README_CMAKE_USERS.txt b/docs/README_CMAKE_USERS_OPENCV2.txt similarity index 70% rename from README_CMAKE_USERS.txt rename to docs/README_CMAKE_USERS_OPENCV2.txt index 974e941..26c6997 100644 --- a/README_CMAKE_USERS.txt +++ b/docs/README_CMAKE_USERS_OPENCV2.txt @@ -15,18 +15,17 @@ Please follow the instructions below: 1) Go to Windows console. 2) Clone BGSLibrary git repository: -e.g.: git clone https://github.com/andrewssobral/bgslibrary.git +git clone https://github.com/andrewssobral/bgslibrary.git 3) Go to bgslibrary/build folder. -e.g.: C:\bgslibrary\build>_ -2) Set your OpenCV PATH: -e.g.: -\> setlocal -\> set OpenCV_DIR=C:\OpenCV2.4.10\build -\> cmake -DOpenCV_DIR=%OpenCV_DIR% -G "Visual Studio 12" .. -or: -\> cmake -DOpenCV_DIR=%OpenCV_DIR% -G "Visual Studio 12 Win64" .. +4) Set your OpenCV PATH: +setlocal +set OpenCV_DIR=C:\OpenCV2.4.10\build + +5) Launch CMAKE: +(For Windows x86 32bits) cmake -DOpenCV_DIR=%OpenCV_DIR% -G "Visual Studio 12" .. +(For Windows x64 64bits) cmake -DOpenCV_DIR=%OpenCV_DIR% -G "Visual Studio 12 Win64" .. Now, you will see something like (for win64): ------------------------------------------------- @@ -52,27 +51,25 @@ Now, you will see something like (for win64): -- Build files have been written to: C:/bgslibrary/build ------------------------------------------------- -3) Include OpenCV binaries in the system path: -\> set PATH=%PATH%;%OpenCV_DIR%\x86\vc12\bin -or: -\> set PATH=%PATH%;%OpenCV_DIR%\x64\vc12\bin +6) Include OpenCV binaries in the system path: +(For Windows x86 32bits) set PATH=%PATH%;%OpenCV_DIR%\x86\vc12\bin +(For Windows x64 64bits) set PATH=%PATH%;%OpenCV_DIR%\x64\vc12\bin -4) Open 'bgs.sln' in Visual Studio and switch to 'RELEASE' mode -4.1) Note if you are using a Visual Studio version superior than 2013, you will need to CANCEL the project wizard update. However, you can go to step (2) and change the Visual Studio version, e.g.: -G "Visual Studio XX", where XX is your Visual Studio version. +7) Open 'bgs.sln' in Visual Studio and switch to 'RELEASE' mode +7.1) Note if you are using a Visual Studio version superior than 2013, you will need to CANCEL the project wizard update. However, you can go to step (5) and change the Visual Studio version, e.g.: -G "Visual Studio XX", where XX is your Visual Studio version. -5) Click on 'ALL_BUILD' project and build! +8) Click on 'ALL_BUILD' project and build! -6) If everything goes well, you can run bgslibrary in the Windows console as follows: +9) If everything goes well, you can run bgslibrary in the Windows console as follows: -6.1) Running BGSLibrary with a webcamera: +9.1) Running BGSLibrary with a webcamera: C:\bgslibrary> build\bgslibrary.exe --use_cam --camera=0 -6.2) Running demo code: +9.2) Running demo code: C:\bgslibrary> build\bgs_demo.exe dataset/video.avi -6.3) Running demo2 code: +9.3) Running demo2 code: C:\bgslibrary> build\bgs_demo2.exe Additional information: * Note that bgslibrary requires a 'config' folder in the working directory. -e.g.: C:\bgslibrary\config diff --git a/docs/README_CMAKE_USERS_OPENCV3.txt b/docs/README_CMAKE_USERS_OPENCV3.txt new file mode 100644 index 0000000..543fd86 --- /dev/null +++ b/docs/README_CMAKE_USERS_OPENCV3.txt @@ -0,0 +1,77 @@ +------------------------------------------------- +-------------- WINDOWS CMAKE USERS -------------- + +How to build BGSLibrary with OpenCV 3.2.0 and Visual Studio 2015 from CMAKE. + +For Linux users, please see the instruction in README_LINUX_USERS.txt file. + +Dependencies: +* GIT (tested with git version 2.7.2.windows.1). +* CMAKE for Windows (tested with cmake version 3.1.1). +* Microsoft Visual Studio (tested with VS2015). + +Please follow the instructions below: + +1) Go to Windows console. + +2) Clone BGSLibrary git repository: +git clone https://github.com/andrewssobral/bgslibrary.git + +3) Go to bgslibrary/build folder. + +4) Set your OpenCV PATH: +setlocal +set OpenCV_DIR=C:\OpenCV3.2.0\build + +5) Launch CMAKE: +cmake -DOpenCV_DIR=%OpenCV_DIR% -G "Visual Studio 14 Win64" .. + +Now, you will see something like: +------------------------------------------------- +-- The C compiler identification is MSVC 19.0.24215.1 +-- The CXX compiler identification is MSVC 19.0.24215.1 +-- Check for working C compiler using: Visual Studio 14 2015 Win64 +-- Check for working C compiler using: Visual Studio 14 2015 Win64 -- works +-- Detecting C compiler ABI info +-- Detecting C compiler ABI info - done +-- Check for working CXX compiler using: Visual Studio 14 2015 Win64 +-- Check for working CXX compiler using: Visual Studio 14 2015 Win64 -- works +-- Detecting CXX compiler ABI info +-- Detecting CXX compiler ABI info - done +-- Detecting CXX compile features +-- Detecting CXX compile features - done +-- OpenCV ARCH: x64 +-- OpenCV RUNTIME: vc14 +-- OpenCV STATIC: ON +-- Found OpenCV 3.2.0 in C:/OpenCV3.2.0/build/x64/vc14/lib +-- You might need to add C:\OpenCV3.2.0\build\x64\vc14\bin to your PATH to be able to run your applications. +-- OpenCV library status: +-- version: 3.2.0 +-- libraries: opencv_world;opencv_videostab;opencv_videoio;opencv_video;opencv_superres;opencv_stitching;opencv_shape;opencv_photo;opencv_objdetect;opencv_ml;opencv_imgproc;opencv_imgcodecs;opencv_highgui;opencv_flann;opencv_features2d;opencv_core;opencv_calib3d +-- include path: C:/OpenCV3.2.0/build/include;C:/OpenCV3.2.0/build/include/opencv +-- Configuring done +-- Generating done +-- Build files have been written to: C:/bgslibrary/build +------------------------------------------------- + +6) Include OpenCV binaries in the system path: +set PATH=%PATH%;%OpenCV_DIR%\x64\vc14\bin + +7) Open 'bgs.sln' in Visual Studio and switch to 'RELEASE' mode +7.1) Note if you are using a Visual Studio version superior than 2015, you will need to CANCEL the project wizard update. However, you can go to step (2) and change the Visual Studio version, e.g.: -G "Visual Studio XX", where XX is your Visual Studio version. + +8) Click on 'ALL_BUILD' project and build! + +9) If everything goes well, you can run bgslibrary in the Windows console as follows: + +9.1) Running BGSLibrary with a webcamera: +C:\bgslibrary> build\bgslibrary.exe --use_cam --camera=0 + +9.2) Running demo code: +C:\bgslibrary> build\bgs_demo.exe dataset/video.avi + +9.3) Running demo2 code: +C:\bgslibrary> build\bgs_demo2.exe + +Additional information: +* Note that bgslibrary requires a 'config' folder in the working directory. diff --git a/README_LINUX_USERS.txt b/docs/README_LINUX_USERS.txt similarity index 85% rename from README_LINUX_USERS.txt rename to docs/README_LINUX_USERS.txt index 5a2611f..9482f33 100644 --- a/README_LINUX_USERS.txt +++ b/docs/README_LINUX_USERS.txt @@ -4,6 +4,8 @@ # Requirements: # cmake >= 2.8 # opencv >= 2.3.1 +# +# Tested with: Ubuntu 14.04 and Ubuntu 16.04 cd build cmake .. @@ -17,7 +19,8 @@ export LD_LIBRARY_PATH # Now you can run bgslibrary by: bgs -i video.avi ######################## cd .. -chmod +x run_video.sh run_camera.sh run_demo.sh +chmod +x *.sh ./run_video.sh ./run_camera.sh ./run_demo.sh +./run_demo2.sh diff --git a/README_VISUAL_STUDIO_USERS.txt b/docs/README_VS2010_OPENCV2.txt similarity index 81% rename from README_VISUAL_STUDIO_USERS.txt rename to docs/README_VS2010_OPENCV2.txt index ee9fe12..66ee0f4 100644 --- a/README_VISUAL_STUDIO_USERS.txt +++ b/docs/README_VS2010_OPENCV2.txt @@ -1,16 +1,13 @@ --------------------------------------------------- BGSLibrary with Visual Studio 2010 and Opencv 2.4.x --------------------------------------------------- - -1) Clone our VS2010 example project at [vs2010] folder -https://github.com/andrewssobral/bgslibrary/tree/master/vs2010 - -Or configure manually by: +--- Tutorial for Windows x86 32 bits --- +---------------------------------------- 1) Install OpenCV 1.a) Download OpenCV 2.4.x from http://opencv.org/ -2.b) Install in: C:\OpenCV2.4.x -2.c) Add OpenCV binaries in your Path +1.b) Install in: C:\OpenCV2.4.x +1.c) Add OpenCV binaries in your Path C:\OpenCV2.4.x\build\x86\vc10\bin 2) Download BGSLibrary @@ -22,7 +19,7 @@ C:\OpenCV2.4.x\build\x86\vc10\bin 3.c) Set project location: C:\bgslibrary 3.d) Set project name: bgslibrary 3.e) Set Empty project -3.f) Add Demo.cpp in [Source Files] +3.f) Add Main.cpp in [Source Files] 3.g) Add content of c:\bgslibrary\package_bgs\*.* in [Header Files] 3.h) Add content of c:\bgslibrary\package_analysis\*.* in [Header Files] 3.i) Change to [Release] [Win32] mode diff --git a/example_linux/config/KEEP_THIS_FOLDER b/example_linux/config/KEEP_THIS_FOLDER deleted file mode 100644 index 24353bc..0000000 --- a/example_linux/config/KEEP_THIS_FOLDER +++ /dev/null @@ -1 +0,0 @@ -bgslibrary uses this folder to store the configuration files \ No newline at end of file diff --git a/example_macosx/config/KEEP_THIS_FOLDER b/example_macosx/config/KEEP_THIS_FOLDER deleted file mode 100644 index 24353bc..0000000 --- a/example_macosx/config/KEEP_THIS_FOLDER +++ /dev/null @@ -1 +0,0 @@ -bgslibrary uses this folder to store the configuration files \ No newline at end of file diff --git a/java_gui/README.txt b/gui_java/README.txt similarity index 100% rename from java_gui/README.txt rename to gui_java/README.txt diff --git a/java_gui/_COPY_bgslibrary.exe_HERE_ b/gui_java/_COPY_bgslibrary.exe_HERE_ similarity index 100% rename from java_gui/_COPY_bgslibrary.exe_HERE_ rename to gui_java/_COPY_bgslibrary.exe_HERE_ diff --git a/java_gui/bgslibrary_gui.jar b/gui_java/bgslibrary_gui.jar similarity index 100% rename from java_gui/bgslibrary_gui.jar rename to gui_java/bgslibrary_gui.jar diff --git a/java_gui/bgslibrary_gui.properties b/gui_java/bgslibrary_gui.properties similarity index 100% rename from java_gui/bgslibrary_gui.properties rename to gui_java/bgslibrary_gui.properties diff --git a/java_gui/build.xml b/gui_java/build.xml similarity index 100% rename from java_gui/build.xml rename to gui_java/build.xml diff --git a/java_gui/config/.gitignore b/gui_java/config/.gitignore similarity index 100% rename from java_gui/config/.gitignore rename to gui_java/config/.gitignore diff --git a/java_gui/config/FrameProcessor.xml b/gui_java/config/FrameProcessor.xml similarity index 100% rename from java_gui/config/FrameProcessor.xml rename to gui_java/config/FrameProcessor.xml diff --git a/java_gui/config/PreProcessor.xml b/gui_java/config/PreProcessor.xml similarity index 100% rename from java_gui/config/PreProcessor.xml rename to gui_java/config/PreProcessor.xml diff --git a/java_gui/config/VideoCapture.xml b/gui_java/config/VideoCapture.xml similarity index 100% rename from java_gui/config/VideoCapture.xml rename to gui_java/config/VideoCapture.xml diff --git a/java_gui/images/bgslibrary_gui_screen01.png b/gui_java/images/bgslibrary_gui_screen01.png similarity index 100% rename from java_gui/images/bgslibrary_gui_screen01.png rename to gui_java/images/bgslibrary_gui_screen01.png diff --git a/java_gui/images/bgslibrary_gui_screen02.png b/gui_java/images/bgslibrary_gui_screen02.png similarity index 100% rename from java_gui/images/bgslibrary_gui_screen02.png rename to gui_java/images/bgslibrary_gui_screen02.png diff --git a/java_gui/images/bgslibrary_gui_screen03.png b/gui_java/images/bgslibrary_gui_screen03.png similarity index 100% rename from java_gui/images/bgslibrary_gui_screen03.png rename to gui_java/images/bgslibrary_gui_screen03.png diff --git a/java_gui/images/bgslibrary_gui_screen04.png b/gui_java/images/bgslibrary_gui_screen04.png similarity index 100% rename from java_gui/images/bgslibrary_gui_screen04.png rename to gui_java/images/bgslibrary_gui_screen04.png diff --git a/java_gui/images/logo.jpg b/gui_java/images/logo.jpg similarity index 100% rename from java_gui/images/logo.jpg rename to gui_java/images/logo.jpg diff --git a/java_gui/lib/commons-configuration-1.8.jar b/gui_java/lib/commons-configuration-1.8.jar similarity index 100% rename from java_gui/lib/commons-configuration-1.8.jar rename to gui_java/lib/commons-configuration-1.8.jar diff --git a/java_gui/lib/commons-io-2.3.jar b/gui_java/lib/commons-io-2.3.jar similarity index 100% rename from java_gui/lib/commons-io-2.3.jar rename to gui_java/lib/commons-io-2.3.jar diff --git a/java_gui/lib/commons-lang-2.6.jar b/gui_java/lib/commons-lang-2.6.jar similarity index 100% rename from java_gui/lib/commons-lang-2.6.jar rename to gui_java/lib/commons-lang-2.6.jar diff --git a/java_gui/lib/commons-logging-1.1.1.jar b/gui_java/lib/commons-logging-1.1.1.jar similarity index 100% rename from java_gui/lib/commons-logging-1.1.1.jar rename to gui_java/lib/commons-logging-1.1.1.jar diff --git a/java_gui/lib/swingx-all-1.6.3.jar b/gui_java/lib/swingx-all-1.6.3.jar similarity index 100% rename from java_gui/lib/swingx-all-1.6.3.jar rename to gui_java/lib/swingx-all-1.6.3.jar diff --git a/java_gui/lib/swingx-beaninfo-1.6.3.jar b/gui_java/lib/swingx-beaninfo-1.6.3.jar similarity index 100% rename from java_gui/lib/swingx-beaninfo-1.6.3.jar rename to gui_java/lib/swingx-beaninfo-1.6.3.jar diff --git a/java_gui/manifest.mf b/gui_java/manifest.mf similarity index 100% rename from java_gui/manifest.mf rename to gui_java/manifest.mf diff --git a/java_gui/nbproject/build-impl.xml b/gui_java/nbproject/build-impl.xml similarity index 100% rename from java_gui/nbproject/build-impl.xml rename to gui_java/nbproject/build-impl.xml diff --git a/java_gui/nbproject/genfiles.properties b/gui_java/nbproject/genfiles.properties similarity index 100% rename from java_gui/nbproject/genfiles.properties rename to gui_java/nbproject/genfiles.properties diff --git a/java_gui/nbproject/private/config.properties b/gui_java/nbproject/private/config.properties similarity index 100% rename from java_gui/nbproject/private/config.properties rename to gui_java/nbproject/private/config.properties diff --git a/java_gui/nbproject/private/private.properties b/gui_java/nbproject/private/private.properties similarity index 100% rename from java_gui/nbproject/private/private.properties rename to gui_java/nbproject/private/private.properties diff --git a/java_gui/nbproject/private/private.xml b/gui_java/nbproject/private/private.xml similarity index 100% rename from java_gui/nbproject/private/private.xml rename to gui_java/nbproject/private/private.xml diff --git a/java_gui/nbproject/project.properties b/gui_java/nbproject/project.properties similarity index 100% rename from java_gui/nbproject/project.properties rename to gui_java/nbproject/project.properties diff --git a/java_gui/nbproject/project.xml b/gui_java/nbproject/project.xml similarity index 100% rename from java_gui/nbproject/project.xml rename to gui_java/nbproject/project.xml diff --git a/java_gui/run_camera.bat b/gui_java/run_camera.bat similarity index 100% rename from java_gui/run_camera.bat rename to gui_java/run_camera.bat diff --git a/java_gui/run_java_gui.bat b/gui_java/run_java_gui.bat similarity index 100% rename from java_gui/run_java_gui.bat rename to gui_java/run_java_gui.bat diff --git a/java_gui/run_java_gui_with_console.bat b/gui_java/run_java_gui_with_console.bat similarity index 100% rename from java_gui/run_java_gui_with_console.bat rename to gui_java/run_java_gui_with_console.bat diff --git a/java_gui/run_video.bat b/gui_java/run_video.bat similarity index 100% rename from java_gui/run_video.bat rename to gui_java/run_video.bat diff --git a/java_gui/src/br/com/bgslibrary/Main.java b/gui_java/src/br/com/bgslibrary/Main.java similarity index 100% rename from java_gui/src/br/com/bgslibrary/Main.java rename to gui_java/src/br/com/bgslibrary/Main.java diff --git a/java_gui/src/br/com/bgslibrary/entity/Command.java b/gui_java/src/br/com/bgslibrary/entity/Command.java similarity index 100% rename from java_gui/src/br/com/bgslibrary/entity/Command.java rename to gui_java/src/br/com/bgslibrary/entity/Command.java diff --git a/java_gui/src/br/com/bgslibrary/entity/Configuration.java b/gui_java/src/br/com/bgslibrary/entity/Configuration.java similarity index 100% rename from java_gui/src/br/com/bgslibrary/entity/Configuration.java rename to gui_java/src/br/com/bgslibrary/entity/Configuration.java diff --git a/java_gui/src/br/com/bgslibrary/gui/AboutDialog.form b/gui_java/src/br/com/bgslibrary/gui/AboutDialog.form similarity index 100% rename from java_gui/src/br/com/bgslibrary/gui/AboutDialog.form rename to gui_java/src/br/com/bgslibrary/gui/AboutDialog.form diff --git a/java_gui/src/br/com/bgslibrary/gui/AboutDialog.java b/gui_java/src/br/com/bgslibrary/gui/AboutDialog.java similarity index 100% rename from java_gui/src/br/com/bgslibrary/gui/AboutDialog.java rename to gui_java/src/br/com/bgslibrary/gui/AboutDialog.java diff --git a/java_gui/src/br/com/bgslibrary/gui/MainFrame.form b/gui_java/src/br/com/bgslibrary/gui/MainFrame.form similarity index 100% rename from java_gui/src/br/com/bgslibrary/gui/MainFrame.form rename to gui_java/src/br/com/bgslibrary/gui/MainFrame.form diff --git a/java_gui/src/br/com/bgslibrary/gui/MainFrame.java b/gui_java/src/br/com/bgslibrary/gui/MainFrame.java similarity index 100% rename from java_gui/src/br/com/bgslibrary/gui/MainFrame.java rename to gui_java/src/br/com/bgslibrary/gui/MainFrame.java diff --git a/java_gui/src/br/com/bgslibrary/resources/logo.jpg b/gui_java/src/br/com/bgslibrary/resources/logo.jpg similarity index 100% rename from java_gui/src/br/com/bgslibrary/resources/logo.jpg rename to gui_java/src/br/com/bgslibrary/resources/logo.jpg diff --git a/vs2010mfc/.gitignore b/gui_mfc/.gitignore similarity index 100% rename from vs2010mfc/.gitignore rename to gui_mfc/.gitignore diff --git a/vs2013mfc/ReadMe.txt b/gui_mfc/ReadMe.txt similarity index 100% rename from vs2013mfc/ReadMe.txt rename to gui_mfc/ReadMe.txt diff --git a/vs2010mfc/config/AdaptiveBackgroundLearning.xml b/gui_mfc/config/AdaptiveBackgroundLearning.xml similarity index 100% rename from vs2010mfc/config/AdaptiveBackgroundLearning.xml rename to gui_mfc/config/AdaptiveBackgroundLearning.xml diff --git a/vs2010mfc/config/AdaptiveSelectiveBackgroundLearning.xml b/gui_mfc/config/AdaptiveSelectiveBackgroundLearning.xml similarity index 100% rename from vs2010mfc/config/AdaptiveSelectiveBackgroundLearning.xml rename to gui_mfc/config/AdaptiveSelectiveBackgroundLearning.xml diff --git a/vs2010mfc/config/DPAdaptiveMedianBGS.xml b/gui_mfc/config/DPAdaptiveMedianBGS.xml similarity index 100% rename from vs2010mfc/config/DPAdaptiveMedianBGS.xml rename to gui_mfc/config/DPAdaptiveMedianBGS.xml diff --git a/vs2010mfc/config/DPEigenbackgroundBGS.xml b/gui_mfc/config/DPEigenbackgroundBGS.xml similarity index 100% rename from vs2010mfc/config/DPEigenbackgroundBGS.xml rename to gui_mfc/config/DPEigenbackgroundBGS.xml diff --git a/vs2010mfc/config/DPGrimsonGMMBGS.xml b/gui_mfc/config/DPGrimsonGMMBGS.xml similarity index 100% rename from vs2010mfc/config/DPGrimsonGMMBGS.xml rename to gui_mfc/config/DPGrimsonGMMBGS.xml diff --git a/vs2010mfc/config/DPMeanBGS.xml b/gui_mfc/config/DPMeanBGS.xml similarity index 100% rename from vs2010mfc/config/DPMeanBGS.xml rename to gui_mfc/config/DPMeanBGS.xml diff --git a/vs2010mfc/config/DPPratiMediodBGS.xml b/gui_mfc/config/DPPratiMediodBGS.xml similarity index 100% rename from vs2010mfc/config/DPPratiMediodBGS.xml rename to gui_mfc/config/DPPratiMediodBGS.xml diff --git a/vs2010mfc/config/DPTextureBGS.xml b/gui_mfc/config/DPTextureBGS.xml similarity index 100% rename from vs2010mfc/config/DPTextureBGS.xml rename to gui_mfc/config/DPTextureBGS.xml diff --git a/vs2010mfc/config/DPWrenGABGS.xml b/gui_mfc/config/DPWrenGABGS.xml similarity index 100% rename from vs2010mfc/config/DPWrenGABGS.xml rename to gui_mfc/config/DPWrenGABGS.xml diff --git a/vs2010mfc/config/DPZivkovicAGMMBGS.xml b/gui_mfc/config/DPZivkovicAGMMBGS.xml similarity index 100% rename from vs2010mfc/config/DPZivkovicAGMMBGS.xml rename to gui_mfc/config/DPZivkovicAGMMBGS.xml diff --git a/vs2010mfc/config/FrameDifferenceBGS.xml b/gui_mfc/config/FrameDifferenceBGS.xml similarity index 100% rename from vs2010mfc/config/FrameDifferenceBGS.xml rename to gui_mfc/config/FrameDifferenceBGS.xml diff --git a/vs2010mfc/config/FuzzyChoquetIntegral.xml b/gui_mfc/config/FuzzyChoquetIntegral.xml similarity index 100% rename from vs2010mfc/config/FuzzyChoquetIntegral.xml rename to gui_mfc/config/FuzzyChoquetIntegral.xml diff --git a/vs2010mfc/config/FuzzySugenoIntegral.xml b/gui_mfc/config/FuzzySugenoIntegral.xml similarity index 100% rename from vs2010mfc/config/FuzzySugenoIntegral.xml rename to gui_mfc/config/FuzzySugenoIntegral.xml diff --git a/vs2010mfc/config/GMG.xml b/gui_mfc/config/GMG.xml similarity index 100% rename from vs2010mfc/config/GMG.xml rename to gui_mfc/config/GMG.xml diff --git a/vs2010mfc/config/IndependentMultimodalBGS.xml b/gui_mfc/config/IndependentMultimodalBGS.xml similarity index 100% rename from vs2010mfc/config/IndependentMultimodalBGS.xml rename to gui_mfc/config/IndependentMultimodalBGS.xml diff --git a/vs2010mfc/config/KDE.xml b/gui_mfc/config/KDE.xml similarity index 100% rename from vs2010mfc/config/KDE.xml rename to gui_mfc/config/KDE.xml diff --git a/vs2010mfc/config/LBAdaptiveSOM.xml b/gui_mfc/config/LBAdaptiveSOM.xml similarity index 100% rename from vs2010mfc/config/LBAdaptiveSOM.xml rename to gui_mfc/config/LBAdaptiveSOM.xml diff --git a/vs2010mfc/config/LBFuzzyAdaptiveSOM.xml b/gui_mfc/config/LBFuzzyAdaptiveSOM.xml similarity index 100% rename from vs2010mfc/config/LBFuzzyAdaptiveSOM.xml rename to gui_mfc/config/LBFuzzyAdaptiveSOM.xml diff --git a/vs2010mfc/config/LBFuzzyGaussian.xml b/gui_mfc/config/LBFuzzyGaussian.xml similarity index 100% rename from vs2010mfc/config/LBFuzzyGaussian.xml rename to gui_mfc/config/LBFuzzyGaussian.xml diff --git a/vs2010mfc/config/LBMixtureOfGaussians.xml b/gui_mfc/config/LBMixtureOfGaussians.xml similarity index 100% rename from vs2010mfc/config/LBMixtureOfGaussians.xml rename to gui_mfc/config/LBMixtureOfGaussians.xml diff --git a/vs2010mfc/config/LBSimpleGaussian.xml b/gui_mfc/config/LBSimpleGaussian.xml similarity index 100% rename from vs2010mfc/config/LBSimpleGaussian.xml rename to gui_mfc/config/LBSimpleGaussian.xml diff --git a/vs2010mfc/config/LOBSTERBGS.xml b/gui_mfc/config/LOBSTERBGS.xml similarity index 100% rename from vs2010mfc/config/LOBSTERBGS.xml rename to gui_mfc/config/LOBSTERBGS.xml diff --git a/vs2010mfc/config/MixtureOfGaussianV1BGS.xml b/gui_mfc/config/MixtureOfGaussianV1BGS.xml similarity index 100% rename from vs2010mfc/config/MixtureOfGaussianV1BGS.xml rename to gui_mfc/config/MixtureOfGaussianV1BGS.xml diff --git a/vs2010mfc/config/MixtureOfGaussianV2BGS.xml b/gui_mfc/config/MixtureOfGaussianV2BGS.xml similarity index 100% rename from vs2010mfc/config/MixtureOfGaussianV2BGS.xml rename to gui_mfc/config/MixtureOfGaussianV2BGS.xml diff --git a/vs2010mfc/config/MultiCueBGS.xml b/gui_mfc/config/MultiCueBGS.xml similarity index 100% rename from vs2010mfc/config/MultiCueBGS.xml rename to gui_mfc/config/MultiCueBGS.xml diff --git a/vs2010mfc/config/MultiLayerBGS.xml b/gui_mfc/config/MultiLayerBGS.xml similarity index 100% rename from vs2010mfc/config/MultiLayerBGS.xml rename to gui_mfc/config/MultiLayerBGS.xml diff --git a/vs2010mfc/config/SigmaDeltaBGS.xml b/gui_mfc/config/SigmaDeltaBGS.xml similarity index 100% rename from vs2010mfc/config/SigmaDeltaBGS.xml rename to gui_mfc/config/SigmaDeltaBGS.xml diff --git a/vs2010mfc/config/StaticFrameDifferenceBGS.xml b/gui_mfc/config/StaticFrameDifferenceBGS.xml similarity index 100% rename from vs2010mfc/config/StaticFrameDifferenceBGS.xml rename to gui_mfc/config/StaticFrameDifferenceBGS.xml diff --git a/vs2010mfc/config/SuBSENSEBGS.xml b/gui_mfc/config/SuBSENSEBGS.xml similarity index 100% rename from vs2010mfc/config/SuBSENSEBGS.xml rename to gui_mfc/config/SuBSENSEBGS.xml diff --git a/vs2010mfc/config/T2FGMM_UM.xml b/gui_mfc/config/T2FGMM_UM.xml similarity index 100% rename from vs2010mfc/config/T2FGMM_UM.xml rename to gui_mfc/config/T2FGMM_UM.xml diff --git a/vs2010mfc/config/T2FGMM_UV.xml b/gui_mfc/config/T2FGMM_UV.xml similarity index 100% rename from vs2010mfc/config/T2FGMM_UV.xml rename to gui_mfc/config/T2FGMM_UV.xml diff --git a/vs2010mfc/config/T2FMRF_UM.xml b/gui_mfc/config/T2FMRF_UM.xml similarity index 100% rename from vs2010mfc/config/T2FMRF_UM.xml rename to gui_mfc/config/T2FMRF_UM.xml diff --git a/vs2010mfc/config/T2FMRF_UV.xml b/gui_mfc/config/T2FMRF_UV.xml similarity index 100% rename from vs2010mfc/config/T2FMRF_UV.xml rename to gui_mfc/config/T2FMRF_UV.xml diff --git a/vs2010mfc/config/VuMeter.xml b/gui_mfc/config/VuMeter.xml similarity index 100% rename from vs2010mfc/config/VuMeter.xml rename to gui_mfc/config/VuMeter.xml diff --git a/vs2010mfc/config/WeightedMovingMeanBGS.xml b/gui_mfc/config/WeightedMovingMeanBGS.xml similarity index 100% rename from vs2010mfc/config/WeightedMovingMeanBGS.xml rename to gui_mfc/config/WeightedMovingMeanBGS.xml diff --git a/vs2010mfc/config/WeightedMovingVarianceBGS.xml b/gui_mfc/config/WeightedMovingVarianceBGS.xml similarity index 100% rename from vs2010mfc/config/WeightedMovingVarianceBGS.xml rename to gui_mfc/config/WeightedMovingVarianceBGS.xml diff --git a/vs2010mfc/dataset/video.avi b/gui_mfc/dataset/video.avi similarity index 100% rename from vs2010mfc/dataset/video.avi rename to gui_mfc/dataset/video.avi diff --git a/vs2013mfc/outputs/background/.gitignore b/gui_mfc/outputs/background/.gitignore similarity index 100% rename from vs2013mfc/outputs/background/.gitignore rename to gui_mfc/outputs/background/.gitignore diff --git a/vs2013mfc/outputs/foreground/.gitignore b/gui_mfc/outputs/foreground/.gitignore similarity index 100% rename from vs2013mfc/outputs/foreground/.gitignore rename to gui_mfc/outputs/foreground/.gitignore diff --git a/vs2013mfc/outputs/input/.gitignore b/gui_mfc/outputs/input/.gitignore similarity index 100% rename from vs2013mfc/outputs/input/.gitignore rename to gui_mfc/outputs/input/.gitignore diff --git a/vs2010mfc/src/.gitignore b/gui_mfc/src/.gitignore similarity index 100% rename from vs2010mfc/src/.gitignore rename to gui_mfc/src/.gitignore diff --git a/vs2010mfc/src/App.cpp b/gui_mfc/src/App.cpp similarity index 100% rename from vs2010mfc/src/App.cpp rename to gui_mfc/src/App.cpp diff --git a/vs2010mfc/src/App.h b/gui_mfc/src/App.h similarity index 100% rename from vs2010mfc/src/App.h rename to gui_mfc/src/App.h diff --git a/vs2010mfc/src/Dlg.cpp b/gui_mfc/src/Dlg.cpp similarity index 100% rename from vs2010mfc/src/Dlg.cpp rename to gui_mfc/src/Dlg.cpp diff --git a/vs2010mfc/src/Dlg.h b/gui_mfc/src/Dlg.h similarity index 100% rename from vs2010mfc/src/Dlg.h rename to gui_mfc/src/Dlg.h diff --git a/vs2013mfc/src/ReadMe.txt b/gui_mfc/src/ReadMe.txt similarity index 100% rename from vs2013mfc/src/ReadMe.txt rename to gui_mfc/src/ReadMe.txt diff --git a/vs2013mfc/src/bgslibrary_vs2013_mfc.rc b/gui_mfc/src/bgslibrary_vs2013_mfc.rc similarity index 100% rename from vs2013mfc/src/bgslibrary_vs2013_mfc.rc rename to gui_mfc/src/bgslibrary_vs2013_mfc.rc diff --git a/vs2013mfc/src/bgslibrary_vs2013_mfc.sln b/gui_mfc/src/bgslibrary_vs2013_mfc.sln similarity index 100% rename from vs2013mfc/src/bgslibrary_vs2013_mfc.sln rename to gui_mfc/src/bgslibrary_vs2013_mfc.sln diff --git a/vs2013mfc/src/bgslibrary_vs2013_mfc.vcxproj b/gui_mfc/src/bgslibrary_vs2013_mfc.vcxproj similarity index 100% rename from vs2013mfc/src/bgslibrary_vs2013_mfc.vcxproj rename to gui_mfc/src/bgslibrary_vs2013_mfc.vcxproj diff --git a/vs2013mfc/src/bgslibrary_vs2013_mfc.vcxproj.filters b/gui_mfc/src/bgslibrary_vs2013_mfc.vcxproj.filters similarity index 100% rename from vs2013mfc/src/bgslibrary_vs2013_mfc.vcxproj.filters rename to gui_mfc/src/bgslibrary_vs2013_mfc.vcxproj.filters diff --git a/vs2013mfc/src/bgslibrary_vs2013_mfc.vcxproj.user b/gui_mfc/src/bgslibrary_vs2013_mfc.vcxproj.user similarity index 100% rename from vs2013mfc/src/bgslibrary_vs2013_mfc.vcxproj.user rename to gui_mfc/src/bgslibrary_vs2013_mfc.vcxproj.user diff --git a/vs2013mfc/src/res/bgslibrary_vs2013_mfc.ico b/gui_mfc/src/res/bgslibrary_vs2013_mfc.ico similarity index 100% rename from vs2013mfc/src/res/bgslibrary_vs2013_mfc.ico rename to gui_mfc/src/res/bgslibrary_vs2013_mfc.ico diff --git a/vs2013mfc/src/res/bgslibrary_vs2013_mfc.rc2 b/gui_mfc/src/res/bgslibrary_vs2013_mfc.rc2 similarity index 100% rename from vs2013mfc/src/res/bgslibrary_vs2013_mfc.rc2 rename to gui_mfc/src/res/bgslibrary_vs2013_mfc.rc2 diff --git a/vs2010mfc/src/resource.h b/gui_mfc/src/resource.h similarity index 100% rename from vs2010mfc/src/resource.h rename to gui_mfc/src/resource.h diff --git a/vs2010mfc/src/stdafx.cpp b/gui_mfc/src/stdafx.cpp similarity index 100% rename from vs2010mfc/src/stdafx.cpp rename to gui_mfc/src/stdafx.cpp diff --git a/vs2010mfc/src/stdafx.h b/gui_mfc/src/stdafx.h similarity index 100% rename from vs2010mfc/src/stdafx.h rename to gui_mfc/src/stdafx.h diff --git a/vs2010mfc/src/targetver.h b/gui_mfc/src/targetver.h similarity index 100% rename from vs2010mfc/src/targetver.h rename to gui_mfc/src/targetver.h diff --git a/gui_qt/.gitignore b/gui_qt/.gitignore new file mode 100644 index 0000000..9c28032 --- /dev/null +++ b/gui_qt/.gitignore @@ -0,0 +1,9 @@ +_*/ +debug/ +release/ +build_*/ +dataset*/ +binaries*/ +Makefile* +*.exe +*.dll diff --git a/gui_qt/CMakeLists.txt b/gui_qt/CMakeLists.txt new file mode 100644 index 0000000..83c8920 --- /dev/null +++ b/gui_qt/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 2.8.11) + +project(bgslibrary_gui) + +# Find includes in corresponding build directories +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Instruct CMake to run moc automatically when needed. +set(CMAKE_AUTOMOC ON) + +# Handle the Qt uic code generator automatically +set(CMAKE_AUTOUIC ON) + +# Find the Qt5Widgets library +find_package(Qt5Widgets) + +SET(app_RESOURCES application.qrc) +QT5_ADD_RESOURCES(app_RESOURCES_RCC ${app_RESOURCES}) + +# Find the OpenCV library +set(OpenCV_STATIC OFF) +find_package(OpenCV REQUIRED) + +message(STATUS "OpenCV library status:") +message(STATUS " version: ${OpenCV_VERSION}") +message(STATUS " libraries: ${OpenCV_LIBS}") +message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}") + +file(GLOB main bgslibrary_gui.cpp mainwindow.cpp qt_utils.cpp texteditor.cpp) + +file(GLOB_RECURSE analysis_src ../package_analysis/*.cpp) +file(GLOB_RECURSE analysis_inc ../package_analysis/*.h) +file(GLOB_RECURSE bgs_src ../package_bgs/*.cpp ../package_bgs/*.c) +file(GLOB_RECURSE bgs_inc ../package_bgs/*.h) + +include_directories(${CMAKE_SOURCE_DIR} ${OpenCV_INCLUDE_DIRS}) + +add_library(libbgs STATIC ${bgs_src} ${analysis_src}) +target_link_libraries(libbgs ${OpenCV_LIBS}) +set_property(TARGET libbgs PROPERTY PUBLIC_HEADER ${bgs_inc} ${analysis_inc}) + +if(WIN32) + # set_property(TARGET libbgs PROPERTY SUFFIX ".lib") +else() + set_property(TARGET libbgs PROPERTY OUTPUT_NAME "bgs") +endif() + +# Tell CMake to create the bgslibrary_gui executable +add_executable(bgslibrary_gui ${main} ${app_RESOURCES_RCC}) + +# Use the Widgets module from Qt 5. +target_link_libraries(bgslibrary_gui Qt5::Widgets ${OpenCV_LIBS} libbgs) diff --git a/gui_qt/README.txt b/gui_qt/README.txt new file mode 100644 index 0000000..41a4cd2 --- /dev/null +++ b/gui_qt/README.txt @@ -0,0 +1,17 @@ +#------------------------------------------------- +# +# Project created with Qt 5.6.2 +# +# Compiling BGSLibrary QT GUI with CMAKE +# +#------------------------------------------------- +# Qt 5.x 64-bit for Desktop (MSVC 2015) +#------------------------------------------------- + +mkdir build + +cd build + +set OpenCV_DIR=C:\OpenCV3.2.0\build + +cmake -DOpenCV_DIR=%OpenCV_DIR% -G "Visual Studio 14 Win64" .. diff --git a/gui_qt/application.qrc b/gui_qt/application.qrc new file mode 100644 index 0000000..3e8687f --- /dev/null +++ b/gui_qt/application.qrc @@ -0,0 +1,10 @@ + + + figs/copy.png + figs/cut.png + figs/new.png + figs/open.png + figs/paste.png + figs/save.png + + \ No newline at end of file diff --git a/gui_qt/bgslibrary_gui.cpp b/gui_qt/bgslibrary_gui.cpp new file mode 100644 index 0000000..e886262 --- /dev/null +++ b/gui_qt/bgslibrary_gui.cpp @@ -0,0 +1,38 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + std::cout << "--------------------------------------------" << std::endl; + std::cout << "Background Subtraction Library v2.0.0 " << std::endl; + std::cout << "https://github.com/andrewssobral/bgslibrary " << std::endl; + std::cout << "by: " << std::endl; + std::cout << "Andrews Sobral (andrewssobral@gmail.com) " << std::endl; + std::cout << "--------------------------------------------" << std::endl; + std::cout << "Using OpenCV version " << CV_VERSION << std::endl; + + QApplication a(argc, argv); + + QCoreApplication::setApplicationName("BGSLibrary"); + QCoreApplication::setApplicationVersion("2.0.0"); + + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/gui_qt/bgslibrary_gui.pro b/gui_qt/bgslibrary_gui.pro new file mode 100644 index 0000000..feedbf6 --- /dev/null +++ b/gui_qt/bgslibrary_gui.pro @@ -0,0 +1,248 @@ +#------------------------------------------------- +# +# Project created by QtCreator +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = bgslibrary_gui +TEMPLATE = app + +# For Windows x64 + Visual Studio 2015 + OpenCV 3.1.0 +#INCLUDEPATH += C:/OpenCV3.1.0/build/include +#LIBS += -LC:/OpenCV3.1.0/build/x64/vc14/lib -lopencv_world310 + +# For Windows x64 + Visual Studio 2015 + OpenCV 3.2.0 +INCLUDEPATH += C:/OpenCV3.2.0/build/include +LIBS += -LC:/OpenCV3.2.0/build/x64/vc14/lib -lopencv_world320 + +# For Linux +# INCLUDEPATH += /usr/local/include/opencv +# LIBS += -L/usr/local/lib + +RESOURCES = application.qrc + +SOURCES += bgslibrary_gui.cpp\ + mainwindow.cpp \ + qt_utils.cpp \ + texteditor.cpp \ + ../package_analysis/ForegroundMaskAnalysis.cpp \ + ../package_analysis/PerformanceUtils.cpp \ + ../package_analysis/PixelUtils.cpp \ + ../package_bgs/_template_/Amber.cpp \ + ../package_bgs/_template_/MyBGS.cpp \ + ../package_bgs/dp/AdaptiveMedianBGS.cpp \ + ../package_bgs/dp/Eigenbackground.cpp \ + ../package_bgs/dp/Error.cpp \ + ../package_bgs/dp/GrimsonGMM.cpp \ + ../package_bgs/dp/Image.cpp \ + ../package_bgs/dp/MeanBGS.cpp \ + ../package_bgs/dp/PratiMediodBGS.cpp \ + ../package_bgs/dp/TextureBGS.cpp \ + ../package_bgs/dp/WrenGA.cpp \ + ../package_bgs/dp/ZivkovicAGMM.cpp \ + ../package_bgs/IMBS/IMBS.cpp \ + ../package_bgs/KDE/KernelTable.cpp \ + ../package_bgs/KDE/NPBGmodel.cpp \ + ../package_bgs/KDE/NPBGSubtractor.cpp \ + ../package_bgs/lb/BGModel.cpp \ + ../package_bgs/lb/BGModelFuzzyGauss.cpp \ + ../package_bgs/lb/BGModelFuzzySom.cpp \ + ../package_bgs/lb/BGModelGauss.cpp \ + ../package_bgs/lb/BGModelMog.cpp \ + ../package_bgs/lb/BGModelSom.cpp \ + ../package_bgs/LBP_MRF/graph.cpp \ + ../package_bgs/LBP_MRF/maxflow.cpp \ + ../package_bgs/LBP_MRF/MEDefs.cpp \ + ../package_bgs/LBP_MRF/MEHistogram.cpp \ + ../package_bgs/LBP_MRF/MEImage.cpp \ + ../package_bgs/LBP_MRF/MotionDetection.cpp \ + ../package_bgs/LBSP/BackgroundSubtractorLBSP.cpp \ + ../package_bgs/LBSP/BackgroundSubtractorLBSP_.cpp \ + ../package_bgs/LBSP/BackgroundSubtractorLOBSTER.cpp \ + ../package_bgs/LBSP/BackgroundSubtractorPAWCS.cpp \ + ../package_bgs/LBSP/BackgroundSubtractorSuBSENSE.cpp \ + ../package_bgs/LBSP/LBSP.cpp \ + ../package_bgs/LBSP/LBSP_.cpp \ + ../package_bgs/MultiLayer/blob.cpp \ + ../package_bgs/MultiLayer/BlobExtraction.cpp \ + ../package_bgs/MultiLayer/BlobResult.cpp \ + ../package_bgs/MultiLayer/CMultiLayerBGS.cpp \ + ../package_bgs/MultiLayer/LocalBinaryPattern.cpp \ + ../package_bgs/PBAS/PBAS.cpp \ + ../package_bgs/SigmaDelta/sdLaMa091.cpp \ + ../package_bgs/T2F/FuzzyUtils.cpp \ + ../package_bgs/T2F/MRF.cpp \ + ../package_bgs/T2F/T2FGMM.cpp \ + ../package_bgs/T2F/T2FMRF.cpp \ + ../package_bgs/TwoPoints/two_points.cpp \ + ../package_bgs/ViBe/vibe-background-sequential.cpp \ + ../package_bgs/VuMeter/TBackground.cpp \ + ../package_bgs/VuMeter/TBackgroundVuMeter.cpp \ + ../package_bgs/AdaptiveBackgroundLearning.cpp \ + ../package_bgs/AdaptiveSelectiveBackgroundLearning.cpp \ + ../package_bgs/DPAdaptiveMedian.cpp \ + ../package_bgs/DPEigenbackground.cpp \ + ../package_bgs/DPGrimsonGMM.cpp \ + ../package_bgs/DPMean.cpp \ + ../package_bgs/DPPratiMediod.cpp \ + ../package_bgs/DPTexture.cpp \ + ../package_bgs/DPWrenGA.cpp \ + ../package_bgs/DPZivkovicAGMM.cpp \ + ../package_bgs/FrameDifference.cpp \ + ../package_bgs/FuzzyChoquetIntegral.cpp \ + ../package_bgs/FuzzySugenoIntegral.cpp \ + ../package_bgs/GMG.cpp \ + ../package_bgs/IndependentMultimodal.cpp \ + ../package_bgs/KDE.cpp \ + ../package_bgs/KNN.cpp \ + ../package_bgs/LBAdaptiveSOM.cpp \ + ../package_bgs/LBFuzzyAdaptiveSOM.cpp \ + ../package_bgs/LBFuzzyGaussian.cpp \ + ../package_bgs/LBMixtureOfGaussians.cpp \ + ../package_bgs/LBP_MRF.cpp \ + ../package_bgs/LBSimpleGaussian.cpp \ + ../package_bgs/LOBSTER.cpp \ + ../package_bgs/MixtureOfGaussianV1.cpp \ + ../package_bgs/MixtureOfGaussianV2.cpp \ + ../package_bgs/MultiCue.cpp \ + ../package_bgs/MultiLayer.cpp \ + ../package_bgs/PAWCS.cpp \ + ../package_bgs/PixelBasedAdaptiveSegmenter.cpp \ + ../package_bgs/SigmaDelta.cpp \ + ../package_bgs/StaticFrameDifference.cpp \ + ../package_bgs/SuBSENSE.cpp \ + ../package_bgs/T2FGMM_UM.cpp \ + ../package_bgs/T2FGMM_UV.cpp \ + ../package_bgs/T2FMRF_UM.cpp \ + ../package_bgs/T2FMRF_UV.cpp \ + ../package_bgs/TwoPoints.cpp \ + ../package_bgs/ViBe.cpp \ + ../package_bgs/VuMeter.cpp \ + ../package_bgs/WeightedMovingMean.cpp \ + ../package_bgs/WeightedMovingVariance.cpp \ + ../package_bgs/_template_/amber/amber.c + +HEADERS += mainwindow.h \ + qt_utils.h \ + texteditor.h \ + ../package_analysis/ForegroundMaskAnalysis.h \ + ../package_analysis/PerformanceUtils.h \ + ../package_analysis/PixelUtils.h \ + ../package_bgs/_template_/amber/amber.h \ + ../package_bgs/_template_/Amber.h \ + ../package_bgs/_template_/MyBGS.h \ + ../package_bgs/dp/AdaptiveMedianBGS.h \ + ../package_bgs/dp/Bgs.h \ + ../package_bgs/dp/BgsParams.h \ + ../package_bgs/dp/Eigenbackground.h \ + ../package_bgs/dp/Error.h \ + ../package_bgs/dp/GrimsonGMM.h \ + ../package_bgs/dp/Image.h \ + ../package_bgs/dp/MeanBGS.h \ + ../package_bgs/dp/PratiMediodBGS.h \ + ../package_bgs/dp/TextureBGS.h \ + ../package_bgs/dp/WrenGA.h \ + ../package_bgs/dp/ZivkovicAGMM.h \ + ../package_bgs/IMBS/IMBS.hpp \ + ../package_bgs/KDE/KernelTable.h \ + ../package_bgs/KDE/NPBGmodel.h \ + ../package_bgs/KDE/NPBGSubtractor.h \ + ../package_bgs/lb/BGModel.h \ + ../package_bgs/lb/BGModelFuzzyGauss.h \ + ../package_bgs/lb/BGModelFuzzySom.h \ + ../package_bgs/lb/BGModelGauss.h \ + ../package_bgs/lb/BGModelMog.h \ + ../package_bgs/lb/BGModelSom.h \ + ../package_bgs/lb/Types.h \ + ../package_bgs/LBP_MRF/block.h \ + ../package_bgs/LBP_MRF/graph.h \ + ../package_bgs/LBP_MRF/MEDefs.hpp \ + ../package_bgs/LBP_MRF/MEHistogram.hpp \ + ../package_bgs/LBP_MRF/MEImage.hpp \ + ../package_bgs/LBP_MRF/MotionDetection.hpp \ + ../package_bgs/LBSP/BackgroundSubtractorLBSP.h \ + ../package_bgs/LBSP/BackgroundSubtractorLBSP_.h \ + ../package_bgs/LBSP/BackgroundSubtractorLOBSTER.h \ + ../package_bgs/LBSP/BackgroundSubtractorPAWCS.h \ + ../package_bgs/LBSP/BackgroundSubtractorSuBSENSE.h \ + ../package_bgs/LBSP/DistanceUtils.h \ + ../package_bgs/LBSP/LBSP.h \ + ../package_bgs/LBSP/LBSP_.h \ + ../package_bgs/LBSP/RandUtils.h \ + ../package_bgs/MultiLayer/BackgroundSubtractionAPI.h \ + ../package_bgs/MultiLayer/BGS.h \ + ../package_bgs/MultiLayer/blob.h \ + ../package_bgs/MultiLayer/BlobExtraction.h \ + ../package_bgs/MultiLayer/BlobLibraryConfiguration.h \ + ../package_bgs/MultiLayer/BlobResult.h \ + ../package_bgs/MultiLayer/CMultiLayerBGS.h \ + ../package_bgs/MultiLayer/LocalBinaryPattern.h \ + ../package_bgs/MultiLayer/OpenCvDataConversion.h \ + ../package_bgs/MultiLayer/OpenCvLegacyIncludes.h \ + ../package_bgs/PBAS/PBAS.h \ + ../package_bgs/SigmaDelta/sdLaMa091.h \ + ../package_bgs/T2F/FuzzyUtils.h \ + ../package_bgs/T2F/MRF.h \ + ../package_bgs/T2F/T2FGMM.h \ + ../package_bgs/T2F/T2FMRF.h \ + ../package_bgs/TwoPoints/two_points.h \ + ../package_bgs/ViBe/vibe-background-sequential.h \ + ../package_bgs/VuMeter/TBackground.h \ + ../package_bgs/VuMeter/TBackgroundVuMeter.h \ + ../package_bgs/AdaptiveBackgroundLearning.h \ + ../package_bgs/AdaptiveSelectiveBackgroundLearning.h \ + ../package_bgs/bgslibrary.h \ + ../package_bgs/DPAdaptiveMedian.h \ + ../package_bgs/DPEigenbackground.h \ + ../package_bgs/DPGrimsonGMM.h \ + ../package_bgs/DPMean.h \ + ../package_bgs/DPPratiMediod.h \ + ../package_bgs/DPTexture.h \ + ../package_bgs/DPWrenGA.h \ + ../package_bgs/DPZivkovicAGMM.h \ + ../package_bgs/FrameDifference.h \ + ../package_bgs/FuzzyChoquetIntegral.h \ + ../package_bgs/FuzzySugenoIntegral.h \ + ../package_bgs/GMG.h \ + ../package_bgs/IBGS.h \ + ../package_bgs/IndependentMultimodal.h \ + ../package_bgs/KDE.h \ + ../package_bgs/KNN.h \ + ../package_bgs/LBAdaptiveSOM.h \ + ../package_bgs/LBFuzzyAdaptiveSOM.h \ + ../package_bgs/LBFuzzyGaussian.h \ + ../package_bgs/LBMixtureOfGaussians.h \ + ../package_bgs/LBP_MRF.h \ + ../package_bgs/LBSimpleGaussian.h \ + ../package_bgs/LOBSTER.h \ + ../package_bgs/MixtureOfGaussianV1.h \ + ../package_bgs/MixtureOfGaussianV2.h \ + ../package_bgs/MultiCue.h \ + ../package_bgs/MultiLayer.h \ + ../package_bgs/PAWCS.h \ + ../package_bgs/PixelBasedAdaptiveSegmenter.h \ + ../package_bgs/SigmaDelta.h \ + ../package_bgs/StaticFrameDifference.h \ + ../package_bgs/SuBSENSE.h \ + ../package_bgs/T2FGMM_UM.h \ + ../package_bgs/T2FGMM_UV.h \ + ../package_bgs/T2FMRF_UM.h \ + ../package_bgs/T2FMRF_UV.h \ + ../package_bgs/TwoPoints.h \ + ../package_bgs/ViBe.h \ + ../package_bgs/VuMeter.h \ + ../package_bgs/WeightedMovingMean.h \ + ../package_bgs/WeightedMovingVariance.h + +FORMS += mainwindow.ui + +DISTFILES += \ + ../package_bgs/LBSP/LBSP_16bits_dbcross_1ch.i \ + ../package_bgs/LBSP/LBSP_16bits_dbcross_3ch1t.i \ + ../package_bgs/LBSP/LBSP_16bits_dbcross_3ch3t.i \ + ../package_bgs/LBSP/LBSP_16bits_dbcross_s3ch.i \ + ../package_bgs/ViBe/LICENSE diff --git a/gui_qt/build/.gitignore b/gui_qt/build/.gitignore new file mode 100644 index 0000000..4e2a98b --- /dev/null +++ b/gui_qt/build/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except these files +!.gitignore diff --git a/gui_qt/figs/copy.png b/gui_qt/figs/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..2aeb28288f58ddffdd1d75115f170c5bf2773814 GIT binary patch literal 1338 zcmV-A1;zS_P)3P|jKV4ArrNQZsr&q&3Fam_48Lh`MiYI|sB6GiaYuZZ? zasnKC=d9Ws*vOa?K!llv;~9}LDH=;*C6q)snnH7j#=aB8{{oN_lX)cZ$XJrkAB0_u z!sQ7f5=*1>!|zQrby)$h>)1Bcke)jH%(>@ZE)hRgo<&7f4Jw)5udTzKv5Ch3thOcm zx#)*$6jZ zxjE=2CQwjNfFaFqeBMc>`320FTpYmRPeJsB@I$Z8%>Zil0#HL{*yW1HLMx&9BQ>hew>g`42C!b-4K{%G& zPNKpdi`TSeSQ5jt zz}D6l-wA<00HP?u@AqSEZ4IXsmC*dI=c&nj!3&r@K%3kE+d(!rH~Ds?RumQ0==)XR zsU3kw0Ovao0D#OTfFv_dBoZhqui^lIxB!co0iZTo)dw&I|ClF}JUvZG$*D>XptrLF zi9N;m7cj8^d~x*v0Ho^4m=ue}P^PGYdPu;S8Mtc&=A4=P=T~F|kkV6X0*FSV+&mNt zp}4dH*9HaLW{2dU0UTt+1SmUX21q@ea}t@;xj-PrWmUM?EueooW3GLWY2c%FnE?J> zJ*6hCuC8*3w@NB-xmUnF8@PU+0q)ELXD;m%K&qWYi;j}haZKLWGrnrG9>`fBl6@D%o`^aXzvlAyk=W}Pc`B^ahku5Y4`j}CIOJCBx$u% zLNX5qgK)WA+>l;M=(ES;f!poI@e{8lpM9a6Ogos0nT|fmct-%0O2q-B6)w#;8B$U@ z_bIi*;ou_U#l=Mq_2TiDT=X8flj*JiEiEl>>0qJM(w7a5mzS5}^Z8)2+4y~FX^BG+ z0F4U^3(c|s(4vx3lM#*SPTzP`J2hanT6y)<3jNMtFeLKw^6s-|9N8B@Q&W>$nrBK+ zA`_~b#ukg2H$b09M@J)VZEfEC{CpGp+d-zXy_)|gKy!05p8`rp5L7=^KR-Xu>(*#A zk-EA%=fkvJOdl{E&Bk~~0Hsptb~>HBVoFMHK9sb+zTTOWlOvfHXH#`n0F8}}Zi~f& wfq{WYZEdYHH#b*S)7=30e@0BgL78sX-$OZ87=>`i1ONa407*qoM6N<$g6NH5LI3~& literal 0 HcmV?d00001 diff --git a/gui_qt/figs/cut.png b/gui_qt/figs/cut.png new file mode 100644 index 0000000000000000000000000000000000000000..54638e9386dc8af40dcc9a3ee2f57c62e248e406 GIT binary patch literal 1323 zcmV+`1=RY9P)J0(1!|L=@Q+aTEb7Mu4u}WZlMQB24&|#EVDBq0{%sj+Z+)GGQx0 zAcdU1Fi0-lfEhqCt9ecIzWZ_$Xk-SF1YV<2^ak#QNA=LXgUd}Io1OMRbT7RYGCd%P zKS-r48v%?ldBMA#h)c%T#wHG+5V`$A{>3XcMvM|%P6Cssr>70f(>b}AV>T2D(&VEn zK_HANo+{rrN9){swakg0Ktz^QgDWfxHbzJY6ZK>&doEN10r&$qzrYSODpcQMh*H%o zSOat&76=4-MOF34plx)*)N}VCzH?EPkgku=6VK(8gNL*Np3E)-)CS$Jnai-RudhD8 z(qOgQ?SS}sjv4h}*IqNacG`39XxPWcN1v;!unrFm!4QYC z_Acj^H}`jQVc5OsUVtxVnxKZ?sWw9Q@B`QxpVZH#=NgKbEq?xOmsA>SUtb@%(9v@T zVsaH$PQ-_o)Qm6`=@qvgI2;Z@q}CZRo`stQer>5G8e5my4CXEh^CT4CHjaC}9?{K0 z$O&($1U%8bR;v{dcYhm%^O9V6PFgxKporEb{=Djv}7NhUQZ#8TNvL0_NhN zpdjG!cwn(uKwDk``_Co}Ev8e>XOzzgOoQTEd*;Gmnz9rW3I&M8V#v(QgrcG%P^;C@ z+1bfp?EcBQ$=j!OF1OE1fUdXLDR2(JYL~2dh{}J0;wgy^K9?&YTb2(8W8+7JA12t6 za%7Hg8ovjl(FiTAE%3|0ARIXHz6G;l!311sgoTBKM%*UnGOR4mGqn@!JL9_3Gp~Z> z{(6Z9p5FXo8}~etjeBORPg}}0?N~!YJv1~lfTgz=G8Dzb^Xb54Jd))oo;F?OoGLPS zee{DaT5J+A3>zitarZCFC+`J^U;fkNL<>x4^vOPTl^zTR1GKld!`H@^F|L6LX7M&{ zIA}acIyz>{0a00^F4~fx%15b|k+|p_1^WSy+Zxk*z z((l+8X-U2;oygOcJEspZj3vh3pSpKnaW$YpY_$lC`IPbC@H0!Ad}tF|z0RZAYfnh` z3=gmm{_}^(`7iFdNp1l4dp_;=2p%dvUFBG+@kd(qwpj}i9kWBpkvIuC`Kgv6`dk_f h&$IvkH~dc%_#Nkm8M#Em0nY#c002ovPDHLkV1hsWYZL$g literal 0 HcmV?d00001 diff --git a/gui_qt/figs/new.png b/gui_qt/figs/new.png new file mode 100644 index 0000000000000000000000000000000000000000..12131b01008a3ec29ec69f8b3f65c4b3c15b60d6 GIT binary patch literal 852 zcmV-a1FQUrP)IE2xvpcKl890$E8eWxeF8o0mWjmIG{M&7eWO9CVRojsy7@;&dpQ!a9t0J7gPX(5I|>76wd9YM;~Wq>Ghkp6rY@= zsi|pt_x=M#qtRtve?bMn?-1>_C5DBX;{}vm&C%raD|#~il%B<2&`e?uix*V@+JPAL z6CgbQkZR2~6*sGtFK$zbV~w|k*M3m}@OjM5T^_Z^d)w}xJHF(IUAVpO;*`oW;C1B2 zbys585IO4RGepz@rv~8kI;}Y(n3!mCfS7AgQ=I?+DlNG&;!abH5Df(KZTl#;d_@48 zJKJjfX^uQ>h#Iar6M#C8w^So4*Tn<|1^_iEoENLSCA-QKz)fKe6)JLW%zjz|XlmW9 z@suY3Q`{VJZCgNKnyVajp6h`@6-N=kYKbun)^`LT3}hx^wrO>XsHzE2w#B^A1Ngov zV}~hV3>btQ09#E(0CWRWn5~q>9Op*n90161U8$i6fc^Yd5_2;F2sIc&=-PaaDX2~W zTL1&dm}fQw05ax<-uM>_{4Cgt02pUFPqb7NBbWjJ0t`O;#ZxHoL%3#1_iOo5hu6MT z0mxO4x=yW24v?>f-nYV%Jx`{y-lMT`>)O!u6_~SbsQ_#=q-z-e;1PdX7gN*|3t4aE z?!Drr!OKg0ZH#q?HK_Wx9NlHvJdYn4{+`bsH@(TacqVT~0BG|6fti$^=|8_20YcOC e{Jor>rG5iZY$c(Rl?rzN0000RP)CWtKz1rEEwa&Ubd$6s{da=&VIP1;2`BM0`Wb;D z{M-!)vd0-ix#k=pmKgFxQgYk(eOi$0QlJD2NbOI5d5567avIIj< zv@<}9dj`!tE;Q@xs8{wNhf{?zUNu9!u=J3D!C+h_ze{}K1V|htI6?j0>T@UYxhDij zhnKMl|M#(rT!AcZ}eF)wgP_d zAX10jekLr}U#(CrCA<(opAbNs2#S{v>vbjZBPMG2>Dlh2!oC#Mk@9m9Zc z2%^^3CBjkLC=!RpAFqNhVw6pZB#Q!^oqj*T-F(Vw$mLX*nO_ zNX-3DeeTpEomGUYnz~IsN92z9JUkMF)7=lf!HB}*Vw`?29z6pA^e+N~guKQ7E$%u) zC{dC3_66}y+`oN81yr%;K@{IH8|1O-3y*riL6}qGd@SEo}8X#Q_kq zZ|eG3QMpHgF(8Pc07FLLD$kpMaaUZZl&Qk=tr>Kq76&g@g}Kbh`}mD}bLg zSob6#9DIaI(-=7VVTPPm{Z+FbtPVT4Iv?2~9(pg}6TG@z0w%t}gOqRBIG>z~1AjdP zW3QR%ZYsA%*Mp?X$w>b?i;UGgQ?+sSsoXX8X`%zES@sOGBngFV;lVB@d3_10@@V;lg>dY2BN>v^RZ2^?j@k8NdQ$X;_ z_a$H}ZfeJ=gha#~If~qTHdcoJ4Lhqwh-3dbkKgY5JsSA}oWGEOlkpdDqpb%S#7{~T>2A7M zsP8QLl$cN`w?OF*Jo#7bDJpj@!Dj^fwz=d)jlRsXTKwbW85}(HH?VT^u<$rb~rBzaiB|lB=W)A?(%51dO$!?mU^ ze8O#IHd*n50bG}w!BKl4^90wps7K`#)>4RXJ_(`usyx1v$_9j`-;Pq{6K%jV&lNRO%>FPb&x$A5bBEmwG~ zsNAo;tlZJ!2;Bu-bsyf#uSL0b05WG_^}?r@soeJn(y81#_yhC8@e_i|9PXV^3ptdy z4Z_G4qZfz6FCzC2|JrSe-C>#48@AYMyIwNBFNyyFX1QPd5r93<00000NkvXXu0mjf DP*&js literal 0 HcmV?d00001 diff --git a/gui_qt/figs/paste.png b/gui_qt/figs/paste.png new file mode 100644 index 0000000000000000000000000000000000000000..c14425cad1ff1b2c5628be5769c9e9e52b78635f GIT binary patch literal 1645 zcmV-z29o)SP)3Gx zefpoBhy8x@pP6$JN-2UQgm?vbp2urQDfM^OC?_DeL%RWJmr}mQV`N#@2O9RfK(_`t z{}ccw{I~7(;$U4wL~hQBiYr&HP+e1vKmedn2xTZiDQ44YUa1!^ytGbiJMka6M}vG@ zLrgqP2loG9i`^_G_)0Q7JWOR}B`e8wT^_)0sjwm&qAGGfr|N;V0|+ezNTCh%UA#yx zm%}d>QA&}|=UH4_q);eO)MK@^wUhx^fcS#;fB^tDLmDAYR7=GY}hKA6kOPAcP zuC561)~X!<6GT-k!gJLz@}5tjD3MBG2|?Jgh=y$Das?8ijz6FOD-BH>0V2R=mW1#7 zc%Dafb+w*QOZ4j1tA~UTD5c(75x}xR0JbfV+O`xp2&58N!i*b_Ou6iP^*huzHW7R; z!Gvcr8OqDc&1OD!?ARu~e-17|ECJBj*{Kd4I;3nN2w4!eEgZ+jLQu5L8XEw|vWPgA zt^#0Lco4wc+#EGEH3lFWjdJA3kD9q>HlpG95}EZ=m1y-ptN)rjt$|Ez*CB`q|1$rgVVB? zrhTuowc{78+xt2VFSK*}&LsW){Unn~lv3vU`0?ZDx~wzj&Yk1v(W4wbe3;(eUJ{9f z1T3Ky+yU+X&gS^%luv8N&-msq-zN6;H<^5HH8r836d;2i zMi$(072p5qZ>Vh8NaE}}Opji}iIlOmc?X+Yw)07OC1VMf&t~%6oz60$?T8IbC+FC= zE6VyM05f|%YwGIi0L*nNl`;TID-fW$T5#c=V<;@j=Em6&k5ZJ9d&7MsWd+;Xe@so= z%X~bXX3}54&3G)#YcF^p0JU^FLZOfeE#2xLjsYt8K48JdxS&uRBOHrUx22Yncy@gS z>8!_S|DTwyyiCjXgSZP}yn>`ylz7@9$3iWtfa7VPmemyj0M^GtgvulQziMM4o~aCT z;opPo+4&R7qYk+;M*}DWVQT;uOChBkB`_)#q+A6+Sfwh=^&|RlY=-e!kMI5ZkKD@C z;6z-a5gX5w6g>yi0i}>q@=$770agVN2uuwG7@cUt@bD?; zK3j0A)Rg|7%OH8oYxFCwW^r%olu$Hy-NXMuhl?5?;8OW$_d zPbP%{KnOr#TKwMNV7j}zJEh->%p}lW?Q_jx?V5KpZV_N`@cndOUtjvnnKP!vrO=Of r0*SSrV$g&__6vQz4|I&^4Jxf00000NkvXXu0mjfT=)$w literal 0 HcmV?d00001 diff --git a/gui_qt/figs/save.png b/gui_qt/figs/save.png new file mode 100644 index 0000000000000000000000000000000000000000..daba865fafd22fa18e7c0488eb699b79d3554170 GIT binary patch literal 1187 zcmV;U1YG-xP)5-PJz!8#8DIjU4h)0S{a*K!i>mJVVCD^kQ5IHGsm^=1>z+FI)Tw&v zz2|;5 zYXiKvXAppke-?cB?jC^K0C4>59N#}M1b8m}xWN9m&Tq?d4;^`$3nP}(pC4rW)qR_5 zKt-??KzML0Bg?XF0WdI@VU185d=w)kA`~mw7?IVLbLY;3D!2sF6tWD`ER0DCNfO?7 zI*{j(x1rU7b}No;C~~s;IMxc|ue?cX@%rWftQD*k1h7Wfy?f^tYwL!tfLf?lf$D8h zjjsf?g7=`NgaKy-HG&AJ3Y9FwBOo3K!0;X~0^TDMCKY^$Bg7#dJRx2{O;{K}*Gh>7 ztf1Os0Gczu1&UJ#X#$l5vNZMt5(iG9=s;dTE04VeOF6W2$O}}5aXkPkPoHKY21CJ!U{_^v8Xy`U*q`vX%#{A#01X5mtL``3Wj}4_b=eEU&o)}BH zcC!Nrol+61khoCqTE#GZvCBZkunzPltylR~F5%(E(lYhTaDB;yE$K=b5S$vobLD22 zD>u8WQCC0Ls$_n}C!#5=1r@`b3$=HqY!|4(sUXN2HLsgB8jo2qp8k93?-T&>g72GH zFe_J90#zZ=ko@OU-}3936D%#>xTpG8Y6Cp=>`tC}VFC}RGg~nb1AK?~_rJly!UDH$ z-J&Q8L>2*RmC}ns$#9hnDF1_1m8Hc<-5*m|${p@{U>9F*?@YhOX2JRd6<}qE@f>YdJGB!_d$W)>^#xG@H%40#K{h zaM6gg)QA+E3nyHlDgdZft7KW$r?@xXX>%H>7ePy{I3CBnBed6yF^ELpk2<)ko3Ayf z4fFOGE. +*/ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +namespace bgslibrary +{ + //template IBGS* createInstance() { return new T; } + //typedef std::map map_ibgs; + + IBGS* get_alg(std::string alg_name) { + map_ibgs map; + map["FrameDifference"] = &createInstance; + map["StaticFrameDifference"] = &createInstance; + map["WeightedMovingMean"] = &createInstance; + map["WeightedMovingVariance"] = &createInstance; +#if CV_MAJOR_VERSION == 2 + map["MixtureOfGaussianV1"] = &createInstance; // only for OpenCV 2.x +#endif + map["MixtureOfGaussianV2"] = &createInstance; + map["AdaptiveBackgroundLearning"] = &createInstance; + map["AdaptiveSelectiveBackgroundLearning"] = &createInstance; +#if CV_MAJOR_VERSION == 2 && CV_MINOR_VERSION >= 4 && CV_SUBMINOR_VERSION >= 3 + map["GMG"] = &createInstance; // only for OpenCV >= 2.4.3 +#endif +#if CV_MAJOR_VERSION == 3 + map["KNN"] = &createInstance; // only on OpenCV 3.x +#endif + map["DPAdaptiveMedian"] = &createInstance; + map["DPGrimsonGMM"] = &createInstance; + map["DPZivkovicAGMM"] = &createInstance; + map["DPMean"] = &createInstance; + map["DPWrenGA"] = &createInstance; + map["DPPratiMediod"] = &createInstance; + map["DPEigenbackground"] = &createInstance; + map["DPTexture"] = &createInstance; + map["T2FGMM_UM"] = &createInstance; + map["T2FGMM_UV"] = &createInstance; + map["T2FMRF_UM"] = &createInstance; + map["T2FMRF_UV"] = &createInstance; + map["FuzzySugenoIntegral"] = &createInstance; + map["FuzzyChoquetIntegral"] = &createInstance; + map["MultiLayer"] = &createInstance; + map["PixelBasedAdaptiveSegmenter"] = &createInstance; + map["LBSimpleGaussian"] = &createInstance; + map["LBFuzzyGaussian"] = &createInstance; + map["LBMixtureOfGaussians"] = &createInstance; + map["LBAdaptiveSOM"] = &createInstance; + map["LBFuzzyAdaptiveSOM"] = &createInstance; + map["LBP_MRF"] = &createInstance; + map["VuMeter"] = &createInstance; + map["KDE"] = &createInstance; + map["IndependentMultimodal"] = &createInstance; + map["MultiCue"] = &createInstance; + map["SigmaDelta"] = &createInstance; + map["SuBSENSE"] = &createInstance; + map["LOBSTER"] = &createInstance; + map["PAWCS"] = &createInstance; + map["TwoPoints"] = &createInstance; + map["ViBe"] = &createInstance; + + return map[alg_name](); + } + + QStringList get_algs_name() + { + QStringList stringList; + stringList.append("FrameDifference"); + stringList.append("StaticFrameDifference"); + stringList.append("WeightedMovingMean"); + stringList.append("WeightedMovingVariance"); +#if CV_MAJOR_VERSION == 2 + stringList.append("MixtureOfGaussianV1"); // only for OpenCV 2.x +#endif + stringList.append("MixtureOfGaussianV2"); + stringList.append("AdaptiveBackgroundLearning"); + stringList.append("AdaptiveSelectiveBackgroundLearning"); +#if CV_MAJOR_VERSION == 2 && CV_MINOR_VERSION >= 4 && CV_SUBMINOR_VERSION >= 3 + stringList.append("GMG"); // only for OpenCV >= 2.4.3 +#endif +#if CV_MAJOR_VERSION == 3 + stringList.append("KNN"); // only on OpenCV 3.x +#endif + stringList.append("DPAdaptiveMedian"); + stringList.append("DPGrimsonGMM"); + stringList.append("DPZivkovicAGMM"); + stringList.append("DPMean"); + stringList.append("DPWrenGA"); + stringList.append("DPPratiMediod"); + stringList.append("DPEigenbackground"); + stringList.append("DPTexture"); + stringList.append("T2FGMM_UM"); + stringList.append("T2FGMM_UV"); + stringList.append("T2FMRF_UM"); + stringList.append("T2FMRF_UV"); + stringList.append("FuzzySugenoIntegral"); + stringList.append("FuzzyChoquetIntegral"); + stringList.append("MultiLayer"); + stringList.append("PixelBasedAdaptiveSegmenter"); + stringList.append("LBSimpleGaussian"); + stringList.append("LBFuzzyGaussian"); + stringList.append("LBMixtureOfGaussians"); + stringList.append("LBAdaptiveSOM"); + stringList.append("LBFuzzyAdaptiveSOM"); + stringList.append("LBP_MRF"); + stringList.append("VuMeter"); + stringList.append("KDE"); + stringList.append("IndependentMultimodal"); + stringList.append("MultiCue"); + stringList.append("SigmaDelta"); + stringList.append("SuBSENSE"); + stringList.append("LOBSTER"); + stringList.append("PAWCS"); + stringList.append("TwoPoints"); + stringList.append("ViBe"); + return stringList; + } +} + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + //QDir applicationPath(QCoreApplication::applicationDirPath()); + fileName = QDir(".").filePath("dataset/video.avi"); + //fileName = applicationPath.absolutePath() + "dataset"; + ui->lineEdit_inputdata->setText(fileName); + //fileName = ui->lineEdit_inputdata->text(); + timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(startCapture())); + QStringListModel* listModel = new QStringListModel(bgslibrary::get_algs_name(), NULL); + listModel->sort(0); + ui->listView_algorithms->setModel(listModel); + QModelIndex index = listModel->index(0); + ui->listView_algorithms->selectionModel()->select(index, QItemSelectionModel::Select); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::on_actionExit_triggered() +{ + this->close(); +} + +void MainWindow::on_pushButton_inputdata_clicked() +{ + QFileDialog dialog(this); + + if (ui->checkBox_imageseq->isChecked()) + dialog.setFileMode(QFileDialog::Directory); + else + dialog.setFileMode(QFileDialog::ExistingFile); + //dialog.setFileMode(QFileDialog::AnyFile); + + dialog.exec(); + QStringList list = dialog.selectedFiles(); + /* + for(int index = 0; index < list.length(); index++) + std::cout << list.at(index).toStdString() << std::endl; + */ + if (list.size() > 0) + { + fileName = list.at(0); + ui->lineEdit_inputdata->setText(fileName); + } +} + +void MainWindow::on_pushButton_out_in_clicked() +{ + QFileDialog dialog(this); + dialog.setDirectory("."); + dialog.setFileMode(QFileDialog::Directory); + dialog.exec(); + QStringList list = dialog.selectedFiles(); + if (list.size() > 0) + { + fileName = list.at(0); + ui->lineEdit_out_in->setText(fileName); + } +} + +void MainWindow::on_pushButton_out_fg_clicked() +{ + QFileDialog dialog(this); + dialog.setDirectory("."); + dialog.setFileMode(QFileDialog::Directory); + dialog.exec(); + QStringList list = dialog.selectedFiles(); + if (list.size() > 0) + { + fileName = list.at(0); + ui->lineEdit_out_fg->setText(fileName); + } +} + +void MainWindow::on_pushButton_out_bg_clicked() +{ + QFileDialog dialog(this); + dialog.setDirectory("."); + dialog.setFileMode(QFileDialog::Directory); + dialog.exec(); + QStringList list = dialog.selectedFiles(); + if (list.size() > 0) + { + fileName = list.at(0); + ui->lineEdit_out_bg->setText(fileName); + } +} + +void MainWindow::on_pushButton_start_clicked() +{ + useCamera = false; + useVideo = false; + useSequence = false; + + if (ui->checkBox_webcamera->isChecked()) + useCamera = true; + else + { + if (ui->checkBox_imageseq->isChecked()) + useSequence = true; + else + useVideo = true; + } + + if (!timer->isActive() && setUpCapture()) + { + createBGS(); + startTimer(); + } +} + +void MainWindow::on_pushButton_stop_clicked() +{ + stopTimer(); +} + +void MainWindow::createBGS() +{ + QString algorithm_name = getSelectedAlgorithmName(); + bgs = bgslibrary::get_alg(algorithm_name.toStdString()); + bgs->setShowOutput(false); +} + +void MainWindow::destroyBGS() +{ + delete bgs; +} + +void MainWindow::startTimer() +{ + std::cout << "startTimer()" << std::endl; + + ui->progressBar->setValue(0); + setFrameNumber(0); + frameNumber_aux = 0; + timer->start(33); + + // disable options +} + +void MainWindow::stopTimer() +{ + if (!timer->isActive()) + return; + + std::cout << "stopTimer()" << std::endl; + + timer->stop(); + //setFrameNumber(0); + //ui->progressBar->setValue(0); + + destroyBGS(); + + if (useCamera || useVideo) + capture.release(); + + // enable options +} + +void MainWindow::setFrameNumber(long long _frameNumber) +{ + //std::cout << "setFrameNumber()" << std::endl; + frameNumber = _frameNumber; + QString txt_frameNumber = QString::fromStdString(its(frameNumber)); + ui->label_framenumber_txt->setText(txt_frameNumber); +} + +bool MainWindow::setUpCapture() +{ + capture_length = 0; + + if (useCamera && !setUpCamera()) { + std::cout << "Cannot initialize webcamera!" << std::endl; + return false; + } + + if (useVideo && !setUpVideo()) { + std::cout << "Cannot open video file " << fileName.toStdString() << std::endl; + return false; + } + + if (useSequence && !setUpSequence()) { + std::cout << "Cannot process images at " << fileName.toStdString() << std::endl; + return false; + } + + if (useCamera || useVideo) { + int capture_fps = capture.get(CV_CAP_PROP_FPS); + std::cout << "capture_fps: " << capture_fps << std::endl; + } + + if (useVideo) { + capture_length = capture.get(CV_CAP_PROP_FRAME_COUNT); + std::cout << "capture_length: " << capture_length << std::endl; + } + + std::cout << "OK!" << std::endl; + return true; +} + +void MainWindow::startCapture() +{ + //std::cout << "startCapture()" << std::endl; + setFrameNumber(frameNumber + 1); + cv::Mat cv_frame; + + if (useCamera || useVideo) + capture >> cv_frame; + + if (useSequence && (frameNumber - 1) < entryList.length()) + { + QString file = entryList.at(frameNumber - 1); + QString filePath = QDir(fileName).filePath(file); + + std::cout << "Processing: " << filePath.toStdString() << std::endl; + if (fileExists(filePath)) + cv_frame = cv::imread(filePath.toStdString()); + } + + if (cv_frame.empty()) + { + stopTimer(); + return; + } + + if (frameNumber == 1) + { + int frame_width = cv_frame.size().width; + int frame_height = cv_frame.size().height; + ui->label_frameresw_txt->setText(QString::fromStdString(its(frame_width))); + ui->label_frameresh_txt->setText(QString::fromStdString(its(frame_height))); + } + + if (useVideo && capture_length > 0) + { + double perc = (double(frameNumber) / double(capture_length)) * 100.0; + //std::cout << "perc: " << perc << std::endl; + ui->progressBar->setValue(perc); + } + + int startAt = ui->spinBox_startat->value(); + if (startAt > 0 && frameNumber < startAt) + { + timer->setInterval(1); + return; + } + else + timer->setInterval(33); + + int stopAt = ui->spinBox_stopat->value(); + if (stopAt > 0 && frameNumber >= stopAt) + { + stopTimer(); + return; + } + + cv::Mat cv_frame_small; + cv::resize(cv_frame, cv_frame_small, cv::Size(250, 250)); + + QImage qt_frame = cv2qimage(cv_frame_small); + ui->label_img_in->setPixmap(QPixmap::fromImage(qt_frame, Qt::MonoOnly)); + + processFrame(cv_frame); +} + +QImage MainWindow::cv2qimage(const cv::Mat &cv_frame) +{ + if (cv_frame.channels() == 3) + return Mat2QImage(cv_frame); + else + return GrayMat2QImage(cv_frame); +} + +void MainWindow::processFrame(const cv::Mat &cv_frame) +{ + cv::Mat cv_fg; + cv::Mat cv_bg; + tic(); + bgs->process(cv_frame, cv_fg, cv_bg); + toc(); + ui->label_fps_txt->setText(QString::fromStdString(its(fps()))); + + cv::Mat cv_fg_small; + cv::resize(cv_fg, cv_fg_small, cv::Size(250, 250)); + QImage qt_fg = cv2qimage(cv_fg_small); + ui->label_img_fg->setPixmap(QPixmap::fromImage(qt_fg, Qt::MonoOnly)); + + cv::Mat cv_bg_small; + cv::resize(cv_bg, cv_bg_small, cv::Size(250, 250)); + QImage qt_bg = cv2qimage(cv_bg_small); + ui->label_img_bg->setPixmap(QPixmap::fromImage(qt_bg, Qt::MonoOnly)); + + if (ui->checkBox_save_im->isChecked() || ui->checkBox_save_fg->isChecked() || ui->checkBox_save_bg->isChecked()) + { + if (ui->checkBox_kfn->isChecked()) + frameNumber_aux = frameNumber; + else + frameNumber_aux = frameNumber_aux + 1; + } + if (ui->checkBox_save_im->isChecked()) + { + QString out_im_path = ui->lineEdit_out_in->text(); + QString out_im_file = QDir(out_im_path).filePath(QString::number(frameNumber_aux) + ".png"); + cv::imwrite(out_im_file.toStdString(), cv_frame); + } + if (ui->checkBox_save_fg->isChecked()) + { + QString out_im_path = ui->lineEdit_out_fg->text(); + QString out_im_file = QDir(out_im_path).filePath(QString::number(frameNumber_aux) + ".png"); + cv::imwrite(out_im_file.toStdString(), cv_fg); + } + if (ui->checkBox_save_bg->isChecked()) + { + QString out_im_path = ui->lineEdit_out_bg->text(); + QString out_im_file = QDir(out_im_path).filePath(QString::number(frameNumber) + ".png"); + cv::imwrite(out_im_file.toStdString(), cv_bg); + } +} + +void MainWindow::tic() +{ + duration = static_cast(cv::getTickCount()); +} + +void MainWindow::toc() +{ + duration = (static_cast(cv::getTickCount()) - duration) / cv::getTickFrequency(); + //std::cout << "time(sec):" << std::fixed << std::setprecision(6) << duration << std::endl; + //std::cout << duration << std::endl; +} + +double MainWindow::fps() +{ + //double fps = frameNumber / duration; + double fps = 1 / duration; + //std::cout << "Estimated frames per second : " << fps << std::endl; + return fps; +} + +bool MainWindow::setUpCamera() +{ + int cameraIndex = ui->spinBox_webcamera->value(); + std::cout << "Camera index: " << cameraIndex << std::endl; + + capture.open(cameraIndex); + return capture.isOpened(); +} + +bool MainWindow::setUpVideo() +{ + std::string videoFileName = fileName.toStdString(); + std::cout << "Openning: " << videoFileName << std::endl; + capture.open(videoFileName.c_str()); + return capture.isOpened(); +} + +bool MainWindow::setUpSequence() +{ + std::cout << "Directory path: " << fileName.toStdString() << std::endl; + if (QDir(fileName).exists()) + { + QDir dir(fileName); + QStringList filters; + filters << "*.png" << "*.jpg" << "*.bmp" << "*.gif"; + dir.setNameFilters(filters); + //entryList = dir.entryList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst); + //entryList = dir.entryList(QDir::Filter::Files, QDir::SortFlag::NoSort); + dir.setFilter(QDir::Files | QDir::NoSymLinks); + dir.setSorting(QDir::NoSort); // will sort manually with std::sort + //dir.setSorting(QDir::LocaleAware); + entryList = dir.entryList(); + + std::cout << entryList.length() << std::endl; + if (entryList.length() == 0) + { + QMessageBox::warning(this, "Warning", "No image found! (png, jpg, bmp, gif)"); + return false; + } + + QCollator collator; + collator.setNumericMode(true); + std::sort( + entryList.begin(), + entryList.end(), + [&collator](const QString &file1, const QString &file2) + { + return collator.compare(file1, file2) < 0; + }); + + // for(int i = 0; i < entryList.length(); i++) + // { + // QString file = entryList.at(i); + // std::cout << file.toStdString() << std::endl; + // } + return true; + } + else + { + QMessageBox::warning(this, "Warning", "Directory path doesn't exist!"); + return false; + } +} + +QString MainWindow::getSelectedAlgorithmName() +{ + QModelIndex index = ui->listView_algorithms->currentIndex(); + QString algorithm_name = index.data(Qt::DisplayRole).toString(); + return algorithm_name; +} + +void MainWindow::on_listView_algorithms_doubleClicked(const QModelIndex &index) +{ + QString algorithm_name = index.data(Qt::DisplayRole).toString(); + std::cout << "Selected algorithm: " << algorithm_name.toStdString() << std::endl; + + // CodeEditor editor; + // editor.setWindowTitle(QObject::tr("Code Editor Example")); + // editor.show(); + + QString configFileName = QDir(".").filePath("config/" + algorithm_name + ".xml"); + std::cout << "Looking for: " << configFileName.toStdString() << std::endl; + + if (fileExists(configFileName)) + { + textEditor.loadFile(configFileName); + textEditor.show(); + } + else + { + QMessageBox::warning(this, "Warning", "XML configuration file not found!\nPlease run the algorithm first!"); + return; + } +} diff --git a/gui_qt/mainwindow.h b/gui_qt/mainwindow.h new file mode 100644 index 0000000..a834882 --- /dev/null +++ b/gui_qt/mainwindow.h @@ -0,0 +1,110 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "qt_utils.h" +#include "texteditor.h" +#include "../package_bgs/bgslibrary.h" + +namespace bgslibrary +{ + template IBGS * createInstance() { return new T; } + typedef std::map map_ibgs; + + IBGS* get_alg(std::string alg_name); + QStringList get_algs_name(); +} + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + + private slots: + void on_actionExit_triggered(); + + void on_pushButton_inputdata_clicked(); + + void on_pushButton_start_clicked(); + + void on_pushButton_stop_clicked(); + + void on_pushButton_out_in_clicked(); + + void on_pushButton_out_fg_clicked(); + + void on_pushButton_out_bg_clicked(); + + void on_listView_algorithms_doubleClicked(const QModelIndex &index); + +private: + Ui::MainWindow *ui; + QString fileName; + cv::VideoCapture capture; + bool useCamera = false; + bool useVideo = false; + bool useSequence = false; + QStringList entryList; + long long frameNumber = 0; + long long frameNumber_aux = 0; + bool setUpCapture(); + bool setUpCamera(); + bool setUpVideo(); + bool setUpSequence(); + QString getSelectedAlgorithmName(); + QTimer* timer; + void startTimer(); + void stopTimer(); + void setFrameNumber(long long frameNumber); + QImage cv2qimage(const cv::Mat &cv_frame); + void processFrame(const cv::Mat &cv_frame); + IBGS *bgs; + void createBGS(); + void destroyBGS(); + double duration = 0; + void tic(); + void toc(); + double fps(); + int capture_length = 0; + TextEditor textEditor; + + public slots: + void startCapture(); +}; diff --git a/gui_qt/mainwindow.ui b/gui_qt/mainwindow.ui new file mode 100644 index 0000000..75e9e61 --- /dev/null +++ b/gui_qt/mainwindow.ui @@ -0,0 +1,631 @@ + + + MainWindow + + + + 0 + 0 + 1070 + 559 + + + + BGSLibrary QT GUI + + + + + + 10 + 10 + 251 + 16 + + + + Algorithms + + + + + + 10 + 30 + 256 + 361 + + + + QAbstractItemView::NoEditTriggers + + + + + + 280 + 10 + 601 + 16 + + + + Input (specify a video file or a directory path containing the sequence of images) + + + + + + 280 + 30 + 721 + 22 + + + + ./dataset/video.avi + + + + + + 1010 + 30 + 40 + 21 + + + + ... + + + + + + 280 + 60 + 131 + 20 + + + + Use web camera + + + + + + 410 + 60 + 42 + 20 + + + + + + + 470 + 60 + 151 + 20 + + + + Sequence of images? + + + + + + 945 + 60 + 55 + 20 + + + + Start at: + + + + + + 1000 + 60 + 50 + 20 + + + + 9999 + + + + + + 945 + 85 + 55 + 20 + + + + Stop at: + + + + + + 1000 + 85 + 50 + 20 + + + + 9999 + + + + + + 280 + 120 + 250 + 16 + + + + Input + + + Qt::AlignCenter + + + + + + 280 + 140 + 250 + 250 + + + + QFrame::Box + + + IMG_INPUT + + + Qt::AlignCenter + + + + + + 540 + 140 + 250 + 250 + + + + QFrame::Box + + + IMG_FOREGROUND + + + Qt::AlignCenter + + + + + + 800 + 140 + 250 + 250 + + + + QFrame::Box + + + IMG_BACKGROUND + + + Qt::AlignCenter + + + + + + 540 + 120 + 250 + 16 + + + + Foreground mask + + + Qt::AlignCenter + + + + + + 800 + 120 + 250 + 16 + + + + Background model + + + Qt::AlignCenter + + + + + + 280 + 400 + 52 + 20 + + + + Save + + + + + + 540 + 400 + 52 + 20 + + + + Save + + + + + + 800 + 400 + 52 + 20 + + + + Save + + + + + + 960 + 470 + 93 + 23 + + + + Stop + + + + + + 850 + 470 + 93 + 23 + + + + Start + + + + + + 280 + 470 + 550 + 23 + + + + 0 + + + + + + 10 + 400 + 121 + 16 + + + + Estimated FPS: + + + + + + 210 + 400 + 55 + 16 + + + + Qt::LeftToRight + + + QFrame::Box + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 420 + 121 + 16 + + + + Frame number: + + + + + + 210 + 420 + 55 + 16 + + + + Qt::LeftToRight + + + QFrame::Box + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 209 + 470 + 61 + 23 + + + + Progress: + + + + + + 10 + 440 + 121 + 16 + + + + Frame resolution: + + + + + + 140 + 440 + 55 + 16 + + + + Qt::LeftToRight + + + QFrame::Box + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 210 + 440 + 55 + 16 + + + + Qt::LeftToRight + + + QFrame::Box + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 193 + 440 + 21 + 16 + + + + x + + + Qt::AlignCenter + + + + + + 348 + 400 + 140 + 20 + + + + ./output/in/ + + + + + + 490 + 400 + 40 + 20 + + + + ... + + + + + + 608 + 400 + 140 + 20 + + + + ./output/fg/ + + + + + + 750 + 400 + 40 + 20 + + + + ... + + + + + + 1010 + 400 + 40 + 20 + + + + ... + + + + + + 868 + 400 + 140 + 20 + + + + ./output/bg/ + + + + + + 280 + 420 + 141 + 20 + + + + Keep frame number + + + true + + + + + + + 0 + 0 + 1070 + 26 + + + + + BGSLibrary + + + + + + + + + Exit + + + + + + + diff --git a/gui_qt/qt_utils.cpp b/gui_qt/qt_utils.cpp new file mode 100644 index 0000000..2464d78 --- /dev/null +++ b/gui_qt/qt_utils.cpp @@ -0,0 +1,74 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ + +#include "qt_utils.h" + +QImage GrayMat2QImage(cv::Mat const& src) { + cv::Mat temp; + src.copyTo(temp); + QImage dest((const uchar *)temp.data, temp.cols, temp.rows, temp.step, QImage::Format_Indexed8); + dest.bits(); + return dest; +} +QImage Mat2QImage(cv::Mat const& src) { + cv::Mat temp; + cvtColor(src, temp, CV_BGR2RGB); + QImage dest((const uchar *)temp.data, temp.cols, temp.rows, temp.step, QImage::Format_RGB888); + dest.bits(); + return dest; +} +cv::Mat QImage2Mat(QImage const& src) { + cv::Mat tmp(src.height(), src.width(), CV_8UC3, (uchar*)src.bits(), src.bytesPerLine()); + cv::Mat result; + cvtColor(tmp, result, CV_RGB2BGR); + return result; +} + +QString base64_encode(const QString string) { + QByteArray ba; + ba.append(string); + return ba.toBase64(); +} +QString base64_decode(const QString string) { + QByteArray ba; + ba.append(string); + return QByteArray::fromBase64(ba); +} +QString md5_encode(const QString string) { + QByteArray ba; + ba.append(string); + return QString(QCryptographicHash::hash((ba), QCryptographicHash::Md5).toHex()); +} + +int sti(const std::string &s) { + int i; + std::stringstream ss; + ss << s; + ss >> i; + return i; +} +std::string its(int i) { + std::stringstream ss; + ss << i; + return ss.str(); +} + +bool fileExists(QString path) { + QFileInfo check_file(path); + // check if file exists and if yes: Is it really a file and no directory? + return check_file.exists() && check_file.isFile(); +} diff --git a/gui_qt/qt_utils.h b/gui_qt/qt_utils.h new file mode 100644 index 0000000..84128c5 --- /dev/null +++ b/gui_qt/qt_utils.h @@ -0,0 +1,99 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +QImage GrayMat2QImage(cv::Mat const& src); +QImage Mat2QImage(cv::Mat const& src); +cv::Mat QImage2Mat(QImage const& src); + +QString base64_encode(const QString string); +QString base64_decode(const QString string); +QString md5_encode(const QString); + +int sti(const std::string &s); +std::string its(int i); + +bool fileExists(QString path); + +/* +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class FileDialog : public QFileDialog +{ + Q_OBJECT + public: + explicit FileDialog(QWidget *parent = Q_NULLPTR) + : QFileDialog(parent) + { + setOption(QFileDialog::DontUseNativeDialog); + setFileMode(QFileDialog::Directory); + //setFileMode(QFileDialog::ExistingFiles); + //setFileMode(QFileDialog::Directory|QFileDialog::ExistingFiles); + for (auto *pushButton : findChildren()) { + qDebug() << pushButton->text(); + if (pushButton->text() == "&Open" || pushButton->text() == "&Choose") { + openButton = pushButton; + break; + } + } + disconnect(openButton, SIGNAL(clicked(bool))); + connect(openButton, &QPushButton::clicked, this, &FileDialog::openClicked); + treeView = findChild(); + } + + QStringList selected() const + { + return selectedFilePaths; + } + + public slots: + void openClicked() + { + selectedFilePaths.clear(); + qDebug() << treeView->selectionModel()->selection(); + for (const auto& modelIndex : treeView->selectionModel()->selectedIndexes()) { + qDebug() << modelIndex.column(); + if (modelIndex.column() == 0) + selectedFilePaths.append(directory().absolutePath() + modelIndex.data().toString()); + } + emit filesSelected(selectedFilePaths); + hide(); + qDebug() << selectedFilePaths; + } + + private: + QTreeView *treeView; + QPushButton *openButton; + QStringList selectedFilePaths; +}; +*/ diff --git a/gui_qt/texteditor.cpp b/gui_qt/texteditor.cpp new file mode 100644 index 0000000..80f45e1 --- /dev/null +++ b/gui_qt/texteditor.cpp @@ -0,0 +1,320 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ + +#include + +#include "texteditor.h" + +TextEditor::TextEditor() + : textEdit(new QPlainTextEdit) +{ + setCentralWidget(textEdit); + + createActions(); + createStatusBar(); + + readSettings(); + + connect(textEdit->document(), &QTextDocument::contentsChanged, + this, &TextEditor::documentWasModified); + +#ifndef QT_NO_SESSIONMANAGER + QGuiApplication::setFallbackSessionManagementEnabled(false); + connect(qApp, &QGuiApplication::commitDataRequest, + this, &TextEditor::commitData); +#endif + + setCurrentFile(QString()); + setUnifiedTitleAndToolBarOnMac(true); +} + +void TextEditor::closeEvent(QCloseEvent *event) +{ + if (maybeSave()) { + writeSettings(); + event->accept(); + } + else { + event->ignore(); + } +} + +void TextEditor::newFile() +{ + if (maybeSave()) { + textEdit->clear(); + setCurrentFile(QString()); + } +} + +void TextEditor::open() +{ + if (maybeSave()) { + QString fileName = QFileDialog::getOpenFileName(this); + if (!fileName.isEmpty()) + loadFile(fileName); + } +} + +bool TextEditor::save() +{ + if (curFile.isEmpty()) { + return saveAs(); + } + else { + return saveFile(curFile); + } +} + +bool TextEditor::saveAs() +{ + QFileDialog dialog(this); + dialog.setWindowModality(Qt::WindowModal); + dialog.setAcceptMode(QFileDialog::AcceptSave); + if (dialog.exec() != QDialog::Accepted) + return false; + return saveFile(dialog.selectedFiles().first()); +} + +void TextEditor::about() +{ + QMessageBox::about(this, tr("About BGSLibrary"), + tr("The BGSLibrary provides an easy-to-use framework " + "to perform foreground-background separation in videos.")); +} + +void TextEditor::documentWasModified() +{ + setWindowModified(textEdit->document()->isModified()); +} + +void TextEditor::createActions() +{ + + QMenu *fileMenu = menuBar()->addMenu(tr("&File")); + QToolBar *fileToolBar = addToolBar(tr("File")); + const QIcon newIcon = QIcon::fromTheme("document-new", QIcon(":/figs/new.png")); + QAction *newAct = new QAction(newIcon, tr("&New"), this); + newAct->setShortcuts(QKeySequence::New); + newAct->setStatusTip(tr("Create a new file")); + connect(newAct, &QAction::triggered, this, &TextEditor::newFile); + fileMenu->addAction(newAct); + fileToolBar->addAction(newAct); + + const QIcon openIcon = QIcon::fromTheme("document-open", QIcon(":/figs/open.png")); + QAction *openAct = new QAction(openIcon, tr("&Open..."), this); + openAct->setShortcuts(QKeySequence::Open); + openAct->setStatusTip(tr("Open an existing file")); + connect(openAct, &QAction::triggered, this, &TextEditor::open); + fileMenu->addAction(openAct); + fileToolBar->addAction(openAct); + + const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(":/figs/save.png")); + QAction *saveAct = new QAction(saveIcon, tr("&Save"), this); + saveAct->setShortcuts(QKeySequence::Save); + saveAct->setStatusTip(tr("Save the document to disk")); + connect(saveAct, &QAction::triggered, this, &TextEditor::save); + fileMenu->addAction(saveAct); + fileToolBar->addAction(saveAct); + + const QIcon saveAsIcon = QIcon::fromTheme("document-save-as"); + QAction *saveAsAct = fileMenu->addAction(saveAsIcon, tr("Save &As..."), this, &TextEditor::saveAs); + saveAsAct->setShortcuts(QKeySequence::SaveAs); + saveAsAct->setStatusTip(tr("Save the document under a new name")); + + + fileMenu->addSeparator(); + + const QIcon exitIcon = QIcon::fromTheme("application-exit"); + QAction *exitAct = fileMenu->addAction(exitIcon, tr("E&xit"), this, &QWidget::close); + exitAct->setShortcuts(QKeySequence::Quit); + exitAct->setStatusTip(tr("Exit the application")); + + QMenu *editMenu = menuBar()->addMenu(tr("&Edit")); + QToolBar *editToolBar = addToolBar(tr("Edit")); +#ifndef QT_NO_CLIPBOARD + const QIcon cutIcon = QIcon::fromTheme("edit-cut", QIcon(":/figs/cut.png")); + QAction *cutAct = new QAction(cutIcon, tr("Cu&t"), this); + cutAct->setShortcuts(QKeySequence::Cut); + cutAct->setStatusTip(tr("Cut the current selection's contents to the " + "clipboard")); + connect(cutAct, &QAction::triggered, textEdit, &QPlainTextEdit::cut); + editMenu->addAction(cutAct); + editToolBar->addAction(cutAct); + + const QIcon copyIcon = QIcon::fromTheme("edit-copy", QIcon(":/figs/copy.png")); + QAction *copyAct = new QAction(copyIcon, tr("&Copy"), this); + copyAct->setShortcuts(QKeySequence::Copy); + copyAct->setStatusTip(tr("Copy the current selection's contents to the " + "clipboard")); + connect(copyAct, &QAction::triggered, textEdit, &QPlainTextEdit::copy); + editMenu->addAction(copyAct); + editToolBar->addAction(copyAct); + + const QIcon pasteIcon = QIcon::fromTheme("edit-paste", QIcon(":/figs/paste.png")); + QAction *pasteAct = new QAction(pasteIcon, tr("&Paste"), this); + pasteAct->setShortcuts(QKeySequence::Paste); + pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current " + "selection")); + connect(pasteAct, &QAction::triggered, textEdit, &QPlainTextEdit::paste); + editMenu->addAction(pasteAct); + editToolBar->addAction(pasteAct); + + menuBar()->addSeparator(); + +#endif // !QT_NO_CLIPBOARD + + QMenu *helpMenu = menuBar()->addMenu(tr("&Help")); + QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &TextEditor::about); + aboutAct->setStatusTip(tr("Show the application's About box")); + + + QAction *aboutQtAct = helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); + aboutQtAct->setStatusTip(tr("Show the Qt library's About box")); + +#ifndef QT_NO_CLIPBOARD + cutAct->setEnabled(false); + copyAct->setEnabled(false); + connect(textEdit, &QPlainTextEdit::copyAvailable, cutAct, &QAction::setEnabled); + connect(textEdit, &QPlainTextEdit::copyAvailable, copyAct, &QAction::setEnabled); +#endif // !QT_NO_CLIPBOARD +} + +void TextEditor::createStatusBar() +{ + statusBar()->showMessage(tr("Ready")); +} + +void TextEditor::readSettings() +{ + QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName()); + const QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray(); + if (geometry.isEmpty()) { + const QRect availableGeometry = QApplication::desktop()->availableGeometry(this); + resize(availableGeometry.width() / 3, availableGeometry.height() / 2); + move((availableGeometry.width() - width()) / 2, + (availableGeometry.height() - height()) / 2); + } + else { + restoreGeometry(geometry); + } +} + +void TextEditor::writeSettings() +{ + QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName()); + settings.setValue("geometry", saveGeometry()); +} + +bool TextEditor::maybeSave() +{ + if (!textEdit->document()->isModified()) + return true; + const QMessageBox::StandardButton ret + = QMessageBox::warning(this, tr("Application"), + tr("The document has been modified.\n" + "Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + switch (ret) { + case QMessageBox::Save: + return save(); + case QMessageBox::Cancel: + return false; + default: + break; + } + return true; +} + +void TextEditor::loadFile(const QString &fileName) +{ + QFile file(fileName); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(this, tr("Application"), + tr("Cannot read file %1:\n%2.") + .arg(QDir::toNativeSeparators(fileName), file.errorString())); + return; + } + + QTextStream in(&file); +#ifndef QT_NO_CURSOR + QApplication::setOverrideCursor(Qt::WaitCursor); +#endif + textEdit->setPlainText(in.readAll()); +#ifndef QT_NO_CURSOR + QApplication::restoreOverrideCursor(); +#endif + + setCurrentFile(fileName); + statusBar()->showMessage(tr("File loaded"), 2000); +} + +bool TextEditor::saveFile(const QString &fileName) +{ + QFile file(fileName); + if (!file.open(QFile::WriteOnly | QFile::Text)) { + QMessageBox::warning(this, tr("Application"), + tr("Cannot write file %1:\n%2.") + .arg(QDir::toNativeSeparators(fileName), + file.errorString())); + return false; + } + + QTextStream out(&file); +#ifndef QT_NO_CURSOR + QApplication::setOverrideCursor(Qt::WaitCursor); +#endif + out << textEdit->toPlainText(); +#ifndef QT_NO_CURSOR + QApplication::restoreOverrideCursor(); +#endif + + setCurrentFile(fileName); + statusBar()->showMessage(tr("File saved"), 2000); + return true; +} + +void TextEditor::setCurrentFile(const QString &fileName) +{ + curFile = fileName; + textEdit->document()->setModified(false); + setWindowModified(false); + + QString shownName = curFile; + if (curFile.isEmpty()) + shownName = "untitled.txt"; + setWindowFilePath(shownName); +} + +QString TextEditor::strippedName(const QString &fullFileName) +{ + return QFileInfo(fullFileName).fileName(); +} +#ifndef QT_NO_SESSIONMANAGER +void TextEditor::commitData(QSessionManager &manager) +{ + if (manager.allowsInteraction()) { + if (!maybeSave()) + manager.cancel(); + } + else { + // Non-interactive: save without asking + if (textEdit->document()->isModified()) + save(); + } +} +#endif diff --git a/gui_qt/texteditor.h b/gui_qt/texteditor.h new file mode 100644 index 0000000..c0d5d8f --- /dev/null +++ b/gui_qt/texteditor.h @@ -0,0 +1,64 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#ifndef TEXTEDITOR_H +#define TEXTEDITOR_H + +#include + +class QAction; +class QMenu; +class QPlainTextEdit; +class QSessionManager; + +class TextEditor : public QMainWindow +{ + Q_OBJECT + +public: + TextEditor(); + + void loadFile(const QString &fileName); + +protected: + void closeEvent(QCloseEvent *event) override; + + private slots: + void newFile(); + void open(); + bool save(); + bool saveAs(); + void about(); + void documentWasModified(); +#ifndef QT_NO_SESSIONMANAGER + void commitData(QSessionManager &); +#endif + +private: + void createActions(); + void createStatusBar(); + void readSettings(); + void writeSettings(); + bool maybeSave(); + bool saveFile(const QString &fileName); + void setCurrentFile(const QString &fileName); + QString strippedName(const QString &fullFileName); + + QPlainTextEdit *textEdit; + QString curFile; +}; + +#endif // TEXTEDITOR_H diff --git a/gui_qt/ui_mainwindow.h b/gui_qt/ui_mainwindow.h new file mode 100644 index 0000000..eb3f468 --- /dev/null +++ b/gui_qt/ui_mainwindow.h @@ -0,0 +1,306 @@ +/******************************************************************************** +** Form generated from reading UI file 'mainwindow.ui' +** +** Created by: Qt User Interface Compiler version 5.6.2 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef UI_MAINWINDOW_H +#define UI_MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_MainWindow +{ +public: + QAction *actionExit; + QWidget *centralWidget; + QLabel *label_algorithms; + QListView *listView_algorithms; + QLabel *label_inputdata; + QLineEdit *lineEdit_inputdata; + QPushButton *pushButton_inputdata; + QCheckBox *checkBox_webcamera; + QSpinBox *spinBox_webcamera; + QCheckBox *checkBox_imageseq; + QLabel *label_startat; + QSpinBox *spinBox_startat; + QLabel *label_stopat; + QSpinBox *spinBox_stopat; + QLabel *label_input; + QLabel *label_img_in; + QLabel *label_img_fg; + QLabel *label_img_bg; + QLabel *label_foreground; + QLabel *label_background; + QCheckBox *checkBox_save_im; + QCheckBox *checkBox_save_fg; + QCheckBox *checkBox_save_bg; + QPushButton *pushButton_stop; + QPushButton *pushButton_start; + QProgressBar *progressBar; + QLabel *label_exectime; + QLabel *label_fps_txt; + QLabel *label_framenumber; + QLabel *label_framenumber_txt; + QLabel *label_status; + QLabel *label_frameres; + QLabel *label_frameresw_txt; + QLabel *label_frameresh_txt; + QLabel *label_frameres_2; + QLineEdit *lineEdit_out_in; + QPushButton *pushButton_out_in; + QLineEdit *lineEdit_out_fg; + QPushButton *pushButton_out_fg; + QPushButton *pushButton_out_bg; + QLineEdit *lineEdit_out_bg; + QCheckBox *checkBox_kfn; + QMenuBar *menuBar; + QMenu *menuBGSLibrary; + QStatusBar *statusBar; + + void setupUi(QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + MainWindow->setObjectName(QStringLiteral("MainWindow")); + MainWindow->resize(1070, 559); + actionExit = new QAction(MainWindow); + actionExit->setObjectName(QStringLiteral("actionExit")); + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QStringLiteral("centralWidget")); + label_algorithms = new QLabel(centralWidget); + label_algorithms->setObjectName(QStringLiteral("label_algorithms")); + label_algorithms->setGeometry(QRect(10, 10, 251, 16)); + listView_algorithms = new QListView(centralWidget); + listView_algorithms->setObjectName(QStringLiteral("listView_algorithms")); + listView_algorithms->setGeometry(QRect(10, 30, 256, 361)); + listView_algorithms->setEditTriggers(QAbstractItemView::NoEditTriggers); + label_inputdata = new QLabel(centralWidget); + label_inputdata->setObjectName(QStringLiteral("label_inputdata")); + label_inputdata->setGeometry(QRect(280, 10, 601, 16)); + lineEdit_inputdata = new QLineEdit(centralWidget); + lineEdit_inputdata->setObjectName(QStringLiteral("lineEdit_inputdata")); + lineEdit_inputdata->setGeometry(QRect(280, 30, 721, 22)); + pushButton_inputdata = new QPushButton(centralWidget); + pushButton_inputdata->setObjectName(QStringLiteral("pushButton_inputdata")); + pushButton_inputdata->setGeometry(QRect(1010, 30, 40, 21)); + checkBox_webcamera = new QCheckBox(centralWidget); + checkBox_webcamera->setObjectName(QStringLiteral("checkBox_webcamera")); + checkBox_webcamera->setGeometry(QRect(280, 60, 131, 20)); + spinBox_webcamera = new QSpinBox(centralWidget); + spinBox_webcamera->setObjectName(QStringLiteral("spinBox_webcamera")); + spinBox_webcamera->setGeometry(QRect(410, 60, 42, 20)); + checkBox_imageseq = new QCheckBox(centralWidget); + checkBox_imageseq->setObjectName(QStringLiteral("checkBox_imageseq")); + checkBox_imageseq->setGeometry(QRect(470, 60, 151, 20)); + label_startat = new QLabel(centralWidget); + label_startat->setObjectName(QStringLiteral("label_startat")); + label_startat->setGeometry(QRect(945, 60, 55, 20)); + spinBox_startat = new QSpinBox(centralWidget); + spinBox_startat->setObjectName(QStringLiteral("spinBox_startat")); + spinBox_startat->setGeometry(QRect(1000, 60, 50, 20)); + spinBox_startat->setMaximum(9999); + label_stopat = new QLabel(centralWidget); + label_stopat->setObjectName(QStringLiteral("label_stopat")); + label_stopat->setGeometry(QRect(945, 85, 55, 20)); + spinBox_stopat = new QSpinBox(centralWidget); + spinBox_stopat->setObjectName(QStringLiteral("spinBox_stopat")); + spinBox_stopat->setGeometry(QRect(1000, 85, 50, 20)); + spinBox_stopat->setMaximum(9999); + label_input = new QLabel(centralWidget); + label_input->setObjectName(QStringLiteral("label_input")); + label_input->setGeometry(QRect(280, 120, 250, 16)); + label_input->setAlignment(Qt::AlignCenter); + label_img_in = new QLabel(centralWidget); + label_img_in->setObjectName(QStringLiteral("label_img_in")); + label_img_in->setGeometry(QRect(280, 140, 250, 250)); + label_img_in->setFrameShape(QFrame::Box); + label_img_in->setAlignment(Qt::AlignCenter); + label_img_fg = new QLabel(centralWidget); + label_img_fg->setObjectName(QStringLiteral("label_img_fg")); + label_img_fg->setGeometry(QRect(540, 140, 250, 250)); + label_img_fg->setFrameShape(QFrame::Box); + label_img_fg->setAlignment(Qt::AlignCenter); + label_img_bg = new QLabel(centralWidget); + label_img_bg->setObjectName(QStringLiteral("label_img_bg")); + label_img_bg->setGeometry(QRect(800, 140, 250, 250)); + label_img_bg->setFrameShape(QFrame::Box); + label_img_bg->setAlignment(Qt::AlignCenter); + label_foreground = new QLabel(centralWidget); + label_foreground->setObjectName(QStringLiteral("label_foreground")); + label_foreground->setGeometry(QRect(540, 120, 250, 16)); + label_foreground->setAlignment(Qt::AlignCenter); + label_background = new QLabel(centralWidget); + label_background->setObjectName(QStringLiteral("label_background")); + label_background->setGeometry(QRect(800, 120, 250, 16)); + label_background->setAlignment(Qt::AlignCenter); + checkBox_save_im = new QCheckBox(centralWidget); + checkBox_save_im->setObjectName(QStringLiteral("checkBox_save_im")); + checkBox_save_im->setGeometry(QRect(280, 400, 52, 20)); + checkBox_save_fg = new QCheckBox(centralWidget); + checkBox_save_fg->setObjectName(QStringLiteral("checkBox_save_fg")); + checkBox_save_fg->setGeometry(QRect(540, 400, 52, 20)); + checkBox_save_bg = new QCheckBox(centralWidget); + checkBox_save_bg->setObjectName(QStringLiteral("checkBox_save_bg")); + checkBox_save_bg->setGeometry(QRect(800, 400, 52, 20)); + pushButton_stop = new QPushButton(centralWidget); + pushButton_stop->setObjectName(QStringLiteral("pushButton_stop")); + pushButton_stop->setGeometry(QRect(960, 470, 93, 23)); + pushButton_start = new QPushButton(centralWidget); + pushButton_start->setObjectName(QStringLiteral("pushButton_start")); + pushButton_start->setGeometry(QRect(850, 470, 93, 23)); + progressBar = new QProgressBar(centralWidget); + progressBar->setObjectName(QStringLiteral("progressBar")); + progressBar->setGeometry(QRect(280, 470, 550, 23)); + progressBar->setValue(0); + label_exectime = new QLabel(centralWidget); + label_exectime->setObjectName(QStringLiteral("label_exectime")); + label_exectime->setGeometry(QRect(10, 400, 121, 16)); + label_fps_txt = new QLabel(centralWidget); + label_fps_txt->setObjectName(QStringLiteral("label_fps_txt")); + label_fps_txt->setGeometry(QRect(210, 400, 55, 16)); + label_fps_txt->setLayoutDirection(Qt::LeftToRight); + label_fps_txt->setFrameShape(QFrame::Box); + label_fps_txt->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + label_framenumber = new QLabel(centralWidget); + label_framenumber->setObjectName(QStringLiteral("label_framenumber")); + label_framenumber->setGeometry(QRect(10, 420, 121, 16)); + label_framenumber_txt = new QLabel(centralWidget); + label_framenumber_txt->setObjectName(QStringLiteral("label_framenumber_txt")); + label_framenumber_txt->setGeometry(QRect(210, 420, 55, 16)); + label_framenumber_txt->setLayoutDirection(Qt::LeftToRight); + label_framenumber_txt->setFrameShape(QFrame::Box); + label_framenumber_txt->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + label_status = new QLabel(centralWidget); + label_status->setObjectName(QStringLiteral("label_status")); + label_status->setGeometry(QRect(209, 470, 61, 23)); + label_frameres = new QLabel(centralWidget); + label_frameres->setObjectName(QStringLiteral("label_frameres")); + label_frameres->setGeometry(QRect(10, 440, 121, 16)); + label_frameresw_txt = new QLabel(centralWidget); + label_frameresw_txt->setObjectName(QStringLiteral("label_frameresw_txt")); + label_frameresw_txt->setGeometry(QRect(140, 440, 55, 16)); + label_frameresw_txt->setLayoutDirection(Qt::LeftToRight); + label_frameresw_txt->setFrameShape(QFrame::Box); + label_frameresw_txt->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + label_frameresh_txt = new QLabel(centralWidget); + label_frameresh_txt->setObjectName(QStringLiteral("label_frameresh_txt")); + label_frameresh_txt->setGeometry(QRect(210, 440, 55, 16)); + label_frameresh_txt->setLayoutDirection(Qt::LeftToRight); + label_frameresh_txt->setFrameShape(QFrame::Box); + label_frameresh_txt->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + label_frameres_2 = new QLabel(centralWidget); + label_frameres_2->setObjectName(QStringLiteral("label_frameres_2")); + label_frameres_2->setGeometry(QRect(193, 440, 21, 16)); + label_frameres_2->setAlignment(Qt::AlignCenter); + lineEdit_out_in = new QLineEdit(centralWidget); + lineEdit_out_in->setObjectName(QStringLiteral("lineEdit_out_in")); + lineEdit_out_in->setGeometry(QRect(348, 400, 140, 20)); + pushButton_out_in = new QPushButton(centralWidget); + pushButton_out_in->setObjectName(QStringLiteral("pushButton_out_in")); + pushButton_out_in->setGeometry(QRect(490, 400, 40, 20)); + lineEdit_out_fg = new QLineEdit(centralWidget); + lineEdit_out_fg->setObjectName(QStringLiteral("lineEdit_out_fg")); + lineEdit_out_fg->setGeometry(QRect(608, 400, 140, 20)); + pushButton_out_fg = new QPushButton(centralWidget); + pushButton_out_fg->setObjectName(QStringLiteral("pushButton_out_fg")); + pushButton_out_fg->setGeometry(QRect(750, 400, 40, 20)); + pushButton_out_bg = new QPushButton(centralWidget); + pushButton_out_bg->setObjectName(QStringLiteral("pushButton_out_bg")); + pushButton_out_bg->setGeometry(QRect(1010, 400, 40, 20)); + lineEdit_out_bg = new QLineEdit(centralWidget); + lineEdit_out_bg->setObjectName(QStringLiteral("lineEdit_out_bg")); + lineEdit_out_bg->setGeometry(QRect(868, 400, 140, 20)); + checkBox_kfn = new QCheckBox(centralWidget); + checkBox_kfn->setObjectName(QStringLiteral("checkBox_kfn")); + checkBox_kfn->setGeometry(QRect(280, 420, 141, 20)); + checkBox_kfn->setChecked(true); + MainWindow->setCentralWidget(centralWidget); + menuBar = new QMenuBar(MainWindow); + menuBar->setObjectName(QStringLiteral("menuBar")); + menuBar->setGeometry(QRect(0, 0, 1070, 26)); + menuBGSLibrary = new QMenu(menuBar); + menuBGSLibrary->setObjectName(QStringLiteral("menuBGSLibrary")); + MainWindow->setMenuBar(menuBar); + statusBar = new QStatusBar(MainWindow); + statusBar->setObjectName(QStringLiteral("statusBar")); + MainWindow->setStatusBar(statusBar); + + menuBar->addAction(menuBGSLibrary->menuAction()); + menuBGSLibrary->addAction(actionExit); + + retranslateUi(MainWindow); + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + MainWindow->setWindowTitle(QApplication::translate("MainWindow", "BGSLibrary QT GUI", 0)); + actionExit->setText(QApplication::translate("MainWindow", "Exit", 0)); + label_algorithms->setText(QApplication::translate("MainWindow", "Algorithms", 0)); + label_inputdata->setText(QApplication::translate("MainWindow", "Input (specify a video file or a directory path containing the sequence of images)", 0)); + lineEdit_inputdata->setText(QApplication::translate("MainWindow", "./dataset/video.avi", 0)); + pushButton_inputdata->setText(QApplication::translate("MainWindow", "...", 0)); + checkBox_webcamera->setText(QApplication::translate("MainWindow", "Use web camera", 0)); + checkBox_imageseq->setText(QApplication::translate("MainWindow", "Sequence of images?", 0)); + label_startat->setText(QApplication::translate("MainWindow", "Start at:", 0)); + label_stopat->setText(QApplication::translate("MainWindow", "Stop at:", 0)); + label_input->setText(QApplication::translate("MainWindow", "Input", 0)); + label_img_in->setText(QApplication::translate("MainWindow", "IMG_INPUT", 0)); + label_img_fg->setText(QApplication::translate("MainWindow", "IMG_FOREGROUND", 0)); + label_img_bg->setText(QApplication::translate("MainWindow", "IMG_BACKGROUND", 0)); + label_foreground->setText(QApplication::translate("MainWindow", "Foreground mask", 0)); + label_background->setText(QApplication::translate("MainWindow", "Background model", 0)); + checkBox_save_im->setText(QApplication::translate("MainWindow", "Save", 0)); + checkBox_save_fg->setText(QApplication::translate("MainWindow", "Save", 0)); + checkBox_save_bg->setText(QApplication::translate("MainWindow", "Save", 0)); + pushButton_stop->setText(QApplication::translate("MainWindow", "Stop", 0)); + pushButton_start->setText(QApplication::translate("MainWindow", "Start", 0)); + label_exectime->setText(QApplication::translate("MainWindow", "Estimated FPS:", 0)); + label_fps_txt->setText(QApplication::translate("MainWindow", "0", 0)); + label_framenumber->setText(QApplication::translate("MainWindow", "Frame number:", 0)); + label_framenumber_txt->setText(QApplication::translate("MainWindow", "0", 0)); + label_status->setText(QApplication::translate("MainWindow", "Progress:", 0)); + label_frameres->setText(QApplication::translate("MainWindow", "Frame resolution:", 0)); + label_frameresw_txt->setText(QApplication::translate("MainWindow", "0", 0)); + label_frameresh_txt->setText(QApplication::translate("MainWindow", "0", 0)); + label_frameres_2->setText(QApplication::translate("MainWindow", "x", 0)); + lineEdit_out_in->setText(QApplication::translate("MainWindow", "./output/in/", 0)); + pushButton_out_in->setText(QApplication::translate("MainWindow", "...", 0)); + lineEdit_out_fg->setText(QApplication::translate("MainWindow", "./output/fg/", 0)); + pushButton_out_fg->setText(QApplication::translate("MainWindow", "...", 0)); + pushButton_out_bg->setText(QApplication::translate("MainWindow", "...", 0)); + lineEdit_out_bg->setText(QApplication::translate("MainWindow", "./output/bg/", 0)); + checkBox_kfn->setText(QApplication::translate("MainWindow", "Keep frame number", 0)); + menuBGSLibrary->setTitle(QApplication::translate("MainWindow", "BGSLibrary", 0)); + } // retranslateUi + +}; + +namespace Ui { + class MainWindow: public Ui_MainWindow {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_MAINWINDOW_H diff --git a/output/bg/.gitignore b/output/bg/.gitignore new file mode 100644 index 0000000..4e2a98b --- /dev/null +++ b/output/bg/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except these files +!.gitignore diff --git a/output/fg/.gitignore b/output/fg/.gitignore new file mode 100644 index 0000000..4e2a98b --- /dev/null +++ b/output/fg/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except these files +!.gitignore diff --git a/output/in/.gitignore b/output/in/.gitignore new file mode 100644 index 0000000..4e2a98b --- /dev/null +++ b/output/in/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except these files +!.gitignore diff --git a/package_bgs/tb/PerformanceUtils.cpp b/package_analysis/PerformanceUtils.cpp similarity index 59% rename from package_bgs/tb/PerformanceUtils.cpp rename to package_analysis/PerformanceUtils.cpp index 0d7cf77..6ad5e5f 100644 --- a/package_bgs/tb/PerformanceUtils.cpp +++ b/package_analysis/PerformanceUtils.cpp @@ -15,15 +15,16 @@ You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ #include "PerformanceUtils.h" -#include +//#include +//#include -PerformanceUtils::PerformanceUtils(void){} +PerformanceUtils::PerformanceUtils(void) {} -PerformanceUtils::~PerformanceUtils(void){} +PerformanceUtils::~PerformanceUtils(void) {} float PerformanceUtils::NrPixels(IplImage *image) { - return (float) (image->width * image->height); + return (float)(image->width * image->height); } float PerformanceUtils::NrAllDetectedPixNotNULL(IplImage *image, IplImage *ground_truth) @@ -31,19 +32,19 @@ float PerformanceUtils::NrAllDetectedPixNotNULL(IplImage *image, IplImage *groun //Nombre de tous les pixels non nuls dans Groundthruth et dans image float Union12 = 0.0; - unsigned char *pixelGT = (unsigned char*) malloc(1*sizeof(unsigned char)); - unsigned char *pixelI = (unsigned char*) malloc(1*sizeof(unsigned char)); + unsigned char *pixelGT = (unsigned char*)malloc(1 * sizeof(unsigned char)); + unsigned char *pixelI = (unsigned char*)malloc(1 * sizeof(unsigned char)); PixelUtils p; - for(int y = 0; y < image->height; y++) + for (int y = 0; y < image->height; y++) { - for(int x = 0; x < image->width; x++) - { - p.GetGrayPixel(ground_truth,x,y,pixelGT); - p.GetGrayPixel(image,x,y,pixelI); + for (int x = 0; x < image->width; x++) + { + p.GetGrayPixel(ground_truth, x, y, pixelGT); + p.GetGrayPixel(image, x, y, pixelI); - if((pixelGT[0] != 0) || (pixelI[0] != 0)) + if ((pixelGT[0] != 0) || (pixelI[0] != 0)) Union12++; } } @@ -51,44 +52,44 @@ float PerformanceUtils::NrAllDetectedPixNotNULL(IplImage *image, IplImage *groun free(pixelGT); free(pixelI); - return Union12; + return Union12; } float PerformanceUtils::NrTruePositives(IplImage *image, IplImage *ground_truth, bool debug) { float nTP = 0.0; - unsigned char *pixelGT = (unsigned char*) malloc(1*sizeof(unsigned char)); - unsigned char *pixelI = (unsigned char*) malloc(1*sizeof(unsigned char)); + unsigned char *pixelGT = (unsigned char*)malloc(1 * sizeof(unsigned char)); + unsigned char *pixelI = (unsigned char*)malloc(1 * sizeof(unsigned char)); IplImage *TPimage = 0; - if(debug) + if (debug) { - TPimage = cvCreateImage(cvSize(image->width,image->height),image->depth,image->nChannels); - cvFillImage(TPimage,0.0); + TPimage = cvCreateImage(cvSize(image->width, image->height), image->depth, image->nChannels); + cvSetZero(TPimage); } PixelUtils p; - for(int y = 0; y < image->height; y++) + for (int y = 0; y < image->height; y++) { - for(int x = 0; x < image->width; x++) - { - p.GetGrayPixel(ground_truth,x,y,pixelGT); - p.GetGrayPixel(image,x,y,pixelI); + for (int x = 0; x < image->width; x++) + { + p.GetGrayPixel(ground_truth, x, y, pixelGT); + p.GetGrayPixel(image, x, y, pixelI); - if((pixelGT[0] != 0) && (pixelI[0] != 0)) + if ((pixelGT[0] != 0) && (pixelI[0] != 0)) { - if(debug) - p.PutGrayPixel(TPimage,x,y,*pixelI); + if (debug) + p.PutGrayPixel(TPimage, x, y, *pixelI); nTP++; } } } - if(debug) + if (debug) { cvNamedWindow("TPImage", 0); cvShowImage("TPImage", TPimage); @@ -101,46 +102,46 @@ float PerformanceUtils::NrTruePositives(IplImage *image, IplImage *ground_truth, free(pixelGT); free(pixelI); - return nTP; + return nTP; } float PerformanceUtils::NrTrueNegatives(IplImage* image, IplImage* ground_truth, bool debug) { float nTN = 0.0; - unsigned char *pixelGT = (unsigned char *)malloc(1*sizeof(unsigned char)); - unsigned char *pixelI = (unsigned char *)malloc(1*sizeof(unsigned char)); + unsigned char *pixelGT = (unsigned char *)malloc(1 * sizeof(unsigned char)); + unsigned char *pixelI = (unsigned char *)malloc(1 * sizeof(unsigned char)); IplImage *TNimage = 0; - if(debug) + if (debug) { - TNimage = cvCreateImage(cvSize(image->width,image->height),image->depth,image->nChannels); - cvFillImage(TNimage, 0.0); + TNimage = cvCreateImage(cvSize(image->width, image->height), image->depth, image->nChannels); + cvSetZero(TNimage); } PixelUtils p; - for(int y = 0; y < image->height; y++) + for (int y = 0; y < image->height; y++) { - for(int x = 0; x < image->width; x++) + for (int x = 0; x < image->width; x++) { - p.GetGrayPixel(ground_truth,x,y,pixelGT); - p.GetGrayPixel(image,x,y,pixelI); + p.GetGrayPixel(ground_truth, x, y, pixelGT); + p.GetGrayPixel(image, x, y, pixelI); - if((pixelGT[0] == 0) && (pixelI[0] == 0.0)) + if ((pixelGT[0] == 0) && (pixelI[0] == 0.0)) { *pixelI = 255; - if(debug) - p.PutGrayPixel(TNimage,x,y,*pixelI); + if (debug) + p.PutGrayPixel(TNimage, x, y, *pixelI); nTN++; - } + } } } - if(debug) + if (debug) { cvNamedWindow("TNImage", 0); cvShowImage("TNImage", TNimage); @@ -156,41 +157,41 @@ float PerformanceUtils::NrTrueNegatives(IplImage* image, IplImage* ground_truth, return nTN; } -float PerformanceUtils::NrFalsePositives(IplImage *image, IplImage *ground_truth,bool debug) +float PerformanceUtils::NrFalsePositives(IplImage *image, IplImage *ground_truth, bool debug) { float nFP = 0.0; - unsigned char *pixelGT = (unsigned char*) malloc(1*sizeof(unsigned char)); - unsigned char *pixelI = (unsigned char*) malloc(1*sizeof(unsigned char)); + unsigned char *pixelGT = (unsigned char*)malloc(1 * sizeof(unsigned char)); + unsigned char *pixelI = (unsigned char*)malloc(1 * sizeof(unsigned char)); IplImage *FPimage = 0; - if(debug) + if (debug) { - FPimage = cvCreateImage(cvSize(image->width,image->height),image->depth,image->nChannels); - cvFillImage(FPimage, 0.0); + FPimage = cvCreateImage(cvSize(image->width, image->height), image->depth, image->nChannels); + cvSetZero(FPimage); } PixelUtils p; - for(int y = 0; y < image->height; y++) + for (int y = 0; y < image->height; y++) { - for(int x = 0; x < image->width; x++) + for (int x = 0; x < image->width; x++) { - p.GetGrayPixel(ground_truth,x,y,pixelGT); - p.GetGrayPixel(image,x,y,pixelI); + p.GetGrayPixel(ground_truth, x, y, pixelGT); + p.GetGrayPixel(image, x, y, pixelI); - if((pixelGT[0] == 0) && (pixelI[0] != 0)) + if ((pixelGT[0] == 0) && (pixelI[0] != 0)) { - if(debug) - p.PutGrayPixel(FPimage,x,y,*pixelI); + if (debug) + p.PutGrayPixel(FPimage, x, y, *pixelI); nFP++; } } } - if(debug) + if (debug) { cvNamedWindow("FPImage", 0); cvShowImage("FPImage", FPimage); @@ -203,44 +204,44 @@ float PerformanceUtils::NrFalsePositives(IplImage *image, IplImage *ground_truth free(pixelGT); free(pixelI); - return nFP; + return nFP; } float PerformanceUtils::NrFalseNegatives(IplImage * image, IplImage *ground_truth, bool debug) { float nFN = 0.0; - unsigned char *pixelGT = (unsigned char*) malloc(1*sizeof(unsigned char)); - unsigned char *pixelI = (unsigned char*) malloc(1*sizeof(unsigned char)); + unsigned char *pixelGT = (unsigned char*)malloc(1 * sizeof(unsigned char)); + unsigned char *pixelI = (unsigned char*)malloc(1 * sizeof(unsigned char)); IplImage *FNimage = 0; - if(debug) + if (debug) { - FNimage = cvCreateImage(cvSize(image->width,image->height),image->depth,image->nChannels); - cvFillImage(FNimage, 0.0); + FNimage = cvCreateImage(cvSize(image->width, image->height), image->depth, image->nChannels); + cvSetZero(FNimage); } PixelUtils p; - for(int y = 0; y < image->height; y++) + for (int y = 0; y < image->height; y++) { - for(int x = 0; x < image->width; x++) + for (int x = 0; x < image->width; x++) { - p.GetGrayPixel(ground_truth,x,y,pixelGT); - p.GetGrayPixel(image,x,y,pixelI); + p.GetGrayPixel(ground_truth, x, y, pixelGT); + p.GetGrayPixel(image, x, y, pixelI); - if((pixelGT[0] != 0) && (pixelI[0] == 0)) + if ((pixelGT[0] != 0) && (pixelI[0] == 0)) { - if(debug) - p.PutGrayPixel(FNimage,x,y,*pixelGT); + if (debug) + p.PutGrayPixel(FNimage, x, y, *pixelGT); nFN++; } } } - if(debug) + if (debug) { cvNamedWindow("FNImage", 0); cvShowImage("FNImage", FNimage); @@ -253,19 +254,19 @@ float PerformanceUtils::NrFalseNegatives(IplImage * image, IplImage *ground_trut free(pixelGT); free(pixelI); - return nFN; + return nFN; } float PerformanceUtils::SimilarityMeasure(IplImage *image, IplImage *ground_truth, bool debug) { - cv::Mat img_input(image,true); - cv::Mat img_ref(ground_truth,true); + cv::Mat img_input = cv::cvarrToMat(image, true); + cv::Mat img_ref = cv::cvarrToMat(ground_truth, true); int rn = cv::countNonZero(img_ref); cv::Mat i; cv::Mat u; - if(rn > 0) + if (rn > 0) { i = img_input & img_ref; u = img_input | img_ref; @@ -278,16 +279,16 @@ float PerformanceUtils::SimilarityMeasure(IplImage *image, IplImage *ground_trut int in = cv::countNonZero(i); int un = cv::countNonZero(u); - + double s = (((double)in) / ((double)un)); - - if(debug) + + if (debug) { cv::imshow("A^B", i); cv::imshow("AvB", u); //std::cout << "Similarity Measure: " << s << std::endl; - + //<< " press ENTER to continue" << std::endl; //cv::waitKey(0); } @@ -295,45 +296,45 @@ float PerformanceUtils::SimilarityMeasure(IplImage *image, IplImage *ground_trut return s; } -void PerformanceUtils::ImageROC(IplImage *image, IplImage* ground_truth, bool saveResults, char* filename) +void PerformanceUtils::ImageROC(IplImage *image, IplImage* ground_truth, bool saveResults, std::string filename) { - unsigned char *pixelGT = (unsigned char*) malloc(1*sizeof(unsigned char)); - unsigned char *pixelI = (unsigned char*) malloc(1*sizeof(unsigned char)); + unsigned char *pixelGT = (unsigned char*)malloc(1 * sizeof(unsigned char)); + unsigned char *pixelI = (unsigned char*)malloc(1 * sizeof(unsigned char)); - IplImage *ROCimage = cvCreateImage(cvSize(image->width,image->height),image->depth,image->nChannels); - cvFillImage(ROCimage, 0.0); + IplImage *ROCimage = cvCreateImage(cvSize(image->width, image->height), image->depth, image->nChannels); + cvSetZero(ROCimage); PixelUtils p; - for(int y = 0; y < image->height; y++) + for (int y = 0; y < image->height; y++) { - for(int x = 0; x < image->width; x++) + for (int x = 0; x < image->width; x++) { - p.GetGrayPixel(ground_truth,x,y,pixelGT); - p.GetGrayPixel(image,x,y,pixelI); + p.GetGrayPixel(ground_truth, x, y, pixelGT); + p.GetGrayPixel(image, x, y, pixelI); - if((pixelGT[0] != 0) && (pixelI[0] != 0)) // TP + if ((pixelGT[0] != 0) && (pixelI[0] != 0)) // TP { *pixelI = 30; - p.PutGrayPixel(ROCimage,x,y,*pixelI); + p.PutGrayPixel(ROCimage, x, y, *pixelI); } - if((pixelGT[0] == 0) && (pixelI[0] == 0.0)) // TN + if ((pixelGT[0] == 0) && (pixelI[0] == 0.0)) // TN { *pixelI = 0; - p.PutGrayPixel(ROCimage,x,y,*pixelI); - } + p.PutGrayPixel(ROCimage, x, y, *pixelI); + } - if((pixelGT[0] == 0) && (pixelI[0] != 0)) // FP + if ((pixelGT[0] == 0) && (pixelI[0] != 0)) // FP { *pixelI = 255; - p.PutGrayPixel(ROCimage,x,y,*pixelI); + p.PutGrayPixel(ROCimage, x, y, *pixelI); } - if((pixelGT[0] != 0) && (pixelI[0] == 0)) // FN + if ((pixelGT[0] != 0) && (pixelI[0] == 0)) // FN { *pixelI = 100; - p.PutGrayPixel(ROCimage,x,y,*pixelI); + p.PutGrayPixel(ROCimage, x, y, *pixelI); } } } @@ -341,10 +342,10 @@ void PerformanceUtils::ImageROC(IplImage *image, IplImage* ground_truth, bool sa cvNamedWindow("ROC image", 0); cvShowImage("ROC image", ROCimage); - if(saveResults) + if (saveResults) { - unsigned char *pixelOI = (unsigned char*) malloc(1*sizeof(unsigned char)); - unsigned char *pixelROC = (unsigned char*) malloc(1*sizeof(unsigned char)); + unsigned char *pixelOI = (unsigned char*)malloc(1 * sizeof(unsigned char)); + unsigned char *pixelROC = (unsigned char*)malloc(1 * sizeof(unsigned char)); float** freq; float nTP = 0.0; @@ -352,45 +353,45 @@ void PerformanceUtils::ImageROC(IplImage *image, IplImage* ground_truth, bool sa float nFP = 0.0; float nFN = 0.0; - freq = (float**) malloc(256*(sizeof(float*))); - for(int i = 0; i < 256; i++) - freq[i] = (float*) malloc(7 * (sizeof(float))); + freq = (float**)malloc(256 * (sizeof(float*))); + for (int i = 0; i < 256; i++) + freq[i] = (float*)malloc(7 * (sizeof(float))); - for(int i = 0; i < 256; i++) - for(int j = 0; j < 6; j++) + for (int i = 0; i < 256; i++) + for (int j = 0; j < 6; j++) freq[i][j] = 0.0; - for(int y = 0; y < image->height; y++) + for (int y = 0; y < image->height; y++) { - for(int x = 0; x < image->width; x++) + for (int x = 0; x < image->width; x++) { - for(int i = 0; i < 256; i++) + for (int i = 0; i < 256; i++) { - p.GetGrayPixel(image,x,y,pixelOI); - p.GetGrayPixel(ROCimage,x,y,pixelROC); + p.GetGrayPixel(image, x, y, pixelOI); + p.GetGrayPixel(ROCimage, x, y, pixelROC); - if((pixelOI[0] == i) && (pixelROC[0] == 30.0)) // TP + if ((pixelOI[0] == i) && (pixelROC[0] == 30.0)) // TP { nTP++; freq[i][0] = nTP; break; } - if((pixelOI[0] == i) && (pixelROC[0] == 0.0)) // TN + if ((pixelOI[0] == i) && (pixelROC[0] == 0.0)) // TN { nTN++; freq[i][1] = nTN; break; } - if((pixelOI[0] == i) && (pixelROC[0] == 255.0)) // FP + if ((pixelOI[0] == i) && (pixelROC[0] == 255.0)) // FP { nFP++; freq[i][2] = nFP; break; } - if((pixelOI[0] == i) && (pixelROC[0] == 100)) // FN + if ((pixelOI[0] == i) && (pixelROC[0] == 100)) // FN { nFN++; freq[i][3] = nFN; @@ -409,17 +410,17 @@ void PerformanceUtils::ImageROC(IplImage *image, IplImage* ground_truth, bool sa std::ofstream f(filename); - if(!f.is_open()) + if (!f.is_open()) std::cout << "Failed to open file " << filename << " for writing!" << std::endl; else { f << " I TP TN FP FN FPR FNR DR \n" << std::endl; - - for(int i = 0; i < 256; i++) + + for (int i = 0; i < 256; i++) { //printf("%4d - TP:%5.0f, TN:%5.0f, FP:%5.0f, FN:%5.0f,", i, freq[i][0], freq[i][1], freq[i][2], freq[i][3]); - if((freq[i][3] + freq[i][0] != 0.0) && (freq[i][2] + freq[i][1] != 0.0)) + if ((freq[i][3] + freq[i][0] != 0.0) && (freq[i][2] + freq[i][1] != 0.0)) { freq[i][4] = freq[i][3] / (freq[i][3] + freq[i][0]); // FNR = FN / (TP + FN); freq[i][5] = freq[i][2] / (freq[i][2] + freq[i][1]); // FPR = FP / (FP + TN); @@ -429,12 +430,12 @@ void PerformanceUtils::ImageROC(IplImage *image, IplImage* ground_truth, bool sa ////fprintf(f," %4d %1.6f %1.6f\n",i,freq[i][5],freq[i][4]); ////fprintf(f," %1.6f %1.6f\n",freq[i][5],freq[i][4]); char line[255]; - sprintf(line,"%3d %6.0f %6.0f %6.0f %6.0f %1.6f %1.6f %1.6f\n", + sprintf(line, "%3d %6.0f %6.0f %6.0f %6.0f %1.6f %1.6f %1.6f\n", i, freq[i][0], freq[i][1], freq[i][2], freq[i][3], freq[i][5], freq[i][4], freq[i][6]); f << line; } //else - //printf("\n"); + //printf("\n"); } std::cout << "Results saved in " << filename << std::endl; @@ -454,36 +455,36 @@ void PerformanceUtils::ImageROC(IplImage *image, IplImage* ground_truth, bool sa free(pixelI); } -void PerformanceUtils::PerformanceEvaluation(IplImage *image, IplImage *ground_truth, bool saveResults, char* filename, bool debug) +void PerformanceUtils::PerformanceEvaluation(IplImage *image, IplImage *ground_truth, bool saveResults, std::string filename, bool debug) { float N = 0; N = NrPixels(image); float U = 0; U = NrAllDetectedPixNotNULL(image, ground_truth); - + float TP = 0; TP = NrTruePositives(image, ground_truth, debug); - + float TN = 0; TN = NrTrueNegatives(image, ground_truth, debug); - + float FP = 0; FP = NrFalsePositives(image, ground_truth, debug); - + float FN = 0; FN = NrFalseNegatives(image, ground_truth, debug); - + float DetectionRate = TP / (TP + FN); float Precision = TP / (TP + FP); float Fmeasure = (2 * DetectionRate * Precision) / (DetectionRate + Precision); float Accuracy = (TN + TP) / N; float FalseNegativeRate = FN / (TP + FN); - + float FalsePositiveRate = FP / (FP + TN); float TruePositiveRate = TP / (TP + FN); - + float SM = 0; SM = SimilarityMeasure(image, ground_truth, debug); @@ -506,11 +507,11 @@ void PerformanceUtils::PerformanceEvaluation(IplImage *image, IplImage *ground_t std::string results = sstm.str(); std::cout << results; - if(saveResults) + if (saveResults) { std::ofstream f(filename); - if(!f.is_open()) + if (!f.is_open()) std::cout << "Failed to open file " << filename << " for writing!" << std::endl; else { diff --git a/package_bgs/tb/PerformanceUtils.h b/package_analysis/PerformanceUtils.h similarity index 82% rename from package_bgs/tb/PerformanceUtils.h rename to package_analysis/PerformanceUtils.h index 9257098..4be392f 100644 --- a/package_bgs/tb/PerformanceUtils.h +++ b/package_analysis/PerformanceUtils.h @@ -15,23 +15,11 @@ You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ #pragma once -/* -Code provided by Thierry BOUWMANS - -Maitre de Conférences -Laboratoire MIA -Université de La Rochelle -17000 La Rochelle -France -tbouwman@univ-lr.fr -http://sites.google.com/site/thierrybouwmans/ -*/ #include #include #include - #include "PixelUtils.h" class PerformanceUtils @@ -48,7 +36,7 @@ class PerformanceUtils float NrFalseNegatives(IplImage *image, IplImage *ground_truth, bool debug = false); float SimilarityMeasure(IplImage *image, IplImage *ground_truth, bool debug = false); - void ImageROC(IplImage *image, IplImage* ground_truth, bool saveResults = false, char* filename = ""); - void PerformanceEvaluation(IplImage *image, IplImage *ground_truth, bool saveResults = false, char* filename = "", bool debug = false); + void ImageROC(IplImage *image, IplImage* ground_truth, bool saveResults = false, std::string filename = ""); + void PerformanceEvaluation(IplImage *image, IplImage *ground_truth, bool saveResults = false, std::string filename = "", bool debug = false); }; diff --git a/package_analysis/PixelUtils.cpp b/package_analysis/PixelUtils.cpp new file mode 100644 index 0000000..adce122 --- /dev/null +++ b/package_analysis/PixelUtils.cpp @@ -0,0 +1,351 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "PixelUtils.h" + +PixelUtils::PixelUtils(void) {} +PixelUtils::~PixelUtils(void) {} + +void PixelUtils::ColorConversion(IplImage* RGBImage, IplImage* ConvertedImage, int color_space) +{ + // Space Color RGB - Nothing to do! + if (color_space == 1) + cvCopy(RGBImage, ConvertedImage); + + // Space Color Ohta + if (color_space == 2) + cvttoOTHA(RGBImage, ConvertedImage); + + // Space Color HSV - V Intensity - (H,S) Chromaticity + if (color_space == 3) + cvCvtColor(RGBImage, ConvertedImage, CV_BGR2HSV); + + // Space Color YCrCb - Y Intensity - (Cr,Cb) Chromaticity + if (color_space == 4) + cvCvtColor(RGBImage, ConvertedImage, CV_BGR2YCrCb); +} + +void PixelUtils::cvttoOTHA(IplImage* RGBImage, IplImage* OthaImage) +{ + float* OhtaPixel = (float*)malloc(3 * (sizeof(float))); + float* RGBPixel = (float*)malloc(3 * (sizeof(float))); + + for (int i = 0; i < RGBImage->width; i++) + { + for (int j = 0; j < RGBImage->height; j++) + { + GetPixel(RGBImage, i, j, RGBPixel); + + // I1 = (R + G + B) / 3 + *OhtaPixel = (*(RGBPixel)+(*(RGBPixel + 1)) + (*(RGBPixel + 2))) / 3.0; + + // I2 = (R - B) / 2 + *(OhtaPixel + 1) = (*RGBPixel - (*(RGBPixel + 2))) / 2.0; + + // I3 = (2G - R - B) / 4 + *(OhtaPixel + 2) = (2 * (*(RGBPixel + 1)) - (*RGBPixel) - (*(RGBPixel + 2))) / 4.0; + + PutPixel(OthaImage, i, j, OhtaPixel); + } + } + + free(OhtaPixel); + free(RGBPixel); +} + +void PixelUtils::PostProcessing(IplImage *InputImage) +{ + IplImage *ResultImage = cvCreateImage(cvSize(InputImage->width, InputImage->height), IPL_DEPTH_32F, 3); + + cvErode(InputImage, ResultImage, NULL, 1); + cvDilate(ResultImage, InputImage, NULL, 0); + + cvReleaseImage(&ResultImage); +} + +void PixelUtils::GetPixel(IplImage *image, int m, int n, unsigned char *pixelcourant) +{ + for (int k = 0; k < 3; k++) + pixelcourant[k] = ((unsigned char*)(image->imageData + image->widthStep*n))[m * 3 + k]; +} + +void PixelUtils::GetGrayPixel(IplImage *image, int m, int n, unsigned char *pixelcourant) +{ + *pixelcourant = ((unsigned char*)(image->imageData + image->widthStep*n))[m]; +} + +void PixelUtils::PutPixel(IplImage *image, int p, int q, unsigned char *pixelcourant) +{ + for (int r = 0; r < 3; r++) + ((unsigned char*)(image->imageData + image->widthStep*q))[p * 3 + r] = pixelcourant[r]; +} + +void PixelUtils::PutGrayPixel(IplImage *image, int p, int q, unsigned char pixelcourant) +{ + ((unsigned char*)(image->imageData + image->widthStep*q))[p] = pixelcourant; +} + +void PixelUtils::GetPixel(IplImage *image, int m, int n, float *pixelcourant) +{ + for (int k = 0; k < 3; k++) + pixelcourant[k] = ((float*)(image->imageData + image->widthStep*n))[m * 3 + k]; +} + +void PixelUtils::GetGrayPixel(IplImage *image, int m, int n, float *pixelcourant) +{ + *pixelcourant = ((float*)(image->imageData + image->widthStep*n))[m]; +} + +void PixelUtils::PutPixel(IplImage *image, int p, int q, float *pixelcourant) +{ + for (int r = 0; r < 3; r++) + ((float*)(image->imageData + image->widthStep*q))[p * 3 + r] = pixelcourant[r]; +} + +void PixelUtils::PutGrayPixel(IplImage *image, int p, int q, float pixelcourant) +{ + ((float*)(image->imageData + image->widthStep*q))[p] = pixelcourant; +} + +void PixelUtils::getNeighberhoodGrayPixel(IplImage* InputImage, int x, int y, float* neighberPixel) +{ + int i, j, k; + + float* pixelCourant = (float*)malloc(1 * (sizeof(float))); + + //le calcul de voisinage pour les 4 coins; + /* 1.*/ + if (x == 0 && y == 0) + { + k = 0; + for (i = x; i < x + 2; i++) + for (j = y; j < y + 2; j++) + { + GetGrayPixel(InputImage, i, j, pixelCourant); + *(neighberPixel + k) = *pixelCourant; + k++; + } + } + + /* 2.*/ + if (x == 0 && y == InputImage->width) + { + k = 0; + for (i = x; i < x + 2; i++) + for (j = y - 1; j < y + 1; j++) + { + GetGrayPixel(InputImage, i, j, pixelCourant); + *(neighberPixel + k) = *pixelCourant; + k++; + } + } + + /* 3.*/ + if (x == InputImage->height && y == 0) + { + k = 0; + for (i = x - 1; i < x + 1; i++) + for (j = y; j < y + 2; j++) + { + GetGrayPixel(InputImage, i, j, pixelCourant); + *(neighberPixel + k) = *pixelCourant; + k++; + } + } + + /* 4.*/ + if (x == InputImage->height && y == InputImage->width) + { + k = 0; + for (i = x - 1; i < x + 1; i++) + for (j = y - 1; j < y + 1; j++) + { + GetGrayPixel(InputImage, i, j, pixelCourant); + *(neighberPixel + k) = *pixelCourant; + k++; + } + } + + // Voisinage de la premiere ligne : L(0) + if (x == 0 && (y != 0 && y != InputImage->width)) + { + k = 0; + for (i = x + 1; i >= x; i--) + for (j = y - 1; j < y + 2; j++) + { + GetGrayPixel(InputImage, i, j, pixelCourant); + *(neighberPixel + k) = *pixelCourant; + k++; + } + } + + // Voisinage de la dernière colonne : C(w) + if ((x != 0 && x != InputImage->height) && y == InputImage->width) + { + k = 0; + for (i = x + 1; i > x - 2; i--) + for (j = y - 1; j < y + 1; j++) + { + GetGrayPixel(InputImage, i, j, pixelCourant); + *(neighberPixel + k) = *pixelCourant; + k++; + } + } + + // Voisinage de la dernière ligne : L(h) + if (x == InputImage->height && (y != 0 && y != InputImage->width)) + { + k = 0; + for (i = x; i > x - 2; i--) + for (j = y - 1; j < y + 2; j++) + { + GetGrayPixel(InputImage, i, j, pixelCourant); + *(neighberPixel + k) = *pixelCourant; + k++; + } + } + + // Voisinage de la premiere colonne : C(0) + if ((x != 0 && x != InputImage->height) && y == 0) + { + k = 0; + for (i = x - 1; i < x + 2; i++) + for (j = y; j < y + 2; j++) + { + GetGrayPixel(InputImage, i, j, pixelCourant); + *(neighberPixel + k) = *pixelCourant; + k++; + } + } + + //le calcul du voisinage pour le reste des elementes d'image + if ((x != 0 && x != InputImage->height) && (y != 0 && y != InputImage->width)) + { + k = 0; + for (i = x + 1; i > x - 2; i--) + for (j = y - 1; j < y + 2; j++) + { + GetGrayPixel(InputImage, i, j, pixelCourant); + *(neighberPixel + k) = *pixelCourant; + k++; + } + } + + free(pixelCourant); +} + +void PixelUtils::ForegroundMinimum(IplImage *Foreground, float *Minimum, int n) +{ + int i, j, k; + float *pixelcourant; + + pixelcourant = (float *)malloc(n * sizeof(float)); + + for (k = 0; k < n; k++) + *(Minimum + k) = 255; + + for (i = 0; i < Foreground->width; i++) + for (j = 0; j < Foreground->height; j++) + { + if (n == 3) + { + GetPixel(Foreground, i, j, pixelcourant); + + for (k = 0; k < n; k++) + if (*(pixelcourant + k) < *(Minimum + k)) + *(Minimum + k) = *(pixelcourant + k); + } + + if (n == 1) + { + GetGrayPixel(Foreground, i, j, pixelcourant); + + if (*pixelcourant < *Minimum) + *Minimum = *pixelcourant; + } + } + + free(pixelcourant); +} + +void PixelUtils::ForegroundMaximum(IplImage *Foreground, float *Maximum, int n) +{ + int i, j, k; + float *pixelcourant; + + pixelcourant = (float *)malloc(n * sizeof(float)); + + for (k = 0; k < n; k++) + *(Maximum + k) = 0; + + for (i = 0; i < Foreground->width; i++) + for (j = 0; j < Foreground->height; j++) + { + if (n == 3) + { + GetPixel(Foreground, i, j, pixelcourant); + + for (k = 0; k < n; k++) + if (*(pixelcourant + k) > *(Maximum + k)) + *(Maximum + k) = *(pixelcourant + k); + } + + if (n == 1) + { + GetGrayPixel(Foreground, i, j, pixelcourant); + + if (*pixelcourant > *Maximum) + *Maximum = *pixelcourant; + } + } + + free(pixelcourant); +} + +void PixelUtils::ComplementaryAlphaImageCreation(IplImage *AlphaImage, IplImage *ComplementaryAlphaImage, int n) +{ + int i, j, k; + float *pixelcourant, *pixelcourant1; + + pixelcourant = (float *)malloc(n * sizeof(float)); + pixelcourant1 = (float *)malloc(n * sizeof(float)); + + for (i = 0; i < AlphaImage->width; i++) + for (j = 0; j < AlphaImage->height; j++) + { + if (n == 1) + { + GetGrayPixel(AlphaImage, i, j, pixelcourant); + *pixelcourant1 = 1 - *(pixelcourant); + PutGrayPixel(ComplementaryAlphaImage, i, j, *pixelcourant1); + } + + if (n == 3) + { + GetPixel(AlphaImage, i, j, pixelcourant); + for (k = 0; k < 3; k++) + { + *pixelcourant1 = 1.0 - *(pixelcourant); + *(pixelcourant1 + 1) = 1.0 - *(pixelcourant + 1); + *(pixelcourant1 + 2) = 1.0 - *(pixelcourant + 2); + } + PutPixel(ComplementaryAlphaImage, i, j, pixelcourant1); + } + } + + free(pixelcourant); + free(pixelcourant1); +} diff --git a/package_bgs/tb/PixelUtils.h b/package_analysis/PixelUtils.h similarity index 90% rename from package_bgs/tb/PixelUtils.h rename to package_analysis/PixelUtils.h index a2d3a4a..0e35c40 100644 --- a/package_bgs/tb/PixelUtils.h +++ b/package_analysis/PixelUtils.h @@ -15,22 +15,10 @@ You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ #pragma once -/* -Code provided by Thierry BOUWMANS - -Maitre de Conférences -Laboratoire MIA -Université de La Rochelle -17000 La Rochelle -France -tbouwman@univ-lr.fr -http://sites.google.com/site/thierrybouwmans/ -*/ #include #include - class PixelUtils { public: @@ -39,9 +27,9 @@ class PixelUtils void ColorConversion(IplImage* RGBImage, IplImage* ConvertedImage, int color_space); void cvttoOTHA(IplImage* RGBImage, IplImage* OthaImage); - + void PostProcessing(IplImage *InputImage); - + void GetPixel(IplImage *image, int m, int n, unsigned char *pixelcourant); void GetGrayPixel(IplImage *image, int m, int n, unsigned char *pixelcourant); @@ -58,4 +46,4 @@ class PixelUtils void ForegroundMaximum(IplImage *Foreground, float *Maximum, int n); void ForegroundMinimum(IplImage *Foreground, float *Minimum, int n); void ComplementaryAlphaImageCreation(IplImage *AlphaImage, IplImage *ComplementaryAlphaImage, int n); -}; \ No newline at end of file +}; diff --git a/package_bgs/AdaptiveBackgroundLearning.cpp b/package_bgs/AdaptiveBackgroundLearning.cpp index b111e65..d2cb8a0 100644 --- a/package_bgs/AdaptiveBackgroundLearning.cpp +++ b/package_bgs/AdaptiveBackgroundLearning.cpp @@ -16,10 +16,14 @@ along with BGSLibrary. If not, see . */ #include "AdaptiveBackgroundLearning.h" -AdaptiveBackgroundLearning::AdaptiveBackgroundLearning() : firstTime(true), alpha(0.05), limit(-1), counter(0), minVal(0.0), maxVal(1.0), - enableThreshold(true), threshold(15), showForeground(true), showBackground(true) +using namespace bgslibrary::algorithms; + +AdaptiveBackgroundLearning::AdaptiveBackgroundLearning() : + alpha(0.05), limit(-1), counter(0), minVal(0.0), maxVal(1.0), + enableThreshold(true), threshold(15) { std::cout << "AdaptiveBackgroundLearning()" << std::endl; + setup("./config/AdaptiveBackgroundLearning.xml"); } AdaptiveBackgroundLearning::~AdaptiveBackgroundLearning() @@ -29,52 +33,48 @@ AdaptiveBackgroundLearning::~AdaptiveBackgroundLearning() void AdaptiveBackgroundLearning::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); + init(img_input, img_output, img_bgmodel); - if(img_background.empty()) + if (img_background.empty()) img_input.copyTo(img_background); cv::Mat img_input_f(img_input.size(), CV_32F); - img_input.convertTo(img_input_f, CV_32F, 1./255.); + img_input.convertTo(img_input_f, CV_32F, 1. / 255.); cv::Mat img_background_f(img_background.size(), CV_32F); - img_background.convertTo(img_background_f, CV_32F, 1./255.); + img_background.convertTo(img_background_f, CV_32F, 1. / 255.); cv::Mat img_diff_f(img_input.size(), CV_32F); cv::absdiff(img_input_f, img_background_f, img_diff_f); - if((limit > 0 && limit < counter) || limit == -1) + if ((limit > 0 && limit < counter) || limit == -1) { - img_background_f = alpha*img_input_f + (1-alpha)*img_background_f; - + img_background_f = alpha*img_input_f + (1 - alpha)*img_background_f; + cv::Mat img_new_background(img_input.size(), CV_8U); - img_background_f.convertTo(img_new_background, CV_8U, 255.0/(maxVal - minVal), -minVal); + img_background_f.convertTo(img_new_background, CV_8U, 255.0 / (maxVal - minVal), -minVal); img_new_background.copyTo(img_background); - if(limit > 0 && limit < counter) + if (limit > 0 && limit < counter) counter++; } - - cv::Mat img_foreground(img_input.size(), CV_8U); - img_diff_f.convertTo(img_foreground, CV_8U, 255.0/(maxVal - minVal), -minVal); - if(img_foreground.channels() == 3) + //cv::Mat img_foreground(img_input.size(), CV_8U); + img_diff_f.convertTo(img_foreground, CV_8UC1, 255.0 / (maxVal - minVal), -minVal); + + if (img_foreground.channels() == 3) cv::cvtColor(img_foreground, img_foreground, CV_BGR2GRAY); - if(enableThreshold) + if (enableThreshold) cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY); - - if(showForeground) - cv::imshow("A-Learning FG", img_foreground); - if(showBackground) +#ifndef MEX_COMPILE_FLAG + if (showOutput) + { + cv::imshow("A-Learning FG", img_foreground); cv::imshow("A-Learning BG", img_background); + } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); @@ -84,28 +84,26 @@ void AdaptiveBackgroundLearning::process(const cv::Mat &img_input, cv::Mat &img_ void AdaptiveBackgroundLearning::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/AdaptiveBackgroundLearning.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), 0, CV_STORAGE_WRITE); cvWriteReal(fs, "alpha", alpha); cvWriteInt(fs, "limit", limit); cvWriteInt(fs, "enableThreshold", enableThreshold); cvWriteInt(fs, "threshold", threshold); - cvWriteInt(fs, "showForeground", showForeground); - cvWriteInt(fs, "showBackground", showBackground); + cvWriteInt(fs, "showOutput", showOutput); cvReleaseFileStorage(&fs); } void AdaptiveBackgroundLearning::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/AdaptiveBackgroundLearning.xml", 0, CV_STORAGE_READ); - - alpha = cvReadRealByName(fs, 0, "alpha", 0.05); - limit = cvReadIntByName(fs, 0, "limit", -1); - enableThreshold = cvReadIntByName(fs, 0, "enableThreshold", true); - threshold = cvReadIntByName(fs, 0, "threshold", 15); - showForeground = cvReadIntByName(fs, 0, "showForeground", true); - showBackground = cvReadIntByName(fs, 0, "showBackground", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.05); + limit = cvReadIntByName(fs, nullptr, "limit", -1); + enableThreshold = cvReadIntByName(fs, nullptr, "enableThreshold", true); + threshold = cvReadIntByName(fs, nullptr, "threshold", 15); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/AdaptiveBackgroundLearning.h b/package_bgs/AdaptiveBackgroundLearning.h index 4bbac54..3cfc686 100644 --- a/package_bgs/AdaptiveBackgroundLearning.h +++ b/package_bgs/AdaptiveBackgroundLearning.h @@ -16,35 +16,32 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - #include "IBGS.h" -class AdaptiveBackgroundLearning : public IBGS +namespace bgslibrary { -private: - bool firstTime; - cv::Mat img_background; - double alpha; - long limit; - long counter; - double minVal; - double maxVal; - bool enableThreshold; - int threshold; - bool showForeground; - bool showBackground; - -public: - AdaptiveBackgroundLearning(); - ~AdaptiveBackgroundLearning(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class AdaptiveBackgroundLearning : public IBGS + { + private: + double alpha; + long limit; + long counter; + double minVal; + double maxVal; + bool enableThreshold; + int threshold; + + public: + AdaptiveBackgroundLearning(); + ~AdaptiveBackgroundLearning(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/AdaptiveSelectiveBackgroundLearning.cpp b/package_bgs/AdaptiveSelectiveBackgroundLearning.cpp index cd3e7d3..2f6f9fe 100644 --- a/package_bgs/AdaptiveSelectiveBackgroundLearning.cpp +++ b/package_bgs/AdaptiveSelectiveBackgroundLearning.cpp @@ -16,11 +16,14 @@ along with BGSLibrary. If not, see . */ #include "AdaptiveSelectiveBackgroundLearning.h" -AdaptiveSelectiveBackgroundLearning::AdaptiveSelectiveBackgroundLearning() : firstTime(true), -alphaLearn(0.05), alphaDetection(0.05), learningFrames(-1), counter(0), minVal(0.0), maxVal(1.0), -threshold(15), showOutput(true) +using namespace bgslibrary::algorithms; + +AdaptiveSelectiveBackgroundLearning::AdaptiveSelectiveBackgroundLearning() : + alphaLearn(0.05), alphaDetection(0.05), learningFrames(-1), counter(0), minVal(0.0), maxVal(1.0), + threshold(15) { std::cout << "AdaptiveSelectiveBackgroundLearning()" << std::endl; + setup("./config/AdaptiveSelectiveBackgroundLearning.xml"); } AdaptiveSelectiveBackgroundLearning::~AdaptiveSelectiveBackgroundLearning() @@ -30,8 +33,7 @@ AdaptiveSelectiveBackgroundLearning::~AdaptiveSelectiveBackgroundLearning() void AdaptiveSelectiveBackgroundLearning::process(const cv::Mat &img_input_, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input_.empty()) - return; + init(img_input_, img_output, img_bgmodel); cv::Mat img_input; if (img_input_.channels() == 3) @@ -39,24 +41,19 @@ void AdaptiveSelectiveBackgroundLearning::process(const cv::Mat &img_input_, cv: else img_input_.copyTo(img_input); - loadConfig(); - - if(firstTime) - saveConfig(); - - if(img_background.empty()) + if (img_background.empty()) img_input.copyTo(img_background); cv::Mat img_input_f(img_input.size(), CV_32F); - img_input.convertTo(img_input_f, CV_32F, 1./255.); + img_input.convertTo(img_input_f, CV_32F, 1. / 255.); cv::Mat img_background_f(img_background.size(), CV_32F); - img_background.convertTo(img_background_f, CV_32F, 1./255.); + img_background.convertTo(img_background_f, CV_32F, 1. / 255.); cv::Mat img_diff_f(img_input.size(), CV_32F); cv::absdiff(img_input_f, img_background_f, img_diff_f); - cv::Mat img_foreground(img_input.size(), CV_8U); + //cv::Mat img_foreground(img_input.size(), CV_8U); img_diff_f.convertTo(img_foreground, CV_8U, 255.0 / (maxVal - minVal), -minVal); cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY); @@ -88,15 +85,17 @@ void AdaptiveSelectiveBackgroundLearning::process(const cv::Mat &img_input_, cv: } } - cv::Mat img_new_background(img_input.size(), CV_8U); - img_background_f.convertTo(img_new_background, CV_8U, 255.0 / (maxVal - minVal), -minVal); - img_new_background.copyTo(img_background); - - if(showOutput) + //cv::Mat img_new_background(img_input.size(), CV_8U); + img_background_f.convertTo(img_background, CV_8UC1, 255.0 / (maxVal - minVal), -minVal); + //img_new_background.copyTo(img_background); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) { cv::imshow("AS-Learning FG", img_foreground); cv::imshow("AS-Learning BG", img_background); } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); @@ -106,7 +105,7 @@ void AdaptiveSelectiveBackgroundLearning::process(const cv::Mat &img_input_, cv: void AdaptiveSelectiveBackgroundLearning::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/AdaptiveSelectiveBackgroundLearning.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "learningFrames", learningFrames); cvWriteReal(fs, "alphaLearn", alphaLearn); @@ -119,13 +118,13 @@ void AdaptiveSelectiveBackgroundLearning::saveConfig() void AdaptiveSelectiveBackgroundLearning::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/AdaptiveSelectiveBackgroundLearning.xml", 0, CV_STORAGE_READ); - - learningFrames = cvReadIntByName(fs, 0, "learningFrames", 90); - alphaLearn = cvReadRealByName(fs, 0, "alphaLearn", 0.05); - alphaDetection = cvReadRealByName(fs, 0, "alphaDetection", 0.05); - threshold = cvReadIntByName(fs, 0, "threshold", 25); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), 0, CV_STORAGE_READ); + + learningFrames = cvReadIntByName(fs, nullptr, "learningFrames", 90); + alphaLearn = cvReadRealByName(fs, nullptr, "alphaLearn", 0.05); + alphaDetection = cvReadRealByName(fs, nullptr, "alphaDetection", 0.05); + threshold = cvReadIntByName(fs, nullptr, "threshold", 25); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/AdaptiveSelectiveBackgroundLearning.h b/package_bgs/AdaptiveSelectiveBackgroundLearning.h index 2276747..24da44c 100644 --- a/package_bgs/AdaptiveSelectiveBackgroundLearning.h +++ b/package_bgs/AdaptiveSelectiveBackgroundLearning.h @@ -16,34 +16,32 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - #include "IBGS.h" -class AdaptiveSelectiveBackgroundLearning : public IBGS +namespace bgslibrary { -private: - bool firstTime; - cv::Mat img_background; - double alphaLearn; - double alphaDetection; - long learningFrames; - long counter; - double minVal; - double maxVal; - int threshold; - bool showOutput; - -public: - AdaptiveSelectiveBackgroundLearning(); - ~AdaptiveSelectiveBackgroundLearning(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class AdaptiveSelectiveBackgroundLearning : public IBGS + { + private: + double alphaLearn; + double alphaDetection; + long learningFrames; + long counter; + double minVal; + double maxVal; + int threshold; + + public: + AdaptiveSelectiveBackgroundLearning(); + ~AdaptiveSelectiveBackgroundLearning(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/DPAdaptiveMedian.cpp b/package_bgs/DPAdaptiveMedian.cpp new file mode 100644 index 0000000..0885580 --- /dev/null +++ b/package_bgs/DPAdaptiveMedian.cpp @@ -0,0 +1,110 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "DPAdaptiveMedian.h" + +using namespace bgslibrary::algorithms; + +DPAdaptiveMedian::DPAdaptiveMedian() : + frameNumber(0), threshold(40), samplingRate(7), learningFrames(30) +{ + std::cout << "DPAdaptiveMedian()" << std::endl; + setup("./config/DPAdaptiveMedian.xml"); +} + +DPAdaptiveMedian::~DPAdaptiveMedian() +{ + std::cout << "~DPAdaptiveMedian()" << std::endl; +} + +void DPAdaptiveMedian::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + frame = new IplImage(img_input); + + if (firstTime) + frame_data.ReleaseMemory(false); + frame_data = frame; + + if (firstTime) + { + int width = img_input.size().width; + int height = img_input.size().height; + + lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); + lowThresholdMask.Ptr()->origin = IPL_ORIGIN_BL; + + highThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); + highThresholdMask.Ptr()->origin = IPL_ORIGIN_BL; + + params.SetFrameSize(width, height); + params.LowThreshold() = threshold; + params.HighThreshold() = 2 * params.LowThreshold(); // Note: high threshold is used by post-processing + params.SamplingRate() = samplingRate; + params.LearningFrames() = learningFrames; + + bgs.Initalize(params); + bgs.InitModel(frame_data); + } + + bgs.Subtract(frameNumber, frame_data, lowThresholdMask, highThresholdMask); + lowThresholdMask.Clear(); + bgs.Update(frameNumber, frame_data, lowThresholdMask); + + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + //bitwise_not(img_foreground, img_foreground); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + { + cv::imshow("Adaptive Median FG (McFarlane&Schofield)", img_foreground); + cv::imshow("Adaptive Median BG (McFarlane&Schofield)", img_background); + } +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); + + delete frame; + firstTime = false; + frameNumber++; +} + +void DPAdaptiveMedian::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteInt(fs, "threshold", threshold); + cvWriteInt(fs, "samplingRate", samplingRate); + cvWriteInt(fs, "learningFrames", learningFrames); + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void DPAdaptiveMedian::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadIntByName(fs, nullptr, "threshold", 40); + samplingRate = cvReadIntByName(fs, nullptr, "samplingRate", 7); + learningFrames = cvReadIntByName(fs, nullptr, "learningFrames", 30); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + + cvReleaseFileStorage(&fs); +} diff --git a/package_bgs/DPAdaptiveMedian.h b/package_bgs/DPAdaptiveMedian.h new file mode 100644 index 0000000..7d2b7fa --- /dev/null +++ b/package_bgs/DPAdaptiveMedian.h @@ -0,0 +1,53 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "dp/AdaptiveMedianBGS.h" + +using namespace Algorithms::BackgroundSubtraction; + +namespace bgslibrary +{ + namespace algorithms + { + class DPAdaptiveMedian : public IBGS + { + private: + long frameNumber; + IplImage* frame; + RgbImage frame_data; + AdaptiveMedianParams params; + AdaptiveMedianBGS bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + int threshold; + int samplingRate; + int learningFrames; + + public: + DPAdaptiveMedian(); + ~DPAdaptiveMedian(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/dp/DPEigenbackgroundBGS.cpp b/package_bgs/DPEigenbackground.cpp similarity index 53% rename from package_bgs/dp/DPEigenbackgroundBGS.cpp rename to package_bgs/DPEigenbackground.cpp index 630d9cc..75763ce 100644 --- a/package_bgs/dp/DPEigenbackgroundBGS.cpp +++ b/package_bgs/DPEigenbackground.cpp @@ -14,37 +14,35 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "DPEigenbackgroundBGS.h" +#include "DPEigenbackground.h" -DPEigenbackgroundBGS::DPEigenbackgroundBGS() : firstTime(true), frameNumber(0), threshold(225), historySize(20), embeddedDim(10), showOutput(true) +using namespace bgslibrary::algorithms; + +DPEigenbackground::DPEigenbackground() : + frameNumber(0), threshold(225), historySize(20), embeddedDim(10) { - std::cout << "DPEigenbackgroundBGS()" << std::endl; + std::cout << "DPEigenbackground()" << std::endl; + setup("./config/DPEigenbackground.xml"); } -DPEigenbackgroundBGS::~DPEigenbackgroundBGS() +DPEigenbackground::~DPEigenbackground() { - std::cout << "~DPEigenbackgroundBGS()" << std::endl; + std::cout << "~DPEigenbackground()" << std::endl; } -void DPEigenbackgroundBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void DPEigenbackground::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) frame_data.ReleaseMemory(false); frame_data = frame; - if(firstTime) + if (firstTime) { - int width = img_input.size().width; + int width = img_input.size().width; int height = img_input.size().height; lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); @@ -55,7 +53,7 @@ void DPEigenbackgroundBGS::process(const cv::Mat &img_input, cv::Mat &img_output params.SetFrameSize(width, height); params.LowThreshold() = threshold; //15*15; - params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing + params.HighThreshold() = 2 * params.LowThreshold(); // Note: high threshold is used by post-processing //params.HistorySize() = 100; params.HistorySize() = historySize; //params.EmbeddedDim() = 20; @@ -68,22 +66,27 @@ void DPEigenbackgroundBGS::process(const cv::Mat &img_input, cv::Mat &img_output bgs.Subtract(frameNumber, frame_data, lowThresholdMask, highThresholdMask); lowThresholdMask.Clear(); bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - if(showOutput) - cv::imshow("Eigenbackground (Oliver)", foreground); + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + //img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("Eigenbackground (Oliver)", img_foreground); +#endif - foreground.copyTo(img_output); + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); delete frame; firstTime = false; frameNumber++; } -void DPEigenbackgroundBGS::saveConfig() +void DPEigenbackground::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPEigenbackgroundBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "threshold", threshold); cvWriteInt(fs, "historySize", historySize); @@ -93,14 +96,14 @@ void DPEigenbackgroundBGS::saveConfig() cvReleaseFileStorage(&fs); } -void DPEigenbackgroundBGS::loadConfig() +void DPEigenbackground::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPEigenbackgroundBGS.xml", 0, CV_STORAGE_READ); - - threshold = cvReadIntByName(fs, 0, "threshold", 225); - historySize = cvReadIntByName(fs, 0, "historySize", 20); - embeddedDim = cvReadIntByName(fs, 0, "embeddedDim", 10); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadIntByName(fs, nullptr, "threshold", 225); + historySize = cvReadIntByName(fs, nullptr, "historySize", 20); + embeddedDim = cvReadIntByName(fs, nullptr, "embeddedDim", 10); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/DPEigenbackground.h b/package_bgs/DPEigenbackground.h new file mode 100644 index 0000000..f84fee7 --- /dev/null +++ b/package_bgs/DPEigenbackground.h @@ -0,0 +1,55 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "dp/Eigenbackground.h" + +using namespace Algorithms::BackgroundSubtraction; + +namespace bgslibrary +{ + namespace algorithms + { + class DPEigenbackground : public IBGS + { + private: + long frameNumber; + IplImage* frame; + RgbImage frame_data; + + EigenbackgroundParams params; + Eigenbackground bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + + int threshold; + int historySize; + int embeddedDim; + + public: + DPEigenbackground(); + ~DPEigenbackground(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/dp/DPGrimsonGMMBGS.cpp b/package_bgs/DPGrimsonGMM.cpp similarity index 54% rename from package_bgs/dp/DPGrimsonGMMBGS.cpp rename to package_bgs/DPGrimsonGMM.cpp index ae54860..c72b4d2 100644 --- a/package_bgs/dp/DPGrimsonGMMBGS.cpp +++ b/package_bgs/DPGrimsonGMM.cpp @@ -14,37 +14,35 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "DPGrimsonGMMBGS.h" +#include "DPGrimsonGMM.h" -DPGrimsonGMMBGS::DPGrimsonGMMBGS() : firstTime(true), frameNumber(0), threshold(9.0), alpha(0.01), gaussians(3), showOutput(true) +using namespace bgslibrary::algorithms; + +DPGrimsonGMM::DPGrimsonGMM() : + frameNumber(0), threshold(9.0), alpha(0.01), gaussians(3) { - std::cout << "DPGrimsonGMMBGS()" << std::endl; + std::cout << "DPGrimsonGMM()" << std::endl; + setup("./config/DPGrimsonGMM.xml"); } -DPGrimsonGMMBGS::~DPGrimsonGMMBGS() +DPGrimsonGMM::~DPGrimsonGMM() { - std::cout << "~DPGrimsonGMMBGS()" << std::endl; + std::cout << "~DPGrimsonGMM()" << std::endl; } -void DPGrimsonGMMBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void DPGrimsonGMM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) frame_data.ReleaseMemory(false); frame_data = frame; - if(firstTime) + if (firstTime) { - int width = img_input.size().width; + int width = img_input.size().width; int height = img_input.size().height; lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); @@ -55,7 +53,7 @@ void DPGrimsonGMMBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv: params.SetFrameSize(width, height); params.LowThreshold() = threshold; //3.0f*3.0f; - params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing + params.HighThreshold() = 2 * params.LowThreshold(); // Note: high threshold is used by post-processing //params.Alpha() = 0.001f; params.Alpha() = alpha; //0.01f; params.MaxModes() = gaussians; //3; @@ -67,22 +65,27 @@ void DPGrimsonGMMBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv: bgs.Subtract(frameNumber, frame_data, lowThresholdMask, highThresholdMask); lowThresholdMask.Clear(); bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - if(showOutput) - cv::imshow("GMM (Grimson)", foreground); - - foreground.copyTo(img_output); + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + //img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("GMM (Grimson)", img_foreground); +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); delete frame; firstTime = false; frameNumber++; } -void DPGrimsonGMMBGS::saveConfig() +void DPGrimsonGMM::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPGrimsonGMMBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteReal(fs, "threshold", threshold); cvWriteReal(fs, "alpha", alpha); @@ -92,14 +95,14 @@ void DPGrimsonGMMBGS::saveConfig() cvReleaseFileStorage(&fs); } -void DPGrimsonGMMBGS::loadConfig() +void DPGrimsonGMM::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPGrimsonGMMBGS.xml", 0, CV_STORAGE_READ); - - threshold = cvReadRealByName(fs, 0, "threshold", 9.0); - alpha = cvReadRealByName(fs, 0, "alpha", 0.01); - gaussians = cvReadIntByName(fs, 0, "gaussians", 3); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadRealByName(fs, nullptr, "threshold", 9.0); + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.01); + gaussians = cvReadIntByName(fs, nullptr, "gaussians", 3); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/dp/DPEigenbackgroundBGS.h b/package_bgs/DPGrimsonGMM.h similarity index 53% rename from package_bgs/dp/DPEigenbackgroundBGS.h rename to package_bgs/DPGrimsonGMM.h index cd4e54c..dcc05eb 100644 --- a/package_bgs/dp/DPEigenbackgroundBGS.h +++ b/package_bgs/DPGrimsonGMM.h @@ -16,41 +16,40 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "../IBGS.h" -#include "Eigenbackground.h" +#include "IBGS.h" +#include "dp/GrimsonGMM.h" using namespace Algorithms::BackgroundSubtraction; -class DPEigenbackgroundBGS : public IBGS +namespace bgslibrary { -private: - bool firstTime; - long frameNumber; - IplImage* frame; - RgbImage frame_data; - - EigenbackgroundParams params; - Eigenbackground bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - int threshold; - int historySize; - int embeddedDim; - bool showOutput; - -public: - DPEigenbackgroundBGS(); - ~DPEigenbackgroundBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class DPGrimsonGMM : public IBGS + { + private: + long frameNumber; + IplImage* frame; + RgbImage frame_data; + + GrimsonParams params; + GrimsonGMM bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + + double threshold; + double alpha; + int gaussians; + + public: + DPGrimsonGMM(); + ~DPGrimsonGMM(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/dp/DPMeanBGS.cpp b/package_bgs/DPMean.cpp similarity index 55% rename from package_bgs/dp/DPMeanBGS.cpp rename to package_bgs/DPMean.cpp index 13260b4..3af1fb6 100644 --- a/package_bgs/dp/DPMeanBGS.cpp +++ b/package_bgs/DPMean.cpp @@ -14,37 +14,35 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "DPMeanBGS.h" +#include "DPMean.h" -DPMeanBGS::DPMeanBGS() : firstTime(true), frameNumber(0), threshold(2700), alpha(1e-6f), learningFrames(30), showOutput(true) +using namespace bgslibrary::algorithms; + +DPMean::DPMean() : + frameNumber(0), threshold(2700), alpha(1e-6f), learningFrames(30) { - std::cout << "DPMeanBGS()" << std::endl; + std::cout << "DPMean()" << std::endl; + setup("./config/DPMean.xml"); } -DPMeanBGS::~DPMeanBGS() +DPMean::~DPMean() { - std::cout << "~DPMeanBGS()" << std::endl; + std::cout << "~DPMean()" << std::endl; } -void DPMeanBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void DPMean::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) frame_data.ReleaseMemory(false); frame_data = frame; - if(firstTime) + if (firstTime) { - int width = img_input.size().width; + int width = img_input.size().width; int height = img_input.size().height; lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); @@ -55,7 +53,7 @@ void DPMeanBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & params.SetFrameSize(width, height); params.LowThreshold() = threshold; //3*30*30; // 2700 - params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing + params.HighThreshold() = 2 * params.LowThreshold(); // Note: high threshold is used by post-processing //params.Alpha() = 1e-6f; params.Alpha() = alpha; params.LearningFrames() = learningFrames;//30; @@ -67,22 +65,27 @@ void DPMeanBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & bgs.Subtract(frameNumber, frame_data, lowThresholdMask, highThresholdMask); lowThresholdMask.Clear(); bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - if(showOutput) - cv::imshow("Temporal Mean (Donovan Parks)", foreground); + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + //img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("Temporal Mean (Donovan Parks)", img_foreground); +#endif - foreground.copyTo(img_output); + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); delete frame; firstTime = false; frameNumber++; } -void DPMeanBGS::saveConfig() +void DPMean::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPMeanBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "threshold", threshold); cvWriteReal(fs, "alpha", alpha); @@ -92,14 +95,14 @@ void DPMeanBGS::saveConfig() cvReleaseFileStorage(&fs); } -void DPMeanBGS::loadConfig() +void DPMean::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPMeanBGS.xml", 0, CV_STORAGE_READ); - - threshold = cvReadIntByName(fs, 0, "threshold", 2700); - alpha = cvReadRealByName(fs, 0, "alpha", 1e-6f); - learningFrames = cvReadIntByName(fs, 0, "learningFrames", 30); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadIntByName(fs, nullptr, "threshold", 2700); + alpha = cvReadRealByName(fs, nullptr, "alpha", 1e-6f); + learningFrames = cvReadIntByName(fs, nullptr, "learningFrames", 30); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/dp/DPZivkovicAGMMBGS.h b/package_bgs/DPMean.h similarity index 56% rename from package_bgs/dp/DPZivkovicAGMMBGS.h rename to package_bgs/DPMean.h index 775226a..6029968 100644 --- a/package_bgs/dp/DPZivkovicAGMMBGS.h +++ b/package_bgs/DPMean.h @@ -16,41 +16,40 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "../IBGS.h" -#include "ZivkovicAGMM.h" +#include "IBGS.h" +#include "dp/MeanBGS.h" using namespace Algorithms::BackgroundSubtraction; -class DPZivkovicAGMMBGS : public IBGS +namespace bgslibrary { -private: - bool firstTime; - long frameNumber; - IplImage* frame; - RgbImage frame_data; - - ZivkovicParams params; - ZivkovicAGMM bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - double threshold; - double alpha; - int gaussians; - bool showOutput; - -public: - DPZivkovicAGMMBGS(); - ~DPZivkovicAGMMBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class DPMean : public IBGS + { + private: + long frameNumber; + IplImage* frame; + RgbImage frame_data; + + MeanParams params; + MeanBGS bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + + int threshold; + double alpha; + int learningFrames; + + public: + DPMean(); + ~DPMean(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/dp/DPPratiMediodBGS.cpp b/package_bgs/DPPratiMediod.cpp similarity index 53% rename from package_bgs/dp/DPPratiMediodBGS.cpp rename to package_bgs/DPPratiMediod.cpp index ba1d6be..d1942c5 100644 --- a/package_bgs/dp/DPPratiMediodBGS.cpp +++ b/package_bgs/DPPratiMediod.cpp @@ -14,37 +14,35 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "DPPratiMediodBGS.h" +#include "DPPratiMediod.h" -DPPratiMediodBGS::DPPratiMediodBGS() : firstTime(true), frameNumber(0), threshold(30), samplingRate(5), historySize(16), weight(5), showOutput(true) +using namespace bgslibrary::algorithms; + +DPPratiMediod::DPPratiMediod() : + frameNumber(0), threshold(30), samplingRate(5), historySize(16), weight(5) { - std::cout << "DPPratiMediodBGS()" << std::endl; + std::cout << "DPPratiMediod()" << std::endl; + setup("./config/DPPratiMediod.xml"); } -DPPratiMediodBGS::~DPPratiMediodBGS() +DPPratiMediod::~DPPratiMediod() { - std::cout << "~DPPratiMediodBGS()" << std::endl; + std::cout << "~DPPratiMediod()" << std::endl; } -void DPPratiMediodBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void DPPratiMediod::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) frame_data.ReleaseMemory(false); frame_data = frame; - if(firstTime) + if (firstTime) { - int width = img_input.size().width; + int width = img_input.size().width; int height = img_input.size().height; lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); @@ -55,7 +53,7 @@ void DPPratiMediodBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv params.SetFrameSize(width, height); params.LowThreshold() = threshold; - params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing + params.HighThreshold() = 2 * params.LowThreshold(); // Note: high threshold is used by post-processing params.SamplingRate() = samplingRate; params.HistorySize() = historySize; params.Weight() = weight; @@ -67,26 +65,29 @@ void DPPratiMediodBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv bgs.Subtract(frameNumber, frame_data, lowThresholdMask, highThresholdMask); lowThresholdMask.Clear(); bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - cv::Mat background(bgs.Background()->Ptr()); - if(showOutput){ - cv::imshow("Temporal Median FG (Cucchiara&Calderara)", foreground); - cv::imshow("Temporal Median BG (Cucchiara&Calderara)", background); + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + { + cv::imshow("Temporal Median FG (Cucchiara&Calderara)", img_foreground); + cv::imshow("Temporal Median BG (Cucchiara&Calderara)", img_background); } +#endif - foreground.copyTo(img_output); - background.copyTo(img_bgmodel); + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); delete frame; firstTime = false; frameNumber++; } -void DPPratiMediodBGS::saveConfig() +void DPPratiMediod::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPPratiMediodBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "threshold", threshold); cvWriteInt(fs, "samplingRate", samplingRate); @@ -97,15 +98,15 @@ void DPPratiMediodBGS::saveConfig() cvReleaseFileStorage(&fs); } -void DPPratiMediodBGS::loadConfig() +void DPPratiMediod::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPPratiMediodBGS.xml", 0, CV_STORAGE_READ); - - threshold = cvReadIntByName(fs, 0, "threshold", 30); - samplingRate = cvReadIntByName(fs, 0, "samplingRate", 5); - historySize = cvReadIntByName(fs, 0, "historySize", 16); - weight = cvReadIntByName(fs, 0, "weight", 5); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadIntByName(fs, nullptr, "threshold", 30); + samplingRate = cvReadIntByName(fs, nullptr, "samplingRate", 5); + historySize = cvReadIntByName(fs, nullptr, "historySize", 16); + weight = cvReadIntByName(fs, nullptr, "weight", 5); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); -} \ No newline at end of file +} diff --git a/package_bgs/dp/DPMeanBGS.h b/package_bgs/DPPratiMediod.h similarity index 52% rename from package_bgs/dp/DPMeanBGS.h rename to package_bgs/DPPratiMediod.h index 8829686..d37a77a 100644 --- a/package_bgs/dp/DPMeanBGS.h +++ b/package_bgs/DPPratiMediod.h @@ -16,41 +16,41 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "../IBGS.h" -#include "MeanBGS.h" +#include "IBGS.h" +#include "dp/PratiMediodBGS.h" using namespace Algorithms::BackgroundSubtraction; -class DPMeanBGS : public IBGS +namespace bgslibrary { -private: - bool firstTime; - long frameNumber; - IplImage* frame; - RgbImage frame_data; - - MeanParams params; - MeanBGS bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - int threshold; - double alpha; - int learningFrames; - bool showOutput; - -public: - DPMeanBGS(); - ~DPMeanBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class DPPratiMediod : public IBGS + { + private: + long frameNumber; + IplImage* frame; + RgbImage frame_data; + + PratiParams params; + PratiMediodBGS bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + + int threshold; + int samplingRate; + int historySize; + int weight; + + public: + DPPratiMediod(); + ~DPPratiMediod(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/dp/DPTextureBGS.cpp b/package_bgs/DPTexture.cpp similarity index 65% rename from package_bgs/dp/DPTextureBGS.cpp rename to package_bgs/DPTexture.cpp index 2d34342..3285ccf 100644 --- a/package_bgs/dp/DPTextureBGS.cpp +++ b/package_bgs/DPTexture.cpp @@ -14,15 +14,18 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "DPTextureBGS.h" +#include "DPTexture.h" -DPTextureBGS::DPTextureBGS() : firstTime(true), showOutput(true) - //, enableFiltering(true) +using namespace bgslibrary::algorithms; + +DPTexture::DPTexture() +// : enableFiltering(true) { - std::cout << "DPTextureBGS()" << std::endl; + std::cout << "DPTexture()" << std::endl; + setup("./config/DPTexture.xml"); } -DPTextureBGS::~DPTextureBGS() +DPTexture::~DPTexture() { delete[] bgModel; // ~10Kb (25.708-15.968) delete[] modeArray; @@ -33,34 +36,31 @@ DPTextureBGS::~DPTextureBGS() fgMask.ReleaseImage(); tempMask.ReleaseImage(); texture.ReleaseImage(); - std::cout << "~DPTextureBGS()" << std::endl; + std::cout << "~DPTexture()" << std::endl; } -void DPTextureBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void DPTexture::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) { - width = img_input.size().width; + width = img_input.size().width; height = img_input.size().height; size = width * height; // input image image = cvCreateImage(cvSize(width, height), 8, 3); - cvCopy(frame, image.Ptr()); + cvCopy(frame, image.Ptr()); // foreground masks fgMask = cvCreateImage(cvSize(width, height), 8, 1); tempMask = cvCreateImage(cvSize(width, height), 8, 1); cvZero(fgMask.Ptr()); cvZero(tempMask.Ptr()); - + // create background model bgModel = new TextureArray[size]; texture = cvCreateImage(cvSize(width, height), 8, 3); @@ -71,16 +71,16 @@ void DPTextureBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Ma // initialize background model bgs.LBP(image, texture); bgs.Histogram(texture, curTextureHist); - for(int y = REGION_R+TEXTURE_R; y < height-REGION_R-TEXTURE_R; ++y) + for (int y = REGION_R + TEXTURE_R; y < height - REGION_R - TEXTURE_R; ++y) { - for(int x = REGION_R+TEXTURE_R; x < width-REGION_R-TEXTURE_R; ++x) + for (int x = REGION_R + TEXTURE_R; x < width - REGION_R - TEXTURE_R; ++x) { - int index = x+y*width; - - for(int m = 0; m < NUM_MODES; ++m) + int index = x + y*width; + + for (int m = 0; m < NUM_MODES; ++m) { - for(int i = 0; i < NUM_BINS; ++i) - { + for (int i = 0; i < NUM_BINS; ++i) + { bgModel[index].mode[m].r[i] = curTextureHist[index].r[i]; bgModel[index].mode[m].g[i] = curTextureHist[index].g[i]; bgModel[index].mode[m].b[i] = curTextureHist[index].b[i]; @@ -91,18 +91,16 @@ void DPTextureBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Ma //dilateElement = cvCreateStructuringElementEx(7, 7, 3, 3, CV_SHAPE_RECT); //erodeElement = cvCreateStructuringElementEx(3, 3, 1, 1, CV_SHAPE_RECT); - - saveConfig(); firstTime = false; } - - cvCopy(frame, image.Ptr()); + + cvCopy(frame, image.Ptr()); // perform background subtraction bgs.LBP(image, texture); bgs.Histogram(texture, curTextureHist); bgs.BgsCompare(bgModel, curTextureHist, modeArray, THRESHOLD, fgMask); - + //if(enableFiltering) //{ // // size filtering @@ -120,22 +118,27 @@ void DPTextureBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Ma // cvErode(fgMask.Ptr(), fgMask.Ptr(), erodeElement, 1); //} - cv::Mat foreground(fgMask.Ptr()); - if(!foreground.empty()) - foreground.copyTo(img_output); - - if(showOutput) - cv::imshow("Texture BGS (Donovan Parks)", foreground); + img_foreground = cv::cvarrToMat(fgMask.Ptr()); + //img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("Texture BGS (Donovan Parks)", img_foreground); +#endif - // update background subtraction + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); + + // update background subtraction bgs.UpdateModel(fgMask, bgModel, curTextureHist, modeArray); - + delete frame; } -void DPTextureBGS::saveConfig() +void DPTexture::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPTextureBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); //cvWriteReal(fs, "alpha", alpha); //cvWriteInt(fs, "enableFiltering", enableFiltering); @@ -144,13 +147,13 @@ void DPTextureBGS::saveConfig() cvReleaseFileStorage(&fs); } -void DPTextureBGS::loadConfig() +void DPTexture::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPTextureBGS.xml", 0, CV_STORAGE_READ); - - //alpha = cvReadRealByName(fs, 0, "alpha", 1e-6f); - //enableFiltering = cvReadIntByName(fs, 0, "enableFiltering", true); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + //alpha = cvReadRealByName(fs, nullptr, "alpha", 1e-6f); + //enableFiltering = cvReadIntByName(fs, nullptr, "enableFiltering", true); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/DPTexture.h b/package_bgs/DPTexture.h new file mode 100644 index 0000000..3cfdcea --- /dev/null +++ b/package_bgs/DPTexture.h @@ -0,0 +1,59 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "dp/TextureBGS.h" +//#include "ConnectedComponents.h" + +namespace bgslibrary +{ + namespace algorithms + { + class DPTexture : public IBGS + { + private: + int width; + int height; + int size; + TextureBGS bgs; + IplImage* frame; + RgbImage image; + BwImage fgMask; + BwImage tempMask; + TextureArray* bgModel; + RgbImage texture; + unsigned char* modeArray; + TextureHistogram* curTextureHist; + //ConnectedComponents cc; + //CBlobResult largeBlobs; + //IplConvKernel* dilateElement; + //IplConvKernel* erodeElement; + //bool enableFiltering; + + public: + DPTexture(); + ~DPTexture(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/dp/DPWrenGABGS.cpp b/package_bgs/DPWrenGA.cpp similarity index 54% rename from package_bgs/dp/DPWrenGABGS.cpp rename to package_bgs/DPWrenGA.cpp index d7241b1..7fc3313 100644 --- a/package_bgs/dp/DPWrenGABGS.cpp +++ b/package_bgs/DPWrenGA.cpp @@ -14,37 +14,35 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "DPWrenGABGS.h" +#include "DPWrenGA.h" -DPWrenGABGS::DPWrenGABGS() : firstTime(true), frameNumber(0), threshold(12.25f), alpha(0.005f), learningFrames(30), showOutput(true) +using namespace bgslibrary::algorithms; + +DPWrenGA::DPWrenGA() : + frameNumber(0), threshold(12.25f), alpha(0.005f), learningFrames(30) { - std::cout << "DPWrenGABGS()" << std::endl; + std::cout << "DPWrenGA()" << std::endl; + setup("./config/DPWrenGA.xml"); } -DPWrenGABGS::~DPWrenGABGS() +DPWrenGA::~DPWrenGA() { - std::cout << "~DPWrenGABGS()" << std::endl; + std::cout << "~DPWrenGA()" << std::endl; } -void DPWrenGABGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void DPWrenGA::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) frame_data.ReleaseMemory(false); frame_data = frame; - if(firstTime) + if (firstTime) { - int width = img_input.size().width; + int width = img_input.size().width; int height = img_input.size().height; lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); @@ -55,7 +53,7 @@ void DPWrenGABGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat params.SetFrameSize(width, height); params.LowThreshold() = threshold; //3.5f*3.5f; - params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing + params.HighThreshold() = 2 * params.LowThreshold(); // Note: high threshold is used by post-processing params.Alpha() = alpha; //0.005f; params.LearningFrames() = learningFrames; //30; @@ -66,22 +64,27 @@ void DPWrenGABGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat bgs.Subtract(frameNumber, frame_data, lowThresholdMask, highThresholdMask); lowThresholdMask.Clear(); bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - if(showOutput) - cv::imshow("Gaussian Average (Wren)", foreground); - - foreground.copyTo(img_output); + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + //img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("Gaussian Average (Wren)", img_foreground); +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); delete frame; firstTime = false; frameNumber++; } -void DPWrenGABGS::saveConfig() +void DPWrenGA::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPWrenGABGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteReal(fs, "threshold", threshold); cvWriteReal(fs, "alpha", alpha); @@ -91,15 +94,14 @@ void DPWrenGABGS::saveConfig() cvReleaseFileStorage(&fs); } -void DPWrenGABGS::loadConfig() +void DPWrenGA::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPWrenGABGS.xml", 0, CV_STORAGE_READ); - - threshold = cvReadRealByName(fs, 0, "threshold", 12.25f); - alpha = cvReadRealByName(fs, 0, "alpha", 0.005f); - learningFrames = cvReadIntByName(fs, 0, "learningFrames", 30); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadRealByName(fs, nullptr, "threshold", 12.25f); + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.005f); + learningFrames = cvReadIntByName(fs, nullptr, "learningFrames", 30); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } - diff --git a/package_bgs/dp/DPWrenGABGS.h b/package_bgs/DPWrenGA.h similarity index 54% rename from package_bgs/dp/DPWrenGABGS.h rename to package_bgs/DPWrenGA.h index 30ab614..e4b0b5f 100644 --- a/package_bgs/dp/DPWrenGABGS.h +++ b/package_bgs/DPWrenGA.h @@ -16,41 +16,40 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "../IBGS.h" -#include "WrenGA.h" +#include "IBGS.h" +#include "dp/WrenGA.h" using namespace Algorithms::BackgroundSubtraction; -class DPWrenGABGS : public IBGS +namespace bgslibrary { -private: - bool firstTime; - long frameNumber; - IplImage* frame; - RgbImage frame_data; - - WrenParams params; - WrenGA bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - double threshold; - double alpha; - int learningFrames; - bool showOutput; - -public: - DPWrenGABGS(); - ~DPWrenGABGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class DPWrenGA : public IBGS + { + private: + long frameNumber; + IplImage* frame; + RgbImage frame_data; + + WrenParams params; + WrenGA bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + + double threshold; + double alpha; + int learningFrames; + + public: + DPWrenGA(); + ~DPWrenGA(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/dp/DPZivkovicAGMMBGS.cpp b/package_bgs/DPZivkovicAGMM.cpp similarity index 53% rename from package_bgs/dp/DPZivkovicAGMMBGS.cpp rename to package_bgs/DPZivkovicAGMM.cpp index e8276f6..a5a9735 100644 --- a/package_bgs/dp/DPZivkovicAGMMBGS.cpp +++ b/package_bgs/DPZivkovicAGMM.cpp @@ -14,37 +14,35 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "DPZivkovicAGMMBGS.h" +#include "DPZivkovicAGMM.h" -DPZivkovicAGMMBGS::DPZivkovicAGMMBGS() : firstTime(true), frameNumber(0), threshold(25.0f), alpha(0.001f), gaussians(3), showOutput(true) +using namespace bgslibrary::algorithms; + +DPZivkovicAGMM::DPZivkovicAGMM() : + frameNumber(0), threshold(25.0f), alpha(0.001f), gaussians(3) { - std::cout << "DPZivkovicAGMMBGS()" << std::endl; + std::cout << "DPZivkovicAGMM()" << std::endl; + setup("./config/DPZivkovicAGMM.xml"); } -DPZivkovicAGMMBGS::~DPZivkovicAGMMBGS() +DPZivkovicAGMM::~DPZivkovicAGMM() { - std::cout << "~DPZivkovicAGMMBGS()" << std::endl; + std::cout << "~DPZivkovicAGMM()" << std::endl; } -void DPZivkovicAGMMBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void DPZivkovicAGMM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) frame_data.ReleaseMemory(false); frame_data = frame; - if(firstTime) + if (firstTime) { - int width = img_input.size().width; + int width = img_input.size().width; int height = img_input.size().height; lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); @@ -55,7 +53,7 @@ void DPZivkovicAGMMBGS::process(const cv::Mat &img_input, cv::Mat &img_output, c params.SetFrameSize(width, height); params.LowThreshold() = threshold; //5.0f*5.0f; - params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing + params.HighThreshold() = 2 * params.LowThreshold(); // Note: high threshold is used by post-processing params.Alpha() = alpha; //0.001f; params.MaxModes() = gaussians; //3; @@ -66,22 +64,27 @@ void DPZivkovicAGMMBGS::process(const cv::Mat &img_input, cv::Mat &img_output, c bgs.Subtract(frameNumber, frame_data, lowThresholdMask, highThresholdMask); lowThresholdMask.Clear(); bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - if(showOutput) - cv::imshow("Gaussian Mixture Model (Zivkovic)", foreground); - - foreground.copyTo(img_output); + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + //img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("Gaussian Mixture Model (Zivkovic)", img_foreground); +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); delete frame; firstTime = false; frameNumber++; } -void DPZivkovicAGMMBGS::saveConfig() +void DPZivkovicAGMM::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPZivkovicAGMMBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteReal(fs, "threshold", threshold); cvWriteReal(fs, "alpha", alpha); @@ -91,14 +94,14 @@ void DPZivkovicAGMMBGS::saveConfig() cvReleaseFileStorage(&fs); } -void DPZivkovicAGMMBGS::loadConfig() +void DPZivkovicAGMM::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/DPZivkovicAGMMBGS.xml", 0, CV_STORAGE_READ); - - threshold = cvReadRealByName(fs, 0, "threshold", 25.0f); - alpha = cvReadRealByName(fs, 0, "alpha", 0.001f); - gaussians = cvReadIntByName(fs, 0, "gaussians", 3); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadRealByName(fs, nullptr, "threshold", 25.0f); + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.001f); + gaussians = cvReadIntByName(fs, nullptr, "gaussians", 3); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/dp/DPGrimsonGMMBGS.h b/package_bgs/DPZivkovicAGMM.h similarity index 53% rename from package_bgs/dp/DPGrimsonGMMBGS.h rename to package_bgs/DPZivkovicAGMM.h index 4f955b9..f35504b 100644 --- a/package_bgs/dp/DPGrimsonGMMBGS.h +++ b/package_bgs/DPZivkovicAGMM.h @@ -16,41 +16,40 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "../IBGS.h" -#include "GrimsonGMM.h" +#include "IBGS.h" +#include "dp/ZivkovicAGMM.h" using namespace Algorithms::BackgroundSubtraction; -class DPGrimsonGMMBGS : public IBGS +namespace bgslibrary { -private: - bool firstTime; - long frameNumber; - IplImage* frame; - RgbImage frame_data; - - GrimsonParams params; - GrimsonGMM bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - double threshold; - double alpha; - int gaussians; - bool showOutput; - -public: - DPGrimsonGMMBGS(); - ~DPGrimsonGMMBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class DPZivkovicAGMM : public IBGS + { + private: + long frameNumber; + IplImage* frame; + RgbImage frame_data; + + ZivkovicParams params; + ZivkovicAGMM bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + + double threshold; + double alpha; + int gaussians; + + public: + DPZivkovicAGMM(); + ~DPZivkovicAGMM(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/FrameDifference.cpp b/package_bgs/FrameDifference.cpp new file mode 100644 index 0000000..4d5c076 --- /dev/null +++ b/package_bgs/FrameDifference.cpp @@ -0,0 +1,84 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "FrameDifference.h" + +using namespace bgslibrary::algorithms; + +FrameDifference::FrameDifference() : + enableThreshold(true), threshold(15) +{ + std::cout << "FrameDifference()" << std::endl; + setup("./config/FrameDifference.xml"); +} + +FrameDifference::~FrameDifference() +{ + std::cout << "~FrameDifference()" << std::endl; +} + +void FrameDifference::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + if (img_background.empty()) + { + img_input.copyTo(img_background); + return; + } + + cv::absdiff(img_background, img_input, img_foreground); + + if (img_foreground.channels() == 3) + cv::cvtColor(img_foreground, img_foreground, CV_BGR2GRAY); + + if (enableThreshold) + cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("Frame Difference", img_foreground); +#endif + + img_foreground.copyTo(img_output); + + img_input.copyTo(img_background); + img_background.copyTo(img_bgmodel); + + firstTime = false; +} + +void FrameDifference::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteInt(fs, "enableThreshold", enableThreshold); + cvWriteInt(fs, "threshold", threshold); + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void FrameDifference::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + enableThreshold = cvReadIntByName(fs, nullptr, "enableThreshold", true); + threshold = cvReadIntByName(fs, nullptr, "threshold", 15); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + + cvReleaseFileStorage(&fs); +} diff --git a/package_bgs/FrameDifferenceBGS.h b/package_bgs/FrameDifference.h similarity index 61% rename from package_bgs/FrameDifferenceBGS.h rename to package_bgs/FrameDifference.h index 338979f..07bed8e 100644 --- a/package_bgs/FrameDifferenceBGS.h +++ b/package_bgs/FrameDifference.h @@ -16,29 +16,28 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - #include "IBGS.h" -class FrameDifferenceBGS : public IBGS +namespace bgslibrary { -private: - bool firstTime; - cv::Mat img_input_prev; - cv::Mat img_foreground; - bool enableThreshold; - int threshold; - bool showOutput; - -public: - FrameDifferenceBGS(); - ~FrameDifferenceBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; \ No newline at end of file + namespace algorithms + { + class FrameDifference : public IBGS + { + private: + bool enableThreshold; + int threshold; + + public: + FrameDifference(); + ~FrameDifference(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} + diff --git a/package_bgs/FrameDifferenceBGS.cpp b/package_bgs/FrameDifferenceBGS.cpp deleted file mode 100644 index e871651..0000000 --- a/package_bgs/FrameDifferenceBGS.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#include "FrameDifferenceBGS.h" - -FrameDifferenceBGS::FrameDifferenceBGS() : firstTime(true), enableThreshold(true), threshold(15), showOutput(true) -{ - std::cout << "FrameDifferenceBGS()" << std::endl; -} - -FrameDifferenceBGS::~FrameDifferenceBGS() -{ - std::cout << "~FrameDifferenceBGS()" << std::endl; -} - -void FrameDifferenceBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) -{ - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); - - if(img_input_prev.empty()) - { - img_input.copyTo(img_input_prev); - return; - } - - cv::absdiff(img_input_prev, img_input, img_foreground); - - if(img_foreground.channels() == 3) - cv::cvtColor(img_foreground, img_foreground, CV_BGR2GRAY); - - if(enableThreshold) - cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY); - - if(showOutput) - cv::imshow("Frame Difference", img_foreground); - - img_foreground.copyTo(img_output); - - img_input.copyTo(img_input_prev); - - firstTime = false; -} - -void FrameDifferenceBGS::saveConfig() -{ - CvFileStorage* fs = cvOpenFileStorage("./config/FrameDifferenceBGS.xml", 0, CV_STORAGE_WRITE); - - cvWriteInt(fs, "enableThreshold", enableThreshold); - cvWriteInt(fs, "threshold", threshold); - cvWriteInt(fs, "showOutput", showOutput); - - cvReleaseFileStorage(&fs); -} - -void FrameDifferenceBGS::loadConfig() -{ - CvFileStorage* fs = cvOpenFileStorage("./config/FrameDifferenceBGS.xml", 0, CV_STORAGE_READ); - - enableThreshold = cvReadIntByName(fs, 0, "enableThreshold", true); - threshold = cvReadIntByName(fs, 0, "threshold", 15); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - - cvReleaseFileStorage(&fs); -} \ No newline at end of file diff --git a/package_bgs/tb/FuzzyChoquetIntegral.cpp b/package_bgs/FuzzyChoquetIntegral.cpp similarity index 62% rename from package_bgs/tb/FuzzyChoquetIntegral.cpp rename to package_bgs/FuzzyChoquetIntegral.cpp index c97e174..3d24126 100644 --- a/package_bgs/tb/FuzzyChoquetIntegral.cpp +++ b/package_bgs/FuzzyChoquetIntegral.cpp @@ -15,12 +15,15 @@ You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ #include "FuzzyChoquetIntegral.h" -#include -FuzzyChoquetIntegral::FuzzyChoquetIntegral() : firstTime(true), frameNumber(0), showOutput(true), - framesToLearn(10), alphaLearn(0.1), alphaUpdate(0.01), colorSpace(1), option(2), smooth(true), threshold(0.67) +using namespace bgslibrary::algorithms; + +FuzzyChoquetIntegral::FuzzyChoquetIntegral() : + frameNumber(0), framesToLearn(10), alphaLearn(0.1), alphaUpdate(0.01), + colorSpace(1), option(2), smooth(true), threshold(0.67) { std::cout << "FuzzyChoquetIntegral()" << std::endl; + setup("./config/FuzzyChoquetIntegral.xml"); } FuzzyChoquetIntegral::~FuzzyChoquetIntegral() @@ -30,48 +33,52 @@ FuzzyChoquetIntegral::~FuzzyChoquetIntegral() void FuzzyChoquetIntegral::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; + init(img_input, img_output, img_bgmodel); cv::Mat img_input_f3(img_input.size(), CV_32F); - img_input.convertTo(img_input_f3, CV_32F, 1./255.); - - loadConfig(); + img_input.convertTo(img_input_f3, CV_32F, 1. / 255.); - if(firstTime) + if (firstTime) { std::cout << "FuzzyChoquetIntegral parameters:" << std::endl; - + std::string colorSpaceName = ""; - switch(colorSpace) + switch (colorSpace) { - case 1: colorSpaceName = "RGB"; break; - case 2: colorSpaceName = "OHTA"; break; - case 3: colorSpaceName = "HSV"; break; - case 4: colorSpaceName = "YCrCb"; break; + case 1: colorSpaceName = "RGB"; break; + case 2: colorSpaceName = "OHTA"; break; + case 3: colorSpaceName = "HSV"; break; + case 4: colorSpaceName = "YCrCb"; break; } std::cout << "Color space: " << colorSpaceName << std::endl; - if(option == 1) + if (option == 1) std::cout << "Fuzzing by 3 color components" << std::endl; - if(option == 2) + if (option == 2) std::cout << "Fuzzing by 2 color components + 1 texture component" << std::endl; - - saveConfig(); } - if(frameNumber <= framesToLearn) + if (frameNumber <= framesToLearn) { - if(frameNumber == 0) + if (frameNumber == 0) std::cout << "FuzzyChoquetIntegral initializing background model by adaptive learning..." << std::endl; - if(img_background_f3.empty()) + if (img_background_f3.empty()) img_input_f3.copyTo(img_background_f3); else - img_background_f3 = alphaLearn*img_input_f3 + (1-alphaLearn)*img_background_f3; + img_background_f3 = alphaLearn*img_input_f3 + (1 - alphaLearn)*img_background_f3; + + double minVal = 0., maxVal = 1.; + img_background_f3.convertTo(img_background, CV_8U, 255.0 / (maxVal - minVal), -minVal); + img_background.copyTo(img_bgmodel); + + img_foreground = cv::Mat::zeros(img_input.size(), img_input.type()); + img_foreground.copyTo(img_output); - if(showOutput) - cv::imshow("CI BG Model", img_background_f3); +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("CI BG Model", img_background); +#endif } else { @@ -87,72 +94,74 @@ void FuzzyChoquetIntegral::process(const cv::Mat &img_input, cv::Mat &img_output IplImage* background_f1 = new IplImage(img_background_f1); IplImage* lbp_input_f1 = cvCreateImage(cvSize(input_f1->width, input_f1->height), IPL_DEPTH_32F, 1); - cvFillImage(lbp_input_f1, 0.0); + cvSetZero(lbp_input_f1); fu.LBP(input_f1, lbp_input_f1); - IplImage* lbp_background_f1 = cvCreateImage(cvSize(background_f1->width, background_f1->height), IPL_DEPTH_32F , 1); - cvFillImage(lbp_background_f1, 0.0); + IplImage* lbp_background_f1 = cvCreateImage(cvSize(background_f1->width, background_f1->height), IPL_DEPTH_32F, 1); + cvSetZero(lbp_background_f1); fu.LBP(background_f1, lbp_background_f1); IplImage* sim_texture_f1 = cvCreateImage(cvSize(input_f1->width, input_f1->height), IPL_DEPTH_32F, 1); fu.SimilarityDegreesImage(lbp_input_f1, lbp_background_f1, sim_texture_f1, 1, colorSpace); IplImage* sim_color_f3 = cvCreateImage(cvSize(input_f3->width, input_f3->height), IPL_DEPTH_32F, 3); - fu.SimilarityDegreesImage(input_f3, background_f3, sim_color_f3, 3, colorSpace); + fu.SimilarityDegreesImage(input_f3, background_f3, sim_color_f3, 3, colorSpace); - float* measureG = (float*) malloc(3*(sizeof(float))); + float* measureG = (float*)malloc(3 * (sizeof(float))); IplImage* integral_choquet_f1 = cvCreateImage(cvSize(input_f1->width, input_f1->height), IPL_DEPTH_32F, 1); // 3 color components - if(option == 1) + if (option == 1) { fu.FuzzyMeasureG(0.4f, 0.3f, 0.3f, measureG); fu.getFuzzyIntegralChoquet(sim_texture_f1, sim_color_f3, option, measureG, integral_choquet_f1); } // 2 color components + 1 texture component - if(option == 2) + if (option == 2) { fu.FuzzyMeasureG(0.6f, 0.3f, 0.1f, measureG); fu.getFuzzyIntegralChoquet(sim_texture_f1, sim_color_f3, option, measureG, integral_choquet_f1); } free(measureG); - cv::Mat img_integral_choquet_f1(integral_choquet_f1); + cv::Mat img_integral_choquet_f1 = cv::cvarrToMat(integral_choquet_f1); - if(smooth) + if (smooth) cv::medianBlur(img_integral_choquet_f1, img_integral_choquet_f1, 3); cv::Mat img_foreground_f1(img_input.size(), CV_32F); cv::threshold(img_integral_choquet_f1, img_foreground_f1, threshold, 255, cv::THRESH_BINARY_INV); - cv::Mat img_foreground_u1(img_input.size(), CV_8U); + //cv::Mat img_foreground_u1(img_input.size(), CV_8U); double minVal = 0., maxVal = 1.; - img_foreground_f1.convertTo(img_foreground_u1, CV_8U, 255.0/(maxVal - minVal), -minVal); - img_foreground_u1.copyTo(img_output); + img_foreground_f1.convertTo(img_foreground, CV_8U, 255.0 / (maxVal - minVal), -minVal); + img_foreground.copyTo(img_output); - cv::Mat img_background_u3(img_input.size(), CV_8U); + //cv::Mat img_background_u3(img_input.size(), CV_8U); //double minVal = 0., maxVal = 1.; - img_background_f3.convertTo(img_background_u3, CV_8U, 255.0/(maxVal - minVal), -minVal); - img_background_u3.copyTo(img_bgmodel); + img_background_f3.convertTo(img_background, CV_8U, 255.0 / (maxVal - minVal), -minVal); + img_background.copyTo(img_bgmodel); - if(showOutput) +#ifndef MEX_COMPILE_FLAG + if (showOutput) { cvShowImage("CI LBP Input", lbp_input_f1); cvShowImage("CI LBP Background", lbp_background_f1); cvShowImage("CI Prob FG Mask", integral_choquet_f1); - cv::imshow("CI BG Model", img_background_f3); - cv::imshow("CI FG Mask", img_foreground_u1); + cv::imshow("CI BG Model", img_background); + cv::imshow("CI FG Mask", img_foreground); } +#endif - if(frameNumber == (framesToLearn + 1)) + if (frameNumber == (framesToLearn + 1)) std::cout << "FuzzyChoquetIntegral updating background model by adaptive-selective learning..." << std::endl; IplImage* updated_background_f3 = cvCreateImage(cvSize(input_f1->width, input_f1->height), IPL_DEPTH_32F, 3); - cvFillImage(updated_background_f3, 0.0); + cvSetZero(updated_background_f3); fu.AdaptativeSelectiveBackgroundModelUpdate(input_f3, background_f3, updated_background_f3, integral_choquet_f1, threshold, alphaUpdate); - cv::Mat img_updated_background_f3(updated_background_f3); + cv::Mat img_updated_background_f3 = cv::cvarrToMat(updated_background_f3); img_updated_background_f3.copyTo(img_background_f3); cvReleaseImage(&lbp_input_f1); @@ -161,7 +170,7 @@ void FuzzyChoquetIntegral::process(const cv::Mat &img_input, cv::Mat &img_output cvReleaseImage(&sim_color_f3); cvReleaseImage(&integral_choquet_f1); cvReleaseImage(&updated_background_f3); - + delete background_f1; delete background_f3; delete input_f1; @@ -174,8 +183,8 @@ void FuzzyChoquetIntegral::process(const cv::Mat &img_input, cv::Mat &img_output void FuzzyChoquetIntegral::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/FuzzyChoquetIntegral.xml", 0, CV_STORAGE_WRITE); - + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + cvWriteInt(fs, "showOutput", showOutput); cvWriteInt(fs, "framesToLearn", framesToLearn); cvWriteReal(fs, "alphaLearn", alphaLearn); @@ -190,16 +199,16 @@ void FuzzyChoquetIntegral::saveConfig() void FuzzyChoquetIntegral::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/FuzzyChoquetIntegral.xml", 0, CV_STORAGE_READ); - - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - framesToLearn = cvReadIntByName(fs, 0, "framesToLearn", 10); - alphaLearn = cvReadRealByName(fs, 0, "alphaLearn", 0.1); - alphaUpdate = cvReadRealByName(fs, 0, "alphaUpdate", 0.01); - colorSpace = cvReadIntByName(fs, 0, "colorSpace", 1); - option = cvReadIntByName(fs, 0, "option", 2); - smooth = cvReadIntByName(fs, 0, "smooth", true); - threshold = cvReadRealByName(fs, 0, "threshold", 0.67); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + framesToLearn = cvReadIntByName(fs, nullptr, "framesToLearn", 10); + alphaLearn = cvReadRealByName(fs, nullptr, "alphaLearn", 0.1); + alphaUpdate = cvReadRealByName(fs, nullptr, "alphaUpdate", 0.01); + colorSpace = cvReadIntByName(fs, nullptr, "colorSpace", 1); + option = cvReadIntByName(fs, nullptr, "option", 2); + smooth = cvReadIntByName(fs, nullptr, "smooth", true); + threshold = cvReadRealByName(fs, nullptr, "threshold", 0.67); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/FuzzyChoquetIntegral.h b/package_bgs/FuzzyChoquetIntegral.h new file mode 100644 index 0000000..25681b0 --- /dev/null +++ b/package_bgs/FuzzyChoquetIntegral.h @@ -0,0 +1,53 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "T2F/FuzzyUtils.h" + +namespace bgslibrary +{ + namespace algorithms + { + class FuzzyChoquetIntegral : public IBGS + { + private: + long frameNumber; + + int framesToLearn; + double alphaLearn; + double alphaUpdate; + int colorSpace; + int option; + bool smooth; + double threshold; + + FuzzyUtils fu; + cv::Mat img_background_f3; + + public: + FuzzyChoquetIntegral(); + ~FuzzyChoquetIntegral(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/tb/FuzzySugenoIntegral.cpp b/package_bgs/FuzzySugenoIntegral.cpp similarity index 62% rename from package_bgs/tb/FuzzySugenoIntegral.cpp rename to package_bgs/FuzzySugenoIntegral.cpp index 859f14e..e62fa02 100644 --- a/package_bgs/tb/FuzzySugenoIntegral.cpp +++ b/package_bgs/FuzzySugenoIntegral.cpp @@ -15,12 +15,15 @@ You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ #include "FuzzySugenoIntegral.h" -#include -FuzzySugenoIntegral::FuzzySugenoIntegral() : firstTime(true), frameNumber(0), showOutput(true), - framesToLearn(10), alphaLearn(0.1), alphaUpdate(0.01), colorSpace(1), option(2), smooth(true), threshold(0.67) +using namespace bgslibrary::algorithms; + +FuzzySugenoIntegral::FuzzySugenoIntegral() : + frameNumber(0), framesToLearn(10), alphaLearn(0.1), alphaUpdate(0.01), + colorSpace(1), option(2), smooth(true), threshold(0.67) { std::cout << "FuzzySugenoIntegral()" << std::endl; + setup("./config/FuzzySugenoIntegral.xml"); } FuzzySugenoIntegral::~FuzzySugenoIntegral() @@ -30,48 +33,52 @@ FuzzySugenoIntegral::~FuzzySugenoIntegral() void FuzzySugenoIntegral::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; + init(img_input, img_output, img_bgmodel); cv::Mat img_input_f3(img_input.size(), CV_32F); - img_input.convertTo(img_input_f3, CV_32F, 1./255.); - - loadConfig(); + img_input.convertTo(img_input_f3, CV_32F, 1. / 255.); - if(firstTime) + if (firstTime) { std::cout << "FuzzySugenoIntegral parameters:" << std::endl; - + std::string colorSpaceName = ""; - switch(colorSpace) + switch (colorSpace) { - case 1: colorSpaceName = "RGB"; break; - case 2: colorSpaceName = "OHTA"; break; - case 3: colorSpaceName = "HSV"; break; - case 4: colorSpaceName = "YCrCb"; break; + case 1: colorSpaceName = "RGB"; break; + case 2: colorSpaceName = "OHTA"; break; + case 3: colorSpaceName = "HSV"; break; + case 4: colorSpaceName = "YCrCb"; break; } std::cout << "Color space: " << colorSpaceName << std::endl; - if(option == 1) + if (option == 1) std::cout << "Fuzzing by 3 color components" << std::endl; - if(option == 2) + if (option == 2) std::cout << "Fuzzing by 2 color components + 1 texture component" << std::endl; - - saveConfig(); } - if(frameNumber <= framesToLearn) + if (frameNumber <= framesToLearn) { - if(frameNumber == 0) + if (frameNumber == 0) std::cout << "FuzzySugenoIntegral initializing background model by adaptive learning..." << std::endl; - if(img_background_f3.empty()) + if (img_background_f3.empty()) img_input_f3.copyTo(img_background_f3); else - img_background_f3 = alphaLearn*img_input_f3 + (1-alphaLearn)*img_background_f3; + img_background_f3 = alphaLearn*img_input_f3 + (1 - alphaLearn)*img_background_f3; + + double minVal = 0., maxVal = 1.; + img_background_f3.convertTo(img_background, CV_8U, 255.0 / (maxVal - minVal), -minVal); + img_background.copyTo(img_bgmodel); + + img_foreground = cv::Mat::zeros(img_input.size(), img_input.type()); + img_foreground.copyTo(img_output); - if(showOutput) - cv::imshow("SI BG Model", img_background_f3); +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("SI BG Model", img_background); +#endif } else { @@ -87,72 +94,74 @@ void FuzzySugenoIntegral::process(const cv::Mat &img_input, cv::Mat &img_output, IplImage* background_f1 = new IplImage(img_background_f1); IplImage* lbp_input_f1 = cvCreateImage(cvSize(input_f1->width, input_f1->height), IPL_DEPTH_32F, 1); - cvFillImage(lbp_input_f1, 0.0); + cvSetZero(lbp_input_f1); fu.LBP(input_f1, lbp_input_f1); - IplImage* lbp_background_f1 = cvCreateImage(cvSize(background_f1->width, background_f1->height), IPL_DEPTH_32F , 1); - cvFillImage(lbp_background_f1, 0.0); + IplImage* lbp_background_f1 = cvCreateImage(cvSize(background_f1->width, background_f1->height), IPL_DEPTH_32F, 1); + cvSetZero(lbp_background_f1); fu.LBP(background_f1, lbp_background_f1); IplImage* sim_texture_f1 = cvCreateImage(cvSize(input_f1->width, input_f1->height), IPL_DEPTH_32F, 1); fu.SimilarityDegreesImage(lbp_input_f1, lbp_background_f1, sim_texture_f1, 1, colorSpace); IplImage* sim_color_f3 = cvCreateImage(cvSize(input_f3->width, input_f3->height), IPL_DEPTH_32F, 3); - fu.SimilarityDegreesImage(input_f3, background_f3, sim_color_f3, 3, colorSpace); + fu.SimilarityDegreesImage(input_f3, background_f3, sim_color_f3, 3, colorSpace); - float* measureG = (float*) malloc(3*(sizeof(float))); + float* measureG = (float*)malloc(3 * (sizeof(float))); IplImage* integral_sugeno_f1 = cvCreateImage(cvSize(input_f1->width, input_f1->height), IPL_DEPTH_32F, 1); // 3 color components - if(option == 1) + if (option == 1) { fu.FuzzyMeasureG(0.4f, 0.3f, 0.3f, measureG); fu.getFuzzyIntegralSugeno(sim_texture_f1, sim_color_f3, option, measureG, integral_sugeno_f1); } // 2 color components + 1 texture component - if(option == 2) + if (option == 2) { fu.FuzzyMeasureG(0.6f, 0.3f, 0.1f, measureG); fu.getFuzzyIntegralSugeno(sim_texture_f1, sim_color_f3, option, measureG, integral_sugeno_f1); } free(measureG); - cv::Mat img_integral_sugeno_f1(integral_sugeno_f1); + cv::Mat img_integral_sugeno_f1 = cv::cvarrToMat(integral_sugeno_f1); - if(smooth) + if (smooth) cv::medianBlur(img_integral_sugeno_f1, img_integral_sugeno_f1, 3); cv::Mat img_foreground_f1(img_input.size(), CV_32F); cv::threshold(img_integral_sugeno_f1, img_foreground_f1, threshold, 255, cv::THRESH_BINARY_INV); - cv::Mat img_foreground_u1(img_input.size(), CV_8U); + //cv::Mat img_foreground_u1(img_input.size(), CV_8U); double minVal = 0., maxVal = 1.; - img_foreground_f1.convertTo(img_foreground_u1, CV_8U, 255.0/(maxVal - minVal), -minVal); - img_foreground_u1.copyTo(img_output); - - cv::Mat img_background_u3(img_input.size(), CV_8U); + img_foreground_f1.convertTo(img_foreground, CV_8U, 255.0 / (maxVal - minVal), -minVal); + img_foreground.copyTo(img_output); + + //cv::Mat img_background_u3(img_input.size(), CV_8U); //double minVal = 0., maxVal = 1.; - img_background_f3.convertTo(img_background_u3, CV_8U, 255.0/(maxVal - minVal), -minVal); - img_background_u3.copyTo(img_bgmodel); + img_background_f3.convertTo(img_background, CV_8U, 255.0 / (maxVal - minVal), -minVal); + img_background.copyTo(img_bgmodel); - if(showOutput) +#ifndef MEX_COMPILE_FLAG + if (showOutput) { cvShowImage("SI LBP Input", lbp_input_f1); cvShowImage("SI LBP Background", lbp_background_f1); cvShowImage("SI Prob FG Mask", integral_sugeno_f1); - cv::imshow("SI BG Model", img_background_f3); - cv::imshow("SI FG Mask", img_foreground_u1); + cv::imshow("SI BG Model", img_background); + cv::imshow("SI FG Mask", img_foreground); } +#endif - if(frameNumber == (framesToLearn + 1)) + if (frameNumber == (framesToLearn + 1)) std::cout << "FuzzySugenoIntegral updating background model by adaptive-selective learning..." << std::endl; IplImage* updated_background_f3 = cvCreateImage(cvSize(input_f1->width, input_f1->height), IPL_DEPTH_32F, 3); - cvFillImage(updated_background_f3, 0.0); + cvSetZero(updated_background_f3); fu.AdaptativeSelectiveBackgroundModelUpdate(input_f3, background_f3, updated_background_f3, integral_sugeno_f1, threshold, alphaUpdate); - cv::Mat img_updated_background_f3(updated_background_f3); + cv::Mat img_updated_background_f3 = cv::cvarrToMat(updated_background_f3); img_updated_background_f3.copyTo(img_background_f3); cvReleaseImage(&lbp_input_f1); @@ -161,7 +170,7 @@ void FuzzySugenoIntegral::process(const cv::Mat &img_input, cv::Mat &img_output, cvReleaseImage(&sim_color_f3); cvReleaseImage(&integral_sugeno_f1); cvReleaseImage(&updated_background_f3); - + delete background_f1; delete background_f3; delete input_f1; @@ -174,8 +183,8 @@ void FuzzySugenoIntegral::process(const cv::Mat &img_input, cv::Mat &img_output, void FuzzySugenoIntegral::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/FuzzySugenoIntegral.xml", 0, CV_STORAGE_WRITE); - + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + cvWriteInt(fs, "showOutput", showOutput); cvWriteInt(fs, "framesToLearn", framesToLearn); cvWriteReal(fs, "alphaLearn", alphaLearn); @@ -184,22 +193,22 @@ void FuzzySugenoIntegral::saveConfig() cvWriteInt(fs, "option", option); cvWriteInt(fs, "smooth", smooth); cvWriteReal(fs, "threshold", threshold); - + cvReleaseFileStorage(&fs); } void FuzzySugenoIntegral::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/FuzzySugenoIntegral.xml", 0, CV_STORAGE_READ); - - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - framesToLearn = cvReadIntByName(fs, 0, "framesToLearn", 10); - alphaLearn = cvReadRealByName(fs, 0, "alphaLearn", 0.1); - alphaUpdate = cvReadRealByName(fs, 0, "alphaUpdate", 0.01); - colorSpace = cvReadIntByName(fs, 0, "colorSpace", 1); - option = cvReadIntByName(fs, 0, "option", 2); - smooth = cvReadIntByName(fs, 0, "smooth", true); - threshold = cvReadRealByName(fs, 0, "threshold", 0.67); - + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + framesToLearn = cvReadIntByName(fs, nullptr, "framesToLearn", 10); + alphaLearn = cvReadRealByName(fs, nullptr, "alphaLearn", 0.1); + alphaUpdate = cvReadRealByName(fs, nullptr, "alphaUpdate", 0.01); + colorSpace = cvReadIntByName(fs, nullptr, "colorSpace", 1); + option = cvReadIntByName(fs, nullptr, "option", 2); + smooth = cvReadIntByName(fs, nullptr, "smooth", true); + threshold = cvReadRealByName(fs, nullptr, "threshold", 0.67); + cvReleaseFileStorage(&fs); } diff --git a/package_bgs/FuzzySugenoIntegral.h b/package_bgs/FuzzySugenoIntegral.h new file mode 100644 index 0000000..70bde15 --- /dev/null +++ b/package_bgs/FuzzySugenoIntegral.h @@ -0,0 +1,53 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "T2F/FuzzyUtils.h" + +namespace bgslibrary +{ + namespace algorithms + { + class FuzzySugenoIntegral : public IBGS + { + private: + long long frameNumber; + + int framesToLearn; + double alphaLearn; + double alphaUpdate; + int colorSpace; + int option; + bool smooth; + double threshold; + + FuzzyUtils fu; + cv::Mat img_background_f3; + + public: + FuzzySugenoIntegral(); + ~FuzzySugenoIntegral(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/GMG.cpp b/package_bgs/GMG.cpp index 675b23c..19bd8ab 100644 --- a/package_bgs/GMG.cpp +++ b/package_bgs/GMG.cpp @@ -16,9 +16,14 @@ along with BGSLibrary. If not, see . */ #include "GMG.h" -GMG::GMG() : firstTime(true), initializationFrames(20), decisionThreshold(0.7), showOutput(true) +#if CV_MAJOR_VERSION == 2 + +using namespace bgslibrary::algorithms; + +GMG::GMG() : initializationFrames(20), decisionThreshold(0.7) { std::cout << "GMG()" << std::endl; + setup("./config/GMG.xml"); cv::initModule_video(); cv::setUseOptimized(true); @@ -34,41 +39,33 @@ GMG::~GMG() void GMG::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; + init(img_input, img_output, img_bgmodel); - loadConfig(); - - if(firstTime) + if (firstTime) { fgbg->set("initializationFrames", initializationFrames); fgbg->set("decisionThreshold", decisionThreshold); - - saveConfig(); } - - if(fgbg.empty()) + + if (fgbg.empty()) { std::cerr << "Failed to create BackgroundSubtractor.GMG Algorithm." << std::endl; return; } (*fgbg)(img_input, img_foreground); - - cv::Mat img_background; (*fgbg).getBackgroundImage(img_background); img_input.copyTo(img_segmentation); cv::add(img_input, cv::Scalar(100, 100, 0), img_segmentation, img_foreground); - if(showOutput) +#ifndef MEX_COMPILE_FLAG + if (showOutput) { - if (!img_foreground.empty()) - cv::imshow("GMG FG (Godbehere-Matsukawa-Goldberg)", img_foreground); - - if (!img_background.empty()) - cv::imshow("GMG BG (Godbehere-Matsukawa-Goldberg)", img_background); + cv::imshow("GMG FG (Godbehere-Matsukawa-Goldberg)", img_foreground); + cv::imshow("GMG BG (Godbehere-Matsukawa-Goldberg)", img_background); } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); @@ -78,7 +75,7 @@ void GMG::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bg void GMG::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/GMG.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "initializationFrames", initializationFrames); cvWriteReal(fs, "decisionThreshold", decisionThreshold); @@ -89,11 +86,13 @@ void GMG::saveConfig() void GMG::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/GMG.xml", 0, CV_STORAGE_READ); - - initializationFrames = cvReadIntByName(fs, 0, "initializationFrames", 20); - decisionThreshold = cvReadRealByName(fs, 0, "decisionThreshold", 0.7); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + initializationFrames = cvReadIntByName(fs, nullptr, "initializationFrames", 20); + decisionThreshold = cvReadRealByName(fs, nullptr, "decisionThreshold", 0.7); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + cvReleaseFileStorage(&fs); } + +#endif diff --git a/package_bgs/GMG.h b/package_bgs/GMG.h index 9da28ad..1b4af30 100644 --- a/package_bgs/GMG.h +++ b/package_bgs/GMG.h @@ -16,30 +16,34 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include +#include "opencv2/core/version.hpp" +#if CV_MAJOR_VERSION == 2 #include "IBGS.h" -class GMG : public IBGS +namespace bgslibrary { -private: - bool firstTime; - cv::Ptr fgbg; - int initializationFrames; - double decisionThreshold; - cv::Mat img_foreground; - cv::Mat img_segmentation; - bool showOutput; - -public: - GMG(); - ~GMG(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class GMG : public IBGS + { + private: + cv::Ptr fgbg; + int initializationFrames; + double decisionThreshold; + cv::Mat img_segmentation; + + public: + GMG(); + ~GMG(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} + +#endif diff --git a/package_bgs/IBGS.h b/package_bgs/IBGS.h index 073ce18..718bf51 100644 --- a/package_bgs/IBGS.h +++ b/package_bgs/IBGS.h @@ -16,18 +16,59 @@ along with BGSLibrary. If not, see . */ #pragma once +#include +#include #include -class IBGS +namespace bgslibrary { -public: - virtual void process(const cv::Mat &img_input, cv::Mat &img_foreground, cv::Mat &img_background) = 0; - /*virtual void process(const cv::Mat &img_input, cv::Mat &img_foreground){ - process(img_input, img_foreground, cv::Mat()); - }*/ - virtual ~IBGS(){} + namespace algorithms + { + class IBGS + { + public: + void setShowOutput(const bool _showOutput) { + showOutput = _showOutput; + } + cv::Mat apply(const cv::Mat &img_input) { + setShowOutput(false); + cv::Mat _img_foreground; + cv::Mat _img_background; + process(img_input, _img_foreground, _img_background); + _img_background.copyTo(img_background); + return _img_foreground; + } + cv::Mat getBackgroundModel() { + return img_background; + } + virtual void process(const cv::Mat &img_input, cv::Mat &img_foreground, cv::Mat &img_background) = 0; + virtual ~IBGS() {} -private: - virtual void saveConfig() = 0; - virtual void loadConfig() = 0; -}; + protected: + bool firstTime = true; + bool showOutput = true; + cv::Mat img_background; + cv::Mat img_foreground; + std::string config_xml; + void setup(const std::string _config_xml) { + config_xml = _config_xml; + if (!config_xml.empty()) { + if (!std::ifstream(config_xml)) + saveConfig(); + loadConfig(); + } + } + void init(const cv::Mat &img_input, cv::Mat &img_outfg, cv::Mat &img_outbg) { + assert(img_input.empty() == false); + //img_outfg = cv::Mat::zeros(img_input.size(), img_input.type()); + //img_outbg = cv::Mat::zeros(img_input.size(), img_input.type()); + img_outfg = cv::Mat::zeros(img_input.size(), CV_8UC1); + img_outbg = cv::Mat::zeros(img_input.size(), CV_8UC3); + } + + private: + virtual void saveConfig() = 0; + virtual void loadConfig() = 0; + }; + } +} diff --git a/package_bgs/db/imbs.cpp b/package_bgs/IMBS/IMBS.cpp similarity index 74% rename from package_bgs/db/imbs.cpp rename to package_bgs/IMBS/IMBS.cpp index f5fc0d0..b1e177b 100644 --- a/package_bgs/db/imbs.cpp +++ b/package_bgs/IMBS/IMBS.cpp @@ -1,5 +1,21 @@ /* -* IMBS Background Subtraction Library +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* +* IMBS Background Subtraction Library * * This file imbs.hpp contains the C++ OpenCV based implementation for * IMBS algorithm described in @@ -8,8 +24,8 @@ * In Proc. of the Third Int. Conf. on Computational Modeling of Objects * Presented in Images: Fundamentals, Methods and Applications, pp. 39-44, 2012. * Please, cite the above paper if you use IMBS. -* -* This software is provided without any warranty about its usability. +* +* This software is provided without any warranty about its usability. * It is for educational purposes and should be regarded as such. * * Written by Domenico D. Bloisi @@ -19,7 +35,7 @@ * */ -#include "imbs.hpp" +#include "IMBS.hpp" using namespace std; using namespace cv; @@ -29,15 +45,15 @@ BackgroundSubtractorIMBS::BackgroundSubtractorIMBS() fps = 0.; fgThreshold = 15; associationThreshold = 5; - samplingPeriod = 250.;//500.ms + samplingPeriod = 250;//500.ms minBinHeight = 2; numSamples = 10; //30 alpha = 0.65f; beta = 1.15f; - tau_s = 60.; - tau_h = 40.; + tau_s = 60; + tau_h = 40; minArea = 30.; - persistencePeriod = samplingPeriod*numSamples/3.;//ms + persistencePeriod = samplingPeriod*numSamples / 3.;//ms initial_tick_count = (double)getTickCount(); @@ -63,7 +79,7 @@ BackgroundSubtractorIMBS::BackgroundSubtractorIMBS( this->fps = fps; this->fgThreshold = fgThreshold; this->persistencePeriod = persistencePeriod; - if(minBinHeight <= 1){ + if (minBinHeight <= 1) { this->minBinHeight = 1; } else { @@ -79,7 +95,7 @@ BackgroundSubtractorIMBS::BackgroundSubtractorIMBS( this->tau_h = tau_h; this->minArea = minArea; - if(fps == 0.) + if (fps == 0.) initial_tick_count = (double)getTickCount(); else initial_tick_count = 0; @@ -106,7 +122,7 @@ void BackgroundSubtractorIMBS::initialize(Size frameSize, int frameType) this->numPixels = frameSize.width*frameSize.height; persistenceMap = new unsigned int[numPixels]; - for(unsigned int i = 0; i < numPixels; i++) { + for (unsigned int i = 0; i < numPixels; i++) { persistenceMap[i] = 0; } @@ -134,20 +150,20 @@ void BackgroundSubtractorIMBS::initialize(Size frameSize, int frameType) //initial message to be shown until the first fg mask is computed initialMsgGray = Mat::zeros(frameSize, CV_8UC1); - putText(initialMsgGray, "Creating", Point(10,20), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); - putText(initialMsgGray, "initial", Point(10,40), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); - putText(initialMsgGray, "background...", Point(10,60), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); + putText(initialMsgGray, "Creating", Point(10, 20), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); + putText(initialMsgGray, "initial", Point(10, 40), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); + putText(initialMsgGray, "background...", Point(10, 60), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); initialMsgRGB = Mat::zeros(frameSize, CV_8UC3); - putText(initialMsgRGB, "Creating", Point(10,20), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); - putText(initialMsgRGB, "initial", Point(10,40), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); - putText(initialMsgRGB, "background...", Point(10,60), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); + putText(initialMsgRGB, "Creating", Point(10, 20), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); + putText(initialMsgRGB, "initial", Point(10, 40), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); + putText(initialMsgRGB, "background...", Point(10, 60), FONT_HERSHEY_SIMPLEX, 0.4, CV_RGB(255, 255, 255)); - if(minBinHeight <= 1){ + if (minBinHeight <= 1) { minBinHeight = 1; } - for(unsigned int p = 0; p < numPixels; ++p) + for (unsigned int p = 0; p < numPixels; ++p) { bgBins[p].binValues = new Vec3b[numSamples]; bgBins[p].binHeights = new uchar[numSamples]; @@ -169,7 +185,7 @@ void BackgroundSubtractorIMBS::apply(InputArray _frame, OutputArray _fgmask, dou CV_Assert(frame.channels() == 3); bool needToInitialize = nframes == 0 || frame.type() != frameType; - if( needToInitialize ) { + if (needToInitialize) { initialize(frame.size(), frame.type()); } @@ -179,29 +195,29 @@ void BackgroundSubtractorIMBS::apply(InputArray _frame, OutputArray _fgmask, dou //get current time prev_timestamp = timestamp; - if(fps == 0.) { + if (fps == 0.) { timestamp = getTimestamp();//ms } else { - timestamp += 1000./fps;//ms + timestamp += 1000. / fps;//ms } //check for global changes - if(sudden_change) { + if (sudden_change) { changeBg(); } //wait for the first model to be generated - if(bgModel[0].isValid[0]) { - getFg(); + if (bgModel[0].isValid[0]) { + getFg(); hsvSuppression(); filterFg(); - } + } //update the bg model updateBg(); //show an initial message if the first bg is not yet ready - if(!bgModel[0].isValid[0]) { + if (!bgModel[0].isValid[0]) { initialMsgGray.copyTo(fgmask); initialMsgRGB.copyTo(bgImage); } @@ -209,23 +225,23 @@ void BackgroundSubtractorIMBS::apply(InputArray _frame, OutputArray _fgmask, dou } void BackgroundSubtractorIMBS::updateBg() { - if(bg_reset) { - if(bg_frame_counter > numSamples - 1) { + if (bg_reset) { + if (bg_frame_counter > numSamples - 1) { bg_frame_counter = numSamples - 1; } } - if(prev_bg_frame_time > timestamp) { + if (prev_bg_frame_time > timestamp) { prev_bg_frame_time = timestamp; } - if(bg_frame_counter == numSamples - 1) { + if (bg_frame_counter == numSamples - 1) { createBg(bg_frame_counter); bg_frame_counter = 0; } else { //bg_frame_counter < (numSamples - 1) - if((timestamp - prev_bg_frame_time) >= samplingPeriod) + if ((timestamp - prev_bg_frame_time) >= samplingPeriod) { //get a new sample for creating the bg model prev_bg_frame_time = timestamp; @@ -237,7 +253,7 @@ void BackgroundSubtractorIMBS::updateBg() { } double BackgroundSubtractorIMBS::getTimestamp() { - return ((double)getTickCount() - initial_tick_count)*1000./getTickFrequency(); + return ((double)getTickCount() - initial_tick_count)*1000. / getTickFrequency(); } void BackgroundSubtractorIMBS::hsvSuppression() { @@ -251,35 +267,35 @@ void BackgroundSubtractorIMBS::hsvSuppression() { vector imHSV; cv::split(convertImageRGBtoHSV(frame), imHSV); - for(unsigned int p = 0; p < numPixels; ++p) { - if(fgmask.data[p]) { + for (unsigned int p = 0; p < numPixels; ++p) { + if (fgmask.data[p]) { h_i = imHSV[0].data[p]; s_i = imHSV[1].data[p]; v_i = imHSV[2].data[p]; - for(unsigned int n = 0; n < maxBgBins; ++n) { - if(!bgModel[p].isValid[n]) { + for (unsigned int n = 0; n < maxBgBins; ++n) { + if (!bgModel[p].isValid[n]) { break; } - if(bgModel[p].isFg[n]) { + if (bgModel[p].isFg[n]) { continue; } - bgrPixel.at(0,0) = bgModel[p].values[n]; + bgrPixel.at(0, 0) = bgModel[p].values[n]; cv::Mat hsvPixel = convertImageRGBtoHSV(bgrPixel); - h_b = hsvPixel.at(0,0)[0]; - s_b = hsvPixel.at(0,0)[1]; - v_b = hsvPixel.at(0,0)[2]; + h_b = hsvPixel.at(0, 0)[0]; + s_b = hsvPixel.at(0, 0)[1]; + v_b = hsvPixel.at(0, 0)[2]; v_ratio = (float)v_i / (float)v_b; s_diff = std::abs(s_i - s_b); - h_diff = std::min( std::abs(h_i - h_b), 255 - std::abs(h_i - h_b)); + h_diff = std::min(std::abs(h_i - h_b), 255 - std::abs(h_i - h_b)); - if( h_diff <= tau_h && + if (h_diff <= tau_h && s_diff <= tau_s && v_ratio >= alpha && v_ratio < beta) @@ -293,7 +309,7 @@ void BackgroundSubtractorIMBS::hsvSuppression() { } void BackgroundSubtractorIMBS::createBg(unsigned int bg_sample_number) { - if(!bgSample.data) { + if (!bgSample.data) { //cerr << "createBg -- an error occurred: " << // " unable to retrieve frame no. " << bg_sample_number << endl; @@ -305,18 +321,18 @@ void BackgroundSubtractorIMBS::createBg(unsigned int bg_sample_number) { //split bgSample in channels cv::split(bgSample, bgSampleBGR); //create a statistical model for each pixel (a set of bins of variable size) - for(unsigned int p = 0; p < numPixels; ++p) { + for (unsigned int p = 0; p < numPixels; ++p) { //create an initial bin for each pixel from the first sample (bg_sample_number = 0) - if(bg_sample_number == 0) { - for(int k = 0; k < 3; ++k) { + if (bg_sample_number == 0) { + for (int k = 0; k < 3; ++k) { bgBins[p].binValues[0][k] = bgSampleBGR[k].data[p]; } bgBins[p].binHeights[0] = 1; - for(unsigned int s = 1; s < numSamples; ++s) { + for (unsigned int s = 1; s < numSamples; ++s) { bgBins[p].binHeights[s] = 0; } //if the sample pixel is from foreground keep track of that situation - if(fgmask.data[p] == FOREGROUND_LABEL) { + if (fgmask.data[p] == FOREGROUND_LABEL) { bgBins[p].isFg[0] = true; } else { @@ -324,32 +340,32 @@ void BackgroundSubtractorIMBS::createBg(unsigned int bg_sample_number) { } }//if(bg_sample_number == 0) else { //bg_sample_number > 0 - for(int k = 0; k < 3; ++k) { + for (int k = 0; k < 3; ++k) { currentPixel[k] = bgSampleBGR[k].data[p]; } int den = 0; - for(unsigned int s = 0; s < bg_sample_number; ++s) { + for (unsigned int s = 0; s < bg_sample_number; ++s) { //try to associate the current pixel values to an existing bin - if( std::abs(currentPixel[2] - bgBins[p].binValues[s][2]) <= associationThreshold && + if (std::abs(currentPixel[2] - bgBins[p].binValues[s][2]) <= associationThreshold && std::abs(currentPixel[1] - bgBins[p].binValues[s][1]) <= associationThreshold && - std::abs(currentPixel[0] - bgBins[p].binValues[s][0]) <= associationThreshold ) + std::abs(currentPixel[0] - bgBins[p].binValues[s][0]) <= associationThreshold) { den = (bgBins[p].binHeights[s] + 1); - for(int k = 0; k < 3; ++k) { + for (int k = 0; k < 3; ++k) { bgBins[p].binValues[s][k] = (bgBins[p].binValues[s][k] * bgBins[p].binHeights[s] + currentPixel[k]) / den; } bgBins[p].binHeights[s]++; //increment the height of the bin - if(fgmask.data[p] == FOREGROUND_LABEL) { + if (fgmask.data[p] == FOREGROUND_LABEL) { bgBins[p].isFg[s] = true; } break; } //if the association is not possible, create a new bin - else if(bgBins[p].binHeights[s] == 0) { + else if (bgBins[p].binHeights[s] == 0) { bgBins[p].binValues[s] = currentPixel; bgBins[p].binHeights[s]++; - if(fgmask.data[p] == FOREGROUND_LABEL) { + if (fgmask.data[p] == FOREGROUND_LABEL) { bgBins[p].isFg[s] = true; } else { @@ -362,44 +378,44 @@ void BackgroundSubtractorIMBS::createBg(unsigned int bg_sample_number) { //if all samples have been processed //it is time to compute the fg mask - if(bg_sample_number == (numSamples - 1)) { + if (bg_sample_number == (numSamples - 1)) { unsigned int index = 0; int max_height = -1; - for(unsigned int s = 0; s < numSamples; ++s){ - if(bgBins[p].binHeights[s] == 0) { + for (unsigned int s = 0; s < numSamples; ++s) { + if (bgBins[p].binHeights[s] == 0) { bgModel[p].isValid[index] = false; break; } - if(index == maxBgBins) { + if (index == maxBgBins) { break; } - else if(bgBins[p].binHeights[s] >= minBinHeight) { - if(fgmask.data[p] == PERSISTENCE_LABEL) { - for(unsigned int n = 0; n < maxBgBins; n++) { - if(!bgModel[p].isValid[n]) { + else if (bgBins[p].binHeights[s] >= minBinHeight) { + if (fgmask.data[p] == PERSISTENCE_LABEL) { + for (unsigned int n = 0; n < maxBgBins; n++) { + if (!bgModel[p].isValid[n]) { break; } unsigned int d = std::max((int)std::abs(bgModel[p].values[n][0] - bgBins[p].binValues[s][0]), - std::abs(bgModel[p].values[n][1] - bgBins[p].binValues[s][1]) ); - d = std::max((int)d, std::abs(bgModel[p].values[n][2] - bgBins[p].binValues[s][2]) ); - if(d < fgThreshold){ + std::abs(bgModel[p].values[n][1] - bgBins[p].binValues[s][1])); + d = std::max((int)d, std::abs(bgModel[p].values[n][2] - bgBins[p].binValues[s][2])); + if (d < fgThreshold) { bgModel[p].isFg[n] = false; bgBins[p].isFg[s] = false; } } } - if(bgBins[p].binHeights[s] > max_height) { + if (bgBins[p].binHeights[s] > max_height) { max_height = bgBins[p].binHeights[s]; - for(int k = 0; k < 3; ++k) { + for (int k = 0; k < 3; ++k) { bgModel[p].values[index][k] = bgModel[p].values[0][k]; } bgModel[p].isValid[index] = true; bgModel[p].isFg[index] = bgModel[p].isFg[0]; bgModel[p].counter[index] = bgModel[p].counter[0]; - for(int k = 0; k < 3; ++k) { + for (int k = 0; k < 3; ++k) { bgModel[p].values[0][k] = bgBins[p].binValues[s][k]; } bgModel[p].isValid[0] = true; @@ -407,7 +423,7 @@ void BackgroundSubtractorIMBS::createBg(unsigned int bg_sample_number) { bgModel[p].counter[0] = bgBins[p].binHeights[s]; } else { - for(int k = 0; k < 3; ++k) { + for (int k = 0; k < 3; ++k) { bgModel[p].values[index][k] = bgBins[p].binValues[s][k]; } bgModel[p].isValid[index] = true; @@ -421,25 +437,25 @@ void BackgroundSubtractorIMBS::createBg(unsigned int bg_sample_number) { }//else --> if(frame_number == 0) }//numPixels - if(bg_sample_number == (numSamples - 1)) { - //std::cout << "new bg created" << std::endl; + if (bg_sample_number == (numSamples - 1)) { + //std::cout << "new bg created" << std::endl; persistenceImage = Scalar(0); bg_reset = false; - if(sudden_change) { + if (sudden_change) { numSamples *= 3.; samplingPeriod *= 2.; sudden_change = false; } - for(unsigned int i = 0; i < numPixels; i++) { + for (unsigned int i = 0; i < numPixels; i++) { persistenceMap[i] = 0; } unsigned int p = 0; - for(int i = 0; i < bgImage.rows; ++i) { - for(int j = 0; j < bgImage.cols; ++j, ++p) { - bgImage.at(i,j) = bgModel[p].values[0]; + for (int i = 0; i < bgImage.rows; ++i) { + for (int j = 0; j < bgImage.cols; ++j, ++p) { + bgImage.at(i, j) = bgModel[p].values[0]; } } } @@ -452,13 +468,13 @@ void BackgroundSubtractorIMBS::getFg() { bool isFg = true; bool conditionalUpdated = false; unsigned int d = 0; - for(unsigned int p = 0; p < numPixels; ++p) { + for (unsigned int p = 0; p < numPixels; ++p) { isFg = true; conditionalUpdated = false; d = 0; - for(unsigned int n = 0; n < maxBgBins; ++n) { - if(!bgModel[p].isValid[n]) { - if(n == 0) { + for (unsigned int n = 0; n < maxBgBins; ++n) { + if (!bgModel[p].isValid[n]) { + if (n == 0) { isFg = false; } break; @@ -466,13 +482,13 @@ void BackgroundSubtractorIMBS::getFg() { else { //the model is valid d = std::max( (int)std::abs(bgModel[p].values[n][0] - frameBGR[0].data[p]), - std::abs(bgModel[p].values[n][1] - frameBGR[1].data[p]) ); + std::abs(bgModel[p].values[n][1] - frameBGR[1].data[p])); d = std::max( - (int)d, std::abs(bgModel[p].values[n][2] - frameBGR[2].data[p]) ); - if(d < fgThreshold){ + (int)d, std::abs(bgModel[p].values[n][2] - frameBGR[2].data[p])); + if (d < fgThreshold) { //check if it is a potential background pixel //from stationary object - if(bgModel[p].isFg[n]) { + if (bgModel[p].isFg[n]) { conditionalUpdated = true; break; } @@ -483,13 +499,13 @@ void BackgroundSubtractorIMBS::getFg() { } } } - if(isFg) { - if(conditionalUpdated) { + if (isFg) { + if (conditionalUpdated) { fgmask.data[p] = PERSISTENCE_LABEL; persistenceMap[p] += (timestamp - prev_timestamp); - if(persistenceMap[p] > persistencePeriod) { - for(unsigned int n = 0; n < maxBgBins; ++n) { - if(!bgModel[p].isValid[n]) { + if (persistenceMap[p] > persistencePeriod) { + for (unsigned int n = 0; n < maxBgBins; ++n) { + if (!bgModel[p].isValid[n]) { break; } bgModel[p].isFg[n] = false; @@ -521,13 +537,13 @@ void BackgroundSubtractorIMBS::areaThresholding() if (area < minArea || area >= maxArea) continue; else { - drawContours( tmpBinaryImage, contours, contourIdx, Scalar(255), CV_FILLED ); + drawContours(tmpBinaryImage, contours, contourIdx, Scalar(255), CV_FILLED); } - } - for(int i = 0; i < fgfiltered.rows; ++i) { - for(int j = 0; j < fgfiltered.cols; ++j) { - if(!tmpBinaryImage.at(i,j)) { - fgfiltered.at(i,j) = 0; + } + for (int i = 0; i < fgfiltered.rows; ++i) { + for (int j = 0; j < fgfiltered.cols; ++j) { + if (!tmpBinaryImage.at(i, j)) { + fgfiltered.at(i, j) = 0; } } } @@ -560,9 +576,9 @@ Mat BackgroundSubtractorIMBS::convertImageRGBtoHSV(const Mat& imageRGB) for (int x = 0; x < w; ++x) { // Get the RGB pixel components. NOTE that OpenCV stores RGB pixels in B,G,R order. //uchar *pRGB = (uchar*)(imRGB + y*rowSizeRGB + x*3); - int bB = imageRGB.at(y,x)[0]; //*(uchar*)(pRGB+0); // Blue component - int bG = imageRGB.at(y,x)[1]; //*(uchar*)(pRGB+1); // Green component - int bR = imageRGB.at(y,x)[2]; //*(uchar*)(pRGB+2); // Red component + int bB = imageRGB.at(y, x)[0]; //*(uchar*)(pRGB+0); // Blue component + int bG = imageRGB.at(y, x)[1]; //*(uchar*)(pRGB+1); // Green component + int bR = imageRGB.at(y, x)[2]; //*(uchar*)(pRGB+2); // Red component // Convert from 8-bit integers to floats. fR = bR * BYTE_TO_FLOAT; @@ -619,10 +635,10 @@ Mat BackgroundSubtractorIMBS::convertImageRGBtoHSV(const Mat& imageRGB) fH = (fG - fB) * ANGLE_TO_UNIT; } else if (iMax == bG) { // between cyan and yellow. - fH = (2.0f/6.0f) + ( fB - fR ) * ANGLE_TO_UNIT; + fH = (2.0f / 6.0f) + (fB - fR) * ANGLE_TO_UNIT; } else { // between magenta and cyan. - fH = (4.0f/6.0f) + ( fR - fG ) * ANGLE_TO_UNIT; + fH = (4.0f / 6.0f) + (fR - fG) * ANGLE_TO_UNIT; } // Wrap outlier Hues around the circle. if (fH < 0.0f) @@ -666,14 +682,14 @@ Mat BackgroundSubtractorIMBS::convertImageRGBtoHSV(const Mat& imageRGB) void BackgroundSubtractorIMBS::getBackgroundImage(OutputArray backgroundImage) const { - bgImage.copyTo(backgroundImage); + bgImage.copyTo(backgroundImage); } void BackgroundSubtractorIMBS::filterFg() { unsigned int cnt = 0; - for(unsigned int p = 0; p < numPixels; ++p) { - if(fgmask.data[p] == (uchar)255) { + for (unsigned int p = 0; p < numPixels; ++p) { + if (fgmask.data[p] == (uchar)255) { fgfiltered.data[p] = 255; cnt++; } @@ -682,23 +698,23 @@ void BackgroundSubtractorIMBS::filterFg() { } } - if(cnt > numPixels*0.5) { + if (cnt > numPixels*0.5) { sudden_change = true; } - if(morphologicalFiltering) { - cv::Mat element3(3,3,CV_8U,cv::Scalar(1)); + if (morphologicalFiltering) { + cv::Mat element3(3, 3, CV_8U, cv::Scalar(1)); cv::morphologyEx(fgfiltered, fgfiltered, cv::MORPH_OPEN, element3); cv::morphologyEx(fgfiltered, fgfiltered, cv::MORPH_CLOSE, element3); } areaThresholding(); - for(unsigned int p = 0; p < numPixels; ++p) { - if(fgmask.data[p] == PERSISTENCE_LABEL) { + for (unsigned int p = 0; p < numPixels; ++p) { + if (fgmask.data[p] == PERSISTENCE_LABEL) { fgfiltered.data[p] = PERSISTENCE_LABEL; } - else if(fgmask.data[p] == SHADOW_LABEL) { + else if (fgmask.data[p] == SHADOW_LABEL) { fgfiltered.data[p] = SHADOW_LABEL; } } @@ -715,7 +731,7 @@ void BackgroundSubtractorIMBS::changeBg() { //bg_reset = true; //cout << "qua" << endl; - if(!bg_reset) { + if (!bg_reset) { numSamples /= 3.; samplingPeriod /= 2.; bg_frame_counter = 0; @@ -724,19 +740,19 @@ void BackgroundSubtractorIMBS::changeBg() { } void BackgroundSubtractorIMBS::getBgModel(BgModel bgModel_copy[], int size) { - if(size != numPixels) { + if (size != numPixels) { return; } - for(unsigned int i = 0; i < numPixels; ++i){ + for (unsigned int i = 0; i < numPixels; ++i) { bgModel_copy[i].values = new Vec3b[maxBgBins]; bgModel_copy[i].isValid = new bool[maxBgBins]; bgModel_copy[i].isValid[0] = false; bgModel_copy[i].isFg = new bool[maxBgBins]; bgModel_copy[i].counter = new uchar[maxBgBins]; } - for(unsigned int p = 0; p < numPixels; ++p) { - for(unsigned int n = 0; n < maxBgBins; ++n) { - if(!bgModel[p].isValid[n]) { + for (unsigned int p = 0; p < numPixels; ++p) { + for (unsigned int n = 0; n < maxBgBins; ++n) { + if (!bgModel[p].isValid[n]) { break; } bgModel_copy[p].values[n] = bgModel[p].values[n]; diff --git a/package_bgs/db/imbs.hpp b/package_bgs/IMBS/IMBS.hpp similarity index 77% rename from package_bgs/db/imbs.hpp rename to package_bgs/IMBS/IMBS.hpp index fd7faf1..383e0ae 100644 --- a/package_bgs/db/imbs.hpp +++ b/package_bgs/IMBS/IMBS.hpp @@ -1,5 +1,21 @@ /* -* IMBS Background Subtraction Library +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* +* IMBS Background Subtraction Library * * This file imbs.hpp contains the C++ OpenCV based implementation for * IMBS algorithm described in @@ -8,8 +24,8 @@ * In Proc. of the Third Int. Conf. on Computational Modeling of Objects * Presented in Images: Fundamentals, Methods and Applications, pp. 39-44, 2012. * Please, cite the above paper if you use IMBS. -* -* This software is provided without any warranty about its usability. +* +* This software is provided without any warranty about its usability. * It is for educational purposes and should be regarded as such. * * Written by Domenico D. Bloisi @@ -18,9 +34,7 @@ * domenico.bloisi@gmail.com * */ - -#ifndef __IMBS_HPP__ -#define __IMBS_HPP__ +#pragma once //OPENCV #include @@ -41,23 +55,23 @@ class BackgroundSubtractorIMBS BackgroundSubtractorIMBS(); //! the full constructor BackgroundSubtractorIMBS(double fps, - unsigned int fgThreshold=15, - unsigned int associationThreshold=5, - double samplingPeriod=500., - unsigned int minBinHeight=2, - unsigned int numSamples=30, - double alpha=0.65, - double beta=1.15, - double tau_s=60., - double tau_h=40., - double minArea=30., - double persistencePeriod=10000., - bool morphologicalFiltering=false - ); + unsigned int fgThreshold = 15, + unsigned int associationThreshold = 5, + double samplingPeriod = 500., + unsigned int minBinHeight = 2, + unsigned int numSamples = 30, + double alpha = 0.65, + double beta = 1.15, + double tau_s = 60., + double tau_h = 40., + double minArea = 30., + double persistencePeriod = 10000., + bool morphologicalFiltering = false + ); //! the destructor ~BackgroundSubtractorIMBS(); //! the update operator - void apply(InputArray image, OutputArray fgmask, double learningRate=-1.); + void apply(InputArray image, OutputArray fgmask, double learningRate = -1.); //! computes a background image which shows only the highest bin for each pixel void getBackgroundImage(OutputArray backgroundImage) const; @@ -73,7 +87,7 @@ class BackgroundSubtractorIMBS //method for computing the foreground mask void getFg(); //method for suppressing shadows and highlights - void hsvSuppression(); + void hsvSuppression(); //method for refining foreground mask void filterFg(); //method for filtering out blobs smaller than a given area @@ -82,7 +96,7 @@ class BackgroundSubtractorIMBS double getTimestamp(); //method for converting from RGB to HSV Mat convertImageRGBtoHSV(const Mat& imageRGB); - //method for changing the bg in case of sudden changes + //method for changing the bg in case of sudden changes void changeBg(); //current input RGB frame @@ -112,7 +126,7 @@ class BackgroundSubtractorIMBS //previous time stamp in milliseconds (ms) double prev_timestamp; double initial_tick_count; - //initial message to be shown until the first bg model is ready + //initial message to be shown until the first bg model is ready Mat initialMsgGray; Mat initialMsgRGB; @@ -174,5 +188,3 @@ class BackgroundSubtractorIMBS } void getBgModel(BgModel bgModel_copy[], int size); }; - -#endif //__IMBS_HPP__ diff --git a/package_bgs/IndependentMultimodal.cpp b/package_bgs/IndependentMultimodal.cpp new file mode 100644 index 0000000..7b4de4c --- /dev/null +++ b/package_bgs/IndependentMultimodal.cpp @@ -0,0 +1,74 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "IndependentMultimodal.h" + +using namespace bgslibrary::algorithms; + +IndependentMultimodal::IndependentMultimodal() : fps(10) +{ + std::cout << "IndependentMultimodal()" << std::endl; + pIMBS = new BackgroundSubtractorIMBS(fps); + setup("./config/IndependentMultimodal.xml"); +} + +IndependentMultimodal::~IndependentMultimodal() +{ + std::cout << "~IndependentMultimodal()" << std::endl; + delete pIMBS; +} + +void IndependentMultimodal::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + //get the fgmask and update the background model + pIMBS->apply(img_input, img_foreground); + + //get background image + pIMBS->getBackgroundImage(img_background); + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + { + cv::imshow("IMBS FG", img_foreground); + cv::imshow("IMBS BG", img_background); + } +#endif + + firstTime = false; +} + +void IndependentMultimodal::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void IndependentMultimodal::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + + cvReleaseFileStorage(&fs); +} diff --git a/package_bgs/StaticFrameDifferenceBGS.h b/package_bgs/IndependentMultimodal.h similarity index 60% rename from package_bgs/StaticFrameDifferenceBGS.h rename to package_bgs/IndependentMultimodal.h index 55f7bf1..4e2e3a3 100644 --- a/package_bgs/StaticFrameDifferenceBGS.h +++ b/package_bgs/IndependentMultimodal.h @@ -16,30 +16,28 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - #include "IBGS.h" +#include "IMBS/IMBS.hpp" -class StaticFrameDifferenceBGS : public IBGS +namespace bgslibrary { -private: - bool firstTime; - cv::Mat img_background; - cv::Mat img_foreground; - bool enableThreshold; - int threshold; - bool showOutput; - -public: - StaticFrameDifferenceBGS(); - ~StaticFrameDifferenceBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class IndependentMultimodal : public IBGS + { + private: + BackgroundSubtractorIMBS* pIMBS; + int fps; + + public: + IndependentMultimodal(); + ~IndependentMultimodal(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/ae/KDE.cpp b/package_bgs/KDE.cpp similarity index 56% rename from package_bgs/ae/KDE.cpp rename to package_bgs/KDE.cpp index 2cbbd8c..28564a0 100644 --- a/package_bgs/ae/KDE.cpp +++ b/package_bgs/KDE.cpp @@ -16,11 +16,15 @@ along with BGSLibrary. If not, see . */ #include "KDE.h" -KDE::KDE() : SequenceLength(50), TimeWindowSize(100), SDEstimationFlag(1), lUseColorRatiosFlag(1), - th(10e-8), alpha(0.3), framesToLearn(10), frameNumber(0), firstTime(true), showOutput(true) +using namespace bgslibrary::algorithms; + +KDE::KDE() : + SequenceLength(50), TimeWindowSize(100), SDEstimationFlag(1), lUseColorRatiosFlag(1), + th(10e-8), alpha(0.3), framesToLearn(10), frameNumber(0) { p = new NPBGSubtractor; std::cout << "KDE()" << std::endl; + setup("./config/KDE.xml"); } KDE::~KDE() @@ -32,72 +36,73 @@ KDE::~KDE() void KDE::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); + init(img_input, img_output, img_bgmodel); - if(firstTime) + if (firstTime) { rows = img_input.size().height; cols = img_input.size().width; color_channels = img_input.channels(); // SequenceLength: number of samples for each pixel. - // TimeWindowSize: Time window for sampling. for example in the call above, the bg will sample 50 points out of 100 frames. + // TimeWindowSize: Time window for sampling. for example in the call above, the bg will sample 50 points out of 100 frames. // this rate will affect how fast the model adapt. // SDEstimationFlag: True means to estimate suitable kernel bandwidth to each pixel, False uses a default value. // lUseColorRatiosFlag: True means use normalized RGB for color (recommended.) - p->Intialize(rows,cols,color_channels,SequenceLength,TimeWindowSize,SDEstimationFlag,lUseColorRatiosFlag); + p->Intialize(rows, cols, color_channels, SequenceLength, TimeWindowSize, SDEstimationFlag, lUseColorRatiosFlag); // th: 0-1 is the probability threshold for a pixel to be a foregroud. typically make it small as 10e-8. the smaller the value the less false positive and more false negative. // alpha: 0-1, for color. typically set to 0.3. this affect shadow suppression. - p->SetThresholds(th,alpha); + p->SetThresholds(th, alpha); FGImage = new unsigned char[rows*cols]; //FilteredFGImage = new unsigned char[rows*cols]; FilteredFGImage = 0; DisplayBuffers = 0; - img_foreground = cv::Mat::zeros(rows,cols,CV_8UC1); + img_foreground = cv::Mat::zeros(img_input.size(), CV_8UC1); + img_background = cv::Mat::zeros(img_input.size(), img_input.type()); frameNumber = 0; - saveConfig(); firstTime = false; } // Stores the first N frames to build the background model - if(frameNumber < framesToLearn) + if (frameNumber < framesToLearn) { p->AddFrame(img_input.data); frameNumber++; - return; } - - // Build the background model with first 10 frames - if(frameNumber == framesToLearn) + else { - p->Estimation(); - frameNumber++; - } + // Build the background model with first 10 frames + if (frameNumber == framesToLearn) + { + p->Estimation(); + frameNumber++; + } + + // Now, we can subtract the background + ((NPBGSubtractor*)p)->NBBGSubtraction(img_input.data, FGImage, FilteredFGImage, DisplayBuffers); - // Now, we can subtract the background - ((NPBGSubtractor *)p)->NBBGSubtraction(img_input.data,FGImage,FilteredFGImage,DisplayBuffers); - - // At each frame also you can call the update function to adapt the bg - // here you pass a mask where pixels with true value will be masked out of the update. - ((NPBGSubtractor *)p)->Update(FGImage); + // At each frame also you can call the update function to adapt the bg + // here you pass a mask where pixels with true value will be masked out of the update. + ((NPBGSubtractor*)p)->Update(FGImage); - img_foreground.data = FGImage; + img_foreground.data = FGImage; + } - if(showOutput) +#ifndef MEX_COMPILE_FLAG + if (showOutput) cv::imshow("KDE", img_foreground); +#endif img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); } void KDE::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/KDE.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "framesToLearn", framesToLearn); cvWriteInt(fs, "SequenceLength", SequenceLength); @@ -113,16 +118,16 @@ void KDE::saveConfig() void KDE::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/KDE.xml", 0, CV_STORAGE_READ); - - framesToLearn = cvReadIntByName(fs, 0, "framesToLearn", 10); - SequenceLength = cvReadIntByName(fs, 0, "SequenceLength", 50); - TimeWindowSize = cvReadIntByName(fs, 0, "TimeWindowSize", 100); - SDEstimationFlag = cvReadIntByName(fs, 0, "SDEstimationFlag", 1); - lUseColorRatiosFlag = cvReadIntByName(fs, 0, "lUseColorRatiosFlag", 1); - th = cvReadRealByName(fs, 0, "th", 10e-8); - alpha = cvReadRealByName(fs, 0, "alpha", 0.3); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + framesToLearn = cvReadIntByName(fs, nullptr, "framesToLearn", 10); + SequenceLength = cvReadIntByName(fs, nullptr, "SequenceLength", 50); + TimeWindowSize = cvReadIntByName(fs, nullptr, "TimeWindowSize", 100); + SDEstimationFlag = cvReadIntByName(fs, nullptr, "SDEstimationFlag", 1); + lUseColorRatiosFlag = cvReadIntByName(fs, nullptr, "lUseColorRatiosFlag", 1); + th = cvReadRealByName(fs, nullptr, "th", 10e-8); + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.3); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/KDE.h b/package_bgs/KDE.h new file mode 100644 index 0000000..e77996f --- /dev/null +++ b/package_bgs/KDE.h @@ -0,0 +1,57 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "KDE/NPBGSubtractor.h" + +namespace bgslibrary +{ + namespace algorithms + { + class KDE : public IBGS + { + private: + NPBGSubtractor *p; + int rows; + int cols; + int color_channels; + int SequenceLength; + int TimeWindowSize; + int SDEstimationFlag; + int lUseColorRatiosFlag; + double th; + double alpha; + int framesToLearn; + int frameNumber; + + unsigned char *FGImage; + unsigned char *FilteredFGImage; + unsigned char **DisplayBuffers; + + public: + KDE(); + ~KDE(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/ae/KernelTable.cpp b/package_bgs/KDE/KernelTable.cpp similarity index 75% rename from package_bgs/ae/KernelTable.cpp rename to package_bgs/KDE/KernelTable.cpp index 9e20d7c..bd9c368 100644 --- a/package_bgs/ae/KernelTable.cpp +++ b/package_bgs/KDE/KernelTable.cpp @@ -26,21 +26,21 @@ along with BGSLibrary. If not, see . * copyright notice must be included. For any other uses of this software, * in original or modified form, including but not limited to distribution * in whole or in part, specific prior permission must be obtained from -* Author or UMIACS. These programs shall not be used, rewritten, or -* adapted as the basis of a commercial software or hardware product -* without first obtaining appropriate licenses from Author. +* Author or UMIACS. These programs shall not be used, rewritten, or +* adapted as the basis of a commercial software or hardware product +* without first obtaining appropriate licenses from Author. * Other than these cases, no part of this software may be used or * distributed without written permission of the author. * -* Neither the author nor UMIACS make any representations about the -* suitability of this software for any purpose. It is provided +* Neither the author nor UMIACS make any representations about the +* suitability of this software for any purpose. It is provided * "as is" without express or implied warranty. * * Ahmed Elgammal -* +* * University of Maryland at College Park * UMIACS -* A.V. Williams Bldg. +* A.V. Williams Bldg. * CollegePark, MD 20742 * E-mail: elgammal@umiacs.umd.edu * @@ -67,8 +67,8 @@ KernelLUTable::KernelLUTable(int KernelHalfWidth, double Segmamin, double Segmam { std::cout << "KernelLUTable()" << std::endl; - double C1,C2,v,segma,sum; - int bin,b; + double C1, C2, v, segma, sum; + int bin, b; minsegma = Segmamin; maxsegma = Segmamax; @@ -78,27 +78,27 @@ KernelLUTable::KernelLUTable(int KernelHalfWidth, double Segmamin, double Segmam // Generate the Kernel // allocate memory for the Kernal Table - kerneltable = new double[segmabins*(2*KernelHalfWidth+1)]; + kerneltable = new double[segmabins*(2 * KernelHalfWidth + 1)]; kernelsums = new double[segmabins]; double segmastep = (maxsegma - minsegma) / segmabins; double y; - for(segma = minsegma, bin = 0; bin < segmabins; segma += segmastep, bin++) + for (segma = minsegma, bin = 0; bin < segmabins; segma += segmastep, bin++) { - C1 = 1/(sqrt(2*PI)*segma); - C2 = -1/(2*segma*segma); + C1 = 1 / (sqrt(2 * PI)*segma); + C2 = -1 / (2 * segma*segma); - b = (2*KernelHalfWidth+1)*bin; + b = (2 * KernelHalfWidth + 1)*bin; sum = 0; - - for(int x = 0; x <= KernelHalfWidth; x++) + + for (int x = 0; x <= KernelHalfWidth; x++) { - y = x/1.0; + y = x / 1.0; v = C1*exp(C2*y*y); - kerneltable[b+KernelHalfWidth+x]=v; - kerneltable[b+KernelHalfWidth-x]=v; - sum += 2*v; + kerneltable[b + KernelHalfWidth + x] = v; + kerneltable[b + KernelHalfWidth - x] = v; + sum += 2 * v; } sum -= C1; @@ -106,11 +106,11 @@ KernelLUTable::KernelLUTable(int KernelHalfWidth, double Segmamin, double Segmam kernelsums[bin] = sum; // Normailization - for(int x = 0; x <= KernelHalfWidth; x++) + for (int x = 0; x <= KernelHalfWidth; x++) { - v = kerneltable[b+KernelHalfWidth+x] / sum; - kerneltable[b+KernelHalfWidth+x]=v; - kerneltable[b+KernelHalfWidth-x]=v; + v = kerneltable[b + KernelHalfWidth + x] / sum; + kerneltable[b + KernelHalfWidth + x] = v; + kerneltable[b + KernelHalfWidth - x] = v; } } } diff --git a/package_bgs/ae/KernelTable.h b/package_bgs/KDE/KernelTable.h similarity index 86% rename from package_bgs/ae/KernelTable.h rename to package_bgs/KDE/KernelTable.h index bc37cc4..63a20dc 100644 --- a/package_bgs/ae/KernelTable.h +++ b/package_bgs/KDE/KernelTable.h @@ -26,28 +26,26 @@ along with BGSLibrary. If not, see . * copyright notice must be included. For any other uses of this software, * in original or modified form, including but not limited to distribution * in whole or in part, specific prior permission must be obtained from -* Author or UMIACS. These programs shall not be used, rewritten, or -* adapted as the basis of a commercial software or hardware product -* without first obtaining appropriate licenses from Author. +* Author or UMIACS. These programs shall not be used, rewritten, or +* adapted as the basis of a commercial software or hardware product +* without first obtaining appropriate licenses from Author. * Other than these cases, no part of this software may be used or * distributed without written permission of the author. * -* Neither the author nor UMIACS make any representations about the -* suitability of this software for any purpose. It is provided +* Neither the author nor UMIACS make any representations about the +* suitability of this software for any purpose. It is provided * "as is" without express or implied warranty. * * Ahmed Elgammal -* +* * University of Maryland at College Park * UMIACS -* A.V. Williams Bldg. +* A.V. Williams Bldg. * CollegePark, MD 20742 * E-mail: elgammal@umiacs.umd.edu * **/ - -#ifndef __KERNEL_TABLE__ -#define __KERNEL_TABLE__ +#pragma once #include @@ -65,7 +63,5 @@ class KernelLUTable KernelLUTable(); ~KernelLUTable(); - KernelLUTable(int KernelHalfWidth,double Segmamin,double Segmamax,int Segmabins); + KernelLUTable(int KernelHalfWidth, double Segmamin, double Segmamax, int Segmabins); }; - -#endif diff --git a/package_bgs/KDE/NPBGSubtractor.cpp b/package_bgs/KDE/NPBGSubtractor.cpp new file mode 100644 index 0000000..5f004b9 --- /dev/null +++ b/package_bgs/KDE/NPBGSubtractor.cpp @@ -0,0 +1,1160 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* +* +* Copyright 2001 by Ahmed Elgammal All rights reserved. +* +* Permission to use, copy, or modify this software and its documentation +* for educational and research purposes only and without fee is hereby +* granted, provided that this copyright notice and the original authors's +* name appear on all copies and supporting documentation. If individual +* files are separated from this distribution directory structure, this +* copyright notice must be included. For any other uses of this software, +* in original or modified form, including but not limited to distribution +* in whole or in part, specific prior permission must be obtained from +* Author or UMIACS. These programs shall not be used, rewritten, or +* adapted as the basis of a commercial software or hardware product +* without first obtaining appropriate licenses from Author. +* Other than these cases, no part of this software may be used or +* distributed without written permission of the author. +* +* Neither the author nor UMIACS make any representations about the +* suitability of this software for any purpose. It is provided +* "as is" without express or implied warranty. +* +* Ahmed Elgammal +* +* University of Maryland at College Park +* UMIACS +* A.V. Williams Bldg. +* CollegePark, MD 20742 +* E-mail: elgammal@umiacs.umd.edu +* +**/ + +// NPBGSubtractor.cpp: implementation of the NPBGSubtractor class. +// +////////////////////////////////////////////////////////////////////// + +#include "NPBGSubtractor.h" +#include +#include +#include + +//#ifdef _DEBUG +//#undef THIS_FILE +//static char THIS_FILE[]=__FILE__; +//#define new DEBUG_NEW +//#endif + +void BGR2SnGnRn(unsigned char * in_image, + unsigned char * out_image, + unsigned int rows, + unsigned int cols) +{ + unsigned int i; + unsigned int r2, r3; + unsigned int r, g, b; + double s; + + for (i = 0; i < rows*cols * 3; i += 3) + { + b = in_image[i]; + g = in_image[i + 1]; + r = in_image[i + 2]; + + // calculate color ratios + s = (double)255 / (double)(b + g + r + 30); + + r2 = (unsigned int)((g + 10) * s); + r3 = (unsigned int)((r + 10) * s); + + out_image[i] = (unsigned char)(((unsigned int)b + g + r) / 3); + out_image[i + 1] = (unsigned char)(r2 > 255 ? 255 : r2); + out_image[i + 2] = (unsigned char)(r3 > 255 ? 255 : r3); + } +} + +void UpdateDiffHist(unsigned char * image1, unsigned char * image2, DynamicMedianHistogram * pHist) +{ + unsigned int j; + int bin, diff; + + unsigned int imagesize = pHist->imagesize; + unsigned char histbins = pHist->histbins; + unsigned char *pAbsDiffHist = pHist->Hist; + + int histbins_1 = histbins - 1; + + for (j = 0; j < imagesize; j++) + { + diff = (int)image1[j] - (int)image2[j]; + diff = abs(diff); + // update histogram + bin = (diff < histbins ? diff : histbins_1); + pAbsDiffHist[j*histbins + bin]++; + } +} + +void FindHistMedians(DynamicMedianHistogram * pAbsDiffHist) +{ + unsigned char * Hist = pAbsDiffHist->Hist; + unsigned char * MedianBins = pAbsDiffHist->MedianBins; + unsigned char * AccSum = pAbsDiffHist->AccSum; + unsigned char histsum = pAbsDiffHist->histsum; + unsigned char histbins = pAbsDiffHist->histbins; + unsigned int imagesize = pAbsDiffHist->imagesize; + + int sum; + int bin; + unsigned int histindex; + unsigned char medianCount = histsum / 2; + unsigned int j; + + // find medians + for (j = 0; j < imagesize; j++) + { + // find the median + bin = 0; + sum = 0; + + histindex = j*histbins; + + while (sum < medianCount) + { + sum += Hist[histindex + bin]; + bin++; + } + + bin--; + + MedianBins[j] = bin; + AccSum[j] = sum; + } +} + +DynamicMedianHistogram BuildAbsDiffHist(unsigned char * pSequence, + unsigned int rows, + unsigned int cols, + unsigned int color_channels, + unsigned int SequenceLength, + unsigned int histbins) +{ + + unsigned int imagesize = rows*cols*color_channels; + unsigned int i; + + DynamicMedianHistogram Hist; + + unsigned char *pAbsDiffHist = new unsigned char[rows*cols*color_channels*histbins]; + unsigned char *pMedianBins = new unsigned char[rows*cols*color_channels]; + unsigned char *pMedianFreq = new unsigned char[rows*cols*color_channels]; + unsigned char *pAccSum = new unsigned char[rows*cols*color_channels]; + + memset(pAbsDiffHist, 0, rows*cols*color_channels*histbins); + + Hist.Hist = pAbsDiffHist; + Hist.MedianBins = pMedianBins; + Hist.MedianFreq = pMedianFreq; + Hist.AccSum = pAccSum; + Hist.histbins = histbins; + Hist.imagesize = rows*cols*color_channels; + Hist.histsum = SequenceLength - 1; + + unsigned char *image1, *image2; + for (i = 1; i < SequenceLength; i++) + { + // find diff between frame i,i-1; + image1 = pSequence + (i - 1)*imagesize; + image2 = pSequence + (i)*imagesize; + + UpdateDiffHist(image1, image2, &Hist); + } + + FindHistMedians(&Hist); + + return Hist; +} + +void EstimateSDsFromAbsDiffHist(DynamicMedianHistogram * pAbsDiffHist, + unsigned char * pSDs, + unsigned int imagesize, + double MinSD, + double MaxSD, + unsigned int kernelbins) +{ + double v; + double kernelbinfactor = (kernelbins - 1) / (MaxSD - MinSD); + int medianCount; + int sum; + int bin; + unsigned int histindex; + unsigned int j; + unsigned int x1, x2; + + unsigned char *Hist = pAbsDiffHist->Hist; + unsigned char *MedianBins = pAbsDiffHist->MedianBins; + unsigned char *AccSum = pAbsDiffHist->AccSum; + unsigned char histsum = pAbsDiffHist->histsum; + unsigned char histbins = pAbsDiffHist->histbins; + + medianCount = (histsum) / 2; + + for (j = 0; j < imagesize; j++) + { + histindex = j*histbins; + + bin = MedianBins[j]; + sum = AccSum[j]; + + x1 = sum - Hist[histindex + bin]; + x2 = sum; + + // interpolate to get the median + // x1 < 50 % < x2 + + v = 1.04 * ((double)bin - (double)(x2 - medianCount) / (double)(x2 - x1)); + v = (v <= MinSD ? MinSD : v); + + // convert sd to kernel table bin + + bin = (int)(v >= MaxSD ? kernelbins - 1 : floor((v - MinSD)*kernelbinfactor + .5)); + + assert(bin >= 0 && bin < kernelbins); + + pSDs[j] = bin; + } +} + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +NPBGSubtractor::NPBGSubtractor() {} + +NPBGSubtractor::~NPBGSubtractor() +{ + delete AbsDiffHist.Hist; + delete AbsDiffHist.MedianBins; + delete AbsDiffHist.MedianFreq; + delete AbsDiffHist.AccSum; + delete KernelTable; + delete BGModel->SDbinsImage; + delete BGModel; + delete Pimage1; + delete Pimage2; + delete tempFrame; + delete imageindex->List; + delete imageindex; +} + +int NPBGSubtractor::Intialize(unsigned int prows, + unsigned int pcols, + unsigned int pcolor_channels, + unsigned int SequenceLength, + unsigned int pTimeWindowSize, + unsigned char pSDEstimationFlag, + unsigned char pUseColorRatiosFlag) +{ + + rows = prows; + cols = pcols; + color_channels = pcolor_channels; + imagesize = rows*cols*color_channels; + SdEstimateFlag = pSDEstimationFlag; + UseColorRatiosFlag = pUseColorRatiosFlag; + //SampleSize = SequenceLength; + + AdaptBGFlag = FALSE; + // + SubsetFlag = TRUE; + + UpdateSDRate = 0; + + BGModel = new NPBGmodel(rows, cols, color_channels, SequenceLength, pTimeWindowSize, 500); + + Pimage1 = new double[rows*cols]; + Pimage2 = new double[rows*cols]; + + tempFrame = new unsigned char[rows*cols * 3]; + + imageindex = new ImageIndex; + imageindex->List = new unsigned int[rows*cols]; + + // error checking + if (BGModel == NULL) + return 0; + + return 1; +} + +void NPBGSubtractor::AddFrame(unsigned char *ImageBuffer) +{ + if (UseColorRatiosFlag && color_channels == 3) + BGR2SnGnRn(ImageBuffer, ImageBuffer, rows, cols); + + BGModel->AddFrame(ImageBuffer); +} + +void NPBGSubtractor::Estimation() +{ + int SampleSize = BGModel->SampleSize; + + memset(BGModel->TemporalMask, 0, rows*cols*BGModel->TemporalBufferLength); + + //BGModel->AccMask= new unsigned int [rows*cols]; + memset(BGModel->AccMask, 0, rows*cols * sizeof(unsigned int)); + + unsigned char *pSDs = new unsigned char[rows*cols*color_channels]; + + //DynamicMedianHistogram AbsDiffHist; + + int Abshistbins = 20; + + TimeIndex = 0; + + // estimate standard deviations + + if (SdEstimateFlag) + { + AbsDiffHist = BuildAbsDiffHist(BGModel->Sequence, rows, cols, color_channels, SampleSize, Abshistbins); + EstimateSDsFromAbsDiffHist(&AbsDiffHist, pSDs, imagesize, SEGMAMIN, SEGMAMAX, SEGMABINS); + } + else + { + unsigned int bin; + bin = (unsigned int)floor(((DEFAULTSEGMA - SEGMAMIN)*SEGMABINS) / (SEGMAMAX - SEGMAMIN)); + memset(pSDs, bin, rows*cols*color_channels * sizeof(unsigned char)); + } + + BGModel->SDbinsImage = pSDs; + + // Generate the Kernel + KernelTable = new KernelLUTable(KERNELHALFWIDTH, SEGMAMIN, SEGMAMAX, SEGMABINS); +} + +/*********************************************************************/ + +void BuildImageIndex(unsigned char * Image, + ImageIndex * imageIndex, + unsigned int rows, + unsigned int cols) +{ + unsigned int i, j; + unsigned int r, c; + unsigned int * image_list; + + j = cols + 1; + i = 0; + image_list = imageIndex->List; + + for (r = 1; r < rows - 1; r++) + { + for (c = 1; c < cols - 1; c++) + { + if (Image[j]) + image_list[i++] = j; + + j++; + } + j += 2; + } + + imageIndex->cnt = i; +} + +/*********************************************************************/ + +void HystExpandOperatorIndexed(unsigned char * inImage, + ImageIndex * inIndex, + double * Pimage, + double hyst_th, + unsigned char * outImage, + ImageIndex * outIndex, + unsigned int urows, + unsigned int ucols) +{ + unsigned int * in_list; + unsigned int in_cnt; + unsigned int * out_list; + + int rows, cols; + + int Nbr[9]; + unsigned int i, j; + unsigned int k; + unsigned int idx; + + rows = (int)urows; + cols = (int)ucols; + + in_cnt = inIndex->cnt; + in_list = inIndex->List; + + Nbr[0] = -cols - 1; + Nbr[1] = -cols; + Nbr[2] = -cols + 1; + Nbr[3] = -1; + Nbr[4] = 0; + Nbr[5] = 1; + Nbr[6] = cols - 1; + Nbr[7] = cols; + Nbr[8] = cols + 1; + + memset(outImage, 0, rows*cols); + + out_list = outIndex->List; + k = 0; + + for (i = 0; i < in_cnt; i++) + { + for (j = 0; j < 9; j++) + { + idx = in_list[i] + Nbr[j]; + + if (Pimage[idx] < hyst_th) + outImage[idx] = 255; + } + } + + // build index for out image + BuildImageIndex(outImage, outIndex, urows, ucols); +} + +/*********************************************************************/ + +void HystShrinkOperatorIndexed(unsigned char * inImage, + ImageIndex * inIndex, + double * Pimage, + double hyst_th, + unsigned char * outImage, + ImageIndex * outIndex, + unsigned int urows, + unsigned int ucols) +{ + unsigned int * in_list; + unsigned int in_cnt; + unsigned int * out_list; + + int rows, cols; + + int Nbr[9]; + unsigned int i, j; + unsigned int k, idx; + + rows = (int)urows; + cols = (int)ucols; + + in_cnt = inIndex->cnt; + in_list = inIndex->List; + + Nbr[0] = -cols - 1; + Nbr[1] = -cols; + Nbr[2] = -cols + 1; + Nbr[3] = -1; + Nbr[4] = 0; + Nbr[5] = 1; + Nbr[6] = cols - 1; + Nbr[7] = cols; + Nbr[8] = cols + 1; + + memset(outImage, 0, rows*cols); + + out_list = outIndex->List; + k = 0; + + for (i = 0; i < in_cnt; i++) + { + idx = in_list[i]; + j = 0; + + while (j < 9 && inImage[idx + Nbr[j]]) + j++; + + if (j >= 9 || Pimage[idx] <= hyst_th) + outImage[idx] = 255; + } + + BuildImageIndex(outImage, outIndex, rows, cols); +} + +/*********************************************************************/ + +void ExpandOperatorIndexed(unsigned char * inImage, + ImageIndex * inIndex, + unsigned char * outImage, + ImageIndex * outIndex, + unsigned int urows, + unsigned int ucols) +{ + unsigned int * in_list; + unsigned int in_cnt; + unsigned int * out_list; + + int rows, cols; + + int Nbr[9]; + unsigned int i, j; + unsigned int k; + unsigned int idx; + + rows = (int)urows; + cols = (int)ucols; + + in_cnt = inIndex->cnt; + in_list = inIndex->List; + + Nbr[0] = -cols - 1; + Nbr[1] = -cols; + Nbr[2] = -cols + 1; + Nbr[3] = -1; + Nbr[4] = 0; + Nbr[5] = 1; + Nbr[6] = cols - 1; + Nbr[7] = cols; + Nbr[8] = cols + 1; + + + memset(outImage, 0, rows*cols); + + + out_list = outIndex->List; + k = 0; + for (i = 0; i < in_cnt; i++) + for (j = 0; j < 9; j++) { + idx = in_list[i] + Nbr[j]; + outImage[idx] = 255; + } + + + // build index for out image + + BuildImageIndex(outImage, outIndex, rows, cols); + +} + +/*********************************************************************/ + +void ShrinkOperatorIndexed(unsigned char * inImage, + ImageIndex * inIndex, + unsigned char * outImage, + ImageIndex * outIndex, + unsigned int urows, + unsigned int ucols) +{ + + unsigned int * in_list; + unsigned int in_cnt; + unsigned int * out_list; + + int rows, cols; + + int Nbr[9]; + unsigned int i, j; + unsigned int k, idx; + + rows = (int)urows; + cols = (int)ucols; + + in_cnt = inIndex->cnt; + in_list = inIndex->List; + + Nbr[0] = -cols - 1; + Nbr[1] = -cols; + Nbr[2] = -cols + 1; + Nbr[3] = -1; + Nbr[4] = 0; + Nbr[5] = 1; + Nbr[6] = cols - 1; + Nbr[7] = cols; + Nbr[8] = cols + 1; + + + memset(outImage, 0, rows*cols); + + out_list = outIndex->List; + k = 0; + for (i = 0; i < in_cnt; i++) { + idx = in_list[i]; + j = 0; + + while (j < 9 && inImage[idx + Nbr[j]]) { + j++; + } + + if (j >= 9) { + outImage[idx] = 255; + } + } + + BuildImageIndex(outImage, outIndex, rows, cols); +} + +/*********************************************************************/ + +void NoiseFilter_o(unsigned char * Image, + unsigned char * ResultIm, + int rows, + int cols, + unsigned char th) +{ + /* assuming input is 1 for on, 0 for off */ + + + int r, c; + unsigned char *p, *n, *nw, *ne, *e, *w, *s, *sw, *se; + unsigned int v; + unsigned int TH; + + unsigned char * ResultPtr; + + TH = 255 * th; + + memset(ResultIm, 0, rows*cols); + + p = Image + cols + 1; + ResultPtr = ResultIm + cols + 1; + + for (r = 1; r < rows - 1; r++) + { + for (c = 1; c < cols - 1; c++) + { + if (*p) + { + n = p - cols; + ne = n + 1; + nw = n - 1; + e = p + 1; + w = p - 1; + s = p + cols; + se = s + 1; + sw = s - 1; + + v = (unsigned int)*nw + *n + *ne + *w + *e + *sw + *s + *se; + + if (v >= TH) + *ResultPtr = 255; + else + *ResultPtr = 0; + } + p++; + ResultPtr++; + } + p += 2; + ResultPtr += 2; + } +} + +/*********************************************************************/ + +void NPBGSubtractor::SequenceBGUpdate_Pairs(unsigned char * image, + unsigned char * Mask) +{ + unsigned int i, ic; + unsigned char * pSequence = BGModel->Sequence; + unsigned char * PixelQTop = BGModel->PixelQTop; + unsigned int Top = BGModel->Top; + unsigned int rate; + + int TemporalBufferTop = (int)BGModel->TemporalBufferTop; + unsigned char * pTemporalBuffer = BGModel->TemporalBuffer; + unsigned char * pTemporalMask = BGModel->TemporalMask; + int TemporalBufferLength = BGModel->TemporalBufferLength; + + unsigned int * AccMask = BGModel->AccMask; + unsigned int ResetMaskTh = BGModel->ResetMaskTh; + + unsigned char *pAbsDiffHist = AbsDiffHist.Hist; + unsigned char histbins = AbsDiffHist.histbins; + int histbins_1 = histbins - 1; + + int TimeWindowSize = BGModel->TimeWindowSize; + int SampleSize = BGModel->SampleSize; + + int TemporalBufferNext; + + unsigned int imagebuffersize = rows*cols*color_channels; + unsigned int imagespatialsize = rows*cols; + + unsigned char mask; + + unsigned int histindex; + unsigned char diff; + unsigned char bin; + + static int TBCount = 0; + + unsigned char * pTBbase1, *pTBbase2; + unsigned char * pModelbase1, *pModelbase2; + + rate = TimeWindowSize / SampleSize; + rate = (rate > 2) ? rate : 2; + + + TemporalBufferNext = (TemporalBufferTop + 1) + % TemporalBufferLength; + + // pointers to Masks : Top and Next + unsigned char * pTMaskTop = pTemporalMask + TemporalBufferTop*imagespatialsize; + unsigned char * pTMaskNext = pTemporalMask + TemporalBufferNext*imagespatialsize; + + // pointers to TB frames: Top and Next + unsigned char * pTBTop = pTemporalBuffer + TemporalBufferTop*imagebuffersize; + unsigned char * pTBNext = pTemporalBuffer + TemporalBufferNext*imagebuffersize; + + if (((TimeIndex) % rate == 0) && TBCount >= TemporalBufferLength) + { + for (i = 0, ic = 0; i < imagespatialsize; i++, ic += color_channels) + { + mask = *(pTMaskTop + i) || *(pTMaskNext + i); + + if (!mask) + { + // pointer to TB pixels to be added to the model + pTBbase1 = pTBTop + ic; + pTBbase2 = pTBNext + ic; + + // pointers to Model pixels to be replaced + pModelbase1 = pSequence + PixelQTop[i] * imagebuffersize + ic; + pModelbase2 = pSequence + ((PixelQTop[i] + 1) % SampleSize)*imagebuffersize + ic; + + // update Deviation Histogram + if (SdEstimateFlag) + { + if (color_channels == 1) + { + histindex = i*histbins; + + // add new pair from temporal buffer + diff = (unsigned char)abs((int)*pTBbase1 - (int)*pTBbase2); + bin = (diff < histbins ? diff : histbins_1); + pAbsDiffHist[histindex + bin]++; + + + // remove old pair from the model + diff = (unsigned char)abs((int)*pModelbase1 - (int)*pModelbase2); + bin = (diff < histbins ? diff : histbins_1); + pAbsDiffHist[histindex + bin]--; + } + else + { + // color + + // add new pair from temporal buffer + histindex = ic*histbins; + diff = abs(*pTBbase1 - + *pTBbase2); + bin = (diff < histbins ? diff : histbins_1); + pAbsDiffHist[histindex + bin]++; + + histindex += histbins; + diff = abs(*(pTBbase1 + 1) - + *(pTBbase2 + 1)); + bin = (diff < histbins ? diff : histbins_1); + pAbsDiffHist[histindex + bin]++; + + histindex += histbins; + diff = abs(*(pTBbase1 + 2) - + *(pTBbase2 + 2)); + bin = (diff < histbins ? diff : histbins_1); + pAbsDiffHist[histindex + bin]++; + + // remove old pair from the model + histindex = ic*histbins; + + diff = abs(*pModelbase1 - + *pModelbase2); + bin = (diff < histbins ? diff : histbins_1); + pAbsDiffHist[histindex + bin]--; + + histindex += histbins; + diff = abs(*(pModelbase1 + 1) - + *(pModelbase2 + 1)); + bin = (diff < histbins ? diff : histbins_1); + pAbsDiffHist[histindex + bin]--; + + histindex += histbins; + diff = abs(*(pModelbase1 + 2) - + *(pModelbase2 + 2)); + bin = (diff < histbins ? diff : histbins_1); + pAbsDiffHist[histindex + bin]--; + } + } + + // add new pair into the model + memcpy(pModelbase1, pTBbase1, color_channels * sizeof(unsigned char)); + + memcpy(pModelbase2, pTBbase2, color_channels * sizeof(unsigned char)); + + PixelQTop[i] = (PixelQTop[i] + 2) % SampleSize; + } + } + } // end if (sampling event) + + // update temporal buffer + // add new frame to Temporal buffer. + memcpy(pTBTop, image, imagebuffersize); + + // update AccMask + // update new Mask with information in AccMask + + for (i = 0; i < rows*cols; i++) + { + if (Mask[i]) + AccMask[i]++; + else + AccMask[i] = 0; + + if (AccMask[i] > ResetMaskTh) + Mask[i] = 0; + } + + // add new mask + memcpy(pTMaskTop, Mask, imagespatialsize); + + // advance Temporal buffer pointer + TemporalBufferTop = (TemporalBufferTop + 1) % TemporalBufferLength; + + BGModel->TemporalBufferTop = TemporalBufferTop; + + TBCount++; + + // estimate SDs + + if (SdEstimateFlag && UpdateSDRate && ((TimeIndex) % UpdateSDRate == 0)) + { + double MaxSD = KernelTable->maxsegma; + double MinSD = KernelTable->minsegma; + int KernelBins = KernelTable->segmabins; + + unsigned char * pSDs = BGModel->SDbinsImage; + + FindHistMedians(&(AbsDiffHist)); + EstimateSDsFromAbsDiffHist(&(AbsDiffHist), pSDs, imagebuffersize, MinSD, MaxSD, KernelBins); + } + + TimeIndex++; +} + +/*********************************************************************/ + +void DisplayPropabilityImageWithThresholding(double * Pimage, + unsigned char * DisplayImage, + double Threshold, + unsigned int rows, + unsigned int cols) +{ + double p; + + for (unsigned int i = 0; i < rows*cols; i++) + { + p = Pimage[i]; + + DisplayImage[i] = (p > Threshold) ? 0 : 255; + } +} + +/*********************************************************************/ + +void NPBGSubtractor::NPBGSubtraction_Subset_Kernel( + unsigned char * image, + unsigned char * FGImage, + unsigned char * FilteredFGImage) +{ + unsigned int i, j; + unsigned char *pSequence = BGModel->Sequence; + + unsigned int SampleSize = BGModel->SampleSize; + + double *kerneltable = KernelTable->kerneltable; + int KernelHalfWidth = KernelTable->tablehalfwidth; + double *KernelSum = KernelTable->kernelsums; + double KernelMaxSigma = KernelTable->maxsegma; + double KernelMinSigma = KernelTable->minsegma; + int KernelBins = KernelTable->segmabins; + unsigned char * SDbins = BGModel->SDbinsImage; + + unsigned char * SaturationImage = FilteredFGImage; + + // default sigmas .. to be removed. + double sigma1; + double sigma2; + double sigma3; + + sigma1 = 2.25; + sigma2 = 2.25; + sigma3 = 2.25; + + double p; + double th; + + double alpha; + + alpha = AlphaValue; + + /* intialize FG image */ + + memset(FGImage, 0, rows*cols); + + //Threshold=1; + th = Threshold * SampleSize; + + double sum = 0, kernel1, kernel2, kernel3; + int k, g; + + + if (color_channels == 1) + { + // gray scale + + int kernelbase; + + for (i = 0; i < rows*cols; i++) + { + kernelbase = SDbins[i] * (2 * KernelHalfWidth + 1); + sum = 0; + j = 0; + + while (j < SampleSize && sum < th) + { + g = pSequence[j*imagesize + i]; + k = g - image[i] + KernelHalfWidth; + sum += kerneltable[kernelbase + k]; + j++; + } + + p = sum / j; + Pimage1[i] = p; + } + } + else if (UseColorRatiosFlag && SubsetFlag) + { + // color ratios + + unsigned int ig; + int base; + + int kernelbase1; + int kernelbase2; + int kernelbase3; + + unsigned int kerneltablewidth = 2 * KernelHalfWidth + 1; + + double beta = 3.0; // minimum bound on the range. + double betau = 100.0; + + double beta_over_alpha = beta / alpha; + double betau_over_alpha = betau / alpha; + + + double brightness_lowerbound = 1 - alpha; + double brightness_upperbound = 1 + alpha; + int x1, x2; + unsigned int SubsampleCount; + + for (i = 0, ig = 0; i < imagesize; i += 3, ig++) + { + kernelbase1 = SDbins[i] * kerneltablewidth; + kernelbase2 = SDbins[i + 1] * kerneltablewidth; + kernelbase3 = SDbins[i + 2] * kerneltablewidth; + + sum = 0; + j = 0; + SubsampleCount = 0; + + while (j < SampleSize && sum < th) + { + base = j*imagesize + i; + g = pSequence[base]; + + if (g < beta_over_alpha) + { + x1 = (int)(g - beta); + x2 = (int)(g + beta); + } + else if (g > betau_over_alpha) + { + x1 = (int)(g - betau); + x2 = (int)(g + betau); + } + else + { + x1 = (int)(g*brightness_lowerbound + 0.5); + x2 = (int)(g*brightness_upperbound + 0.5); + } + + if (x1 < image[i] && image[i] < x2) + { + g = pSequence[base + 1]; + k = (g - image[i + 1]) + KernelHalfWidth; + kernel2 = kerneltable[kernelbase2 + k]; + + g = pSequence[base + 2]; + k = (g - image[i + 2]) + KernelHalfWidth; + kernel3 = kerneltable[kernelbase3 + k]; + + sum += kernel2*kernel3; + + SubsampleCount++; + } + j++; + } + + p = sum / j; + Pimage1[ig] = p; + } + } + else if (UseColorRatiosFlag && !SubsetFlag) + { + // color ratios + + unsigned int ig; + int base; + int bin; + + int kernelbase1; + int kernelbase2; + int kernelbase3; + + unsigned int kerneltablewidth = 2 * KernelHalfWidth + 1; + + int gmin, gmax; + double gfactor; + + gmax = 200; + gmin = 10; + + gfactor = (KernelMaxSigma - KernelMinSigma) / (double)(gmax - gmin); + + for (i = 0, ig = 0; i < imagesize; i += 3, ig++) + { + + bin = (int)floor(((alpha * 16 - KernelMinSigma)*KernelBins) / (KernelMaxSigma - KernelMinSigma)); + + kernelbase1 = bin*kerneltablewidth; + kernelbase2 = SDbins[i + 1] * kerneltablewidth; + kernelbase3 = SDbins[i + 2] * kerneltablewidth; + + sum = 0; + j = 0; + + while (j < SampleSize && sum < th) + { + base = j*imagesize + i; + g = pSequence[base]; + + if (g < gmin) + bin = 0; + else if (g > gmax) + bin = KernelBins - 1; + else + bin = (int)((g - gmin) * gfactor + 0.5); + + kernelbase1 = bin*kerneltablewidth; + + k = (g - image[i]) + KernelHalfWidth; + kernel1 = kerneltable[kernelbase1 + k]; + + g = pSequence[base + 1]; + k = (g - image[i + 1]) + KernelHalfWidth; + kernel2 = kerneltable[kernelbase2 + k]; + + g = pSequence[base + 2]; + k = (g - image[i + 2]) + KernelHalfWidth; + kernel3 = kerneltable[kernelbase3 + k]; + + sum += kernel1*kernel2*kernel3; + j++; + } + + p = sum / j; + Pimage1[ig] = p; + } + } + else // RGB color + { + unsigned int ig; + int base; + + int kernelbase1; + int kernelbase2; + int kernelbase3; + unsigned int kerneltablewidth = 2 * KernelHalfWidth + 1; + + for (i = 0, ig = 0; i < imagesize; i += 3, ig++) + { + // used extimated kernel width to access the right kernel + kernelbase1 = SDbins[i] * kerneltablewidth; + kernelbase2 = SDbins[i + 1] * kerneltablewidth; + kernelbase3 = SDbins[i + 2] * kerneltablewidth; + + sum = 0; + j = 0; + while (j < SampleSize && sum < th) + { + base = j*imagesize + i; + g = pSequence[base]; + k = (g - image[i]) + KernelHalfWidth; + kernel1 = kerneltable[kernelbase1 + k]; + + g = pSequence[base + 1]; + k = (g - image[i + 1]) + KernelHalfWidth; + kernel2 = kerneltable[kernelbase2 + k]; + + g = pSequence[base + 2]; + k = (g - image[i + 2]) + KernelHalfWidth; + kernel3 = kerneltable[kernelbase3 + k]; + + sum += kernel1*kernel2*kernel3; + j++; + } + + p = sum / j; + Pimage1[ig] = p; + } + } + + DisplayPropabilityImageWithThresholding(Pimage1, FGImage, Threshold, rows, cols); +} + +/*********************************************************************/ + +void NPBGSubtractor::NBBGSubtraction(unsigned char * Frame, + unsigned char * FGImage, + unsigned char * FilteredFGImage, + unsigned char ** DisplayBuffers) +{ + if (UseColorRatiosFlag) + BGR2SnGnRn(Frame, tempFrame, rows, cols); + else + memcpy(tempFrame, Frame, rows*cols*color_channels); + + NPBGSubtraction_Subset_Kernel(tempFrame, FGImage, FilteredFGImage); + /*NoiseFilter_o(FGImage,DisplayBuffers[3],rows,cols,4); + BuildImageIndex(DisplayBuffers[3],imageindex,rows,cols); + + ExpandOperatorIndexed(DisplayBuffers[3],imageindex,DisplayBuffers[4],imageindex,rows,cols); + ShrinkOperatorIndexed(DisplayBuffers[4],imageindex,FilteredFGImage,imageindex,rows,cols); + + memset(DisplayBuffers[3],0,rows*cols);*/ +} + +void NPBGSubtractor::Update(unsigned char * FGMask) +{ + if (UpdateBGFlag) + SequenceBGUpdate_Pairs(tempFrame, FGMask); +} diff --git a/package_bgs/ae/NPBGSubtractor.h b/package_bgs/KDE/NPBGSubtractor.h similarity index 87% rename from package_bgs/ae/NPBGSubtractor.h rename to package_bgs/KDE/NPBGSubtractor.h index e7f3795..ce4bffc 100644 --- a/package_bgs/ae/NPBGSubtractor.h +++ b/package_bgs/KDE/NPBGSubtractor.h @@ -26,21 +26,21 @@ along with BGSLibrary. If not, see . * copyright notice must be included. For any other uses of this software, * in original or modified form, including but not limited to distribution * in whole or in part, specific prior permission must be obtained from -* Author or UMIACS. These programs shall not be used, rewritten, or -* adapted as the basis of a commercial software or hardware product -* without first obtaining appropriate licenses from Author. +* Author or UMIACS. These programs shall not be used, rewritten, or +* adapted as the basis of a commercial software or hardware product +* without first obtaining appropriate licenses from Author. * Other than these cases, no part of this software may be used or * distributed without written permission of the author. * -* Neither the author nor UMIACS make any representations about the -* suitability of this software for any purpose. It is provided +* Neither the author nor UMIACS make any representations about the +* suitability of this software for any purpose. It is provided * "as is" without express or implied warranty. * * Ahmed Elgammal -* +* * University of Maryland at College Park * UMIACS -* A.V. Williams Bldg. +* A.V. Williams Bldg. * CollegePark, MD 20742 * E-mail: elgammal@umiacs.umd.edu * @@ -49,13 +49,7 @@ along with BGSLibrary. If not, see . // NPBGSubtractor.h: interface for the NPBGSubtractor class. // ////////////////////////////////////////////////////////////////////// - -#if !defined(AFX_NPBGSUBTRACTOR_H__84B0F51E_6E65_41E4_AC01_723B406363C4__INCLUDED_) -#define AFX_NPBGSUBTRACTOR_H__84B0F51E_6E65_41E4_AC01_723B406363C4__INCLUDED_ - -#if _MSC_VER > 1000 #pragma once -#endif // _MSC_VER > 1000 #include "NPBGmodel.h" #include "KernelTable.h" @@ -87,7 +81,7 @@ typedef struct unsigned int *List; } ImageIndex; -class NPBGSubtractor +class NPBGSubtractor { private: unsigned int rows; @@ -146,9 +140,7 @@ class NPBGSubtractor AlphaValue = alpha; }; - void SetUpdateFlag(unsigned int bgflag){ + void SetUpdateFlag(unsigned int bgflag) { UpdateBGFlag = bgflag; }; }; - -#endif // !defined(AFX_NPBGSUBTRACTOR_H__84B0F51E_6E65_41E4_AC01_723B406363C4__INCLUDED_) diff --git a/package_bgs/ae/NPBGmodel.cpp b/package_bgs/KDE/NPBGmodel.cpp similarity index 78% rename from package_bgs/ae/NPBGmodel.cpp rename to package_bgs/KDE/NPBGmodel.cpp index 2c8b074..a79a7b4 100644 --- a/package_bgs/ae/NPBGmodel.cpp +++ b/package_bgs/KDE/NPBGmodel.cpp @@ -26,21 +26,21 @@ along with BGSLibrary. If not, see . * copyright notice must be included. For any other uses of this software, * in original or modified form, including but not limited to distribution * in whole or in part, specific prior permission must be obtained from -* Author or UMIACS. These programs shall not be used, rewritten, or -* adapted as the basis of a commercial software or hardware product -* without first obtaining appropriate licenses from Author. +* Author or UMIACS. These programs shall not be used, rewritten, or +* adapted as the basis of a commercial software or hardware product +* without first obtaining appropriate licenses from Author. * Other than these cases, no part of this software may be used or * distributed without written permission of the author. * -* Neither the author nor UMIACS make any representations about the -* suitability of this software for any purpose. It is provided +* Neither the author nor UMIACS make any representations about the +* suitability of this software for any purpose. It is provided * "as is" without express or implied warranty. * * Ahmed Elgammal -* +* * University of Maryland at College Park * UMIACS -* A.V. Williams Bldg. +* A.V. Williams Bldg. * CollegePark, MD 20742 * E-mail: elgammal@umiacs.umd.edu * @@ -55,7 +55,7 @@ along with BGSLibrary. If not, see . #ifdef _DEBUG #undef THIS_FILE -static char THIS_FILE[]=__FILE__; +static char THIS_FILE[] = __FILE__; //#define new DEBUG_NEW #endif @@ -80,11 +80,11 @@ NPBGmodel::~NPBGmodel() } NPBGmodel::NPBGmodel(unsigned int Rows, - unsigned int Cols, - unsigned int ColorChannels, - unsigned int Length, - unsigned int pTimeWindowSize, - unsigned int bg_suppression_time) + unsigned int Cols, + unsigned int ColorChannels, + unsigned int Length, + unsigned int pTimeWindowSize, + unsigned int bg_suppression_time) { std::cout << "NPBGmodel()" << std::endl; @@ -98,14 +98,14 @@ NPBGmodel::NPBGmodel(unsigned int Rows, TimeWindowSize = pTimeWindowSize; - Sequence = new unsigned char[imagesize*Length]; + Sequence = new unsigned char[imagesize*Length]; Top = 0; - memset(Sequence,0,imagesize*Length); + memset(Sequence, 0, imagesize*Length); PixelQTop = new unsigned char[rows*cols]; // temporalBuffer - TemporalBufferLength = (TimeWindowSize/Length > 2 ? TimeWindowSize/Length:2); + TemporalBufferLength = (TimeWindowSize / Length > 2 ? TimeWindowSize / Length : 2); TemporalBuffer = new unsigned char[imagesize*TemporalBufferLength]; TemporalMask = new unsigned char[rows*cols*TemporalBufferLength]; @@ -116,12 +116,12 @@ NPBGmodel::NPBGmodel(unsigned int Rows, ResetMaskTh = bg_suppression_time; } -void NPBGmodel::AddFrame(unsigned char *ImageBuffer) +void NPBGmodel::AddFrame(unsigned char *ImageBuffer) { - memcpy(Sequence+Top*imagesize,ImageBuffer,imagesize); - Top = (Top + 1) % SampleSize; + memcpy(Sequence + Top*imagesize, ImageBuffer, imagesize); + Top = (Top + 1) % SampleSize; - memset(PixelQTop, (unsigned char) Top, rows*cols); + memset(PixelQTop, (unsigned char)Top, rows*cols); - memcpy(TemporalBuffer,ImageBuffer,imagesize); + memcpy(TemporalBuffer, ImageBuffer, imagesize); } diff --git a/package_bgs/ae/NPBGmodel.h b/package_bgs/KDE/NPBGmodel.h similarity index 84% rename from package_bgs/ae/NPBGmodel.h rename to package_bgs/KDE/NPBGmodel.h index 9e28510..ba4a927 100644 --- a/package_bgs/ae/NPBGmodel.h +++ b/package_bgs/KDE/NPBGmodel.h @@ -26,21 +26,21 @@ along with BGSLibrary. If not, see . * copyright notice must be included. For any other uses of this software, * in original or modified form, including but not limited to distribution * in whole or in part, specific prior permission must be obtained from -* Author or UMIACS. These programs shall not be used, rewritten, or -* adapted as the basis of a commercial software or hardware product -* without first obtaining appropriate licenses from Author. +* Author or UMIACS. These programs shall not be used, rewritten, or +* adapted as the basis of a commercial software or hardware product +* without first obtaining appropriate licenses from Author. * Other than these cases, no part of this software may be used or * distributed without written permission of the author. * -* Neither the author nor UMIACS make any representations about the -* suitability of this software for any purpose. It is provided +* Neither the author nor UMIACS make any representations about the +* suitability of this software for any purpose. It is provided * "as is" without express or implied warranty. * * Ahmed Elgammal -* +* * University of Maryland at College Park * UMIACS -* A.V. Williams Bldg. +* A.V. Williams Bldg. * CollegePark, MD 20742 * E-mail: elgammal@umiacs.umd.edu * @@ -49,24 +49,18 @@ along with BGSLibrary. If not, see . // NPBGmodel.h: interface for the NPBGmodel class. // ////////////////////////////////////////////////////////////////////// - -#if !defined(AFX_NPBGMODEL_H__CCAF05D4_D06E_44C2_95D8_979E2249953A__INCLUDED_) -#define AFX_NPBGMODEL_H__CCAF05D4_D06E_44C2_95D8_979E2249953A__INCLUDED_ - -#if _MSC_VER > 1000 #pragma once -#endif // _MSC_VER > 1000 #include -class NPBGmodel +class NPBGmodel { private: unsigned char *Sequence; unsigned int SampleSize; unsigned int TimeWindowSize; - unsigned int rows,cols,color_channels; + unsigned int rows, cols, color_channels; unsigned int imagesize; unsigned int Top; @@ -74,7 +68,7 @@ class NPBGmodel //unsigned int *PixelUpdateCounter; - unsigned char *SDbinsImage; + unsigned char *SDbinsImage; unsigned char *TemporalBuffer; unsigned char TemporalBufferLength; @@ -107,5 +101,3 @@ class NPBGmodel friend class NPBGSubtractor; }; - -#endif // !defined(AFX_NPBGMODEL_H__CCAF05D4_D06E_44C2_95D8_979E2249953A__INCLUDED_) diff --git a/package_bgs/KNN.cpp b/package_bgs/KNN.cpp new file mode 100644 index 0000000..d2e1412 --- /dev/null +++ b/package_bgs/KNN.cpp @@ -0,0 +1,111 @@ +/* + This file is part of BGSLibrary. + + BGSLibrary is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BGSLibrary is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BGSLibrary. If not, see . + */ +#include "KNN.h" + +#if CV_MAJOR_VERSION == 3 + +using namespace bgslibrary::algorithms; + +KNN::KNN() : + history(500), nSamples(7), dist2Threshold(20.0f * 20.0f), knnSamples(0), + doShadowDetection(true), shadowValue(127), shadowThreshold(0.5f) +{ + std::cout << "KNN()" << std::endl; + setup("./config/KNN.xml"); +} + +KNN::~KNN() +{ + std::cout << "~KNN()" << std::endl; +} + +void KNN::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + //------------------------------------------------------------------ + // BackgroundSubtractorKNN + // http://docs.opencv.org/trunk/modules/video/doc/motion_analysis_and_object_tracking.html#backgroundsubtractorknn + // + // The class implements the K nearest neigbours algorithm from: + // "Efficient Adaptive Density Estimation per Image Pixel for the Task of Background Subtraction" + // Z.Zivkovic, F. van der Heijden + // Pattern Recognition Letters, vol. 27, no. 7, pages 773-780, 2006 + // http: //www.zoranz.net/Publications/zivkovicPRL2006.pdf + // + // Fast for small foreground object. Results on the benchmark data is at http://www.changedetection.net. + //------------------------------------------------------------------ + + int prevNSamples = nSamples; + if (firstTime) + knn = cv::createBackgroundSubtractorKNN(history, dist2Threshold, doShadowDetection); + + knn->setNSamples(nSamples); + knn->setkNNSamples(knnSamples); + knn->setShadowValue(shadowValue); + knn->setShadowThreshold(shadowThreshold); + + knn->apply(img_input, img_foreground, prevNSamples != nSamples ? 0.f : 1.f); + knn->getBackgroundImage(img_background); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + { + cv::imshow("KNN FG", img_foreground); + cv::imshow("KNN BG", img_background); + } +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); + + firstTime = false; +} + +void KNN::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteInt(fs, "history", history); + cvWriteInt(fs, "nSamples", nSamples); + cvWriteReal(fs, "dist2Threshold", dist2Threshold); + cvWriteInt(fs, "knnSamples", knnSamples); + cvWriteInt(fs, "doShadowDetection", doShadowDetection); + cvWriteInt(fs, "shadowValue", shadowValue); + cvWriteReal(fs, "shadowThreshold", shadowThreshold); + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void KNN::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + history = cvReadIntByName(fs, nullptr, "history", 500); + nSamples = cvReadIntByName(fs, nullptr, "nSamples", 7); + dist2Threshold = cvReadRealByName(fs, nullptr, "dist2Threshold", 20.0f * 20.0f); + knnSamples = cvReadIntByName(fs, nullptr, "knnSamples", 0); + doShadowDetection = cvReadIntByName(fs, nullptr, "doShadowDetection", 1); + shadowValue = cvReadIntByName(fs, nullptr, "shadowValue", 127); + shadowThreshold = cvReadRealByName(fs, nullptr, "shadowThreshold", 0.5f); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + + cvReleaseFileStorage(&fs); +} + +#endif diff --git a/package_bgs/KNN.h b/package_bgs/KNN.h new file mode 100644 index 0000000..87f20b2 --- /dev/null +++ b/package_bgs/KNN.h @@ -0,0 +1,57 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "opencv2/core/version.hpp" +#if CV_MAJOR_VERSION == 3 + +#include +#include +#include + +#include "IBGS.h" + +namespace bgslibrary +{ + namespace algorithms + { + class KNN : public IBGS + { + private: + cv::Ptr knn; + int history; + int nSamples; + float dist2Threshold; + int knnSamples; + bool doShadowDetection; + int shadowValue; + float shadowThreshold; + + public: + KNN(); + ~KNN(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} + +#endif diff --git a/package_bgs/lb/LBAdaptiveSOM.cpp b/package_bgs/LBAdaptiveSOM.cpp similarity index 61% rename from package_bgs/lb/LBAdaptiveSOM.cpp rename to package_bgs/LBAdaptiveSOM.cpp index ce06433..188e11d 100644 --- a/package_bgs/lb/LBAdaptiveSOM.cpp +++ b/package_bgs/LBAdaptiveSOM.cpp @@ -16,10 +16,13 @@ along with BGSLibrary. If not, see . */ #include "LBAdaptiveSOM.h" -LBAdaptiveSOM::LBAdaptiveSOM() : firstTime(true), showOutput(true), +using namespace bgslibrary::algorithms; + +LBAdaptiveSOM::LBAdaptiveSOM() : sensitivity(75), trainingSensitivity(245), learningRate(62), trainingLearningRate(255), trainingSteps(55) { std::cout << "LBAdaptiveSOM()" << std::endl; + setup("./config/LBAdaptiveSOM.xml"); } LBAdaptiveSOM::~LBAdaptiveSOM() @@ -30,64 +33,55 @@ LBAdaptiveSOM::~LBAdaptiveSOM() void LBAdaptiveSOM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; + init(img_input, img_output, img_bgmodel); - loadConfig(); - IplImage *frame = new IplImage(img_input); - - if(firstTime) - { - saveConfig(); + if (firstTime) + { int w = cvGetSize(frame).width; int h = cvGetSize(frame).height; - m_pBGModel = new BGModelSom(w,h); + m_pBGModel = new BGModelSom(w, h); m_pBGModel->InitModel(frame); } - - m_pBGModel->setBGModelParameter(0,sensitivity); - m_pBGModel->setBGModelParameter(1,trainingSensitivity); - m_pBGModel->setBGModelParameter(2,learningRate); - m_pBGModel->setBGModelParameter(3,trainingLearningRate); - m_pBGModel->setBGModelParameter(5,trainingSteps); + + m_pBGModel->setBGModelParameter(0, sensitivity); + m_pBGModel->setBGModelParameter(1, trainingSensitivity); + m_pBGModel->setBGModelParameter(2, learningRate); + m_pBGModel->setBGModelParameter(3, trainingLearningRate); + m_pBGModel->setBGModelParameter(5, trainingSteps); m_pBGModel->UpdateModel(frame); - img_foreground = cv::Mat(m_pBGModel->GetFG()); - img_background = cv::Mat(m_pBGModel->GetBG()); - - if(showOutput) + img_foreground = cv::cvarrToMat(m_pBGModel->GetFG()); + img_background = cv::cvarrToMat(m_pBGModel->GetBG()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) { cv::imshow("SOM Mask", img_foreground); cv::imshow("SOM Model", img_background); } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); - + delete frame; - + firstTime = false; } -//void LBAdaptiveSOM::finish(void) -//{ -// delete m_pBGModel; -//} - void LBAdaptiveSOM::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LBAdaptiveSOM.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "sensitivity", sensitivity); cvWriteInt(fs, "trainingSensitivity", trainingSensitivity); cvWriteInt(fs, "learningRate", learningRate); cvWriteInt(fs, "trainingLearningRate", trainingLearningRate); cvWriteInt(fs, "trainingSteps", trainingSteps); - cvWriteInt(fs, "showOutput", showOutput); cvReleaseFileStorage(&fs); @@ -95,15 +89,14 @@ void LBAdaptiveSOM::saveConfig() void LBAdaptiveSOM::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LBAdaptiveSOM.xml", 0, CV_STORAGE_READ); - - sensitivity = cvReadIntByName(fs, 0, "sensitivity", 75); - trainingSensitivity = cvReadIntByName(fs, 0, "trainingSensitivity", 245); - learningRate = cvReadIntByName(fs, 0, "learningRate", 62); - trainingLearningRate = cvReadIntByName(fs, 0, "trainingLearningRate", 255); - trainingSteps = cvReadIntByName(fs, 0, "trainingSteps", 55); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + sensitivity = cvReadIntByName(fs, nullptr, "sensitivity", 75); + trainingSensitivity = cvReadIntByName(fs, nullptr, "trainingSensitivity", 245); + learningRate = cvReadIntByName(fs, nullptr, "learningRate", 62); + trainingLearningRate = cvReadIntByName(fs, nullptr, "trainingLearningRate", 255); + trainingSteps = cvReadIntByName(fs, nullptr, "trainingSteps", 55); showOutput = cvReadIntByName(fs, 0, "showOutput", true); cvReleaseFileStorage(&fs); -} \ No newline at end of file +} diff --git a/package_bgs/lb/LBAdaptiveSOM.h b/package_bgs/LBAdaptiveSOM.h similarity index 55% rename from package_bgs/lb/LBAdaptiveSOM.h rename to package_bgs/LBAdaptiveSOM.h index beaf77b..7671ae1 100644 --- a/package_bgs/lb/LBAdaptiveSOM.h +++ b/package_bgs/LBAdaptiveSOM.h @@ -16,41 +16,35 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "BGModelSom.h" - -#include "../IBGS.h" +#include "lb/BGModelSom.h" +#include "IBGS.h" using namespace lb_library; using namespace lb_library::AdaptiveSOM; -class LBAdaptiveSOM : public IBGS +namespace bgslibrary { -private: - bool firstTime; - bool showOutput; - - BGModel* m_pBGModel; - int sensitivity; - int trainingSensitivity; - int learningRate; - int trainingLearningRate; - int trainingSteps; - - cv::Mat img_foreground; - cv::Mat img_background; - -public: - LBAdaptiveSOM(); - ~LBAdaptiveSOM(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - //void finish(void); - -private: - void saveConfig(); - void loadConfig(); -}; \ No newline at end of file + namespace algorithms + { + class LBAdaptiveSOM : public IBGS + { + private: + BGModel* m_pBGModel; + int sensitivity; + int trainingSensitivity; + int learningRate; + int trainingLearningRate; + int trainingSteps; + + public: + LBAdaptiveSOM(); + ~LBAdaptiveSOM(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/lb/LBFuzzyAdaptiveSOM.cpp b/package_bgs/LBFuzzyAdaptiveSOM.cpp similarity index 59% rename from package_bgs/lb/LBFuzzyAdaptiveSOM.cpp rename to package_bgs/LBFuzzyAdaptiveSOM.cpp index 4ef8560..73df1d7 100644 --- a/package_bgs/lb/LBFuzzyAdaptiveSOM.cpp +++ b/package_bgs/LBFuzzyAdaptiveSOM.cpp @@ -16,10 +16,13 @@ along with BGSLibrary. If not, see . */ #include "LBFuzzyAdaptiveSOM.h" -LBFuzzyAdaptiveSOM::LBFuzzyAdaptiveSOM() : firstTime(true), showOutput(true), +using namespace bgslibrary::algorithms; + +LBFuzzyAdaptiveSOM::LBFuzzyAdaptiveSOM() : sensitivity(90), trainingSensitivity(240), learningRate(38), trainingLearningRate(255), trainingSteps(81) { std::cout << "LBFuzzyAdaptiveSOM()" << std::endl; + setup("./config/LBFuzzyAdaptiveSOM.xml"); } LBFuzzyAdaptiveSOM::~LBFuzzyAdaptiveSOM() @@ -30,64 +33,55 @@ LBFuzzyAdaptiveSOM::~LBFuzzyAdaptiveSOM() void LBFuzzyAdaptiveSOM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; + init(img_input, img_output, img_bgmodel); - loadConfig(); - IplImage *frame = new IplImage(img_input); - - if(firstTime) - { - saveConfig(); + if (firstTime) + { int w = cvGetSize(frame).width; int h = cvGetSize(frame).height; - m_pBGModel = new BGModelFuzzySom(w,h); + m_pBGModel = new BGModelFuzzySom(w, h); m_pBGModel->InitModel(frame); } - - m_pBGModel->setBGModelParameter(0,sensitivity); - m_pBGModel->setBGModelParameter(1,trainingSensitivity); - m_pBGModel->setBGModelParameter(2,learningRate); - m_pBGModel->setBGModelParameter(3,trainingLearningRate); - m_pBGModel->setBGModelParameter(5,trainingSteps); + + m_pBGModel->setBGModelParameter(0, sensitivity); + m_pBGModel->setBGModelParameter(1, trainingSensitivity); + m_pBGModel->setBGModelParameter(2, learningRate); + m_pBGModel->setBGModelParameter(3, trainingLearningRate); + m_pBGModel->setBGModelParameter(5, trainingSteps); m_pBGModel->UpdateModel(frame); - img_foreground = cv::Mat(m_pBGModel->GetFG()); - img_background = cv::Mat(m_pBGModel->GetBG()); - - if(showOutput) + img_foreground = cv::cvarrToMat(m_pBGModel->GetFG()); + img_background = cv::cvarrToMat(m_pBGModel->GetBG()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) { cv::imshow("FSOM Mask", img_foreground); cv::imshow("FSOM Model", img_background); } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); - + delete frame; - + firstTime = false; } -//void LBFuzzyAdaptiveSOM::finish(void) -//{ -// //delete m_pBGModel; -//} - void LBFuzzyAdaptiveSOM::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LBFuzzyAdaptiveSOM.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "sensitivity", sensitivity); cvWriteInt(fs, "trainingSensitivity", trainingSensitivity); cvWriteInt(fs, "learningRate", learningRate); cvWriteInt(fs, "trainingLearningRate", trainingLearningRate); cvWriteInt(fs, "trainingSteps", trainingSteps); - cvWriteInt(fs, "showOutput", showOutput); cvReleaseFileStorage(&fs); @@ -95,15 +89,14 @@ void LBFuzzyAdaptiveSOM::saveConfig() void LBFuzzyAdaptiveSOM::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LBFuzzyAdaptiveSOM.xml", 0, CV_STORAGE_READ); - - sensitivity = cvReadIntByName(fs, 0, "sensitivity", 90); - trainingSensitivity = cvReadIntByName(fs, 0, "trainingSensitivity", 240); - learningRate = cvReadIntByName(fs, 0, "learningRate", 38); - trainingLearningRate = cvReadIntByName(fs, 0, "trainingLearningRate", 255); - trainingSteps = cvReadIntByName(fs, 0, "trainingSteps", 81); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + sensitivity = cvReadIntByName(fs, nullptr, "sensitivity", 90); + trainingSensitivity = cvReadIntByName(fs, nullptr, "trainingSensitivity", 240); + learningRate = cvReadIntByName(fs, nullptr, "learningRate", 38); + trainingLearningRate = cvReadIntByName(fs, nullptr, "trainingLearningRate", 255); + trainingSteps = cvReadIntByName(fs, nullptr, "trainingSteps", 81); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); -} \ No newline at end of file +} diff --git a/package_bgs/lb/LBFuzzyAdaptiveSOM.h b/package_bgs/LBFuzzyAdaptiveSOM.h similarity index 55% rename from package_bgs/lb/LBFuzzyAdaptiveSOM.h rename to package_bgs/LBFuzzyAdaptiveSOM.h index 9c11d75..6a4a85c 100644 --- a/package_bgs/lb/LBFuzzyAdaptiveSOM.h +++ b/package_bgs/LBFuzzyAdaptiveSOM.h @@ -16,41 +16,35 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "BGModelFuzzySom.h" - -#include "../IBGS.h" +#include "IBGS.h" +#include "lb/BGModelFuzzySom.h" using namespace lb_library; using namespace lb_library::FuzzyAdaptiveSOM; -class LBFuzzyAdaptiveSOM : public IBGS +namespace bgslibrary { -private: - bool firstTime; - bool showOutput; - - BGModel* m_pBGModel; - int sensitivity; - int trainingSensitivity; - int learningRate; - int trainingLearningRate; - int trainingSteps; - - cv::Mat img_foreground; - cv::Mat img_background; - -public: - LBFuzzyAdaptiveSOM(); - ~LBFuzzyAdaptiveSOM(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - //void finish(void); - -private: - void saveConfig(); - void loadConfig(); -}; \ No newline at end of file + namespace algorithms + { + class LBFuzzyAdaptiveSOM : public IBGS + { + private: + BGModel* m_pBGModel; + int sensitivity; + int trainingSensitivity; + int learningRate; + int trainingLearningRate; + int trainingSteps; + + public: + LBFuzzyAdaptiveSOM(); + ~LBFuzzyAdaptiveSOM(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/lb/LBFuzzyGaussian.cpp b/package_bgs/LBFuzzyGaussian.cpp similarity index 59% rename from package_bgs/lb/LBFuzzyGaussian.cpp rename to package_bgs/LBFuzzyGaussian.cpp index dc257bf..b161026 100644 --- a/package_bgs/lb/LBFuzzyGaussian.cpp +++ b/package_bgs/LBFuzzyGaussian.cpp @@ -16,9 +16,13 @@ along with BGSLibrary. If not, see . */ #include "LBFuzzyGaussian.h" -LBFuzzyGaussian::LBFuzzyGaussian() : firstTime(true), showOutput(true), sensitivity(72), bgThreshold(162), learningRate(49), noiseVariance(195) +using namespace bgslibrary::algorithms; + +LBFuzzyGaussian::LBFuzzyGaussian() : + sensitivity(72), bgThreshold(162), learningRate(49), noiseVariance(195) { std::cout << "LBFuzzyGaussian()" << std::endl; + setup("./config/LBFuzzyGaussian.xml"); } LBFuzzyGaussian::~LBFuzzyGaussian() @@ -29,62 +33,53 @@ LBFuzzyGaussian::~LBFuzzyGaussian() void LBFuzzyGaussian::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; + init(img_input, img_output, img_bgmodel); - loadConfig(); - IplImage *frame = new IplImage(img_input); - - if(firstTime) - { - saveConfig(); + if (firstTime) + { int w = cvGetSize(frame).width; int h = cvGetSize(frame).height; - m_pBGModel = new BGModelFuzzyGauss(w,h); + m_pBGModel = new BGModelFuzzyGauss(w, h); m_pBGModel->InitModel(frame); } - - m_pBGModel->setBGModelParameter(0,sensitivity); - m_pBGModel->setBGModelParameter(1,bgThreshold); - m_pBGModel->setBGModelParameter(2,learningRate); - m_pBGModel->setBGModelParameter(3,noiseVariance); + + m_pBGModel->setBGModelParameter(0, sensitivity); + m_pBGModel->setBGModelParameter(1, bgThreshold); + m_pBGModel->setBGModelParameter(2, learningRate); + m_pBGModel->setBGModelParameter(3, noiseVariance); m_pBGModel->UpdateModel(frame); - img_foreground = cv::Mat(m_pBGModel->GetFG()); - img_background = cv::Mat(m_pBGModel->GetBG()); - - if(showOutput) + img_foreground = cv::cvarrToMat(m_pBGModel->GetFG()); + img_background = cv::cvarrToMat(m_pBGModel->GetBG()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) { cv::imshow("FG Mask", img_foreground); cv::imshow("FG Model", img_background); } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); - + delete frame; - + firstTime = false; } -//void LBFuzzyGaussian::finish(void) -//{ -// delete m_pBGModel; -//} - void LBFuzzyGaussian::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LBFuzzyGaussian.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "sensitivity", sensitivity); cvWriteInt(fs, "bgThreshold", bgThreshold); cvWriteInt(fs, "learningRate", learningRate); cvWriteInt(fs, "noiseVariance", noiseVariance); - cvWriteInt(fs, "showOutput", showOutput); cvReleaseFileStorage(&fs); @@ -92,14 +87,13 @@ void LBFuzzyGaussian::saveConfig() void LBFuzzyGaussian::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LBFuzzyGaussian.xml", 0, CV_STORAGE_READ); - - sensitivity = cvReadIntByName(fs, 0, "sensitivity", 72); - bgThreshold = cvReadIntByName(fs, 0, "bgThreshold", 162); - learningRate = cvReadIntByName(fs, 0, "learningRate", 49); - noiseVariance = cvReadIntByName(fs, 0, "noiseVariance", 195); - - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + sensitivity = cvReadIntByName(fs, nullptr, "sensitivity", 72); + bgThreshold = cvReadIntByName(fs, nullptr, "bgThreshold", 162); + learningRate = cvReadIntByName(fs, nullptr, "learningRate", 49); + noiseVariance = cvReadIntByName(fs, nullptr, "noiseVariance", 195); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/lb/LBFuzzyGaussian.h b/package_bgs/LBFuzzyGaussian.h similarity index 56% rename from package_bgs/lb/LBFuzzyGaussian.h rename to package_bgs/LBFuzzyGaussian.h index f76156e..53c2667 100644 --- a/package_bgs/lb/LBFuzzyGaussian.h +++ b/package_bgs/LBFuzzyGaussian.h @@ -16,40 +16,34 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "BGModelFuzzyGauss.h" - -#include "../IBGS.h" +#include "IBGS.h" +#include "lb/BGModelFuzzyGauss.h" using namespace lb_library; using namespace lb_library::FuzzyGaussian; -class LBFuzzyGaussian : public IBGS +namespace bgslibrary { -private: - bool firstTime; - bool showOutput; - - BGModel* m_pBGModel; - int sensitivity; - int bgThreshold; - int learningRate; - int noiseVariance; - - cv::Mat img_foreground; - cv::Mat img_background; - -public: - LBFuzzyGaussian(); - ~LBFuzzyGaussian(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - //void finish(void); - -private: - void saveConfig(); - void loadConfig(); -}; \ No newline at end of file + namespace algorithms + { + class LBFuzzyGaussian : public IBGS + { + private: + BGModel* m_pBGModel; + int sensitivity; + int bgThreshold; + int learningRate; + int noiseVariance; + + public: + LBFuzzyGaussian(); + ~LBFuzzyGaussian(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/lb/LBMixtureOfGaussians.cpp b/package_bgs/LBMixtureOfGaussians.cpp similarity index 59% rename from package_bgs/lb/LBMixtureOfGaussians.cpp rename to package_bgs/LBMixtureOfGaussians.cpp index 8d235c5..6cd9c91 100644 --- a/package_bgs/lb/LBMixtureOfGaussians.cpp +++ b/package_bgs/LBMixtureOfGaussians.cpp @@ -16,9 +16,13 @@ along with BGSLibrary. If not, see . */ #include "LBMixtureOfGaussians.h" -LBMixtureOfGaussians::LBMixtureOfGaussians() : firstTime(true), showOutput(true), sensitivity(81), bgThreshold(83), learningRate(59), noiseVariance(206) +using namespace bgslibrary::algorithms; + +LBMixtureOfGaussians::LBMixtureOfGaussians() : + sensitivity(81), bgThreshold(83), learningRate(59), noiseVariance(206) { std::cout << "LBMixtureOfGaussians()" << std::endl; + setup("./config/LBMixtureOfGaussians.xml"); } LBMixtureOfGaussians::~LBMixtureOfGaussians() @@ -29,62 +33,53 @@ LBMixtureOfGaussians::~LBMixtureOfGaussians() void LBMixtureOfGaussians::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; + init(img_input, img_output, img_bgmodel); - loadConfig(); - IplImage *frame = new IplImage(img_input); - - if(firstTime) - { - saveConfig(); + if (firstTime) + { int w = cvGetSize(frame).width; int h = cvGetSize(frame).height; - m_pBGModel = new BGModelMog(w,h); + m_pBGModel = new BGModelMog(w, h); m_pBGModel->InitModel(frame); } - - m_pBGModel->setBGModelParameter(0,sensitivity); - m_pBGModel->setBGModelParameter(1,bgThreshold); - m_pBGModel->setBGModelParameter(2,learningRate); - m_pBGModel->setBGModelParameter(3,noiseVariance); + + m_pBGModel->setBGModelParameter(0, sensitivity); + m_pBGModel->setBGModelParameter(1, bgThreshold); + m_pBGModel->setBGModelParameter(2, learningRate); + m_pBGModel->setBGModelParameter(3, noiseVariance); m_pBGModel->UpdateModel(frame); - img_foreground = cv::Mat(m_pBGModel->GetFG()); - img_background = cv::Mat(m_pBGModel->GetBG()); - - if(showOutput) + img_foreground = cv::cvarrToMat(m_pBGModel->GetFG()); + img_background = cv::cvarrToMat(m_pBGModel->GetBG()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) { cv::imshow("MOG Mask", img_foreground); cv::imshow("MOG Model", img_background); } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); - + delete frame; - + firstTime = false; } -//void LBMixtureOfGaussians::finish(void) -//{ -// delete m_pBGModel; -//} - void LBMixtureOfGaussians::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LBMixtureOfGaussians.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "sensitivity", sensitivity); cvWriteInt(fs, "bgThreshold", bgThreshold); cvWriteInt(fs, "learningRate", learningRate); cvWriteInt(fs, "noiseVariance", noiseVariance); - cvWriteInt(fs, "showOutput", showOutput); cvReleaseFileStorage(&fs); @@ -92,14 +87,13 @@ void LBMixtureOfGaussians::saveConfig() void LBMixtureOfGaussians::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LBMixtureOfGaussians.xml", 0, CV_STORAGE_READ); - - sensitivity = cvReadIntByName(fs, 0, "sensitivity", 81); - bgThreshold = cvReadIntByName(fs, 0, "bgThreshold", 83); - learningRate = cvReadIntByName(fs, 0, "learningRate", 59); - noiseVariance = cvReadIntByName(fs, 0, "noiseVariance", 206); - - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + sensitivity = cvReadIntByName(fs, nullptr, "sensitivity", 81); + bgThreshold = cvReadIntByName(fs, nullptr, "bgThreshold", 83); + learningRate = cvReadIntByName(fs, nullptr, "learningRate", 59); + noiseVariance = cvReadIntByName(fs, nullptr, "noiseVariance", 206); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); -} \ No newline at end of file +} diff --git a/package_bgs/lb/LBMixtureOfGaussians.h b/package_bgs/LBMixtureOfGaussians.h similarity index 56% rename from package_bgs/lb/LBMixtureOfGaussians.h rename to package_bgs/LBMixtureOfGaussians.h index 3b1a96d..8d4cb56 100644 --- a/package_bgs/lb/LBMixtureOfGaussians.h +++ b/package_bgs/LBMixtureOfGaussians.h @@ -16,40 +16,35 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "BGModelMog.h" - -#include "../IBGS.h" +#include "IBGS.h" +#include "lb/BGModelMog.h" using namespace lb_library; using namespace lb_library::MixtureOfGaussians; -class LBMixtureOfGaussians : public IBGS +namespace bgslibrary { -private: - bool firstTime; - bool showOutput; - - BGModel* m_pBGModel; - int sensitivity; - int bgThreshold; - int learningRate; - int noiseVariance; - - cv::Mat img_foreground; - cv::Mat img_background; - -public: - LBMixtureOfGaussians(); - ~LBMixtureOfGaussians(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - //void finish(void); - -private: - void saveConfig(); - void loadConfig(); -}; \ No newline at end of file + namespace algorithms + { + class LBMixtureOfGaussians : public IBGS + { + private: + BGModel* m_pBGModel; + int sensitivity; + int bgThreshold; + int learningRate; + int noiseVariance; + + public: + LBMixtureOfGaussians(); + ~LBMixtureOfGaussians(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} + diff --git a/package_bgs/ck/LbpMrf.cpp b/package_bgs/LBP_MRF.cpp similarity index 57% rename from package_bgs/ck/LbpMrf.cpp rename to package_bgs/LBP_MRF.cpp index f3d068c..b9e6bbe 100644 --- a/package_bgs/ck/LbpMrf.cpp +++ b/package_bgs/LBP_MRF.cpp @@ -18,35 +18,29 @@ Csaba, Kertész: Texture-Based Foreground Detection, International Journal of Si Image Processing and Pattern Recognition (IJSIP), Vol. 4, No. 4, 2011. */ -#include "LbpMrf.h" +#include "LBP_MRF.h" -#include "MotionDetection.hpp" +using namespace bgslibrary::algorithms; -LbpMrf::LbpMrf() : firstTime(true), Detector(NULL), showOutput(true) +LBP_MRF::LBP_MRF() : + Detector(nullptr) { - std::cout << "LbpMrf()" << std::endl; + std::cout << "LBP_MRF()" << std::endl; + setup("./config/LBP_MRF.xml"); Detector = new MotionDetection(); Detector->SetMode(MotionDetection::md_LBPHistograms); } -LbpMrf::~LbpMrf() +LBP_MRF::~LBP_MRF() { - std::cout << "~LbpMrf()" << std::endl; + std::cout << "~LBP_MRF()" << std::endl; delete Detector; - Detector = NULL; + Detector = nullptr; } -void LbpMrf::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void LBP_MRF::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - { - saveConfig(); - } + init(img_input, img_output, img_bgmodel); IplImage TempImage(img_input); MEImage InputImage(img_input.cols, img_input.rows, img_input.channels()); @@ -56,32 +50,35 @@ void LbpMrf::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img Detector->DetectMotions(InputImage); Detector->GetMotionsMask(OutputImage); - img_output = (IplImage*)OutputImage.GetIplImage(); - bitwise_not(img_output, img_bgmodel); + img_foreground = cv::cvarrToMat((IplImage*)OutputImage.GetIplImage()); + //bitwise_not(img_foreground, img_background); + img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("LBP-MRF FG", img_foreground); +#endif - if(showOutput) - { - cv::imshow("LBP-MRF FG", img_output); - cv::imshow("LBP-MRF BG", img_bgmodel); - } + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); firstTime = false; } -void LbpMrf::saveConfig() +void LBP_MRF::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LbpMrf.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "showOutput", showOutput); cvReleaseFileStorage(&fs); } -void LbpMrf::loadConfig() +void LBP_MRF::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LbpMrf.xml", 0, CV_STORAGE_READ); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/WeightedMovingMeanBGS.h b/package_bgs/LBP_MRF.h similarity index 58% rename from package_bgs/WeightedMovingMeanBGS.h rename to package_bgs/LBP_MRF.h index 6f72306..a6e5c05 100644 --- a/package_bgs/WeightedMovingMeanBGS.h +++ b/package_bgs/LBP_MRF.h @@ -16,32 +16,28 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - #include "IBGS.h" +#include "LBP_MRF/MotionDetection.hpp" -class WeightedMovingMeanBGS : public IBGS +namespace bgslibrary { -private: - bool firstTime; - cv::Mat img_input_prev_1; - cv::Mat img_input_prev_2; - bool enableWeight; - bool enableThreshold; - int threshold; - bool showOutput; - bool showBackground; - -public: - WeightedMovingMeanBGS(); - ~WeightedMovingMeanBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class LBP_MRF : public IBGS + { + private: + MotionDetection* Detector; + cv::Mat img_segmentation; + + public: + LBP_MRF(); + ~LBP_MRF(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/LBP_MRF/MEDefs.cpp b/package_bgs/LBP_MRF/MEDefs.cpp new file mode 100644 index 0000000..95b8033 --- /dev/null +++ b/package_bgs/LBP_MRF/MEDefs.cpp @@ -0,0 +1,57 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* + * This file is part of the AiBO+ project + * + * Copyright (C) 2005-2013 Csaba Kertész (csaba.kertesz@gmail.com) + * + * AiBO+ is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * AiBO+ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. + * + */ + +#include "MEDefs.hpp" + +#include + +float MERound(float number) +{ + double FracPart = 0.0; + double IntPart = 0.0; + float Ret = 0.0; + + FracPart = modf((double)number, &IntPart); + if (number >= 0) + { + Ret = (float)(FracPart >= 0.5 ? IntPart + 1 : IntPart); + } + else { + Ret = (float)(FracPart <= -0.5 ? IntPart - 1 : IntPart); + } + return Ret; +} diff --git a/package_bgs/ck/MEDefs.hpp b/package_bgs/LBP_MRF/MEDefs.hpp similarity index 73% rename from package_bgs/ck/MEDefs.hpp rename to package_bgs/LBP_MRF/MEDefs.hpp index d5bc246..a886ee9 100644 --- a/package_bgs/ck/MEDefs.hpp +++ b/package_bgs/LBP_MRF/MEDefs.hpp @@ -1,4 +1,20 @@ /* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* * This file is part of the AiBO+ project * * Copyright (C) 2005-2013 Csaba Kertész (csaba.kertesz@gmail.com) @@ -18,16 +34,9 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * */ +#pragma once -#ifndef MEDefs_hpp -#define MEDefs_hpp - -/** - * @addtogroup mindeye - * @{ - */ - -/// Pi value + /// Pi value #ifndef ME_PI_VALUE #define ME_PI_VALUE 3.14159265 #endif @@ -79,5 +88,3 @@ const T& MEBound(const T& min, const T& val, const T& max) float MERound(float number); /** @} */ - -#endif diff --git a/package_bgs/ck/MEHistogram.cpp b/package_bgs/LBP_MRF/MEHistogram.cpp similarity index 54% rename from package_bgs/ck/MEHistogram.cpp rename to package_bgs/LBP_MRF/MEHistogram.cpp index 09a9672..17c77fd 100644 --- a/package_bgs/ck/MEHistogram.cpp +++ b/package_bgs/LBP_MRF/MEHistogram.cpp @@ -1,4 +1,20 @@ /* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* * This file is part of the AiBO+ project * * Copyright (C) 2005-2013 Csaba Kertész (csaba.kertesz@gmail.com) @@ -45,7 +61,7 @@ MEHistogram::~MEHistogram() void MEHistogram::Clear() { - memset(&HistogramData, 0, 256*sizeof(int)); + memset(&HistogramData, 0, 256 * sizeof(int)); } @@ -77,11 +93,11 @@ void MEHistogram::Calculate(MEImage& image, int channel, HistogramType mode) unsigned char *ImageData = image.GetImageData(); int rowStart = 0; - for (int i = image.GetHeight()-1; i >= 0; i--) + for (int i = image.GetHeight() - 1; i >= 0; i--) { - for (int i1 = (image.GetWidth()-1)*image.GetLayers()+Channel-1; i1 >= Channel-1; i1 -= image.GetLayers()) + for (int i1 = (image.GetWidth() - 1)*image.GetLayers() + Channel - 1; i1 >= Channel - 1; i1 -= image.GetLayers()) { - HistogramData[ImageData[rowStart+i1]]++; + HistogramData[ImageData[rowStart + i1]]++; } rowStart += image.GetRowWidth(); } @@ -99,11 +115,11 @@ void MEHistogram::Calculate(MEImage& image, HistogramType mode) int RowStart = 0; int RowWidth = image.GetRowWidth(); - for (int i = image.GetHeight()-1; i >= 0; i--) + for (int i = image.GetHeight() - 1; i >= 0; i--) { - for (int i1 = image.GetWidth()*image.GetLayers()-1; i1 >= 0; i1--) + for (int i1 = image.GetWidth()*image.GetLayers() - 1; i1 >= 0; i1--) { - HistogramData[ImageData[RowStart+i1]]++; + HistogramData[ImageData[RowStart + i1]]++; } RowStart += RowWidth; } @@ -126,16 +142,16 @@ void MEHistogram::Calculate(MEImage& image, int channel, int x0, int y0, int x1, // Compute the correct region coordinates and check them X0 = X0 < 0 ? 0 : X0; Y0 = Y0 < 0 ? 0 : Y0; - X1 = X1 > image.GetWidth()-1 ? image.GetWidth()-1 : X1; - Y1 = Y1 > image.GetHeight()-1 ? image.GetHeight()-1 : Y1; + X1 = X1 > image.GetWidth() - 1 ? image.GetWidth() - 1 : X1; + Y1 = Y1 > image.GetHeight() - 1 ? image.GetHeight() - 1 : Y1; RowStart = Y0*image.GetRowWidth(); for (int i = Y1; i >= Y0; --i) { - for (int i1 = X1*image.GetLayers()+Channel-1; i1 >= X0*image.GetLayers()+Channel-1; - i1 -= image.GetLayers()) + for (int i1 = X1*image.GetLayers() + Channel - 1; i1 >= X0*image.GetLayers() + Channel - 1; + i1 -= image.GetLayers()) { - HistogramData[ImageData[RowStart+i1]]++; + HistogramData[ImageData[RowStart + i1]]++; } RowStart += RowWidth; } @@ -242,70 +258,70 @@ bool MEHistogram::Stretch(StretchType mode) switch (mode) { - case s_OwnMode: - Percent = 20; + case s_OwnMode: + Percent = 20; + MinIndex = GetLowestLimitIndex(Percent); + MaxIndex = GetHighestLimitIndex(Percent); + + while ((abs(MaxIndex - MinIndex) < 52) && (Percent > 1)) + { + Percent = Percent / 2; MinIndex = GetLowestLimitIndex(Percent); MaxIndex = GetHighestLimitIndex(Percent); - while ((abs(MaxIndex-MinIndex) < 52) && (Percent > 1)) + // The calculation gives wrong answer back + if (MinIndex == 0 && MaxIndex == 255) { - Percent = Percent / 2; - MinIndex = GetLowestLimitIndex(Percent); - MaxIndex = GetHighestLimitIndex(Percent); - - // The calculation gives wrong answer back - if (MinIndex == 0 && MaxIndex == 255) - { - MinIndex = 128; - MaxIndex = 128; - Ret = false; - } + MinIndex = 128; + MaxIndex = 128; + Ret = false; } - break; + } + break; - case s_GimpMode: - Count = GetPowerAmount(0, 255); - NewCount = 0; + case s_GimpMode: + Count = GetPowerAmount(0, 255); + NewCount = 0; - for (int i = 0; i < 255; i++) - { - double Value = 0.0; - double NextValue = 0.0; + for (int i = 0; i < 255; i++) + { + double Value = 0.0; + double NextValue = 0.0; - Value = HistogramData[i]; - NextValue = HistogramData[i+1]; - NewCount += Value; - Percentage = NewCount / Count; - NextPercentage = (NewCount+NextValue) / Count; + Value = HistogramData[i]; + NextValue = HistogramData[i + 1]; + NewCount += Value; + Percentage = NewCount / Count; + NextPercentage = (NewCount + NextValue) / Count; - if (fabs(Percentage-0.006) < fabs(NextPercentage-0.006)) - { - MinIndex = i+1; - break; - } - } - NewCount = 0.0; - for (int i = 255; i > 0; i--) + if (fabs(Percentage - 0.006) < fabs(NextPercentage - 0.006)) { - double Value = 0.0; - double NextValue = 0.0; + MinIndex = i + 1; + break; + } + } + NewCount = 0.0; + for (int i = 255; i > 0; i--) + { + double Value = 0.0; + double NextValue = 0.0; - Value = HistogramData[i]; - NextValue = HistogramData[i-1]; - NewCount += Value; - Percentage = NewCount / Count; - NextPercentage = (NewCount+NextValue) / Count; + Value = HistogramData[i]; + NextValue = HistogramData[i - 1]; + NewCount += Value; + Percentage = NewCount / Count; + NextPercentage = (NewCount + NextValue) / Count; - if (fabs(Percentage-0.006) < fabs(NextPercentage-0.006)) - { - MaxIndex = i-1; - break; - } + if (fabs(Percentage - 0.006) < fabs(NextPercentage - 0.006)) + { + MaxIndex = i - 1; + break; } - break; + } + break; - default: - break; + default: + break; } if (MaxIndex <= MinIndex) @@ -314,8 +330,8 @@ bool MEHistogram::Stretch(StretchType mode) MaxIndex = 255; Ret = false; } - if (MaxIndex-MinIndex <= 10 || - (MaxIndex-MinIndex <= 20 && (float)GetPowerAmount(MinIndex, MaxIndex) / GetPowerAmount(0, 255) < 0.20)) + if (MaxIndex - MinIndex <= 10 || + (MaxIndex - MinIndex <= 20 && (float)GetPowerAmount(MinIndex, MaxIndex) / GetPowerAmount(0, 255) < 0.20)) { MinIndex = 0; MaxIndex = 255; @@ -327,7 +343,7 @@ bool MEHistogram::Stretch(StretchType mode) for (int i = 0; i < 256; ++i) { - TransformedHistogram[i] = (unsigned char)MEBound(0, 255*(i-MinIndex) / (MaxIndex-MinIndex), 255); + TransformedHistogram[i] = (unsigned char)MEBound(0, 255 * (i - MinIndex) / (MaxIndex - MinIndex), 255); } for (int i = 0; i < 256; ++i) { @@ -371,7 +387,7 @@ void MEHistogramTransform::HistogramStretch(MEImage& image, TransformType time_m for (int l = 1; l < image.GetLayers(); l++) { - RedChannel.Calculate(image, l+1, MEHistogram::h_Add); + RedChannel.Calculate(image, l + 1, MEHistogram::h_Add); } RedChannel.Stretch(StretchMode); if (time_mode == t_Discrete && !DiscreteStretchingDone) @@ -383,45 +399,46 @@ void MEHistogramTransform::HistogramStretch(MEImage& image, TransformType time_m int RowStart = 0; int RowWidth = image.GetRowWidth(); - for (int i = image.GetHeight()-1; i >= 0; i--) + for (int i = image.GetHeight() - 1; i >= 0; i--) { - for (int i1 = image.GetWidth()*image.GetLayers()-1; i1 >= 0; i1--) + for (int i1 = image.GetWidth()*image.GetLayers() - 1; i1 >= 0; i1--) { - ImageData[RowStart+i1] = RedChannel.HistogramData[ImageData[RowStart+i1]]; + ImageData[RowStart + i1] = RedChannel.HistogramData[ImageData[RowStart + i1]]; } RowStart += RowWidth; } - } else - if (ChannelMode == p_SeparateChannels) - { - if (time_mode == t_Continuous || (time_mode == t_Discrete && !DiscreteStretchingDone)) + } + else + if (ChannelMode == p_SeparateChannels) { - RedChannel.Calculate(image, 1, MEHistogram::h_Overwrite); - GreenChannel.Calculate(image, 2, MEHistogram::h_Overwrite); - BlueChannel.Calculate(image, 3, MEHistogram::h_Overwrite); - RedChannel.Stretch(StretchMode); - GreenChannel.Stretch(StretchMode); - BlueChannel.Stretch(StretchMode); - if (time_mode == t_Discrete && !DiscreteStretchingDone) + if (time_mode == t_Continuous || (time_mode == t_Discrete && !DiscreteStretchingDone)) { - DiscreteStretchingDone = true; + RedChannel.Calculate(image, 1, MEHistogram::h_Overwrite); + GreenChannel.Calculate(image, 2, MEHistogram::h_Overwrite); + BlueChannel.Calculate(image, 3, MEHistogram::h_Overwrite); + RedChannel.Stretch(StretchMode); + GreenChannel.Stretch(StretchMode); + BlueChannel.Stretch(StretchMode); + if (time_mode == t_Discrete && !DiscreteStretchingDone) + { + DiscreteStretchingDone = true; + } } - } - unsigned char *ImageData = image.GetImageData(); - int RowStart = 0; - int RowWidth = image.GetRowWidth(); + unsigned char *ImageData = image.GetImageData(); + int RowStart = 0; + int RowWidth = image.GetRowWidth(); - for (int i = image.GetHeight()-1; i >= 0; i--) - { - for (int i1 = image.GetWidth()*image.GetLayers()-3; i1 >= 0; i1 -= 3) + for (int i = image.GetHeight() - 1; i >= 0; i--) { - ImageData[RowStart+i1] = RedChannel.HistogramData[ImageData[RowStart+i1]]; - ImageData[RowStart+i1+1] = GreenChannel.HistogramData[ImageData[RowStart+i1+1]]; - ImageData[RowStart+i1+2] = BlueChannel.HistogramData[ImageData[RowStart+i1+2]]; + for (int i1 = image.GetWidth()*image.GetLayers() - 3; i1 >= 0; i1 -= 3) + { + ImageData[RowStart + i1] = RedChannel.HistogramData[ImageData[RowStart + i1]]; + ImageData[RowStart + i1 + 1] = GreenChannel.HistogramData[ImageData[RowStart + i1 + 1]]; + ImageData[RowStart + i1 + 2] = BlueChannel.HistogramData[ImageData[RowStart + i1 + 2]]; + } + RowStart += RowWidth; } - RowStart += RowWidth; } - } } @@ -433,72 +450,72 @@ void MEHistogramTransform::HistogramEqualize(MEImage& image) switch (image.GetLayers()) { - case 1: - // Grayscale image - cvDest8bitImg = cvCreateImage(cvSize(image.GetWidth(), image.GetHeight()), 8, 1); - cvEqualizeHist((IplImage*)image.GetIplImage(), cvDest8bitImg); - image.SetIplImage((void*)cvDest8bitImg); - cvReleaseImage(&cvDest8bitImg); - break; - - case 3: - // RGB image - cvDestImg = cvCreateImage(cvSize(image.GetWidth(), image.GetHeight()), 8, 3); - IplImage *cvR, *cvG, *cvB; - - cvR = cvCreateImage(cvSize(image.GetWidth(), image.GetHeight()), 8, 1); - cvG = cvCreateImage(cvSize(image.GetWidth(), image.GetHeight()), 8, 1); - cvB = cvCreateImage(cvSize(image.GetWidth(), image.GetHeight()), 8, 1); - - cvSplit((IplImage*)image.GetIplImage(), cvR, cvG, cvB, NULL); - cvEqualizeHist(cvR, cvR); - cvEqualizeHist(cvG, cvG); - cvEqualizeHist(cvB, cvB); - cvMerge(cvR, cvG, cvB, NULL, cvDestImg); - - image.SetIplImage((void*)cvDestImg); - cvReleaseImage(&cvR); - cvReleaseImage(&cvG); - cvReleaseImage(&cvB); - cvReleaseImage(&cvDestImg); - break; - - default: - break; + case 1: + // Grayscale image + cvDest8bitImg = cvCreateImage(cvSize(image.GetWidth(), image.GetHeight()), 8, 1); + cvEqualizeHist((IplImage*)image.GetIplImage(), cvDest8bitImg); + image.SetIplImage((void*)cvDest8bitImg); + cvReleaseImage(&cvDest8bitImg); + break; + + case 3: + // RGB image + cvDestImg = cvCreateImage(cvSize(image.GetWidth(), image.GetHeight()), 8, 3); + IplImage *cvR, *cvG, *cvB; + + cvR = cvCreateImage(cvSize(image.GetWidth(), image.GetHeight()), 8, 1); + cvG = cvCreateImage(cvSize(image.GetWidth(), image.GetHeight()), 8, 1); + cvB = cvCreateImage(cvSize(image.GetWidth(), image.GetHeight()), 8, 1); + + cvSplit((IplImage*)image.GetIplImage(), cvR, cvG, cvB, NULL); + cvEqualizeHist(cvR, cvR); + cvEqualizeHist(cvG, cvG); + cvEqualizeHist(cvB, cvB); + cvMerge(cvR, cvG, cvB, NULL, cvDestImg); + + image.SetIplImage((void*)cvDestImg); + cvReleaseImage(&cvR); + cvReleaseImage(&cvG); + cvReleaseImage(&cvB); + cvReleaseImage(&cvDestImg); + break; + + default: + break; } } void MEHistogramTransform::SetStretchProcessingMode(ProcessingType new_channel_mode, - MEHistogram::StretchType new_stretch_mode) + MEHistogram::StretchType new_stretch_mode) { DiscreteStretchingDone = false; - switch(new_channel_mode) + switch (new_channel_mode) { - case p_SeparateChannels: - ChannelMode = new_channel_mode; - break; + case p_SeparateChannels: + ChannelMode = new_channel_mode; + break; - case p_Average: - ChannelMode = new_channel_mode; - break; + case p_Average: + ChannelMode = new_channel_mode; + break; - default: - break; + default: + break; } - switch(new_stretch_mode) + switch (new_stretch_mode) { - case MEHistogram::s_OwnMode: - StretchMode = new_stretch_mode; - break; + case MEHistogram::s_OwnMode: + StretchMode = new_stretch_mode; + break; - case MEHistogram::s_GimpMode: - StretchMode = new_stretch_mode; - break; + case MEHistogram::s_GimpMode: + StretchMode = new_stretch_mode; + break; - default: - break; + default: + break; } } diff --git a/package_bgs/ck/MEHistogram.hpp b/package_bgs/LBP_MRF/MEHistogram.hpp similarity index 93% rename from package_bgs/ck/MEHistogram.hpp rename to package_bgs/LBP_MRF/MEHistogram.hpp index 5b2b47f..0b2fb0d 100644 --- a/package_bgs/ck/MEHistogram.hpp +++ b/package_bgs/LBP_MRF/MEHistogram.hpp @@ -1,4 +1,20 @@ /* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* * This file is part of the AiBO+ project * * Copyright (C) 2005-2013 Csaba Kertész (csaba.kertesz@gmail.com) @@ -18,14 +34,12 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * */ +#pragma once -#ifndef MEHistogram_hpp -#define MEHistogram_hpp - -/** - * @addtogroup mindeye - * @{ - */ + /** + * @addtogroup mindeye + * @{ + */ class MEImage; @@ -344,5 +358,3 @@ class MEHistogramTransform }; /** @} */ - -#endif diff --git a/package_bgs/ck/MEImage.cpp b/package_bgs/LBP_MRF/MEImage.cpp similarity index 50% rename from package_bgs/ck/MEImage.cpp rename to package_bgs/LBP_MRF/MEImage.cpp index 7373834..b01d494 100644 --- a/package_bgs/ck/MEImage.cpp +++ b/package_bgs/LBP_MRF/MEImage.cpp @@ -1,4 +1,20 @@ /* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* * This file is part of the AiBO+ project * * Copyright (C) 2005-2013 Csaba Kertész (csaba.kertesz@gmail.com) @@ -30,17 +46,17 @@ cvReleaseImage((IplImage**)&image_ptr); \ image_ptr = NULL; -// RGB to YUV transform + // RGB to YUV transform const float RGBtoYUVMatrix[3][3] = - {{ 0.299, 0.587, 0.114 }, - { -0.147, -0.289, 0.436 }, - { 0.615, -0.515, -0.100 }}; +{ { 0.299, 0.587, 0.114 }, + { -0.147, -0.289, 0.436 }, + { 0.615, -0.515, -0.100 } }; // RGB to YIQ transform const float RGBtoYIQMatrix[3][3] = - {{ 0.299, 0.587, 0.114 }, - { 0.596, -0.274, -0.322 }, - { 0.212, -0.523, 0.311 }}; +{ { 0.299, 0.587, 0.114 }, + { 0.596, -0.274, -0.322 }, + { 0.212, -0.523, 0.311 } }; MEImage::MEImage(int width, int height, int layers) : cvImg(NULL) { @@ -74,15 +90,15 @@ void MEImage::GetLayer(MEImage& new_layer, int layer_number) const int LayerNumber = layer_number; if ((new_layer.GetWidth() != ME_CAST_TO_IPLIMAGE(cvImg)->width) || - (new_layer.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height) || - (new_layer.GetLayers() != 1)) + (new_layer.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height) || + (new_layer.GetLayers() != 1)) { new_layer.Realloc(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height, 1); } if (ME_CAST_TO_IPLIMAGE(cvImg)->nChannels < LayerNumber) { printf("The given layer number is too large (%d > %d)\n", - LayerNumber, ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + LayerNumber, ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); LayerNumber = ME_CAST_TO_IPLIMAGE(cvImg)->nChannels; } @@ -103,17 +119,17 @@ void MEImage::SetLayer(MEImage& layer, int layer_number) int LayerNumber = layer_number; if (layer.GetWidth() != ME_CAST_TO_IPLIMAGE(cvImg)->width || - layer.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height) + layer.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height) { printf("The dimensions of the layer and " - "destination image is different (%dx%d <> %dx%d)\n", - layer.GetWidth(), layer.GetHeight(), ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height); + "destination image is different (%dx%d <> %dx%d)\n", + layer.GetWidth(), layer.GetHeight(), ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height); return; } if (ME_CAST_TO_IPLIMAGE(cvImg)->nChannels < LayerNumber) { printf("The given layer number is too large (%d > %d)\n", - LayerNumber, ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + LayerNumber, ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); LayerNumber = ME_CAST_TO_IPLIMAGE(cvImg)->nChannels; } if (LayerNumber <= 0) @@ -124,7 +140,7 @@ void MEImage::SetLayer(MEImage& layer, int layer_number) if (layer.GetLayers() != 1) { printf("The layer image has not one color channel (1 != %d)\n", - layer.GetLayers()); + layer.GetLayers()); return; } cvSetImageCOI(ME_CAST_TO_IPLIMAGE(cvImg), LayerNumber); @@ -223,7 +239,7 @@ void MEImage::SetData(unsigned char* image_data, int width, int height, int chan { _Init(width, height, channels); - for (int y = height-1; y >= 0; --y) + for (int y = height - 1; y >= 0; --y) { int Start = GetRowWidth()*y; int Start2 = width*channels*y; @@ -235,7 +251,7 @@ void MEImage::SetData(unsigned char* image_data, int width, int height, int chan float MEImage::GetRatio() const { - return ME_CAST_TO_IPLIMAGE(cvImg) ? (float)ME_CAST_TO_IPLIMAGE(cvImg)->height/(float)ME_CAST_TO_IPLIMAGE(cvImg)->width : 0.0; + return ME_CAST_TO_IPLIMAGE(cvImg) ? (float)ME_CAST_TO_IPLIMAGE(cvImg)->height / (float)ME_CAST_TO_IPLIMAGE(cvImg)->width : 0.0; } @@ -289,7 +305,7 @@ void MEImage::ResizeScaleY(int new_height) printf("Invalid new height: %d < 1\n", new_height); return; } - Resize((int)((float)new_height*1/GetRatio()), new_height); + Resize((int)((float)new_height * 1 / GetRatio()), new_height); } @@ -322,19 +338,19 @@ void MEImage::Crop(int x1, int y1, int x2, int y2) NewY2 = (NewY2 < 0) ? 0 : NewY2; NewY2 = (NewY2 > ME_CAST_TO_IPLIMAGE(cvImg)->height) ? ME_CAST_TO_IPLIMAGE(cvImg)->height : NewY2; - if ((NewX2-NewX1) <= 0) + if ((NewX2 - NewX1) <= 0) { - printf("Invalid new width: %d <= 0\n", NewX2-NewX1); + printf("Invalid new width: %d <= 0\n", NewX2 - NewX1); return; } - if ((NewY2-NewY1) <= 0) + if ((NewY2 - NewY1) <= 0) { - printf("Invalid new height: %d <= 0\n", NewY2-NewY1); + printf("Invalid new height: %d <= 0\n", NewY2 - NewY1); return; } - IplImage* TempImg = cvCreateImage(cvSize(NewX2-NewX1, NewY2-NewY1), 8, ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + IplImage* TempImg = cvCreateImage(cvSize(NewX2 - NewX1, NewY2 - NewY1), 8, ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvSetImageROI(ME_CAST_TO_IPLIMAGE(cvImg), cvRect(NewX1, NewY1, NewX2-NewX1, NewY2-NewY1)); + cvSetImageROI(ME_CAST_TO_IPLIMAGE(cvImg), cvRect(NewX1, NewY1, NewX2 - NewX1, NewY2 - NewY1)); cvCopy(ME_CAST_TO_IPLIMAGE(cvImg), TempImg); ME_RELEASE_IPLIMAGE(cvImg); cvImg = TempImg; @@ -367,13 +383,13 @@ void MEImage::CopyImageInside(int x, int y, MEImage& source_image) NewY = 0; if (NewY > ME_CAST_TO_IPLIMAGE(cvImg)->height) NewY = ME_CAST_TO_IPLIMAGE(cvImg)->height; - if (NewX+PasteLengthX > ME_CAST_TO_IPLIMAGE(cvImg)->width) - PasteLengthX = ME_CAST_TO_IPLIMAGE(cvImg)->width-NewX; - if (NewY+PasteLengthY > ME_CAST_TO_IPLIMAGE(cvImg)->height) - PasteLengthY = ME_CAST_TO_IPLIMAGE(cvImg)->height-NewY; + if (NewX + PasteLengthX > ME_CAST_TO_IPLIMAGE(cvImg)->width) + PasteLengthX = ME_CAST_TO_IPLIMAGE(cvImg)->width - NewX; + if (NewY + PasteLengthY > ME_CAST_TO_IPLIMAGE(cvImg)->height) + PasteLengthY = ME_CAST_TO_IPLIMAGE(cvImg)->height - NewY; if (PasteLengthX != source_image.GetWidth() || - PasteLengthY != source_image.GetHeight()) + PasteLengthY != source_image.GetHeight()) { source_image.Resize(PasteLengthX, PasteLengthY); } @@ -386,8 +402,8 @@ void MEImage::CopyImageInside(int x, int y, MEImage& source_image) void MEImage::Erode(int iterations) { IplImage* TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, - ME_CAST_TO_IPLIMAGE(cvImg)->height), - 8, ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + ME_CAST_TO_IPLIMAGE(cvImg)->height), + 8, ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); cvErode(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, NULL, iterations); ME_RELEASE_IPLIMAGE(cvImg); @@ -398,8 +414,8 @@ void MEImage::Erode(int iterations) void MEImage::Dilate(int iterations) { IplImage* TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, - ME_CAST_TO_IPLIMAGE(cvImg)->height), - 8, ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + ME_CAST_TO_IPLIMAGE(cvImg)->height), + 8, ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); cvDilate(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, NULL, iterations); ME_RELEASE_IPLIMAGE(cvImg); @@ -416,22 +432,22 @@ void MEImage::Smooth() void MEImage::SmoothAdvanced(SmoothType filtermode, int filtersize) { IplImage* TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); switch (filtermode) { - case s_Blur: - cvSmooth(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_BLUR, filtersize, filtersize, 0); - break; - case s_Median: - cvSmooth(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_MEDIAN, filtersize, 0, 0); - break; - case s_Gaussian: - cvSmooth(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_GAUSSIAN, filtersize, filtersize, 0); - break; - default: - cvSmooth(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_MEDIAN, filtersize, 0, 0); - break; + case s_Blur: + cvSmooth(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_BLUR, filtersize, filtersize, 0); + break; + case s_Median: + cvSmooth(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_MEDIAN, filtersize, 0, 0); + break; + case s_Gaussian: + cvSmooth(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_GAUSSIAN, filtersize, filtersize, 0); + break; + default: + cvSmooth(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_MEDIAN, filtersize, 0, 0); + break; } ME_RELEASE_IPLIMAGE(cvImg); cvImg = TempImg; @@ -446,7 +462,7 @@ void MEImage::Canny() } IplImage* TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); cvCanny(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, 800, 1100, 5); ME_RELEASE_IPLIMAGE(cvImg); cvImg = TempImg; @@ -460,8 +476,8 @@ void MEImage::Laplace() ConvertToGrayscale(g_OpenCV); } IplImage* TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, - ME_CAST_TO_IPLIMAGE(cvImg)->height), - IPL_DEPTH_16S, 1); + ME_CAST_TO_IPLIMAGE(cvImg)->height), + IPL_DEPTH_16S, 1); cvLaplace(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, 3); cvConvertScale(TempImg, ME_CAST_TO_IPLIMAGE(cvImg), 1, 0); ME_RELEASE_IPLIMAGE(cvImg); @@ -482,7 +498,7 @@ void MEImage::Quantize(int levels) } unsigned char* ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*ME_CAST_TO_IPLIMAGE(cvImg)->height-1; i >= 0; --i) + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; i >= 0; --i) { ImageData[i] = ImageData[i] / (256 / levels)*(256 / levels); } @@ -503,7 +519,7 @@ void MEImage::Threshold(int threshold_limit) } unsigned char* ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*ME_CAST_TO_IPLIMAGE(cvImg)->height-1; i >= 0; --i) + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; i >= 0; --i) { if (ImageData[i] < threshold_limit) { @@ -520,9 +536,9 @@ void MEImage::AdaptiveThreshold() ConvertToGrayscale(g_OpenCV); } IplImage* TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); cvAdaptiveThreshold(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, 25, - CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 7, -7); + CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 7, -7); ME_RELEASE_IPLIMAGE(cvImg); cvImg = TempImg; } @@ -531,7 +547,7 @@ void MEImage::AdaptiveThreshold() void MEImage::ThresholdByMask(MEImage& mask_image) { if (mask_image.GetWidth() != ME_CAST_TO_IPLIMAGE(cvImg)->width || - mask_image.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height) + mask_image.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height) { printf("Image properties are different\n"); return; @@ -543,7 +559,7 @@ void MEImage::ThresholdByMask(MEImage& mask_image) unsigned char* ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; unsigned char* MaskImageData = mask_image.GetImageData(); - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*ME_CAST_TO_IPLIMAGE(cvImg)->height-1; i >= 0; --i) + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; i >= 0; --i) { if (MaskImageData[i] == 0) { @@ -567,123 +583,123 @@ void MEImage::ColorSpace(ColorSpaceConvertType mode) } switch (mode) { - case csc_RGBtoXYZCIED65: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, - ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2XYZ); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; - - case csc_XYZCIED65toRGB: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, - ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_XYZ2RGB); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; - - case csc_RGBtoHSV: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, - ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2HSV); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; - - case csc_HSVtoRGB: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, - ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_HSV2RGB); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; - - case csc_RGBtoHLS: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2HLS); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; - - case csc_HLStoRGB: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_HLS2RGB); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; - - case csc_RGBtoCIELab: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2Lab); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; - - case csc_CIELabtoRGB: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_Lab2RGB); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; - - case csc_RGBtoCIELuv: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2Luv); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; - - case csc_CIELuvtoRGB: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_Luv2RGB); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; - - case csc_RGBtoYUV: - ComputeColorSpace(csc_RGBtoYUV); - break; - - case csc_RGBtoYIQ: - ComputeColorSpace(csc_RGBtoYIQ); - break; - - case csc_RGBtorgI: - ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; - WidthStep = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep; - RowStart = 0; - for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height-1; y >= 0; --y) + case csc_RGBtoXYZCIED65: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, + ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2XYZ); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; + + case csc_XYZCIED65toRGB: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, + ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_XYZ2RGB); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; + + case csc_RGBtoHSV: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, + ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2HSV); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; + + case csc_HSVtoRGB: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, + ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_HSV2RGB); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; + + case csc_RGBtoHLS: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2HLS); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; + + case csc_HLStoRGB: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_HLS2RGB); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; + + case csc_RGBtoCIELab: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2Lab); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; + + case csc_CIELabtoRGB: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_Lab2RGB); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; + + case csc_RGBtoCIELuv: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2Luv); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; + + case csc_CIELuvtoRGB: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_Luv2RGB); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; + + case csc_RGBtoYUV: + ComputeColorSpace(csc_RGBtoYUV); + break; + + case csc_RGBtoYIQ: + ComputeColorSpace(csc_RGBtoYIQ); + break; + + case csc_RGBtorgI: + ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; + WidthStep = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep; + RowStart = 0; + for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; y >= 0; --y) + { + for (int x = (ME_CAST_TO_IPLIMAGE(cvImg)->width - 1) * 3; x >= 0; x -= 3) { - for (int x = (ME_CAST_TO_IPLIMAGE(cvImg)->width-1)*3; x >= 0; x -= 3) - { - int r = 0; - int g = 0; - int I = 0; - - I = (int)ImageData[RowStart+x]+(int)ImageData[RowStart+x+1]+(int)ImageData[RowStart+x+2]; - r = (int)((float)ImageData[RowStart+x] / I*255); - g = (int)((float)ImageData[RowStart+x+1] / I*255); - ImageData[RowStart+x] = (unsigned char)r; - ImageData[RowStart+x+1] = (unsigned char)g; - ImageData[RowStart+x+2] = (unsigned char)(I / 3); - } - RowStart += WidthStep; + int r = 0; + int g = 0; + int I = 0; + + I = (int)ImageData[RowStart + x] + (int)ImageData[RowStart + x + 1] + (int)ImageData[RowStart + x + 2]; + r = (int)((float)ImageData[RowStart + x] / I * 255); + g = (int)((float)ImageData[RowStart + x + 1] / I * 255); + ImageData[RowStart + x] = (unsigned char)r; + ImageData[RowStart + x + 1] = (unsigned char)g; + ImageData[RowStart + x + 2] = (unsigned char)(I / 3); } - break; + RowStart += WidthStep; + } + break; - default: - break; + default: + break; } } @@ -701,27 +717,27 @@ void MEImage::ConvertToGrayscale(GrayscaleType grayscale_mode) switch (grayscale_mode) { - case g_Average: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, 1); - ImageData = (unsigned char*)TempImg->imageData; + case g_Average: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, 1); + ImageData = (unsigned char*)TempImg->imageData; - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*ME_CAST_TO_IPLIMAGE(cvImg)->height-3; i >= 0; i -= 3) - { - ImageData[i / 3] = (ImgData[i]+ImgData[i+1]+ImgData[i+2]) / 3; - } - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*ME_CAST_TO_IPLIMAGE(cvImg)->height - 3; i >= 0; i -= 3) + { + ImageData[i / 3] = (ImgData[i] + ImgData[i + 1] + ImgData[i + 2]) / 3; + } + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; - case g_OpenCV: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, 1); - cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2GRAY); - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; + case g_OpenCV: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, 1); + cvCvtColor(ME_CAST_TO_IPLIMAGE(cvImg), TempImg, CV_RGB2GRAY); + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; - default: - break; + default: + break; } } @@ -760,60 +776,60 @@ void MEImage::LBP(LBPType mode) IplImage* TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, 1); unsigned char* TempImgData = (unsigned char*)TempImg->imageData; int WidthStep = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep; - int WidthStep_2 = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*2; + int WidthStep_2 = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep * 2; cvSetZero(TempImg); switch (mode) { - case lbp_Normal: - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*(ME_CAST_TO_IPLIMAGE(cvImg)->height-2)-1; i >= ME_CAST_TO_IPLIMAGE(cvImg)->widthStep+1; --i) - { - TempImgData[i] = - (ImageData[i] <= ImageData[i-ME_CAST_TO_IPLIMAGE(cvImg)->widthStep-1])+ - ((ImageData[i] <= ImageData[i-ME_CAST_TO_IPLIMAGE(cvImg)->widthStep])*2)+ - ((ImageData[i] <= ImageData[i-ME_CAST_TO_IPLIMAGE(cvImg)->widthStep+1])*4)+ - ((ImageData[i] <= ImageData[i-1])*8)+ - ((ImageData[i] <= ImageData[i+1])*16)+ - ((ImageData[i] <= ImageData[i+ME_CAST_TO_IPLIMAGE(cvImg)->widthStep-1])*32)+ - ((ImageData[i] <= ImageData[i+ME_CAST_TO_IPLIMAGE(cvImg)->widthStep])*64)+ - ((ImageData[i] <= ImageData[i+ME_CAST_TO_IPLIMAGE(cvImg)->widthStep+1])*128); - } - break; + case lbp_Normal: + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*(ME_CAST_TO_IPLIMAGE(cvImg)->height - 2) - 1; i >= ME_CAST_TO_IPLIMAGE(cvImg)->widthStep + 1; --i) + { + TempImgData[i] = + (ImageData[i] <= ImageData[i - ME_CAST_TO_IPLIMAGE(cvImg)->widthStep - 1]) + + ((ImageData[i] <= ImageData[i - ME_CAST_TO_IPLIMAGE(cvImg)->widthStep]) * 2) + + ((ImageData[i] <= ImageData[i - ME_CAST_TO_IPLIMAGE(cvImg)->widthStep + 1]) * 4) + + ((ImageData[i] <= ImageData[i - 1]) * 8) + + ((ImageData[i] <= ImageData[i + 1]) * 16) + + ((ImageData[i] <= ImageData[i + ME_CAST_TO_IPLIMAGE(cvImg)->widthStep - 1]) * 32) + + ((ImageData[i] <= ImageData[i + ME_CAST_TO_IPLIMAGE(cvImg)->widthStep]) * 64) + + ((ImageData[i] <= ImageData[i + ME_CAST_TO_IPLIMAGE(cvImg)->widthStep + 1]) * 128); + } + break; - case lbp_Special: - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*(ME_CAST_TO_IPLIMAGE(cvImg)->height-3)-2; i >= ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*2+2; --i) - { - int CenterPixel = (ImageData[i+1]+ImageData[i-1]+ - ImageData[i-WidthStep]+ImageData[i+WidthStep]) / 4; - TempImgData[i] = ((CenterPixel <= (ImageData[i-(WidthStep_2)-2]+ - ImageData[i-(WidthStep_2)-1]+ - ImageData[i-WidthStep-2]+ - ImageData[i-WidthStep-1]) / 4))+ - ((CenterPixel <= (ImageData[i-WidthStep]+ - ImageData[i-(WidthStep_2)]) / 2)*2)+ - ((CenterPixel <= ((ImageData[i-(WidthStep_2)+2]+ - ImageData[i-(WidthStep_2)+1]+ - ImageData[i-WidthStep+2]+ - ImageData[i-WidthStep+1]) / 4))*4)+ - ((CenterPixel <= (ImageData[i-1]+ - ImageData[i-2]) / 2)*8)+ - ((CenterPixel <= (ImageData[i+1]+ - ImageData[i+2]) / 2)*16)+ - ((CenterPixel <= ((ImageData[i+(WidthStep_2)-2]+ - ImageData[i+(WidthStep_2)-1]+ - ImageData[i+WidthStep-2]+ - ImageData[i+WidthStep-1]) / 4))*32)+ - ((CenterPixel <= (ImageData[i+WidthStep]+ - ImageData[i-WidthStep_2]) / 2)*64)+ - ((CenterPixel <= ((ImageData[i+(WidthStep_2)+2]+ - ImageData[i+(WidthStep_2)+1]+ - ImageData[i+WidthStep+2]+ - ImageData[i+WidthStep+1]) / 4))*128); - } - break; + case lbp_Special: + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*(ME_CAST_TO_IPLIMAGE(cvImg)->height - 3) - 2; i >= ME_CAST_TO_IPLIMAGE(cvImg)->widthStep * 2 + 2; --i) + { + int CenterPixel = (ImageData[i + 1] + ImageData[i - 1] + + ImageData[i - WidthStep] + ImageData[i + WidthStep]) / 4; + TempImgData[i] = ((CenterPixel <= (ImageData[i - (WidthStep_2)-2] + + ImageData[i - (WidthStep_2)-1] + + ImageData[i - WidthStep - 2] + + ImageData[i - WidthStep - 1]) / 4)) + + ((CenterPixel <= (ImageData[i - WidthStep] + + ImageData[i - (WidthStep_2)]) / 2) * 2) + + ((CenterPixel <= ((ImageData[i - (WidthStep_2)+2] + + ImageData[i - (WidthStep_2)+1] + + ImageData[i - WidthStep + 2] + + ImageData[i - WidthStep + 1]) / 4)) * 4) + + ((CenterPixel <= (ImageData[i - 1] + + ImageData[i - 2]) / 2) * 8) + + ((CenterPixel <= (ImageData[i + 1] + + ImageData[i + 2]) / 2) * 16) + + ((CenterPixel <= ((ImageData[i + (WidthStep_2)-2] + + ImageData[i + (WidthStep_2)-1] + + ImageData[i + WidthStep - 2] + + ImageData[i + WidthStep - 1]) / 4)) * 32) + + ((CenterPixel <= (ImageData[i + WidthStep] + + ImageData[i - WidthStep_2]) / 2) * 64) + + ((CenterPixel <= ((ImageData[i + (WidthStep_2)+2] + + ImageData[i + (WidthStep_2)+1] + + ImageData[i + WidthStep + 2] + + ImageData[i + WidthStep + 1]) / 4)) * 128); + } + break; - default: - break; + default: + break; } ME_RELEASE_IPLIMAGE(cvImg); cvImg = TempImg; @@ -824,12 +840,13 @@ void MEImage::Binarize(int threshold) { unsigned char* ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->height*ME_CAST_TO_IPLIMAGE(cvImg)->widthStep-1; i >= 0; --i) + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->height*ME_CAST_TO_IPLIMAGE(cvImg)->widthStep - 1; i >= 0; --i) { if (ImageData[i] >= threshold) { ImageData[i] = 255; - } else { + } + else { ImageData[i] = 0; } } @@ -839,8 +856,8 @@ void MEImage::Binarize(int threshold) void MEImage::Subtract(MEImage& source, SubtractModeType mode) { if (source.GetWidth() != ME_CAST_TO_IPLIMAGE(cvImg)->width || - source.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height || - source.GetLayers() != ME_CAST_TO_IPLIMAGE(cvImg)->nChannels) + source.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height || + source.GetLayers() != ME_CAST_TO_IPLIMAGE(cvImg)->nChannels) { printf("Image properties are different.\n"); return; @@ -852,42 +869,42 @@ void MEImage::Subtract(MEImage& source, SubtractModeType mode) switch (mode) { - case sub_Normal: - ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; - DstData = source.GetImageData(); - RowStart = 0; + case sub_Normal: + ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; + DstData = source.GetImageData(); + RowStart = 0; - for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height-1; y >= 0; --y) + for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; y >= 0; --y) + { + for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels - 1; x >= 0; --x) { - for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels-1; x >= 0; --x) - { - ImageData[RowStart+x] = - ImageData[RowStart+x]-DstData[RowStart+x] < 0 ? 0 : - ImageData[RowStart+x]-DstData[RowStart+x]; - } - RowStart += WidthStep; + ImageData[RowStart + x] = + ImageData[RowStart + x] - DstData[RowStart + x] < 0 ? 0 : + ImageData[RowStart + x] - DstData[RowStart + x]; } - break; + RowStart += WidthStep; + } + break; - case sub_Absolut: - ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; - DstData = source.GetImageData(); - RowStart = 0; + case sub_Absolut: + ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; + DstData = source.GetImageData(); + RowStart = 0; - for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height-1; y >= 0; --y) + for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; y >= 0; --y) + { + for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels - 1; x >= 0; --x) { - for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels-1; x >= 0; --x) - { - ImageData[RowStart+x] = ImageData[RowStart+x]- - DstData[RowStart+x] < 0 ? -ImageData[RowStart+x]+ - DstData[RowStart+x] : ImageData[RowStart+x]-DstData[RowStart+x]; - } - RowStart += WidthStep; + ImageData[RowStart + x] = ImageData[RowStart + x] - + DstData[RowStart + x] < 0 ? -ImageData[RowStart + x] + + DstData[RowStart + x] : ImageData[RowStart + x] - DstData[RowStart + x]; } - break; + RowStart += WidthStep; + } + break; - default: - break; + default: + break; } } @@ -895,8 +912,8 @@ void MEImage::Subtract(MEImage& source, SubtractModeType mode) void MEImage::Multiple(MEImage& source, MultiplicationType mode) { if (source.GetWidth() != ME_CAST_TO_IPLIMAGE(cvImg)->width || - source.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height || - source.GetLayers() != ME_CAST_TO_IPLIMAGE(cvImg)->nChannels) + source.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height || + source.GetLayers() != ME_CAST_TO_IPLIMAGE(cvImg)->nChannels) { printf("Image properties are different.\n"); return; @@ -910,57 +927,59 @@ void MEImage::Multiple(MEImage& source, MultiplicationType mode) switch (mode) { - case m_Normal: - Result = 0; - ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; - DstData = source.GetImageData(); + case m_Normal: + Result = 0; + ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; + DstData = source.GetImageData(); - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->height*ME_CAST_TO_IPLIMAGE(cvImg)->widthStep-1; i >= 0; --i) + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->height*ME_CAST_TO_IPLIMAGE(cvImg)->widthStep - 1; i >= 0; --i) + { + if ((ImageData[i] >= 128) && (DstData[i] >= 128)) { - if ((ImageData[i] >= 128) && (DstData[i] >= 128)) - { - Result = (float)ImageData[i]/128*(float)DstData[i]/128; + Result = (float)ImageData[i] / 128 * (float)DstData[i] / 128; - if (Result >= 1) - { - ImageData[i] = 255; - } else { - ImageData[i] = 0; - } - } else { + if (Result >= 1) + { + ImageData[i] = 255; + } + else { ImageData[i] = 0; } } - break; - - case m_Neighbourhood: - TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); - ImageData2 = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; - DstData = source.GetImageData(); - ImageData3 = (unsigned char*)TempImg->imageData; - - for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height-1; y >= 0; --y) - for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width-1; x >= 0; --x) - for (int l = ME_CAST_TO_IPLIMAGE(cvImg)->nChannels-1; l >= 0; --l) - { - if (((DstData[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+ - x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+l] == 255) || - (ImageData2[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+ - x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+l] == 255)) && - (NeighbourhoodCounter(x-2, y-2, n_5x5) > 3) && - (source.NeighbourhoodCounter(x-2, y-2, n_5x5) > 3)) + else { + ImageData[i] = 0; + } + } + break; + + case m_Neighbourhood: + TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + ImageData2 = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; + DstData = source.GetImageData(); + ImageData3 = (unsigned char*)TempImg->imageData; + + for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; y >= 0; --y) + for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width - 1; x >= 0; --x) + for (int l = ME_CAST_TO_IPLIMAGE(cvImg)->nChannels - 1; l >= 0; --l) { - ImageData3[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+ - x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+l] = 255; + if (((DstData[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + + x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + l] == 255) || + (ImageData2[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + + x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + l] == 255)) && + (NeighbourhoodCounter(x - 2, y - 2, n_5x5) > 3) && + (source.NeighbourhoodCounter(x - 2, y - 2, n_5x5) > 3)) + { + ImageData3[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + + x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + l] = 255; + } } - } - ME_RELEASE_IPLIMAGE(cvImg); - cvImg = TempImg; - break; + ME_RELEASE_IPLIMAGE(cvImg); + cvImg = TempImg; + break; - default: - break; + default: + break; } } @@ -968,8 +987,8 @@ void MEImage::Multiple(MEImage& source, MultiplicationType mode) void MEImage::Addition(MEImage& source, AdditionType mode) { if (source.GetWidth() != ME_CAST_TO_IPLIMAGE(cvImg)->width || - source.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height || - source.GetLayers() != ME_CAST_TO_IPLIMAGE(cvImg)->nChannels) + source.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height || + source.GetLayers() != ME_CAST_TO_IPLIMAGE(cvImg)->nChannels) { printf("Image properties are different.\n"); return; @@ -979,25 +998,25 @@ void MEImage::Addition(MEImage& source, AdditionType mode) switch (mode) { - case a_Average: - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->height*ME_CAST_TO_IPLIMAGE(cvImg)->widthStep-1; i >= 0; --i) - { - ImageData[i] = (ImageData[i]+DstData[i]) / 2; - } - break; + case a_Average: + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->height*ME_CAST_TO_IPLIMAGE(cvImg)->widthStep - 1; i >= 0; --i) + { + ImageData[i] = (ImageData[i] + DstData[i]) / 2; + } + break; - case a_Union: - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->height*ME_CAST_TO_IPLIMAGE(cvImg)->widthStep-1; i >= 0; --i) + case a_Union: + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->height*ME_CAST_TO_IPLIMAGE(cvImg)->widthStep - 1; i >= 0; --i) + { + if (DstData[i] > ImageData[i]) { - if (DstData[i] > ImageData[i]) - { - ImageData[i] = DstData[i]; - } + ImageData[i] = DstData[i]; } - break; + } + break; - default: - break; + default: + break; } } @@ -1005,42 +1024,44 @@ void MEImage::Addition(MEImage& source, AdditionType mode) void MEImage::EliminateSinglePixels() { IplImage* TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); unsigned char* ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; unsigned char* DstData = (unsigned char*)TempImg->imageData; int sum = 0; int xy = 0; int ywidth = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep; - for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height-1; y >= 0; --y) - for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width-1; x >= 0; --x) - { - xy = y*ywidth+x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels; - - for (int l = ME_CAST_TO_IPLIMAGE(cvImg)->nChannels-1; l >= 0; --l) + for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; y >= 0; --y) + for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width - 1; x >= 0; --x) { - if ((ImageData[xy+l] > 0) && (x > 0) && (y > 0) && (x < ME_CAST_TO_IPLIMAGE(cvImg)->width-1) && (y < ME_CAST_TO_IPLIMAGE(cvImg)->height-1)) + xy = y*ywidth + x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels; + + for (int l = ME_CAST_TO_IPLIMAGE(cvImg)->nChannels - 1; l >= 0; --l) { - sum = (ImageData[xy-ywidth-ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+l] > 0)+ - (ImageData[xy-ywidth+l] > 0)+ - (ImageData[xy-ywidth+ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+l] > 0)+ - (ImageData[xy-ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+l] > 0)+ - (ImageData[xy+ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+l] > 0)+ - (ImageData[xy+ywidth-ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+l] > 0)+ - (ImageData[xy+ywidth+l] > 0)+ - (ImageData[xy+ywidth+ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+l] > 0); - - if (sum > 3) + if ((ImageData[xy + l] > 0) && (x > 0) && (y > 0) && (x < ME_CAST_TO_IPLIMAGE(cvImg)->width - 1) && (y < ME_CAST_TO_IPLIMAGE(cvImg)->height - 1)) { - DstData[xy+l] = 255; - } else { - DstData[xy+l] = 0; + sum = (ImageData[xy - ywidth - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + l] > 0) + + (ImageData[xy - ywidth + l] > 0) + + (ImageData[xy - ywidth + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + l] > 0) + + (ImageData[xy - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + l] > 0) + + (ImageData[xy + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + l] > 0) + + (ImageData[xy + ywidth - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + l] > 0) + + (ImageData[xy + ywidth + l] > 0) + + (ImageData[xy + ywidth + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + l] > 0); + + if (sum > 3) + { + DstData[xy + l] = 255; + } + else { + DstData[xy + l] = 0; + } + } + else { + DstData[xy + l] = 0; } - } else { - DstData[xy+l] = 0; } } - } ME_RELEASE_IPLIMAGE(cvImg); cvImg = TempImg; } @@ -1049,8 +1070,8 @@ void MEImage::EliminateSinglePixels() float MEImage::DifferenceAreas(MEImage& reference, int difference) const { if (reference.GetWidth() != GetWidth() || - reference.GetHeight() != GetHeight() || - reference.GetLayers() != GetLayers()) + reference.GetHeight() != GetHeight() || + reference.GetLayers() != GetLayers()) { printf("Image dimensions or channels are different\n"); return -1.0; @@ -1062,16 +1083,16 @@ float MEImage::DifferenceAreas(MEImage& reference, int difference) const int WidthStep = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep; int RowStart = 0; - for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height-1; y >= 0; --y) + for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; y >= 0; --y) { - for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels-1; x >= 0; --x) + for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels - 1; x >= 0; --x) { - if (abs(OrigImgData[RowStart+x]-RefImgData[RowStart+x]) > difference) + if (abs(OrigImgData[RowStart + x] - RefImgData[RowStart + x]) > difference) Pixels++; } RowStart += WidthStep; } - PixelDiff = (float)Pixels / (ME_CAST_TO_IPLIMAGE(cvImg)->height*ME_CAST_TO_IPLIMAGE(cvImg)->widthStep)*100; + PixelDiff = (float)Pixels / (ME_CAST_TO_IPLIMAGE(cvImg)->height*ME_CAST_TO_IPLIMAGE(cvImg)->widthStep) * 100; return PixelDiff; } @@ -1079,8 +1100,8 @@ float MEImage::DifferenceAreas(MEImage& reference, int difference) const int MEImage::AverageDifference(MEImage& reference) const { if (reference.GetWidth() != GetWidth() || - reference.GetHeight() != GetHeight() || - reference.GetLayers() != GetLayers()) + reference.GetHeight() != GetHeight() || + reference.GetLayers() != GetLayers()) { printf("Image dimensions or channels are different\n"); return -1; @@ -1091,11 +1112,11 @@ int MEImage::AverageDifference(MEImage& reference) const int WidthStep = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep; int RowStart = 0; - for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height-1; y >= 0; --y) + for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; y >= 0; --y) { - for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels-1; x >= 0; --x) + for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels - 1; x >= 0; --x) { - Difference += abs(OrigImgData[RowStart+x]-RefImgData[RowStart+x]); + Difference += abs(OrigImgData[RowStart + x] - RefImgData[RowStart + x]); } RowStart += WidthStep; } @@ -1107,8 +1128,8 @@ int MEImage::AverageDifference(MEImage& reference) const void MEImage::Minimum(MEImage& image) { if (image.GetWidth() != ME_CAST_TO_IPLIMAGE(cvImg)->width || - image.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height || - image.GetLayers() != ME_CAST_TO_IPLIMAGE(cvImg)->nChannels) + image.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height || + image.GetLayers() != ME_CAST_TO_IPLIMAGE(cvImg)->nChannels) { printf("Image properties are different\n"); return; @@ -1118,12 +1139,12 @@ void MEImage::Minimum(MEImage& image) int WidthStep = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep; int RowStart = 0; - for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height-1; y >= 0; --y) + for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; y >= 0; --y) { - for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels-1; x >= 0; --x) + for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels - 1; x >= 0; --x) { - ImageData[RowStart+x] = ImageData[RowStart+x] > SecData[RowStart+x] ? - SecData[RowStart+x] : ImageData[RowStart+x]; + ImageData[RowStart + x] = ImageData[RowStart + x] > SecData[RowStart + x] ? + SecData[RowStart + x] : ImageData[RowStart + x]; } RowStart += WidthStep; } @@ -1137,11 +1158,11 @@ float MEImage::AverageBrightnessLevel() const int RowStart = 0; int BrightnessLevel = 0; - for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height-1; y >= 0; --y) + for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; y >= 0; --y) { - for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels-1; x >= 0; --x) + for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels - 1; x >= 0; --x) { - BrightnessLevel += (int)ImageData[RowStart+x]; + BrightnessLevel += (int)ImageData[RowStart + x]; } RowStart += WidthStep; } @@ -1160,8 +1181,8 @@ bool MEImage::Equal(const MEImage& reference, int maxabsdiff) const bool Ret = true; if (reference.GetWidth() != ME_CAST_TO_IPLIMAGE(cvImg)->width || - reference.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height || - reference.GetLayers() != ME_CAST_TO_IPLIMAGE(cvImg)->nChannels) + reference.GetHeight() != ME_CAST_TO_IPLIMAGE(cvImg)->height || + reference.GetLayers() != ME_CAST_TO_IPLIMAGE(cvImg)->nChannels) { printf("Image properties are different\n"); return false; @@ -1171,11 +1192,11 @@ bool MEImage::Equal(const MEImage& reference, int maxabsdiff) const int WidthStep = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep; int RowStart = 0; - for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height-1; y >= 0; --y) + for (int y = ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; y >= 0; --y) { - for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels-1; x >= 0; --x) + for (int x = ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels - 1; x >= 0; --x) { - if (abs(ImageData[RowStart+x]-RefData[RowStart+x]) >= maxabsdiff) + if (abs(ImageData[RowStart + x] - RefData[RowStart + x]) >= maxabsdiff) { Ret = false; return Ret; @@ -1193,16 +1214,16 @@ unsigned char MEImage::GrayscalePixel(int x, int y) const int NewY = y; NewX = NewX < 0 ? 0 : NewX; - NewX = NewX > ME_CAST_TO_IPLIMAGE(cvImg)->width-1 ? ME_CAST_TO_IPLIMAGE(cvImg)->width-1 : NewX; + NewX = NewX > ME_CAST_TO_IPLIMAGE(cvImg)->width - 1 ? ME_CAST_TO_IPLIMAGE(cvImg)->width - 1 : NewX; NewY = NewY < 0 ? 0 : NewY; - NewY = NewY > ME_CAST_TO_IPLIMAGE(cvImg)->height-1 ? ME_CAST_TO_IPLIMAGE(cvImg)->height-1 : NewY; + NewY = NewY > ME_CAST_TO_IPLIMAGE(cvImg)->height - 1 ? ME_CAST_TO_IPLIMAGE(cvImg)->height - 1 : NewY; float Sum = 0; unsigned char* ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; for (int l = 0; l < ME_CAST_TO_IPLIMAGE(cvImg)->nChannels; l++) { - Sum = Sum + (int)ImageData[NewY*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+NewX*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+l]; + Sum = Sum + (int)ImageData[NewY*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + NewX*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + l]; } Sum = Sum / ME_CAST_TO_IPLIMAGE(cvImg)->nChannels; return (unsigned char)(Sum); @@ -1210,7 +1231,7 @@ unsigned char MEImage::GrayscalePixel(int x, int y) const int MEImage::NeighbourhoodCounter(int startx, int starty, - NeighbourhoodType neighbourhood) const + NeighbourhoodType neighbourhood) const { int IterX = 0; int IterY = 0; @@ -1219,60 +1240,60 @@ int MEImage::NeighbourhoodCounter(int startx, int starty, // Determine the iteration numbers switch (neighbourhood) { - case n_2x2: - IterX = 2; - IterY = 2; - break; + case n_2x2: + IterX = 2; + IterY = 2; + break; - case n_3x3: - IterX = 3; - IterY = 3; - break; + case n_3x3: + IterX = 3; + IterY = 3; + break; - case n_3x2: - IterX = 2; - IterY = 3; - break; + case n_3x2: + IterX = 2; + IterY = 3; + break; - case n_5x5: - IterX = 5; - IterY = 5; - break; + case n_5x5: + IterX = 5; + IterY = 5; + break; - case n_7x7: - IterX = 7; - IterY = 7; - break; + case n_7x7: + IterX = 7; + IterY = 7; + break; - default: - IterX = 3; - IterY = 3; - break; + default: + IterX = 3; + IterY = 3; + break; } - int NewStartX = startx ; + int NewStartX = startx; int NewStartY = starty; NewStartX = startx < 0 ? 0 : startx; - NewStartX = startx >= ME_CAST_TO_IPLIMAGE(cvImg)->width-IterX ? ME_CAST_TO_IPLIMAGE(cvImg)->width-IterX-1 : startx; + NewStartX = startx >= ME_CAST_TO_IPLIMAGE(cvImg)->width - IterX ? ME_CAST_TO_IPLIMAGE(cvImg)->width - IterX - 1 : startx; NewStartY = starty < 0 ? 0 : starty; - NewStartY = starty >= ME_CAST_TO_IPLIMAGE(cvImg)->height-IterY ? ME_CAST_TO_IPLIMAGE(cvImg)->height-IterY-1 : starty; + NewStartY = starty >= ME_CAST_TO_IPLIMAGE(cvImg)->height - IterY ? ME_CAST_TO_IPLIMAGE(cvImg)->height - IterY - 1 : starty; int Value = 0; unsigned char* ImageData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; - for (int x = NewStartX; x < NewStartX+IterX; x++) - for (int y = NewStartY; y < NewStartY+IterY; y++) - { - Value = ((int)ImageData[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels]+ - (int)ImageData[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+1]+ - (int)ImageData[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels+2]) / 3; - - if (Value == 255) + for (int x = NewStartX; x < NewStartX + IterX; x++) + for (int y = NewStartY; y < NewStartY + IterY; y++) { - Counter++; + Value = ((int)ImageData[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels] + + (int)ImageData[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + 1] + + (int)ImageData[y*ME_CAST_TO_IPLIMAGE(cvImg)->width*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + x*ME_CAST_TO_IPLIMAGE(cvImg)->nChannels + 2]) / 3; + + if (Value == 255) + { + Counter++; + } } - } return Counter; } @@ -1288,24 +1309,24 @@ void MEImage::GradientVector(bool smooth, int x, int y, int mask_size, int& resu } if (smooth) { - SmoothAdvanced(s_Gaussian, mask_size*3-(mask_size*3-1) % 2); + SmoothAdvanced(s_Gaussian, mask_size * 3 - (mask_size * 3 - 1) % 2); } - Results[0] = (int)GrayscalePixel(x,y)-(int)GrayscalePixel(x,y-mask_size); - Results[1] = (int)GrayscalePixel(x,y)-(int)GrayscalePixel(x+DiagonalMaskSize,y-DiagonalMaskSize); - Results[2] = (int)GrayscalePixel(x,y)-(int)GrayscalePixel(x+mask_size,y); - Results[3] = (int)GrayscalePixel(x,y)-(int)GrayscalePixel(x+DiagonalMaskSize,y+DiagonalMaskSize); - Results[4] = (int)GrayscalePixel(x,y)-(int)GrayscalePixel(x,y+mask_size); - Results[5] = (int)GrayscalePixel(x,y)-(int)GrayscalePixel(x-DiagonalMaskSize,y+DiagonalMaskSize); - Results[6] = (int)GrayscalePixel(x,y)-(int)GrayscalePixel(x-mask_size,y); - Results[7] = (int)GrayscalePixel(x,y)-(int)GrayscalePixel(x+DiagonalMaskSize,y-DiagonalMaskSize); + Results[0] = (int)GrayscalePixel(x, y) - (int)GrayscalePixel(x, y - mask_size); + Results[1] = (int)GrayscalePixel(x, y) - (int)GrayscalePixel(x + DiagonalMaskSize, y - DiagonalMaskSize); + Results[2] = (int)GrayscalePixel(x, y) - (int)GrayscalePixel(x + mask_size, y); + Results[3] = (int)GrayscalePixel(x, y) - (int)GrayscalePixel(x + DiagonalMaskSize, y + DiagonalMaskSize); + Results[4] = (int)GrayscalePixel(x, y) - (int)GrayscalePixel(x, y + mask_size); + Results[5] = (int)GrayscalePixel(x, y) - (int)GrayscalePixel(x - DiagonalMaskSize, y + DiagonalMaskSize); + Results[6] = (int)GrayscalePixel(x, y) - (int)GrayscalePixel(x - mask_size, y); + Results[7] = (int)GrayscalePixel(x, y) - (int)GrayscalePixel(x + DiagonalMaskSize, y - DiagonalMaskSize); - result_x = (DiagonalMaskSize*Results[1]+mask_size*Results[2]+ - DiagonalMaskSize*Results[3]-DiagonalMaskSize*Results[5]- - mask_size*Results[6]+DiagonalMaskSize*Results[7]) / 256; - result_y = (-mask_size*Results[0]-DiagonalMaskSize*Results[1]+ - DiagonalMaskSize*Results[3]+mask_size*Results[4]+ - DiagonalMaskSize*Results[5]-DiagonalMaskSize*Results[7]) / 256; + result_x = (DiagonalMaskSize*Results[1] + mask_size*Results[2] + + DiagonalMaskSize*Results[3] - DiagonalMaskSize*Results[5] - + mask_size*Results[6] + DiagonalMaskSize*Results[7]) / 256; + result_y = (-mask_size*Results[0] - DiagonalMaskSize*Results[1] + + DiagonalMaskSize*Results[3] + mask_size*Results[4] + + DiagonalMaskSize*Results[5] - DiagonalMaskSize*Results[7]) / 256; } @@ -1327,28 +1348,28 @@ void MEImage::GradientVisualize(int vector_x, int vector_y) } int masksize = (ME_CAST_TO_IPLIMAGE(cvImg)->width < ME_CAST_TO_IPLIMAGE(cvImg)->height) ? - ME_CAST_TO_IPLIMAGE(cvImg)->width / (vector_x+1) : - ME_CAST_TO_IPLIMAGE(cvImg)->height / (vector_y+1); + ME_CAST_TO_IPLIMAGE(cvImg)->width / (vector_x + 1) : + ME_CAST_TO_IPLIMAGE(cvImg)->height / (vector_y + 1); - SmoothAdvanced(s_Gaussian, masksize*2-1); + SmoothAdvanced(s_Gaussian, masksize * 2 - 1); for (int i = 1; i < vector_x; i++) for (int i1 = 1; i1 < vector_y; i1++) - { - int Resultx = 0, Resulty = 0; - int x = (int)(((float)ME_CAST_TO_IPLIMAGE(cvImg)->width*i / (vector_x))); - int y = (int)(((float)ME_CAST_TO_IPLIMAGE(cvImg)->height*i1 / (vector_y))); + { + int Resultx = 0, Resulty = 0; + int x = (int)(((float)ME_CAST_TO_IPLIMAGE(cvImg)->width*i / (vector_x))); + int y = (int)(((float)ME_CAST_TO_IPLIMAGE(cvImg)->height*i1 / (vector_y))); - GradientVector(false, x, y, (int)(0.707*masksize), Resultx, Resulty); + GradientVector(false, x, y, (int)(0.707*masksize), Resultx, Resulty); - CvPoint Point1; - CvPoint Point2; + CvPoint Point1; + CvPoint Point2; - Point1.x = x-Resultx / 2; - Point1.y = y-Resulty / 2; - Point2.x = x+Resultx / 2; - Point2.y = y+Resulty / 2; - cvLine(ME_CAST_TO_IPLIMAGE(cvImg), Point1, Point2, CV_RGB(255, 255, 255), 1, 8); - } + Point1.x = x - Resultx / 2; + Point1.y = y - Resulty / 2; + Point2.x = x + Resultx / 2; + Point2.y = y + Resulty / 2; + cvLine(ME_CAST_TO_IPLIMAGE(cvImg), Point1, Point2, CV_RGB(255, 255, 255), 1, 8); + } } @@ -1400,16 +1421,16 @@ void MEImage::ComputeColorSpace(ColorSpaceConvertType mode) return; } IplImage* TempImg = cvCreateImage(cvSize(ME_CAST_TO_IPLIMAGE(cvImg)->width, ME_CAST_TO_IPLIMAGE(cvImg)->height), 8, - ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); + ME_CAST_TO_IPLIMAGE(cvImg)->nChannels); for (int i = 0; i < 3; i++) for (int i1 = 0; i1 < 3; i1++) - { - if (mode == csc_RGBtoYUV) - TransformMatrix[i][i1] = RGBtoYUVMatrix[i][i1]; - if (mode == csc_RGBtoYIQ) - TransformMatrix[i][i1] = RGBtoYIQMatrix[i][i1]; - } + { + if (mode == csc_RGBtoYUV) + TransformMatrix[i][i1] = RGBtoYUVMatrix[i][i1]; + if (mode == csc_RGBtoYIQ) + TransformMatrix[i][i1] = RGBtoYIQMatrix[i][i1]; + } float x = 0.0; float y = 0.0; float z = 0.0; @@ -1441,25 +1462,25 @@ void MEImage::ComputeColorSpace(ColorSpaceConvertType mode) unsigned char* SrcData = (unsigned char*)ME_CAST_TO_IPLIMAGE(cvImg)->imageData; unsigned char* DstData = (unsigned char*)TempImg->imageData; - for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*ME_CAST_TO_IPLIMAGE(cvImg)->height-1; i >= 0; i-=3) + for (int i = ME_CAST_TO_IPLIMAGE(cvImg)->widthStep*ME_CAST_TO_IPLIMAGE(cvImg)->height - 1; i >= 0; i -= 3) { - x = (float)SrcData[i]*TransformMatrix[0][0]+ - (float)SrcData[i+1]*TransformMatrix[0][1]+ - (float)SrcData[i+2]*TransformMatrix[0][2]; - y = (float)SrcData[i]*TransformMatrix[1][0]+ - (float)SrcData[i+1]*TransformMatrix[1][1]+ - (float)SrcData[i+2]*TransformMatrix[1][2]; - z = (float)SrcData[i]*TransformMatrix[2][0]+ - (float)SrcData[i+1]*TransformMatrix[2][1]+ - (float)SrcData[i+2]*TransformMatrix[2][2]; + x = (float)SrcData[i] * TransformMatrix[0][0] + + (float)SrcData[i + 1] * TransformMatrix[0][1] + + (float)SrcData[i + 2] * TransformMatrix[0][2]; + y = (float)SrcData[i] * TransformMatrix[1][0] + + (float)SrcData[i + 1] * TransformMatrix[1][1] + + (float)SrcData[i + 2] * TransformMatrix[1][2]; + z = (float)SrcData[i] * TransformMatrix[2][0] + + (float)SrcData[i + 1] * TransformMatrix[2][1] + + (float)SrcData[i + 2] * TransformMatrix[2][2]; - x = xmax-xmin != 0.0 ? 255.0 : (x-xmin) / (xmax-xmin)*255.0; - y = ymax-ymin != 0.0 ? 255.0 : (y-xmin) / (ymax-ymin)*255.0; - z = zmax-zmin != 0.0 ? 255.0 : (z-xmin) / (zmax-zmin)*255.0; + x = xmax - xmin != 0.0 ? 255.0 : (x - xmin) / (xmax - xmin)*255.0; + y = ymax - ymin != 0.0 ? 255.0 : (y - xmin) / (ymax - ymin)*255.0; + z = zmax - zmin != 0.0 ? 255.0 : (z - xmin) / (zmax - zmin)*255.0; DstData[i] = (unsigned char)MEBound(0, (int)x, 255); - DstData[i+1] = (unsigned char)MEBound(0, (int)y, 255); - DstData[i+2] = (unsigned char)MEBound(0, (int)z, 255); + DstData[i + 1] = (unsigned char)MEBound(0, (int)y, 255); + DstData[i + 2] = (unsigned char)MEBound(0, (int)z, 255); } ME_RELEASE_IPLIMAGE(cvImg); cvImg = TempImg; diff --git a/package_bgs/ck/MEImage.hpp b/package_bgs/LBP_MRF/MEImage.hpp similarity index 97% rename from package_bgs/ck/MEImage.hpp rename to package_bgs/LBP_MRF/MEImage.hpp index 41ada3b..3a52a77 100644 --- a/package_bgs/ck/MEImage.hpp +++ b/package_bgs/LBP_MRF/MEImage.hpp @@ -1,4 +1,20 @@ /* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* * This file is part of the AiBO+ project * * Copyright (C) 2005-2013 Csaba Kertész (csaba.kertesz@gmail.com) @@ -18,19 +34,17 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * */ +#pragma once -#ifndef MEImage_H -#define MEImage_H - -/** - * @addtogroup mindeye - * @{ - */ + /** + * @addtogroup mindeye + * @{ + */ -/** - * MEImage - * @brief Basic image functions - */ + /** + * MEImage + * @brief Basic image functions + */ class MEImage { public: @@ -995,5 +1009,3 @@ class MEImage }; /** @} */ - -#endif diff --git a/package_bgs/ck/MotionDetection.cpp b/package_bgs/LBP_MRF/MotionDetection.cpp similarity index 91% rename from package_bgs/ck/MotionDetection.cpp rename to package_bgs/LBP_MRF/MotionDetection.cpp index 5ac096a..e591faa 100644 --- a/package_bgs/ck/MotionDetection.cpp +++ b/package_bgs/LBP_MRF/MotionDetection.cpp @@ -1,4 +1,20 @@ /* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* * This file is part of the AiBO+ project * * Copyright (C) 2005-2013 Csaba Kertész (csaba.kertesz@gmail.com) @@ -52,15 +68,15 @@ struct MEPixelDataType }; MotionDetection::MotionDetection(DetectorType mode) : -MDMode(md_NotDefined), MDDataState(ps_Uninitialized), Frames(0), ReadyMask(false), -HUColorSpace(MEImage::csc_RGBtoCIELuv), HULBPMode(MEImage::lbp_Special), -HUHistogramsPerPixel(3), HUHistogramArea(5), HUHistogramBins(8), -HUImageWidth(-1), HUImageHeight(-1), HULBPPixelData(NULL), -HUPrThres(0.75), HUBackgrThres(0.95), HUHistLRate(0.01), HUWeightsLRate(0.01), -HUSamplePixels(-1), HUDesiredSamplePixels(-1), HUMinCutWeight(8.0), -HUOFDataState(ps_Uninitialized), HUOFPointsNumber(-1), -HUOFCamMovementX(0), MaxTrackedPoints(0), HUOFFrames(-1), -HUOFCamMovement(false) + MDMode(md_NotDefined), MDDataState(ps_Uninitialized), Frames(0), ReadyMask(false), + HUColorSpace(MEImage::csc_RGBtoCIELuv), HULBPMode(MEImage::lbp_Special), + HUHistogramsPerPixel(3), HUHistogramArea(5), HUHistogramBins(8), + HUImageWidth(-1), HUImageHeight(-1), HULBPPixelData(NULL), + HUPrThres(0.75), HUBackgrThres(0.95), HUHistLRate(0.01), HUWeightsLRate(0.01), + HUSamplePixels(-1), HUDesiredSamplePixels(-1), HUMinCutWeight(8.0), + HUOFDataState(ps_Uninitialized), HUOFPointsNumber(-1), + HUOFCamMovementX(0), MaxTrackedPoints(0), HUOFFrames(-1), + HUOFCamMovement(false) { HUOFPyramid = NULL; HUOFPrevPyramid = NULL; @@ -357,13 +373,13 @@ void MotionDetection::InitHUData(int imagewidth, int imageheight) for (int i = 0; i < HUImageWidth / 2; ++i) for (int i1 = 0; i1 < HUImageHeight; ++i1) { - HULBPPixelData[i][i1] = new MEPixelDataType; - HULBPPixelData[i][i1]->Weights = new float[HUHistogramsPerPixel]; - HULBPPixelData[i][i1]->BackgroundHistogram = new bool[HUHistogramsPerPixel]; - HULBPPixelData[i][i1]->Histograms = new float*[HUHistogramsPerPixel]; - for (int i2 = 0; i2 < HUHistogramsPerPixel; ++i2) - HULBPPixelData[i][i1]->Histograms[i2] = new float[HUHistogramBins]; - HULBPPixelData[i][i1]->PreviousHistogram = new float[HUHistogramBins]; + HULBPPixelData[i][i1] = new MEPixelDataType; + HULBPPixelData[i][i1]->Weights = new float[HUHistogramsPerPixel]; + HULBPPixelData[i][i1]->BackgroundHistogram = new bool[HUHistogramsPerPixel]; + HULBPPixelData[i][i1]->Histograms = new float*[HUHistogramsPerPixel]; + for (int i2 = 0; i2 < HUHistogramsPerPixel; ++i2) + HULBPPixelData[i][i1]->Histograms[i2] = new float[HUHistogramBins]; + HULBPPixelData[i][i1]->PreviousHistogram = new float[HUHistogramBins]; } // Allocate auxiliary variables @@ -399,8 +415,8 @@ void MotionDetection::InitHUOFData(int imagewidth, int imageheight) HUOFPointsNumber = imagewidth*imageheight / 1000; HUOFPyramid = cvCreateImage(cvSize(imagewidth, imageheight), 8, 1); HUOFPrevPyramid = cvCreateImage(cvSize(imagewidth, imageheight), 8, 1); - HUOFPoints[0] = (CvPoint2D32f*)cvAlloc(HUOFPointsNumber*sizeof(HUOFPoints[0][0])); - HUOFPoints[1] = (CvPoint2D32f*)cvAlloc(HUOFPointsNumber*sizeof(HUOFPoints[1][0])); + HUOFPoints[0] = (CvPoint2D32f*)cvAlloc(HUOFPointsNumber * sizeof(HUOFPoints[0][0])); + HUOFPoints[1] = (CvPoint2D32f*)cvAlloc(HUOFPointsNumber * sizeof(HUOFPoints[1][0])); } } @@ -412,13 +428,13 @@ void MotionDetection::ReleaseHUData() for (int i = 0; i < HUImageWidth / 2; i++) for (int i1 = 0; i1 < HUImageHeight; i1++) { - delete[] HULBPPixelData[i][i1]->PreviousHistogram; - for (int i2 = 0; i2 < HUHistogramsPerPixel; ++i2) - delete[] HULBPPixelData[i][i1]->Histograms[i2]; - delete[] HULBPPixelData[i][i1]->Histograms; - delete[] HULBPPixelData[i][i1]->BackgroundHistogram; - delete[] HULBPPixelData[i][i1]->Weights; - delete HULBPPixelData[i][i1]; + delete[] HULBPPixelData[i][i1]->PreviousHistogram; + for (int i2 = 0; i2 < HUHistogramsPerPixel; ++i2) + delete[] HULBPPixelData[i][i1]->Histograms[i2]; + delete[] HULBPPixelData[i][i1]->Histograms; + delete[] HULBPPixelData[i][i1]->BackgroundHistogram; + delete[] HULBPPixelData[i][i1]->Weights; + delete HULBPPixelData[i][i1]; } for (int i = 0; i < HUImageWidth / 2; i++) @@ -486,15 +502,15 @@ void MotionDetection::ClearHUData() for (int i = (HUImageWidth / 2) - 1; i >= 0; --i) for (int i1 = HUImageHeight - 1; i1 >= 0; --i1) { - for (int i2 = HUHistogramsPerPixel - 1; i2 >= 0; --i2) - { - memset(HULBPPixelData[i][i1]->Histograms[i2], 0, - HUHistogramBins*sizeof(float)); - HULBPPixelData[i][i1]->Weights[i2] = 1.0 / HUHistogramsPerPixel; - HULBPPixelData[i][i1]->BackgroundHistogram[i2] = true; - } - HULBPPixelData[i][i1]->BackgroundRate = 1.0; - HULBPPixelData[i][i1]->LifeCycle = 0; + for (int i2 = HUHistogramsPerPixel - 1; i2 >= 0; --i2) + { + memset(HULBPPixelData[i][i1]->Histograms[i2], 0, + HUHistogramBins * sizeof(float)); + HULBPPixelData[i][i1]->Weights[i2] = 1.0 / HUHistogramsPerPixel; + HULBPPixelData[i][i1]->BackgroundHistogram[i2] = true; + } + HULBPPixelData[i][i1]->BackgroundRate = 1.0; + HULBPPixelData[i][i1]->LifeCycle = 0; } MDDataState = ps_Initialized; } @@ -563,13 +579,13 @@ void MotionDetection::DetectMotionsHU(MEImage& image) else if (Frames > 1) { - PreviousImage = CurrentImage; - CurrentImage = image; - // Optical flow correction of the camera movements - if (MDMode == md_DLBPHistograms) - { - OpticalFlowCorrection(); - } + PreviousImage = CurrentImage; + CurrentImage = image; + // Optical flow correction of the camera movements + if (MDMode == md_DLBPHistograms) + { + OpticalFlowCorrection(); + } } newimage.ConvertToGrayscale(MEImage::g_OpenCV); @@ -915,17 +931,17 @@ void MotionDetection::UpdateHUPixelData(MEPixelDataType* PixelData, const float for (int i1 = HUHistogramsPerPixel - 1; i1 >= 2; --i1) for (int i = i1; i >= 1; --i) { - if (Weights[i][1] <= Weights[i - 1][1]) - { - float tmp = Weights[i][0]; - float tmp2 = Weights[i][1]; + if (Weights[i][1] <= Weights[i - 1][1]) + { + float tmp = Weights[i][0]; + float tmp2 = Weights[i][1]; - Weights[i][0] = Weights[i - 1][0]; - Weights[i][1] = Weights[i - 1][1]; + Weights[i][0] = Weights[i - 1][0]; + Weights[i][1] = Weights[i - 1][1]; - Weights[i - 1][0] = tmp; - Weights[i - 1][1] = tmp2; - } + Weights[i - 1][0] = tmp; + Weights[i - 1][1] = tmp2; + } } float Sum = 0; @@ -1056,7 +1072,7 @@ void MotionDetection::OpticalFlowCorrection() if ((Distances[i][2] > Distances[i - 1][2]) || ((Distances[i][2] == Distances[i - 1][2]) && (abs(Distances[i][0]) + abs(Distances[i][1]) < - abs(Distances[i - 1][0]) + abs(Distances[i - 1][1])))) + abs(Distances[i - 1][0]) + abs(Distances[i - 1][1])))) { int tmp = Distances[i][0]; int tmp2 = Distances[i][1]; @@ -1331,10 +1347,10 @@ void MotionDetection::GetMotionsMaskHU(MEImage& mask_image) MaskImgData[RowStart + x + (HUHistogramArea / 2)] = ((int)(x > 1 && HULBPPixelData[(x / 2) - 1][y]->BackgroundRate == 0.0) + (int)(x < mask_image.GetWidth() - HUHistogramArea - 1 && - HULBPPixelData[(x / 2) + 1][y]->BackgroundRate == 0.0) + - (int)(y > 0 && HULBPPixelData[x / 2][y - 1]->BackgroundRate == 0.0) + - (int)(y < mask_image.GetHeight() - HUHistogramArea && - HULBPPixelData[x / 2][y + 1]->BackgroundRate == 0.0) > 1) + HULBPPixelData[(x / 2) + 1][y]->BackgroundRate == 0.0) + + (int)(y > 0 && HULBPPixelData[x / 2][y - 1]->BackgroundRate == 0.0) + + (int)(y < mask_image.GetHeight() - HUHistogramArea && + HULBPPixelData[x / 2][y + 1]->BackgroundRate == 0.0) > 1) ? 255 : 0; } } diff --git a/package_bgs/ck/MotionDetection.hpp b/package_bgs/LBP_MRF/MotionDetection.hpp similarity index 93% rename from package_bgs/ck/MotionDetection.hpp rename to package_bgs/LBP_MRF/MotionDetection.hpp index 6e11521..e58c93c 100644 --- a/package_bgs/ck/MotionDetection.hpp +++ b/package_bgs/LBP_MRF/MotionDetection.hpp @@ -1,4 +1,20 @@ /* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* * This file is part of the AiBO+ project * * Copyright (C) 2005-2013 Csaba Kertész (csaba.kertesz@gmail.com) @@ -18,14 +34,12 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * */ +#pragma once -#ifndef MotionDetection_hpp -#define MotionDetection_hpp - -/** - * @addtogroup mindeye - * @{ - */ + /** + * @addtogroup mindeye + * @{ + */ #include "MEDefs.hpp" #include "MEImage.hpp" @@ -181,7 +195,7 @@ class MotionDetection */ void CalculateResults(MEImage& referenceimage, int& tnegatives, int& tpositives, - int& ttnegatives, int& ttpositives); + int& ttnegatives, int& ttpositives); private: @@ -397,5 +411,3 @@ class MotionDetection }; /** @} */ - -#endif diff --git a/package_bgs/ck/block.h b/package_bgs/LBP_MRF/block.h similarity index 51% rename from package_bgs/ck/block.h rename to package_bgs/LBP_MRF/block.h index 98b1201..bd1ab67 100644 --- a/package_bgs/ck/block.h +++ b/package_bgs/LBP_MRF/block.h @@ -1,3 +1,19 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ /* block.h */ /* Copyright 2001 Vladimir Kolmogorov (vnk@cs.cornell.edu), Yuri Boykov (yuri@csd.uwo.ca). @@ -16,8 +32,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - - /* Template classes Block and DBlock Implement adding and deleting items of the same type in blocks. @@ -105,9 +119,7 @@ simultaneously at earlier moments. All memory is deallocated only when the destructor is called. */ - -#ifndef __BLOCK_H__ -#define __BLOCK_H__ +#pragma once #include #include @@ -118,170 +130,168 @@ namespace ck { - template class Block - { - public: - /* Constructor. Arguments are the block size and +template class Block +{ +public: + /* Constructor. Arguments are the block size and (optionally) the pointer to the function which will be called if allocation failed; the message passed to this function is "Not enough memory!" */ - Block(int size, void(*err_function)(char *) = NULL) { first = last = NULL; block_size = size; error_function = err_function; } + Block(int size, void(*err_function)(char *) = NULL) { first = last = NULL; block_size = size; error_function = err_function; } - /* Destructor. Deallocates all items added so far */ - ~Block() { while (first) { block *next = first->next; delete first; first = next; } } + /* Destructor. Deallocates all items added so far */ + ~Block() { while (first) { block *next = first->next; delete first; first = next; } } - /* Allocates 'num' consecutive items; returns pointer + /* Allocates 'num' consecutive items; returns pointer to the first item. 'num' cannot be greater than the block size since items must fit in one block */ - Type *New(int num = 1) - { - Type *t; + Type *New(int num = 1) + { + Type *t; - if (!last || last->current + num > last->last) + if (!last || last->current + num > last->last) + { + if (last && last->next) last = last->next; + else { - if (last && last->next) last = last->next; - else - { - block *next = (block *) new char[sizeof(block) + (block_size - 1)*sizeof(Type)]; - if (!next) { fprintf(stderr, "Not enough memory!"); exit(1); } - if (last) last->next = next; - else first = next; - last = next; - last->current = &(last->data[0]); - last->last = last->current + block_size; - last->next = NULL; - } + block *next = (block *) new char[sizeof(block) + (block_size - 1)*sizeof(Type)]; + if (!next) { fprintf(stderr, "Not enough memory!"); exit(1); } + if (last) last->next = next; + else first = next; + last = next; + last->current = &(last->data[0]); + last->last = last->current + block_size; + last->next = NULL; } - - t = last->current; - last->current += num; - return t; } - /* Returns the first item (or NULL, if no items were added) */ - Type *ScanFirst() - { - scan_current_block = first; - if (!scan_current_block) return NULL; - scan_current_data = &(scan_current_block->data[0]); - return scan_current_data++; - } + t = last->current; + last->current += num; + return t; + } + + /* Returns the first item (or NULL, if no items were added) */ + Type *ScanFirst() + { + scan_current_block = first; + if (!scan_current_block) return NULL; + scan_current_data = &(scan_current_block->data[0]); + return scan_current_data++; + } - /* Returns the next item (or NULL, if all items have been read) + /* Returns the next item (or NULL, if all items have been read) Can be called only if previous ScanFirst() or ScanNext() call returned not NULL. */ - Type *ScanNext() + Type *ScanNext() + { + if (scan_current_data >= scan_current_block->current) { - if (scan_current_data >= scan_current_block->current) - { - scan_current_block = scan_current_block->next; - if (!scan_current_block) return NULL; - scan_current_data = &(scan_current_block->data[0]); - } - return scan_current_data++; + scan_current_block = scan_current_block->next; + if (!scan_current_block) return NULL; + scan_current_data = &(scan_current_block->data[0]); } + return scan_current_data++; + } - /* Marks all elements as empty */ - void Reset() + /* Marks all elements as empty */ + void Reset() + { + block *b; + if (!first) return; + for (b = first;; b = b->next) { - block *b; - if (!first) return; - for (b = first;; b = b->next) - { - b->current = &(b->data[0]); - if (b == last) break; - } - last = first; + b->current = &(b->data[0]); + if (b == last) break; } + last = first; + } - /***********************************************************************/ + /***********************************************************************/ - private: +private: - typedef struct block_st - { - Type *current, *last; - struct block_st *next; - Type data[1]; - } block; + typedef struct block_st + { + Type *current, *last; + struct block_st *next; + Type data[1]; + } block; - int block_size; - block *first; - block *last; + int block_size; + block *first; + block *last; - block *scan_current_block; - Type *scan_current_data; + block *scan_current_block; + Type *scan_current_data; - void(*error_function)(char *); - }; + void(*error_function)(char *); +}; - /***********************************************************************/ - /***********************************************************************/ - /***********************************************************************/ +/***********************************************************************/ +/***********************************************************************/ +/***********************************************************************/ - template class DBlock - { - public: - /* Constructor. Arguments are the block size and +template class DBlock +{ +public: + /* Constructor. Arguments are the block size and (optionally) the pointer to the function which will be called if allocation failed; the message passed to this function is "Not enough memory!" */ - DBlock(int size, void(*err_function)(char *) = NULL) { first = NULL; first_free = NULL; block_size = size; error_function = err_function; } - - /* Destructor. Deallocates all items added so far */ - ~DBlock() { while (first) { block *next = first->next; delete first; first = next; } } - - /* Allocates one item */ - Type *New() - { - block_item *item; + DBlock(int size, void(*err_function)(char *) = NULL) { first = NULL; first_free = NULL; block_size = size; error_function = err_function; } - if (!first_free) - { - block *next = first; - first = (block *) new char[sizeof(block) + (block_size - 1)*sizeof(block_item)]; - if (!first) { fprintf(stderr, "Not enough memory!"); exit(1); } - first_free = &(first->data[0]); - for (item = first_free; item < first_free + block_size - 1; item++) - item->next_free = item + 1; - item->next_free = NULL; - first->next = next; - } + /* Destructor. Deallocates all items added so far */ + ~DBlock() { while (first) { block *next = first->next; delete first; first = next; } } - item = first_free; - first_free = item->next_free; - return (Type *)item; - } + /* Allocates one item */ + Type *New() + { + block_item *item; - /* Deletes an item allocated previously */ - void Delete(Type *t) + if (!first_free) { - ((block_item *)t)->next_free = first_free; - first_free = (block_item *)t; + block *next = first; + first = (block *) new char[sizeof(block) + (block_size - 1)*sizeof(block_item)]; + if (!first) { fprintf(stderr, "Not enough memory!"); exit(1); } + first_free = &(first->data[0]); + for (item = first_free; item < first_free + block_size - 1; item++) + item->next_free = item + 1; + item->next_free = NULL; + first->next = next; } - /***********************************************************************/ + item = first_free; + first_free = item->next_free; + return (Type *)item; + } - private: + /* Deletes an item allocated previously */ + void Delete(Type *t) + { + ((block_item *)t)->next_free = first_free; + first_free = (block_item *)t; + } - typedef union block_item_st - { - Type t; - block_item_st *next_free; - } block_item; + /***********************************************************************/ - typedef struct block_st - { - struct block_st *next; - block_item data[1]; - } block; +private: - int block_size; - block *first; - block_item *first_free; + typedef union block_item_st + { + Type t; + block_item_st *next_free; + } block_item; - void(*error_function)(char *); - }; -} + typedef struct block_st + { + struct block_st *next; + block_item data[1]; + } block; + + int block_size; + block *first; + block_item *first_free; -#endif + void(*error_function)(char *); +}; +} diff --git a/package_bgs/ck/graph.cpp b/package_bgs/LBP_MRF/graph.cpp similarity index 78% rename from package_bgs/ck/graph.cpp rename to package_bgs/LBP_MRF/graph.cpp index daed395..9301250 100644 --- a/package_bgs/ck/graph.cpp +++ b/package_bgs/LBP_MRF/graph.cpp @@ -1,3 +1,19 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ /* graph.cpp */ /* Copyright 2001 Vladimir Kolmogorov (vnk@cs.cornell.edu), Yuri Boykov (yuri@csd.uwo.ca). @@ -80,4 +96,4 @@ namespace ck flow += (cap_source < cap_sink) ? cap_source : cap_sink; ((node*)i)->tr_cap = cap_source - cap_sink; } -} \ No newline at end of file +} diff --git a/package_bgs/ck/graph.h b/package_bgs/LBP_MRF/graph.h similarity index 60% rename from package_bgs/ck/graph.h rename to package_bgs/LBP_MRF/graph.h index b6799e4..e7b83c7 100644 --- a/package_bgs/ck/graph.h +++ b/package_bgs/LBP_MRF/graph.h @@ -1,3 +1,19 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ /* graph.h */ /* This software library implements the maxflow algorithm @@ -16,41 +32,36 @@ If you use this software for research purposes, you should cite the aforementioned paper in any resulting publication. */ - -/* - Copyright 2001 Vladimir Kolmogorov (vnk@cs.cornell.edu), Yuri Boykov (yuri@csd.uwo.ca). - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - - -/* - For description, example usage, discussion of graph representation - and memory usage see README.TXT. - */ - -#ifndef __GRAPH_H__ -#define __GRAPH_H__ + /* + Copyright 2001 Vladimir Kolmogorov (vnk@cs.cornell.edu), Yuri Boykov (yuri@csd.uwo.ca). + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + /* + For description, example usage, discussion of graph representation + and memory usage see README.TXT. + */ +#pragma once #include "block.h" -/* - Nodes, arcs and pointers to nodes are - added in blocks for memory and time efficiency. - Below are numbers of items in blocks - */ + /* + Nodes, arcs and pointers to nodes are + added in blocks for memory and time efficiency. + Below are numbers of items in blocks + */ #define NODE_BLOCK_SIZE 512 #define ARC_BLOCK_SIZE 1024 #define NODEPTR_BLOCK_SIZE 128 @@ -67,7 +78,7 @@ namespace ck } termtype; /* terminals */ /* Type of edge weights. - Can be changed to char, int, float, double, ... */ + Can be changed to char, int, float, double, ... */ typedef short captype; /* Type of total flow */ typedef int flowtype; @@ -77,9 +88,9 @@ namespace ck /* interface functions */ /* Constructor. Optional argument is the pointer to the - function which will be called if an error occurs; - an error message is passed to this function. If this - argument is omitted, exit(1) will be called. */ + function which will be called if an error occurs; + an error message is passed to this function. If this + argument is omitted, exit(1) will be called. */ Graph(void(*err_function)(char *) = NULL); /* Destructor */ @@ -89,21 +100,21 @@ namespace ck node_id add_node(); /* Adds a bidirectional edge between 'from' and 'to' - with the weights 'cap' and 'rev_cap' */ + with the weights 'cap' and 'rev_cap' */ void add_edge(node_id from, node_id to, captype cap, captype rev_cap); /* Sets the weights of the edges 'SOURCE->i' and 'i->SINK' - Can be called at most once for each node before any call to 'add_tweights'. - Weights can be negative */ + Can be called at most once for each node before any call to 'add_tweights'. + Weights can be negative */ void set_tweights(node_id i, captype cap_source, captype cap_sink); /* Adds new edges 'SOURCE->i' and 'i->SINK' with corresponding weights - Can be called multiple times for each node. - Weights can be negative */ + Can be called multiple times for each node. + Weights can be negative */ void add_tweights(node_id i, captype cap_source, captype cap_sink); /* After the maxflow is computed, this function returns to which - segment the node 'i' belongs (Graph::SOURCE or Graph::SINK) */ + segment the node 'i' belongs (Graph::SOURCE or Graph::SINK) */ termtype what_segment(node_id i); /* Computes the maxflow. Can be called only once. */ @@ -125,13 +136,13 @@ namespace ck arc_st *parent; /* node's parent */ node_st *next; /* pointer to the next active node - (or to itself if it is the last node in the list) */ + (or to itself if it is the last node in the list) */ int TS; /* timestamp showing when DIST was computed */ int DIST; /* distance to the terminal */ short is_sink; /* flag showing whether the node is in the source or in the sink tree */ captype tr_cap; /* if tr_cap > 0 then tr_cap is residual capacity of the arc SOURCE->node - otherwise -tr_cap is residual capacity of the arc node->SINK */ + otherwise -tr_cap is residual capacity of the arc node->SINK */ } node; /* arc structure */ @@ -156,8 +167,8 @@ namespace ck DBlock *nodeptr_block; void(*error_function)(char *); /* this function is called if a error occurs, - with a corresponding error message - (or exit(1) is called if it's NULL) */ + with a corresponding error message + (or exit(1) is called if it's NULL) */ flowtype flow; /* total flow */ @@ -179,5 +190,3 @@ namespace ck void process_sink_orphan(node *i); }; } - -#endif diff --git a/package_bgs/ck/maxflow.cpp b/package_bgs/LBP_MRF/maxflow.cpp similarity index 67% rename from package_bgs/ck/maxflow.cpp rename to package_bgs/LBP_MRF/maxflow.cpp index 53d3705..6ddec50 100644 --- a/package_bgs/ck/maxflow.cpp +++ b/package_bgs/LBP_MRF/maxflow.cpp @@ -1,3 +1,19 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ /* maxflow.cpp */ /* Copyright 2001 Vladimir Kolmogorov (vnk@cs.cornell.edu), Yuri Boykov (yuri@csd.uwo.ca). @@ -16,33 +32,31 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - - #include #include "graph.h" -/* - special constants for node->parent - */ + /* + special constants for node->parent + */ #define TERMINAL ( (arc *) 1 ) /* to terminal */ #define ORPHAN ( (arc *) 2 ) /* orphan */ #define INFINITE_D 1000000000 /* infinite distance to the terminal */ -/***********************************************************************/ + /***********************************************************************/ -/* - Functions for processing active list. - i->next points to the next node in the list - (or to i, if i is the last node in the list). - If i->next is NULL iff i is not in the list. - - There are two queues. Active nodes are added - to the end of the second queue and read from - the front of the first queue. If the first queue - is empty, it is replaced by the second queue - (and the second queue becomes empty). - */ + /* + Functions for processing active list. + i->next points to the next node in the list + (or to i, if i is the last node in the list). + If i->next is NULL iff i is not in the list. + + There are two queues. Active nodes are added + to the end of the second queue and read from + the front of the first queue. If the first queue + is empty, it is replaced by the second queue + (and the second queue becomes empty). + */ namespace ck { inline void Graph::set_active(node *i) @@ -58,10 +72,10 @@ namespace ck } /* - Returns the next active node. - If it is connected to the sink, it stays in the list, - otherwise it is removed from the list - */ + Returns the next active node. + If it is connected to the sink, it stays in the list, + otherwise it is removed from the list + */ inline Graph::node * Graph::next_active() { node *i; @@ -232,45 +246,45 @@ namespace ck for (a0 = i->first; a0; a0 = a0->next) if (a0->sister->r_cap) { - j = a0->head; - if (!j->is_sink && (a = j->parent)) - { - /* checking the origin of j */ - d = 0; - while (1) - { - if (j->TS == TIME) - { - d += j->DIST; - break; - } - a = j->parent; - d++; - if (a == TERMINAL) - { - j->TS = TIME; - j->DIST = 1; - break; - } - if (a == ORPHAN) { d = INFINITE_D; break; } - j = a->head; - } - if (d < INFINITE_D) /* j originates from the source - done */ + j = a0->head; + if (!j->is_sink && (a = j->parent)) { - if (d < d_min) + /* checking the origin of j */ + d = 0; + while (1) { - a0_min = a0; - d_min = d; + if (j->TS == TIME) + { + d += j->DIST; + break; + } + a = j->parent; + d++; + if (a == TERMINAL) + { + j->TS = TIME; + j->DIST = 1; + break; + } + if (a == ORPHAN) { d = INFINITE_D; break; } + j = a->head; } - /* set marks along the path */ - for (j = a0->head; j->TS != TIME; j = j->parent->head) + if (d < INFINITE_D) /* j originates from the source - done */ { - j->TS = TIME; - j->DIST = d--; + if (d < d_min) + { + a0_min = a0; + d_min = d; + } + /* set marks along the path */ + for (j = a0->head; j->TS != TIME; j = j->parent->head) + { + j->TS = TIME; + j->DIST = d--; + } } } } - } if ((i->parent = a0_min)) { @@ -316,45 +330,45 @@ namespace ck for (a0 = i->first; a0; a0 = a0->next) if (a0->r_cap) { - j = a0->head; - if (j->is_sink && (a = j->parent)) - { - /* checking the origin of j */ - d = 0; - while (1) - { - if (j->TS == TIME) - { - d += j->DIST; - break; - } - a = j->parent; - d++; - if (a == TERMINAL) - { - j->TS = TIME; - j->DIST = 1; - break; - } - if (a == ORPHAN) { d = INFINITE_D; break; } - j = a->head; - } - if (d < INFINITE_D) /* j originates from the sink - done */ + j = a0->head; + if (j->is_sink && (a = j->parent)) { - if (d < d_min) + /* checking the origin of j */ + d = 0; + while (1) { - a0_min = a0; - d_min = d; + if (j->TS == TIME) + { + d += j->DIST; + break; + } + a = j->parent; + d++; + if (a == TERMINAL) + { + j->TS = TIME; + j->DIST = 1; + break; + } + if (a == ORPHAN) { d = INFINITE_D; break; } + j = a->head; } - /* set marks along the path */ - for (j = a0->head; j->TS != TIME; j = j->parent->head) + if (d < INFINITE_D) /* j originates from the sink - done */ { - j->TS = TIME; - j->DIST = d--; + if (d < d_min) + { + a0_min = a0; + d_min = d; + } + /* set marks along the path */ + for (j = a0->head; j->TS != TIME; j = j->parent->head) + { + j->TS = TIME; + j->DIST = d--; + } } } } - } if ((i->parent = a0_min)) { @@ -419,24 +433,24 @@ namespace ck for (a = i->first; a; a = a->next) if (a->r_cap) { - j = a->head; - if (!j->parent) - { - j->is_sink = 0; - j->parent = a->sister; - j->TS = i->TS; - j->DIST = i->DIST + 1; - set_active(j); - } - else if (j->is_sink) break; - else if (j->TS <= i->TS && - j->DIST > i->DIST) - { - /* heuristic - trying to make the distance from j to the source shorter */ - j->parent = a->sister; - j->TS = i->TS; - j->DIST = i->DIST + 1; - } + j = a->head; + if (!j->parent) + { + j->is_sink = 0; + j->parent = a->sister; + j->TS = i->TS; + j->DIST = i->DIST + 1; + set_active(j); + } + else if (j->is_sink) break; + else if (j->TS <= i->TS && + j->DIST > i->DIST) + { + /* heuristic - trying to make the distance from j to the source shorter */ + j->parent = a->sister; + j->TS = i->TS; + j->DIST = i->DIST + 1; + } } } else @@ -445,24 +459,24 @@ namespace ck for (a = i->first; a; a = a->next) if (a->sister->r_cap) { - j = a->head; - if (!j->parent) - { - j->is_sink = 1; - j->parent = a->sister; - j->TS = i->TS; - j->DIST = i->DIST + 1; - set_active(j); - } - else if (!j->is_sink) { a = a->sister; break; } - else if (j->TS <= i->TS && - j->DIST > i->DIST) - { - /* heuristic - trying to make the distance from j to the sink shorter */ - j->parent = a->sister; - j->TS = i->TS; - j->DIST = i->DIST + 1; - } + j = a->head; + if (!j->parent) + { + j->is_sink = 1; + j->parent = a->sister; + j->TS = i->TS; + j->DIST = i->DIST + 1; + set_active(j); + } + else if (!j->is_sink) { a = a->sister; break; } + else if (j->TS <= i->TS && + j->DIST > i->DIST) + { + /* heuristic - trying to make the distance from j to the sink shorter */ + j->parent = a->sister; + j->TS = i->TS; + j->DIST = i->DIST + 1; + } } } @@ -513,4 +527,4 @@ namespace ck return SINK; } -} \ No newline at end of file +} diff --git a/package_bgs/LBSP/BackgroundSubtractorLBSP.cpp b/package_bgs/LBSP/BackgroundSubtractorLBSP.cpp new file mode 100644 index 0000000..2865a30 --- /dev/null +++ b/package_bgs/LBSP/BackgroundSubtractorLBSP.cpp @@ -0,0 +1,85 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "BackgroundSubtractorLBSP.h" +#include "DistanceUtils.h" +#include "RandUtils.h" +#include +#include +#include +#include +#include + +#ifndef SIZE_MAX +# if __WORDSIZE == 64 +# define SIZE_MAX (18446744073709551615UL) +# else +# define SIZE_MAX (4294967295U) +# endif +#endif + +// local define used to determine the default median blur kernel size +#define DEFAULT_MEDIAN_BLUR_KERNEL_SIZE (9) + +BackgroundSubtractorLBSP::BackgroundSubtractorLBSP(float fRelLBSPThreshold, size_t nLBSPThresholdOffset) + : m_nImgChannels(0) + , m_nImgType(0) + , m_nLBSPThresholdOffset(nLBSPThresholdOffset) + , m_fRelLBSPThreshold(fRelLBSPThreshold) + , m_nTotPxCount(0) + , m_nTotRelevantPxCount(0) + , m_nFrameIndex(SIZE_MAX) + , m_nFramesSinceLastReset(0) + , m_nModelResetCooldown(0) + , m_aPxIdxLUT(nullptr) + , m_aPxInfoLUT(nullptr) + , m_nDefaultMedianBlurKernelSize(DEFAULT_MEDIAN_BLUR_KERNEL_SIZE) + , m_bInitialized(false) + , m_bAutoModelResetEnabled(true) + , m_bUsingMovingCamera(false) + , nDebugCoordX(0), nDebugCoordY(0) { + CV_Assert(m_fRelLBSPThreshold >= 0); +} + +BackgroundSubtractorLBSP::~BackgroundSubtractorLBSP() {} + +void BackgroundSubtractorLBSP::initialize(const cv::Mat& oInitImg) { + this->initialize(oInitImg, cv::Mat()); +} + +/*cv::AlgorithmInfo* BackgroundSubtractorLBSP::info() const { + return nullptr; +}*/ + +cv::Mat BackgroundSubtractorLBSP::getROICopy() const { + return m_oROI.clone(); +} + +void BackgroundSubtractorLBSP::setROI(cv::Mat& oROI) { + LBSP::validateROI(oROI); + CV_Assert(cv::countNonZero(oROI) > 0); + if (m_bInitialized) { + cv::Mat oLatestBackgroundImage; + getBackgroundImage(oLatestBackgroundImage); + initialize(oLatestBackgroundImage, oROI); + } + else + m_oROI = oROI.clone(); +} + +void BackgroundSubtractorLBSP::setAutomaticModelReset(bool bVal) { + m_bAutoModelResetEnabled = bVal; +} diff --git a/package_bgs/LBSP/BackgroundSubtractorLBSP.h b/package_bgs/LBSP/BackgroundSubtractorLBSP.h new file mode 100644 index 0000000..ef0576c --- /dev/null +++ b/package_bgs/LBSP/BackgroundSubtractorLBSP.h @@ -0,0 +1,101 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include +#include +#include "LBSP.h" + +/*! + Local Binary Similarity Pattern (LBSP)-based change detection algorithm (abstract version/base class). + + For more details on the different parameters, see P.-L. St-Charles and G.-A. Bilodeau, "Improving Background + Subtraction using Local Binary Similarity Patterns", in WACV 2014, or G.-A. Bilodeau et al, "Change Detection + in Feature Space Using Local Binary Similarity Patterns", in CRV 2013. + + This algorithm is currently NOT thread-safe. + */ +class BackgroundSubtractorLBSP : public cv::BackgroundSubtractor { +public: + //! full constructor + BackgroundSubtractorLBSP(float fRelLBSPThreshold, size_t nLBSPThresholdOffset = 0); + //! default destructor + virtual ~BackgroundSubtractorLBSP(); + //! (re)initiaization method; needs to be called before starting background subtraction + virtual void initialize(const cv::Mat& oInitImg); + //! (re)initiaization method; needs to be called before starting background subtraction + virtual void initialize(const cv::Mat& oInitImg, const cv::Mat& oROI) = 0; + //! primary model update function; the learning param is used to override the internal learning speed (ignored when <= 0) + virtual void apply(cv::InputArray image, cv::OutputArray fgmask, double learningRate = 0) = 0; + //! unused, always returns nullptr + //virtual cv::AlgorithmInfo* info() const; + //! returns a copy of the ROI used for descriptor extraction + virtual cv::Mat getROICopy() const; + //! sets the ROI to be used for descriptor extraction (note: this function will reinit the model and return the usable ROI) + virtual void setROI(cv::Mat& oROI); + //! turns automatic model reset on or off + void setAutomaticModelReset(bool); + +protected: + struct PxInfoBase { + int nImgCoord_Y; + int nImgCoord_X; + size_t nModelIdx; + }; + //! background model ROI used for LBSP descriptor extraction (specific to the input image size) + cv::Mat m_oROI; + //! input image size + cv::Size m_oImgSize; + //! input image channel size + size_t m_nImgChannels; + //! input image type + int m_nImgType; + //! LBSP internal threshold offset value, used to reduce texture noise in dark regions + const size_t m_nLBSPThresholdOffset; + //! LBSP relative internal threshold (kept here since we don't keep an LBSP object) + const float m_fRelLBSPThreshold; + //! total number of pixels (depends on the input frame size) & total number of relevant pixels + size_t m_nTotPxCount, m_nTotRelevantPxCount; + //! current frame index, frame count since last model reset & model reset cooldown counters + size_t m_nFrameIndex, m_nFramesSinceLastReset, m_nModelResetCooldown; + //! pre-allocated internal LBSP threshold values LUT for all possible 8-bit intensities + size_t m_anLBSPThreshold_8bitLUT[UCHAR_MAX + 1]; + //! internal pixel index LUT for all relevant analysis regions (based on the provided ROI) + size_t* m_aPxIdxLUT; + //! internal pixel info LUT for all possible pixel indexes + PxInfoBase* m_aPxInfoLUT; + //! default kernel size for median blur post-proc filtering + const int m_nDefaultMedianBlurKernelSize; + //! specifies whether the algorithm is fully initialized or not + bool m_bInitialized; + //! specifies whether automatic model resets are enabled or not + bool m_bAutoModelResetEnabled; + //! specifies whether the camera is considered moving or not + bool m_bUsingMovingCamera; + //! copy of latest pixel intensities (used when refreshing model) + cv::Mat m_oLastColorFrame; + //! copy of latest descriptors (used when refreshing model) + cv::Mat m_oLastDescFrame; + //! the foreground mask generated by the method at [t-1] + cv::Mat m_oLastFGMask; + +public: + // ######## DEBUG PURPOSES ONLY ########## + int nDebugCoordX, nDebugCoordY; + std::string sDebugName; +}; + diff --git a/package_bgs/LBSP/BackgroundSubtractorLBSP_.cpp b/package_bgs/LBSP/BackgroundSubtractorLBSP_.cpp new file mode 100644 index 0000000..4e5d4a6 --- /dev/null +++ b/package_bgs/LBSP/BackgroundSubtractorLBSP_.cpp @@ -0,0 +1,79 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "BackgroundSubtractorLBSP_.h" +#include "DistanceUtils.h" +#include "RandUtils.h" +#include +#include +#include +#include +#include + +// local define used to determine the default median blur kernel size +#define DEFAULT_MEDIAN_BLUR_KERNEL_SIZE (9) + +BackgroundSubtractorLBSP_::BackgroundSubtractorLBSP_(float fRelLBSPThreshold, size_t nLBSPThresholdOffset) + : m_nImgChannels(0) + , m_nImgType(0) + , m_nLBSPThresholdOffset(nLBSPThresholdOffset) + , m_fRelLBSPThreshold(fRelLBSPThreshold) + , m_nTotPxCount(0) + , m_nTotRelevantPxCount(0) + , m_nFrameIndex(SIZE_MAX) + , m_nFramesSinceLastReset(0) + , m_nModelResetCooldown(0) + , m_aPxIdxLUT(nullptr) + , m_aPxInfoLUT(nullptr) + , m_nDefaultMedianBlurKernelSize(DEFAULT_MEDIAN_BLUR_KERNEL_SIZE) + , m_bInitialized(false) + , m_bAutoModelResetEnabled(true) + , m_bUsingMovingCamera(false) + , m_nDebugCoordX(0) + , m_nDebugCoordY(0) + , m_pDebugFS(nullptr) { + CV_Assert(m_fRelLBSPThreshold >= 0); +} + +BackgroundSubtractorLBSP_::~BackgroundSubtractorLBSP_() {} + +void BackgroundSubtractorLBSP_::initialize(const cv::Mat& oInitImg) { + this->initialize(oInitImg, cv::Mat()); +} +/* +cv::AlgorithmInfo* BackgroundSubtractorLBSP_::info() const { + return nullptr; +} +*/ +cv::Mat BackgroundSubtractorLBSP_::getROICopy() const { + return m_oROI.clone(); +} + +void BackgroundSubtractorLBSP_::setROI(cv::Mat& oROI) { + LBSP_::validateROI(oROI); + CV_Assert(cv::countNonZero(oROI) > 0); + if (m_bInitialized) { + cv::Mat oLatestBackgroundImage; + getBackgroundImage(oLatestBackgroundImage); + initialize(oLatestBackgroundImage, oROI); + } + else + m_oROI = oROI.clone(); +} + +void BackgroundSubtractorLBSP_::setAutomaticModelReset(bool bVal) { + m_bAutoModelResetEnabled = bVal; +} diff --git a/package_bgs/LBSP/BackgroundSubtractorLBSP_.h b/package_bgs/LBSP/BackgroundSubtractorLBSP_.h new file mode 100644 index 0000000..861f78a --- /dev/null +++ b/package_bgs/LBSP/BackgroundSubtractorLBSP_.h @@ -0,0 +1,107 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include +#include +#include "LBSP_.h" + +/*! + Local Binary Similarity Pattern (LBSP)-based change detection algorithm (abstract version/base class). + + For more details on the different parameters, see P.-L. St-Charles and G.-A. Bilodeau, "Improving Background + Subtraction using Local Binary Similarity Patterns", in WACV 2014, or G.-A. Bilodeau et al, "Change Detection + in Feature Space Using Local Binary Similarity Patterns", in CRV 2013. + + This algorithm is currently NOT thread-safe. + */ +class BackgroundSubtractorLBSP_ : public cv::BackgroundSubtractor { +public: + //! full constructor + BackgroundSubtractorLBSP_(float fRelLBSPThreshold, size_t nLBSPThresholdOffset = 0); + //! default destructor + virtual ~BackgroundSubtractorLBSP_(); + //! (re)initiaization method; needs to be called before starting background subtraction + virtual void initialize(const cv::Mat& oInitImg); + //! (re)initiaization method; needs to be called before starting background subtraction + virtual void initialize(const cv::Mat& oInitImg, const cv::Mat& oROI) = 0; + //! primary model update function; the learning param is used to override the internal learning speed (ignored when <= 0) + virtual void apply(cv::InputArray image, cv::OutputArray fgmask, double learningRate = 0) = 0; + //! unused, always returns nullptr + //virtual cv::AlgorithmInfo* info() const; + //! returns a copy of the ROI used for descriptor extraction + virtual cv::Mat getROICopy() const; + //! sets the ROI to be used for descriptor extraction (note: this function will reinit the model and return the usable ROI) + virtual void setROI(cv::Mat& oROI); + //! turns automatic model reset on or off + void setAutomaticModelReset(bool); + +protected: + struct PxInfoBase { + int nImgCoord_Y; + int nImgCoord_X; + size_t nModelIdx; + }; + //! background model ROI used for LBSP descriptor extraction (specific to the input image size) + cv::Mat m_oROI; + //! input image size + cv::Size m_oImgSize; + //! input image channel size + size_t m_nImgChannels; + //! input image type + int m_nImgType; + //! LBSP internal threshold offset value, used to reduce texture noise in dark regions + const size_t m_nLBSPThresholdOffset; + //! LBSP relative internal threshold (kept here since we don't keep an LBSP object) + const float m_fRelLBSPThreshold; + //! total number of pixels (depends on the input frame size) & total number of relevant pixels + size_t m_nTotPxCount, m_nTotRelevantPxCount; + //! current frame index, frame count since last model reset & model reset cooldown counters + size_t m_nFrameIndex, m_nFramesSinceLastReset, m_nModelResetCooldown; + //! pre-allocated internal LBSP threshold values LUT for all possible 8-bit intensities + size_t m_anLBSPThreshold_8bitLUT[UCHAR_MAX + 1]; + //! internal pixel index LUT for all relevant analysis regions (based on the provided ROI) + size_t* m_aPxIdxLUT; + //! internal pixel info LUT for all possible pixel indexes + PxInfoBase* m_aPxInfoLUT; + //! default kernel size for median blur post-proc filtering + const int m_nDefaultMedianBlurKernelSize; + //! specifies whether the algorithm is fully initialized or not + bool m_bInitialized; + //! specifies whether automatic model resets are enabled or not + bool m_bAutoModelResetEnabled; + //! specifies whether the camera is considered moving or not + bool m_bUsingMovingCamera; + //! copy of latest pixel intensities (used when refreshing model) + cv::Mat m_oLastColorFrame; + //! copy of latest descriptors (used when refreshing model) + cv::Mat m_oLastDescFrame; + //! the foreground mask generated by the method at [t-1] + cv::Mat m_oLastFGMask; + +private: + //! copy constructor -- disabled since this class (and its children) use lots of dynamic structs based on raw pointers + BackgroundSubtractorLBSP_(const BackgroundSubtractorLBSP_&); + //! assignment operator -- disabled since this class (and its children) use lots of dynamic structs based on raw pointers + BackgroundSubtractorLBSP_& operator=(const BackgroundSubtractorLBSP_&); + +public: + // ######## DEBUG PURPOSES ONLY ########## + int m_nDebugCoordX, m_nDebugCoordY; + std::string m_sDebugName; + cv::FileStorage* m_pDebugFS; +}; diff --git a/package_bgs/LBSP/BackgroundSubtractorLOBSTER.cpp b/package_bgs/LBSP/BackgroundSubtractorLOBSTER.cpp new file mode 100644 index 0000000..9648c5c --- /dev/null +++ b/package_bgs/LBSP/BackgroundSubtractorLOBSTER.cpp @@ -0,0 +1,342 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "BackgroundSubtractorLOBSTER.h" +#include "DistanceUtils.h" +#include "RandUtils.h" +#include +#include +#include +#include + +BackgroundSubtractorLOBSTER::BackgroundSubtractorLOBSTER(float fRelLBSPThreshold + , size_t nLBSPThresholdOffset + , size_t nDescDistThreshold + , size_t nColorDistThreshold + , size_t nBGSamples + , size_t nRequiredBGSamples) + : BackgroundSubtractorLBSP(fRelLBSPThreshold, nLBSPThresholdOffset) + , m_nColorDistThreshold(nColorDistThreshold) + , m_nDescDistThreshold(nDescDistThreshold) + , m_nBGSamples(nBGSamples) + , m_nRequiredBGSamples(nRequiredBGSamples) { + CV_Assert(m_nRequiredBGSamples <= m_nBGSamples); + m_bAutoModelResetEnabled = false; // @@@@@@ not supported here for now +} + +BackgroundSubtractorLOBSTER::~BackgroundSubtractorLOBSTER() { + if (m_aPxIdxLUT) + delete[] m_aPxIdxLUT; + if (m_aPxInfoLUT) + delete[] m_aPxInfoLUT; +} + +void BackgroundSubtractorLOBSTER::initialize(const cv::Mat& oInitImg, const cv::Mat& oROI) { + CV_Assert(!oInitImg.empty() && oInitImg.cols > 0 && oInitImg.rows > 0); + CV_Assert(oInitImg.isContinuous()); + CV_Assert(oInitImg.type() == CV_8UC1 || oInitImg.type() == CV_8UC3); + if (oInitImg.type() == CV_8UC3) { + std::vector voInitImgChannels; + cv::split(oInitImg, voInitImgChannels); + if (!cv::countNonZero((voInitImgChannels[0] != voInitImgChannels[1]) | (voInitImgChannels[2] != voInitImgChannels[1]))) + std::cout << std::endl << "\tBackgroundSubtractorLOBSTER : Warning, grayscale images should always be passed in CV_8UC1 format for optimal performance." << std::endl; + } + cv::Mat oNewBGROI; + if (oROI.empty() && (m_oROI.empty() || oROI.size() != oInitImg.size())) { + oNewBGROI.create(oInitImg.size(), CV_8UC1); + oNewBGROI = cv::Scalar_(UCHAR_MAX); + } + else if (oROI.empty()) + oNewBGROI = m_oROI; + else { + CV_Assert(oROI.size() == oInitImg.size() && oROI.type() == CV_8UC1); + CV_Assert(cv::countNonZero((oROI < UCHAR_MAX)&(oROI > 0)) == 0); + oNewBGROI = oROI.clone(); + } + LBSP::validateROI(oNewBGROI); + const size_t nROIPxCount = (size_t)cv::countNonZero(oNewBGROI); + CV_Assert(nROIPxCount > 0); + m_oROI = oNewBGROI; + m_oImgSize = oInitImg.size(); + m_nImgType = oInitImg.type(); + m_nImgChannels = oInitImg.channels(); + m_nTotPxCount = m_oImgSize.area(); + m_nTotRelevantPxCount = nROIPxCount; + m_nFrameIndex = 0; + m_nFramesSinceLastReset = 0; + m_nModelResetCooldown = 0; + m_oLastFGMask.create(m_oImgSize, CV_8UC1); + m_oLastFGMask = cv::Scalar_(0); + m_oLastColorFrame.create(m_oImgSize, CV_8UC((int)m_nImgChannels)); + m_oLastColorFrame = cv::Scalar_::all(0); + m_oLastDescFrame.create(m_oImgSize, CV_16UC((int)m_nImgChannels)); + m_oLastDescFrame = cv::Scalar_::all(0); + m_voBGColorSamples.resize(m_nBGSamples); + m_voBGDescSamples.resize(m_nBGSamples); + for (size_t s = 0; s < m_nBGSamples; ++s) { + m_voBGColorSamples[s].create(m_oImgSize, CV_8UC((int)m_nImgChannels)); + m_voBGColorSamples[s] = cv::Scalar_::all(0); + m_voBGDescSamples[s].create(m_oImgSize, CV_16UC((int)m_nImgChannels)); + m_voBGDescSamples[s] = cv::Scalar_::all(0); + } + if (m_aPxIdxLUT) + delete[] m_aPxIdxLUT; + if (m_aPxInfoLUT) + delete[] m_aPxInfoLUT; + m_aPxIdxLUT = new size_t[m_nTotRelevantPxCount]; + m_aPxInfoLUT = new PxInfoBase[m_nTotPxCount]; + if (m_nImgChannels == 1) { + CV_Assert(m_oLastColorFrame.step.p[0] == (size_t)m_oImgSize.width && m_oLastColorFrame.step.p[1] == 1); + CV_Assert(m_oLastDescFrame.step.p[0] == m_oLastColorFrame.step.p[0] * 2 && m_oLastDescFrame.step.p[1] == m_oLastColorFrame.step.p[1] * 2); + for (size_t t = 0; t <= UCHAR_MAX; ++t) + m_anLBSPThreshold_8bitLUT[t] = cv::saturate_cast((t*m_fRelLBSPThreshold + m_nLBSPThresholdOffset) / 2); + for (size_t nPxIter = 0, nModelIter = 0; nPxIter < m_nTotPxCount; ++nPxIter) { + if (m_oROI.data[nPxIter]) { + m_aPxIdxLUT[nModelIter] = nPxIter; + m_aPxInfoLUT[nPxIter].nImgCoord_Y = (int)nPxIter / m_oImgSize.width; + m_aPxInfoLUT[nPxIter].nImgCoord_X = (int)nPxIter%m_oImgSize.width; + m_aPxInfoLUT[nPxIter].nModelIdx = nModelIter; + m_oLastColorFrame.data[nPxIter] = oInitImg.data[nPxIter]; + const size_t nDescIter = nPxIter * 2; + LBSP::computeGrayscaleDescriptor(oInitImg, oInitImg.data[nPxIter], m_aPxInfoLUT[nPxIter].nImgCoord_X, m_aPxInfoLUT[nPxIter].nImgCoord_Y, m_anLBSPThreshold_8bitLUT[oInitImg.data[nPxIter]], *((ushort*)(m_oLastDescFrame.data + nDescIter))); + ++nModelIter; + } + } + } + else { //m_nImgChannels==3 + CV_Assert(m_oLastColorFrame.step.p[0] == (size_t)m_oImgSize.width * 3 && m_oLastColorFrame.step.p[1] == 3); + CV_Assert(m_oLastDescFrame.step.p[0] == m_oLastColorFrame.step.p[0] * 2 && m_oLastDescFrame.step.p[1] == m_oLastColorFrame.step.p[1] * 2); + for (size_t t = 0; t <= UCHAR_MAX; ++t) + m_anLBSPThreshold_8bitLUT[t] = cv::saturate_cast(t*m_fRelLBSPThreshold + m_nLBSPThresholdOffset); + for (size_t nPxIter = 0, nModelIter = 0; nPxIter < m_nTotPxCount; ++nPxIter) { + if (m_oROI.data[nPxIter]) { + m_aPxIdxLUT[nModelIter] = nPxIter; + m_aPxInfoLUT[nPxIter].nImgCoord_Y = (int)nPxIter / m_oImgSize.width; + m_aPxInfoLUT[nPxIter].nImgCoord_X = (int)nPxIter%m_oImgSize.width; + m_aPxInfoLUT[nPxIter].nModelIdx = nModelIter; + const size_t nPxRGBIter = nPxIter * 3; + const size_t nDescRGBIter = nPxRGBIter * 2; + for (size_t c = 0; c < 3; ++c) { + m_oLastColorFrame.data[nPxRGBIter + c] = oInitImg.data[nPxRGBIter + c]; + LBSP::computeSingleRGBDescriptor(oInitImg, oInitImg.data[nPxRGBIter + c], m_aPxInfoLUT[nPxIter].nImgCoord_X, m_aPxInfoLUT[nPxIter].nImgCoord_Y, c, m_anLBSPThreshold_8bitLUT[oInitImg.data[nPxRGBIter + c]], ((ushort*)(m_oLastDescFrame.data + nDescRGBIter))[c]); + } + ++nModelIter; + } + } + } + m_bInitialized = true; + refreshModel(1.0f); +} + +void BackgroundSubtractorLOBSTER::refreshModel(float fSamplesRefreshFrac, bool bForceFGUpdate) { + // == refresh + CV_Assert(m_bInitialized); + CV_Assert(fSamplesRefreshFrac > 0.0f && fSamplesRefreshFrac <= 1.0f); + const size_t nModelsToRefresh = fSamplesRefreshFrac < 1.0f ? (size_t)(fSamplesRefreshFrac*m_nBGSamples) : m_nBGSamples; + const size_t nRefreshStartPos = fSamplesRefreshFrac < 1.0f ? rand() % m_nBGSamples : 0; + if (m_nImgChannels == 1) { + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + if (bForceFGUpdate || !m_oLastFGMask.data[nPxIter]) { + for (size_t nCurrModelIdx = nRefreshStartPos; nCurrModelIdx < nRefreshStartPos + nModelsToRefresh; ++nCurrModelIdx) { + int nSampleImgCoord_Y, nSampleImgCoord_X; + getRandSamplePosition(nSampleImgCoord_X, nSampleImgCoord_Y, m_aPxInfoLUT[nPxIter].nImgCoord_X, m_aPxInfoLUT[nPxIter].nImgCoord_Y, LBSP::PATCH_SIZE / 2, m_oImgSize); + const size_t nSamplePxIdx = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; + if (bForceFGUpdate || !m_oLastFGMask.data[nSamplePxIdx]) { + const size_t nCurrRealModelIdx = nCurrModelIdx%m_nBGSamples; + m_voBGColorSamples[nCurrRealModelIdx].data[nPxIter] = m_oLastColorFrame.data[nSamplePxIdx]; + *((ushort*)(m_voBGDescSamples[nCurrRealModelIdx].data + nPxIter * 2)) = *((ushort*)(m_oLastDescFrame.data + nSamplePxIdx * 2)); + } + } + } + } + } + else { //m_nImgChannels==3 + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + if (bForceFGUpdate || !m_oLastFGMask.data[nPxIter]) { + for (size_t nCurrModelIdx = nRefreshStartPos; nCurrModelIdx < nRefreshStartPos + nModelsToRefresh; ++nCurrModelIdx) { + int nSampleImgCoord_Y, nSampleImgCoord_X; + getRandSamplePosition(nSampleImgCoord_X, nSampleImgCoord_Y, m_aPxInfoLUT[nPxIter].nImgCoord_X, m_aPxInfoLUT[nPxIter].nImgCoord_Y, LBSP::PATCH_SIZE / 2, m_oImgSize); + const size_t nSamplePxIdx = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; + if (bForceFGUpdate || !m_oLastFGMask.data[nSamplePxIdx]) { + const size_t nCurrRealModelIdx = nCurrModelIdx%m_nBGSamples; + for (size_t c = 0; c < 3; ++c) { + m_voBGColorSamples[nCurrRealModelIdx].data[nPxIter * 3 + c] = m_oLastColorFrame.data[nSamplePxIdx * 3 + c]; + *((ushort*)(m_voBGDescSamples[nCurrRealModelIdx].data + (nPxIter * 3 + c) * 2)) = *((ushort*)(m_oLastDescFrame.data + (nSamplePxIdx * 3 + c) * 2)); + } + } + } + } + } + } +} + +void BackgroundSubtractorLOBSTER::apply(cv::InputArray _image, cv::OutputArray _fgmask, double learningRate) { + CV_Assert(m_bInitialized); + CV_Assert(learningRate > 0); + cv::Mat oInputImg = _image.getMat(); + CV_Assert(oInputImg.type() == m_nImgType && oInputImg.size() == m_oImgSize); + CV_Assert(oInputImg.isContinuous()); + _fgmask.create(m_oImgSize, CV_8UC1); + cv::Mat oCurrFGMask = _fgmask.getMat(); + oCurrFGMask = cv::Scalar_(0); + const size_t nLearningRate = (size_t)ceil(learningRate); + if (m_nImgChannels == 1) { + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + const size_t nDescIter = nPxIter * 2; + const int nCurrImgCoord_X = m_aPxInfoLUT[nPxIter].nImgCoord_X; + const int nCurrImgCoord_Y = m_aPxInfoLUT[nPxIter].nImgCoord_Y; + const uchar nCurrColor = oInputImg.data[nPxIter]; + size_t nGoodSamplesCount = 0, nModelIdx = 0; + ushort nCurrInputDesc; + while (nGoodSamplesCount < m_nRequiredBGSamples && nModelIdx < m_nBGSamples) { + const uchar nBGColor = m_voBGColorSamples[nModelIdx].data[nPxIter]; + { + const size_t nColorDist = L1dist(nCurrColor, nBGColor); + if (nColorDist > m_nColorDistThreshold / 2) + goto failedcheck1ch; + LBSP::computeGrayscaleDescriptor(oInputImg, nBGColor, nCurrImgCoord_X, nCurrImgCoord_Y, m_anLBSPThreshold_8bitLUT[nBGColor], nCurrInputDesc); + const size_t nDescDist = hdist(nCurrInputDesc, *((ushort*)(m_voBGDescSamples[nModelIdx].data + nDescIter))); + if (nDescDist > m_nDescDistThreshold) + goto failedcheck1ch; + nGoodSamplesCount++; + } + failedcheck1ch: + nModelIdx++; + } + if (nGoodSamplesCount < m_nRequiredBGSamples) + oCurrFGMask.data[nPxIter] = UCHAR_MAX; + else { + if ((rand() % nLearningRate) == 0) { + const size_t nSampleModelIdx = rand() % m_nBGSamples; + ushort& nRandInputDesc = *((ushort*)(m_voBGDescSamples[nSampleModelIdx].data + nDescIter)); + LBSP::computeGrayscaleDescriptor(oInputImg, nCurrColor, nCurrImgCoord_X, nCurrImgCoord_Y, m_anLBSPThreshold_8bitLUT[nCurrColor], nRandInputDesc); + m_voBGColorSamples[nSampleModelIdx].data[nPxIter] = nCurrColor; + } + if ((rand() % nLearningRate) == 0) { + int nSampleImgCoord_Y, nSampleImgCoord_X; + getRandNeighborPosition_3x3(nSampleImgCoord_X, nSampleImgCoord_Y, nCurrImgCoord_X, nCurrImgCoord_Y, LBSP::PATCH_SIZE / 2, m_oImgSize); + const size_t nSampleModelIdx = rand() % m_nBGSamples; + ushort& nRandInputDesc = m_voBGDescSamples[nSampleModelIdx].at(nSampleImgCoord_Y, nSampleImgCoord_X); + LBSP::computeGrayscaleDescriptor(oInputImg, nCurrColor, nCurrImgCoord_X, nCurrImgCoord_Y, m_anLBSPThreshold_8bitLUT[nCurrColor], nRandInputDesc); + m_voBGColorSamples[nSampleModelIdx].at(nSampleImgCoord_Y, nSampleImgCoord_X) = nCurrColor; + } + } + } + } + else { //m_nImgChannels==3 + const size_t nCurrDescDistThreshold = m_nDescDistThreshold * 3; + const size_t nCurrColorDistThreshold = m_nColorDistThreshold * 3; + const size_t nCurrSCDescDistThreshold = nCurrDescDistThreshold / 2; + const size_t nCurrSCColorDistThreshold = nCurrColorDistThreshold / 2; + const size_t desc_row_step = m_voBGDescSamples[0].step.p[0]; + const size_t img_row_step = m_voBGColorSamples[0].step.p[0]; + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + const int nCurrImgCoord_X = m_aPxInfoLUT[nPxIter].nImgCoord_X; + const int nCurrImgCoord_Y = m_aPxInfoLUT[nPxIter].nImgCoord_Y; + const size_t nPxIterRGB = nPxIter * 3; + const size_t nDescIterRGB = nPxIterRGB * 2; + const uchar* const anCurrColor = oInputImg.data + nPxIterRGB; + size_t nGoodSamplesCount = 0, nModelIdx = 0; + ushort anCurrInputDesc[3]; + while (nGoodSamplesCount < m_nRequiredBGSamples && nModelIdx < m_nBGSamples) { + const ushort* const anBGDesc = (ushort*)(m_voBGDescSamples[nModelIdx].data + nDescIterRGB); + const uchar* const anBGColor = m_voBGColorSamples[nModelIdx].data + nPxIterRGB; + size_t nTotColorDist = 0; + size_t nTotDescDist = 0; + for (size_t c = 0; c < 3; ++c) { + const size_t nColorDist = L1dist(anCurrColor[c], anBGColor[c]); + if (nColorDist > nCurrSCColorDistThreshold) + goto failedcheck3ch; + LBSP::computeSingleRGBDescriptor(oInputImg, anBGColor[c], nCurrImgCoord_X, nCurrImgCoord_Y, c, m_anLBSPThreshold_8bitLUT[anBGColor[c]], anCurrInputDesc[c]); + const size_t nDescDist = hdist(anCurrInputDesc[c], anBGDesc[c]); + if (nDescDist > nCurrSCDescDistThreshold) + goto failedcheck3ch; + nTotColorDist += nColorDist; + nTotDescDist += nDescDist; + } + if (nTotDescDist <= nCurrDescDistThreshold && nTotColorDist <= nCurrColorDistThreshold) + nGoodSamplesCount++; + failedcheck3ch: + nModelIdx++; + } + if (nGoodSamplesCount < m_nRequiredBGSamples) + oCurrFGMask.data[nPxIter] = UCHAR_MAX; + else { + if ((rand() % nLearningRate) == 0) { + const size_t nSampleModelIdx = rand() % m_nBGSamples; + ushort* anRandInputDesc = ((ushort*)(m_voBGDescSamples[nSampleModelIdx].data + nDescIterRGB)); + const size_t anCurrIntraLBSPThresholds[3] = { m_anLBSPThreshold_8bitLUT[anCurrColor[0]],m_anLBSPThreshold_8bitLUT[anCurrColor[1]],m_anLBSPThreshold_8bitLUT[anCurrColor[2]] }; + LBSP::computeRGBDescriptor(oInputImg, anCurrColor, nCurrImgCoord_X, nCurrImgCoord_Y, anCurrIntraLBSPThresholds, anRandInputDesc); + for (size_t c = 0; c < 3; ++c) + *(m_voBGColorSamples[nSampleModelIdx].data + nPxIterRGB + c) = anCurrColor[c]; + } + if ((rand() % nLearningRate) == 0) { + int nSampleImgCoord_Y, nSampleImgCoord_X; + getRandNeighborPosition_3x3(nSampleImgCoord_X, nSampleImgCoord_Y, nCurrImgCoord_X, nCurrImgCoord_Y, LBSP::PATCH_SIZE / 2, m_oImgSize); + const size_t nSampleModelIdx = rand() % m_nBGSamples; + ushort* anRandInputDesc = ((ushort*)(m_voBGDescSamples[nSampleModelIdx].data + desc_row_step*nSampleImgCoord_Y + 6 * nSampleImgCoord_X)); + const size_t anCurrIntraLBSPThresholds[3] = { m_anLBSPThreshold_8bitLUT[anCurrColor[0]],m_anLBSPThreshold_8bitLUT[anCurrColor[1]],m_anLBSPThreshold_8bitLUT[anCurrColor[2]] }; + LBSP::computeRGBDescriptor(oInputImg, anCurrColor, nCurrImgCoord_X, nCurrImgCoord_Y, anCurrIntraLBSPThresholds, anRandInputDesc); + for (size_t c = 0; c < 3; ++c) + *(m_voBGColorSamples[nSampleModelIdx].data + img_row_step*nSampleImgCoord_Y + 3 * nSampleImgCoord_X + c) = anCurrColor[c]; + } + } + } + } + cv::medianBlur(oCurrFGMask, m_oLastFGMask, m_nDefaultMedianBlurKernelSize); + m_oLastFGMask.copyTo(oCurrFGMask); +} + +void BackgroundSubtractorLOBSTER::getBackgroundImage(cv::OutputArray backgroundImage) const { + CV_DbgAssert(m_bInitialized); + cv::Mat oAvgBGImg = cv::Mat::zeros(m_oImgSize, CV_32FC((int)m_nImgChannels)); + for (size_t s = 0; s < m_nBGSamples; ++s) { + for (int y = 0; y < m_oImgSize.height; ++y) { + for (int x = 0; x < m_oImgSize.width; ++x) { + const size_t idx_nimg = m_voBGColorSamples[s].step.p[0] * y + m_voBGColorSamples[s].step.p[1] * x; + const size_t idx_flt32 = idx_nimg * 4; + float* oAvgBgImgPtr = (float*)(oAvgBGImg.data + idx_flt32); + const uchar* const oBGImgPtr = m_voBGColorSamples[s].data + idx_nimg; + for (size_t c = 0; c < m_nImgChannels; ++c) + oAvgBgImgPtr[c] += ((float)oBGImgPtr[c]) / m_nBGSamples; + } + } + } + oAvgBGImg.convertTo(backgroundImage, CV_8U); +} + +void BackgroundSubtractorLOBSTER::getBackgroundDescriptorsImage(cv::OutputArray backgroundDescImage) const { + CV_Assert(LBSP::DESC_SIZE == 2); + CV_Assert(m_bInitialized); + cv::Mat oAvgBGDesc = cv::Mat::zeros(m_oImgSize, CV_32FC((int)m_nImgChannels)); + for (size_t n = 0; n < m_voBGDescSamples.size(); ++n) { + for (int y = 0; y < m_oImgSize.height; ++y) { + for (int x = 0; x < m_oImgSize.width; ++x) { + const size_t idx_ndesc = m_voBGDescSamples[n].step.p[0] * y + m_voBGDescSamples[n].step.p[1] * x; + const size_t idx_flt32 = idx_ndesc * 2; + float* oAvgBgDescPtr = (float*)(oAvgBGDesc.data + idx_flt32); + const ushort* const oBGDescPtr = (ushort*)(m_voBGDescSamples[n].data + idx_ndesc); + for (size_t c = 0; c < m_nImgChannels; ++c) + oAvgBgDescPtr[c] += ((float)oBGDescPtr[c]) / m_voBGDescSamples.size(); + } + } + } + oAvgBGDesc.convertTo(backgroundDescImage, CV_16U); +} diff --git a/package_bgs/LBSP/BackgroundSubtractorLOBSTER.h b/package_bgs/LBSP/BackgroundSubtractorLOBSTER.h new file mode 100644 index 0000000..d69fd1c --- /dev/null +++ b/package_bgs/LBSP/BackgroundSubtractorLOBSTER.h @@ -0,0 +1,83 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "BackgroundSubtractorLBSP.h" + +//! defines the default value for BackgroundSubtractorLBSP::m_fRelLBSPThreshold +#define BGSLOBSTER_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD (0.365f) +//! defines the default value for BackgroundSubtractorLBSP::m_nLBSPThresholdOffset +#define BGSLOBSTER_DEFAULT_LBSP_OFFSET_SIMILARITY_THRESHOLD (0) +//! defines the default value for BackgroundSubtractorLOBSTER::m_nDescDistThreshold +#define BGSLOBSTER_DEFAULT_DESC_DIST_THRESHOLD (4) +//! defines the default value for BackgroundSubtractorLOBSTER::m_nColorDistThreshold +#define BGSLOBSTER_DEFAULT_COLOR_DIST_THRESHOLD (30) +//! defines the default value for BackgroundSubtractorLOBSTER::m_nBGSamples +#define BGSLOBSTER_DEFAULT_NB_BG_SAMPLES (35) +//! defines the default value for BackgroundSubtractorLOBSTER::m_nRequiredBGSamples +#define BGSLOBSTER_DEFAULT_REQUIRED_NB_BG_SAMPLES (2) +//! defines the default value for the learning rate passed to BackgroundSubtractorLOBSTER::operator() +#define BGSLOBSTER_DEFAULT_LEARNING_RATE (16) + +/*! + LOcal Binary Similarity segmenTER (LOBSTER) change detection algorithm. + + Note: both grayscale and RGB/BGR images may be used with this extractor (parameters are adjusted automatically). + For optimal grayscale results, use CV_8UC1 frames instead of CV_8UC3. + + For more details on the different parameters or on the algorithm itself, see P.-L. St-Charles and + G.-A. Bilodeau, "Improving Background Subtraction using Local Binary Similarity Patterns", in WACV 2014. + + This algorithm is currently NOT thread-safe. + */ +class BackgroundSubtractorLOBSTER : public BackgroundSubtractorLBSP { +public: + //! full constructor + BackgroundSubtractorLOBSTER(float fRelLBSPThreshold = BGSLOBSTER_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD, + size_t nLBSPThresholdOffset = BGSLOBSTER_DEFAULT_LBSP_OFFSET_SIMILARITY_THRESHOLD, + size_t nDescDistThreshold = BGSLOBSTER_DEFAULT_DESC_DIST_THRESHOLD, + size_t nColorDistThreshold = BGSLOBSTER_DEFAULT_COLOR_DIST_THRESHOLD, + size_t nBGSamples = BGSLOBSTER_DEFAULT_NB_BG_SAMPLES, + size_t nRequiredBGSamples = BGSLOBSTER_DEFAULT_REQUIRED_NB_BG_SAMPLES); + //! default destructor + virtual ~BackgroundSubtractorLOBSTER(); + //! (re)initiaization method; needs to be called before starting background subtraction + virtual void initialize(const cv::Mat& oInitImg, const cv::Mat& oROI); + //! refreshes all samples based on the last analyzed frame + virtual void refreshModel(float fSamplesRefreshFrac, bool bForceFGUpdate = false); + //! primary model update function; the learning param is reinterpreted as an integer and should be > 0 (smaller values == faster adaptation) + virtual void apply(cv::InputArray image, cv::OutputArray fgmask, double learningRateOverride = BGSLOBSTER_DEFAULT_LEARNING_RATE); + //! returns a copy of the latest reconstructed background image + void getBackgroundImage(cv::OutputArray backgroundImage) const; + //! returns a copy of the latest reconstructed background descriptors image + virtual void getBackgroundDescriptorsImage(cv::OutputArray backgroundDescImage) const; + +protected: + //! absolute color distance threshold + const size_t m_nColorDistThreshold; + //! absolute descriptor distance threshold + const size_t m_nDescDistThreshold; + //! number of different samples per pixel/block to be taken from input frames to build the background model + const size_t m_nBGSamples; + //! number of similar samples needed to consider the current pixel/block as 'background' + const size_t m_nRequiredBGSamples; + //! background model pixel intensity samples + std::vector m_voBGColorSamples; + //! background model descriptors samples + std::vector m_voBGDescSamples; +}; + diff --git a/package_bgs/LBSP/BackgroundSubtractorPAWCS.cpp b/package_bgs/LBSP/BackgroundSubtractorPAWCS.cpp new file mode 100644 index 0000000..6c48df3 --- /dev/null +++ b/package_bgs/LBSP/BackgroundSubtractorPAWCS.cpp @@ -0,0 +1,1349 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "BackgroundSubtractorPAWCS.h" +#include "DistanceUtils.h" +#include "RandUtils.h" +#include +#include +#include +#include + +/* + * + * Intrinsic configuration parameters are defined here; tuning these for better + * performance should not be required in most cases -- although improvements in + * very specific scenarios are always possible. + * + */ + //! parameter used for dynamic distance threshold adjustments ('R(x)') +#define FEEDBACK_R_VAR (0.01f) +//! parameters used to adjust the variation step size of 'v(x)' +#define FEEDBACK_V_INCR (1.000f) +#define FEEDBACK_V_DECR (0.100f) +//! parameters used to scale dynamic learning rate adjustments ('T(x)') +#define FEEDBACK_T_DECR (0.2500f) +#define FEEDBACK_T_INCR (0.5000f) +#define FEEDBACK_T_LOWER (1.0000f) +#define FEEDBACK_T_UPPER (256.00f) +//! parameters used to define 'unstable' regions, based on segm noise/bg dynamics and local dist threshold values +#define UNSTABLE_REG_RATIO_MIN (0.100f) +#define UNSTABLE_REG_RDIST_MIN (3.000f) +//! parameters used to scale the relative LBSP intensity threshold used for internal comparisons +#define LBSPDESC_RATIO_MIN (0.100f) +#define LBSPDESC_RATIO_MAX (0.500f) +//! parameters used to trigger auto model resets in our frame-level component +#define FRAMELEVEL_MIN_L1DIST_THRES (45) +#define FRAMELEVEL_MIN_CDIST_THRES (FRAMELEVEL_MIN_L1DIST_THRES/10) +#define FRAMELEVEL_DOWNSAMPLE_RATIO (8) +//! parameters used to downscale gword maps & scale thresholds to make comparisons easier +#define GWORD_LOOKUP_MAPS_DOWNSAMPLE_RATIO (2) +#define GWORD_DEFAULT_NB_INIT_SAMPL_PASSES (2) +#define GWORD_DESC_THRES_BITS_MATCH_FACTOR (4) + +// local define used to specify the default frame size (320x240 = QVGA) +#define DEFAULT_FRAME_SIZE cv::Size(320,240) +// local define used to specify the default lword/gword update rate (16 = like vibe) +#define DEFAULT_RESAMPLING_RATE (16) +// local define used to specify the bootstrap window size for faster model stabilization +#define DEFAULT_BOOTSTRAP_WIN_SIZE (500) +// local define for the amount of weight offset to apply to words, making sure new words aren't always better than old ones +#define DEFAULT_LWORD_WEIGHT_OFFSET (DEFAULT_BOOTSTRAP_WIN_SIZE*2) +// local define used to set the default local word occurrence increment +#define DEFAULT_LWORD_OCC_INCR 1 +// local define for the maximum weight a word can achieve before cutting off occ incr (used to make sure model stays good for long-term uses) +#define DEFAULT_LWORD_MAX_WEIGHT (1.0f) +// local define for the initial weight of a new word (used to make sure old words aren't worse off than new seeds) +#define DEFAULT_LWORD_INIT_WEIGHT (1.0f/m_nLocalWordWeightOffset) +// local define used to specify the desc dist threshold offset used for unstable regions +#define UNSTAB_DESC_DIST_OFFSET (m_nDescDistThresholdOffset) +// local define used to specify the min descriptor bit count for flat regions +#define FLAT_REGION_BIT_COUNT (s_nDescMaxDataRange_1ch/8) + +static const size_t s_nColorMaxDataRange_1ch = UCHAR_MAX; +static const size_t s_nDescMaxDataRange_1ch = LBSP_::DESC_SIZE * 8; +static const size_t s_nColorMaxDataRange_3ch = s_nColorMaxDataRange_1ch * 3; +static const size_t s_nDescMaxDataRange_3ch = s_nDescMaxDataRange_1ch * 3; + +BackgroundSubtractorPAWCS::BackgroundSubtractorPAWCS(float fRelLBSPThreshold + , size_t nDescDistThresholdOffset + , size_t nMinColorDistThreshold + , size_t nMaxNbWords + , size_t nSamplesForMovingAvgs) + : BackgroundSubtractorLBSP_(fRelLBSPThreshold) + , m_nMinColorDistThreshold(nMinColorDistThreshold) + , m_nDescDistThresholdOffset(nDescDistThresholdOffset) + , m_nMaxLocalWords(nMaxNbWords) + , m_nCurrLocalWords(0) + , m_nMaxGlobalWords(nMaxNbWords / 2) + , m_nCurrGlobalWords(0) + , m_nSamplesForMovingAvgs(nSamplesForMovingAvgs) + , m_fLastNonFlatRegionRatio(0.0f) + , m_nMedianBlurKernelSize(m_nDefaultMedianBlurKernelSize) + , m_nDownSampledROIPxCount(0) + , m_nLocalWordWeightOffset(DEFAULT_LWORD_WEIGHT_OFFSET) + , m_apLocalWordDict(nullptr) + , m_aLocalWordList_1ch(nullptr) + , m_pLocalWordListIter_1ch(nullptr) + , m_aLocalWordList_3ch(nullptr) + , m_pLocalWordListIter_3ch(nullptr) + , m_apGlobalWordDict(nullptr) + , m_aGlobalWordList_1ch(nullptr) + , m_pGlobalWordListIter_1ch(nullptr) + , m_aGlobalWordList_3ch(nullptr) + , m_pGlobalWordListIter_3ch(nullptr) + , m_aPxInfoLUT_PAWCS(nullptr) { + CV_Assert(m_nMaxLocalWords > 0 && m_nMaxGlobalWords > 0); +} + +BackgroundSubtractorPAWCS::~BackgroundSubtractorPAWCS() { + CleanupDictionaries(); +} + +void BackgroundSubtractorPAWCS::initialize(const cv::Mat& oInitImg, const cv::Mat& oROI) { + // == init + CV_Assert(!oInitImg.empty() && oInitImg.cols > 0 && oInitImg.rows > 0); + CV_Assert(oInitImg.isContinuous()); + CV_Assert(oInitImg.type() == CV_8UC3 || oInitImg.type() == CV_8UC1); + if (oInitImg.type() == CV_8UC3) { + std::vector voInitImgChannels; + cv::split(oInitImg, voInitImgChannels); + if (!cv::countNonZero((voInitImgChannels[0] != voInitImgChannels[1]) | (voInitImgChannels[2] != voInitImgChannels[1]))) + std::cout << std::endl << "\tBackgroundSubtractorPAWCS : Warning, grayscale images should always be passed in CV_8UC1 format for optimal performance." << std::endl; + } + cv::Mat oNewBGROI; + if (oROI.empty() && (m_oROI.empty() || oROI.size() != oInitImg.size())) { + oNewBGROI.create(oInitImg.size(), CV_8UC1); + oNewBGROI = cv::Scalar_(UCHAR_MAX); + } + else if (oROI.empty()) + oNewBGROI = m_oROI; + else { + CV_Assert(oROI.size() == oInitImg.size() && oROI.type() == CV_8UC1); + CV_Assert(cv::countNonZero((oROI < UCHAR_MAX)&(oROI > 0)) == 0); + oNewBGROI = oROI.clone(); + cv::Mat oTempROI; + cv::dilate(oNewBGROI, oTempROI, cv::Mat(), cv::Point(-1, -1), LBSP_::PATCH_SIZE / 2); + cv::bitwise_or(oNewBGROI, oTempROI / 2, oNewBGROI); + } + const size_t nOrigROIPxCount = (size_t)cv::countNonZero(oNewBGROI); + CV_Assert(nOrigROIPxCount > 0); + LBSP_::validateROI(oNewBGROI); + const size_t nFinalROIPxCount = (size_t)cv::countNonZero(oNewBGROI); + CV_Assert(nFinalROIPxCount > 0); + CleanupDictionaries(); + m_oROI = oNewBGROI; + m_oImgSize = oInitImg.size(); + m_nImgType = oInitImg.type(); + m_nImgChannels = oInitImg.channels(); + m_nTotPxCount = m_oImgSize.area(); + m_nTotRelevantPxCount = nFinalROIPxCount; + m_nFrameIndex = 0; + m_nFramesSinceLastReset = 0; + m_nModelResetCooldown = 0; + m_bUsingMovingCamera = false; + m_oDownSampledFrameSize_MotionAnalysis = cv::Size(m_oImgSize.width / FRAMELEVEL_DOWNSAMPLE_RATIO, m_oImgSize.height / FRAMELEVEL_DOWNSAMPLE_RATIO); + m_oDownSampledFrameSize_GlobalWordLookup = cv::Size(m_oImgSize.width / GWORD_LOOKUP_MAPS_DOWNSAMPLE_RATIO, m_oImgSize.height / GWORD_LOOKUP_MAPS_DOWNSAMPLE_RATIO); + cv::resize(m_oROI, m_oDownSampledROI_MotionAnalysis, m_oDownSampledFrameSize_MotionAnalysis, 0, 0, cv::INTER_AREA); + m_fLastNonFlatRegionRatio = 0.0f; + m_nCurrLocalWords = m_nMaxLocalWords; + if (nOrigROIPxCount >= m_nTotPxCount / 2 && (int)m_nTotPxCount >= DEFAULT_FRAME_SIZE.area()) { + const float fRegionSizeScaleFactor = (float)m_nTotPxCount / DEFAULT_FRAME_SIZE.area(); + const int nRawMedianBlurKernelSize = std::min((int)floor(0.5f + fRegionSizeScaleFactor) + m_nDefaultMedianBlurKernelSize, m_nDefaultMedianBlurKernelSize + 4); + m_nMedianBlurKernelSize = (nRawMedianBlurKernelSize % 2) ? nRawMedianBlurKernelSize : nRawMedianBlurKernelSize - 1; + m_nCurrGlobalWords = m_nMaxGlobalWords; + m_oDownSampledROI_MotionAnalysis |= UCHAR_MAX / 2; + } + else { + const float fRegionSizeScaleFactor = (float)nOrigROIPxCount / DEFAULT_FRAME_SIZE.area(); + const int nRawMedianBlurKernelSize = std::min((int)floor(0.5f + m_nDefaultMedianBlurKernelSize*fRegionSizeScaleFactor * 2) + (m_nDefaultMedianBlurKernelSize - 4), m_nDefaultMedianBlurKernelSize); + m_nMedianBlurKernelSize = (nRawMedianBlurKernelSize % 2) ? nRawMedianBlurKernelSize : nRawMedianBlurKernelSize - 1; + m_nCurrGlobalWords = std::min((size_t)std::pow(m_nMaxGlobalWords*fRegionSizeScaleFactor, 2) + 1, m_nMaxGlobalWords); + } + if (m_nImgChannels == 1) { + m_nCurrLocalWords = std::max(m_nCurrLocalWords / 2, (size_t)1); + m_nCurrGlobalWords = std::max(m_nCurrGlobalWords / 2, (size_t)1); + } + m_nDownSampledROIPxCount = (size_t)cv::countNonZero(m_oDownSampledROI_MotionAnalysis); + m_nLocalWordWeightOffset = DEFAULT_LWORD_WEIGHT_OFFSET; + m_oIllumUpdtRegionMask.create(m_oImgSize, CV_8UC1); + m_oIllumUpdtRegionMask = cv::Scalar_(0); + m_oUpdateRateFrame.create(m_oImgSize, CV_32FC1); + m_oUpdateRateFrame = cv::Scalar(FEEDBACK_T_LOWER); + m_oDistThresholdFrame.create(m_oImgSize, CV_32FC1); + m_oDistThresholdFrame = cv::Scalar(2.0f); + m_oDistThresholdVariationFrame.create(m_oImgSize, CV_32FC1); + m_oDistThresholdVariationFrame = cv::Scalar(FEEDBACK_V_INCR * 10); + m_oMeanMinDistFrame_LT.create(m_oImgSize, CV_32FC1); + m_oMeanMinDistFrame_LT = cv::Scalar(0.0f); + m_oMeanMinDistFrame_ST.create(m_oImgSize, CV_32FC1); + m_oMeanMinDistFrame_ST = cv::Scalar(0.0f); + m_oMeanDownSampledLastDistFrame_LT.create(m_oDownSampledFrameSize_MotionAnalysis, CV_32FC((int)m_nImgChannels)); + m_oMeanDownSampledLastDistFrame_LT = cv::Scalar(0.0f); + m_oMeanDownSampledLastDistFrame_ST.create(m_oDownSampledFrameSize_MotionAnalysis, CV_32FC((int)m_nImgChannels)); + m_oMeanDownSampledLastDistFrame_ST = cv::Scalar(0.0f); + m_oMeanRawSegmResFrame_LT.create(m_oImgSize, CV_32FC1); + m_oMeanRawSegmResFrame_LT = cv::Scalar(0.0f); + m_oMeanRawSegmResFrame_ST.create(m_oImgSize, CV_32FC1); + m_oMeanRawSegmResFrame_ST = cv::Scalar(0.0f); + m_oMeanFinalSegmResFrame_LT.create(m_oImgSize, CV_32FC1); + m_oMeanFinalSegmResFrame_LT = cv::Scalar(0.0f); + m_oMeanFinalSegmResFrame_ST.create(m_oImgSize, CV_32FC1); + m_oMeanFinalSegmResFrame_ST = cv::Scalar(0.0f); + m_oUnstableRegionMask.create(m_oImgSize, CV_8UC1); + m_oUnstableRegionMask = cv::Scalar_(0); + m_oBlinksFrame.create(m_oImgSize, CV_8UC1); + m_oBlinksFrame = cv::Scalar_(0); + m_oDownSampledFrame_MotionAnalysis.create(m_oDownSampledFrameSize_MotionAnalysis, CV_8UC((int)m_nImgChannels)); + m_oDownSampledFrame_MotionAnalysis = cv::Scalar_::all(0); + m_oLastColorFrame.create(m_oImgSize, CV_8UC((int)m_nImgChannels)); + m_oLastColorFrame = cv::Scalar_::all(0); + m_oLastDescFrame.create(m_oImgSize, CV_16UC((int)m_nImgChannels)); + m_oLastDescFrame = cv::Scalar_::all(0); + m_oLastRawFGMask.create(m_oImgSize, CV_8UC1); + m_oLastRawFGMask = cv::Scalar_(0); + m_oLastFGMask.create(m_oImgSize, CV_8UC1); + m_oLastFGMask = cv::Scalar_(0); + m_oLastFGMask_dilated.create(m_oImgSize, CV_8UC1); + m_oLastFGMask_dilated = cv::Scalar_(0); + m_oLastFGMask_dilated_inverted.create(m_oImgSize, CV_8UC1); + m_oLastFGMask_dilated_inverted = cv::Scalar_(0); + m_oFGMask_FloodedHoles.create(m_oImgSize, CV_8UC1); + m_oFGMask_FloodedHoles = cv::Scalar_(0); + m_oFGMask_PreFlood.create(m_oImgSize, CV_8UC1); + m_oFGMask_PreFlood = cv::Scalar_(0); + m_oCurrRawFGBlinkMask.create(m_oImgSize, CV_8UC1); + m_oCurrRawFGBlinkMask = cv::Scalar_(0); + m_oLastRawFGBlinkMask.create(m_oImgSize, CV_8UC1); + m_oLastRawFGBlinkMask = cv::Scalar_(0); + m_oTempGlobalWordWeightDiffFactor.create(m_oDownSampledFrameSize_GlobalWordLookup, CV_32FC1); + m_oTempGlobalWordWeightDiffFactor = cv::Scalar(-0.1f); + m_oMorphExStructElement = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); + m_aPxIdxLUT = new size_t[m_nTotRelevantPxCount]; + memset(m_aPxIdxLUT, 0, sizeof(size_t)*m_nTotRelevantPxCount); + m_aPxInfoLUT_PAWCS = new PxInfo_PAWCS[m_nTotPxCount]; + memset(m_aPxInfoLUT_PAWCS, 0, sizeof(PxInfo_PAWCS)*m_nTotPxCount); + m_aPxInfoLUT = m_aPxInfoLUT_PAWCS; + m_apLocalWordDict = new LocalWordBase*[m_nTotRelevantPxCount*m_nCurrLocalWords]; + memset(m_apLocalWordDict, 0, sizeof(LocalWordBase*)*m_nTotRelevantPxCount*m_nCurrLocalWords); + m_apGlobalWordDict = new GlobalWordBase*[m_nCurrGlobalWords]; + memset(m_apGlobalWordDict, 0, sizeof(GlobalWordBase*)*m_nCurrGlobalWords); + if (m_nImgChannels == 1) { + CV_DbgAssert(m_oLastColorFrame.step.p[0] == (size_t)m_oImgSize.width && m_oLastColorFrame.step.p[1] == 1); + CV_DbgAssert(m_oLastDescFrame.step.p[0] == m_oLastColorFrame.step.p[0] * 2 && m_oLastDescFrame.step.p[1] == m_oLastColorFrame.step.p[1] * 2); + m_aLocalWordList_1ch = new LocalWord_1ch[m_nTotRelevantPxCount*m_nCurrLocalWords]; + memset(m_aLocalWordList_1ch, 0, sizeof(LocalWord_1ch)*m_nTotRelevantPxCount*m_nCurrLocalWords); + m_pLocalWordListIter_1ch = m_aLocalWordList_1ch; + m_aGlobalWordList_1ch = new GlobalWord_1ch[m_nCurrGlobalWords]; + m_pGlobalWordListIter_1ch = m_aGlobalWordList_1ch; + for (size_t t = 0; t <= UCHAR_MAX; ++t) + m_anLBSPThreshold_8bitLUT[t] = cv::saturate_cast((m_nLBSPThresholdOffset + t*m_fRelLBSPThreshold) / 3); + for (size_t nPxIter = 0, nModelIter = 0; nPxIter < m_nTotPxCount; ++nPxIter) { + if (m_oROI.data[nPxIter]) { + m_aPxIdxLUT[nModelIter] = nPxIter; + m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y = (int)nPxIter / m_oImgSize.width; + m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X = (int)nPxIter%m_oImgSize.width; + m_aPxInfoLUT_PAWCS[nPxIter].nModelIdx = nModelIter; + m_aPxInfoLUT_PAWCS[nPxIter].nGlobalWordMapLookupIdx = (size_t)((m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y / GWORD_LOOKUP_MAPS_DOWNSAMPLE_RATIO)*m_oDownSampledFrameSize_GlobalWordLookup.width + (m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X / GWORD_LOOKUP_MAPS_DOWNSAMPLE_RATIO)) * 4; + m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT = new GlobalWordBase*[m_nCurrGlobalWords]; + for (size_t nGlobalWordIdxIter = 0; nGlobalWordIdxIter < m_nCurrGlobalWords; ++nGlobalWordIdxIter) + m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordIdxIter] = &(m_aGlobalWordList_1ch[nGlobalWordIdxIter]); + m_oLastColorFrame.data[nPxIter] = oInitImg.data[nPxIter]; + const size_t nDescIter = nPxIter * 2; + LBSP_::computeGrayscaleDescriptor(oInitImg, oInitImg.data[nPxIter], m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X, m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y, m_anLBSPThreshold_8bitLUT[oInitImg.data[nPxIter]], *((ushort*)(m_oLastDescFrame.data + nDescIter))); + ++nModelIter; + } + } + } + else { //m_nImgChannels==3 + CV_DbgAssert(m_oLastColorFrame.step.p[0] == (size_t)m_oImgSize.width * 3 && m_oLastColorFrame.step.p[1] == 3); + CV_DbgAssert(m_oLastDescFrame.step.p[0] == m_oLastColorFrame.step.p[0] * 2 && m_oLastDescFrame.step.p[1] == m_oLastColorFrame.step.p[1] * 2); + m_aLocalWordList_3ch = new LocalWord_3ch[m_nTotRelevantPxCount*m_nCurrLocalWords]; + memset(m_aLocalWordList_3ch, 0, sizeof(LocalWord_3ch)*m_nTotRelevantPxCount*m_nCurrLocalWords); + m_pLocalWordListIter_3ch = m_aLocalWordList_3ch; + m_aGlobalWordList_3ch = new GlobalWord_3ch[m_nCurrGlobalWords]; + m_pGlobalWordListIter_3ch = m_aGlobalWordList_3ch; + for (size_t t = 0; t <= UCHAR_MAX; ++t) + m_anLBSPThreshold_8bitLUT[t] = cv::saturate_cast(m_nLBSPThresholdOffset + t*m_fRelLBSPThreshold); + for (size_t nPxIter = 0, nModelIter = 0; nPxIter < m_nTotPxCount; ++nPxIter) { + if (m_oROI.data[nPxIter]) { + m_aPxIdxLUT[nModelIter] = nPxIter; + m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y = (int)nPxIter / m_oImgSize.width; + m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X = (int)nPxIter%m_oImgSize.width; + m_aPxInfoLUT_PAWCS[nPxIter].nModelIdx = nModelIter; + m_aPxInfoLUT_PAWCS[nPxIter].nGlobalWordMapLookupIdx = (size_t)((m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y / GWORD_LOOKUP_MAPS_DOWNSAMPLE_RATIO)*m_oDownSampledFrameSize_GlobalWordLookup.width + (m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X / GWORD_LOOKUP_MAPS_DOWNSAMPLE_RATIO)) * 4; + m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT = new GlobalWordBase*[m_nCurrGlobalWords]; + for (size_t nGlobalWordIdxIter = 0; nGlobalWordIdxIter < m_nCurrGlobalWords; ++nGlobalWordIdxIter) + m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordIdxIter] = &(m_aGlobalWordList_3ch[nGlobalWordIdxIter]); + const size_t nPxRGBIter = nPxIter * 3; + const size_t nDescRGBIter = nPxRGBIter * 2; + for (size_t c = 0; c < 3; ++c) { + m_oLastColorFrame.data[nPxRGBIter + c] = oInitImg.data[nPxRGBIter + c]; + LBSP_::computeSingleRGBDescriptor(oInitImg, oInitImg.data[nPxRGBIter + c], m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X, m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y, c, m_anLBSPThreshold_8bitLUT[oInitImg.data[nPxRGBIter + c]], ((ushort*)(m_oLastDescFrame.data + nDescRGBIter))[c]); + } + ++nModelIter; + } + } + } + m_bInitialized = true; + refreshModel(1, 0); +} + +void BackgroundSubtractorPAWCS::refreshModel(size_t nBaseOccCount, float fOccDecrFrac, bool bForceFGUpdate) { + // == refresh + CV_Assert(m_bInitialized); + CV_Assert(fOccDecrFrac >= 0.0f && fOccDecrFrac <= 1.0f); + if (m_nImgChannels == 1) { + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + if (bForceFGUpdate || !m_oLastFGMask_dilated.data[nPxIter]) { + const size_t nLocalDictIdx = nModelIter*m_nCurrLocalWords; + const size_t nFloatIter = nPxIter * 4; + uchar& bCurrRegionIsUnstable = m_oUnstableRegionMask.data[nPxIter]; + const float fCurrDistThresholdFactor = *(float*)(m_oDistThresholdFrame.data + nFloatIter); + const size_t nCurrColorDistThreshold = (size_t)(sqrt(fCurrDistThresholdFactor)*m_nMinColorDistThreshold) / 2; + const size_t nCurrDescDistThreshold = ((size_t)1 << ((size_t)floor(fCurrDistThresholdFactor + 0.5f))) + m_nDescDistThresholdOffset + (bCurrRegionIsUnstable*UNSTAB_DESC_DIST_OFFSET); + // == refresh: local decr + if (fOccDecrFrac > 0.0f) { + for (size_t nLocalWordIdx = 0; nLocalWordIdx < m_nCurrLocalWords; ++nLocalWordIdx) { + LocalWord_1ch* pCurrLocalWord = (LocalWord_1ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + if (pCurrLocalWord) + pCurrLocalWord->nOccurrences -= (size_t)(fOccDecrFrac*pCurrLocalWord->nOccurrences); + } + } + const size_t nCurrWordOccIncr = DEFAULT_LWORD_OCC_INCR; + const size_t nTotLocalSamplingIterCount = (s_nSamplesInitPatternWidth*s_nSamplesInitPatternHeight) * 2; + for (size_t nLocalSamplingIter = 0; nLocalSamplingIter < nTotLocalSamplingIterCount; ++nLocalSamplingIter) { + // == refresh: local resampling + int nSampleImgCoord_Y, nSampleImgCoord_X; + getRandSamplePosition(nSampleImgCoord_X, nSampleImgCoord_Y, m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X, m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y, LBSP_::PATCH_SIZE / 2, m_oImgSize); + const size_t nSamplePxIdx = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; + if (bForceFGUpdate || !m_oLastFGMask_dilated.data[nSamplePxIdx]) { + const uchar nSampleColor = m_oLastColorFrame.data[nSamplePxIdx]; + const size_t nSampleDescIdx = nSamplePxIdx * 2; + const ushort nSampleIntraDesc = *((ushort*)(m_oLastDescFrame.data + nSampleDescIdx)); + bool bFoundUninitd = false; + size_t nLocalWordIdx; + for (nLocalWordIdx = 0; nLocalWordIdx < m_nCurrLocalWords; ++nLocalWordIdx) { + LocalWord_1ch* pCurrLocalWord = (LocalWord_1ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + if (pCurrLocalWord + && L1dist(nSampleColor, pCurrLocalWord->oFeature.anColor[0]) <= nCurrColorDistThreshold + && hdist(nSampleIntraDesc, pCurrLocalWord->oFeature.anDesc[0]) <= nCurrDescDistThreshold) { + pCurrLocalWord->nOccurrences += nCurrWordOccIncr; + pCurrLocalWord->nLastOcc = m_nFrameIndex; + break; + } + else if (!pCurrLocalWord) + bFoundUninitd = true; + } + if (nLocalWordIdx == m_nCurrLocalWords) { + nLocalWordIdx = m_nCurrLocalWords - 1; + LocalWord_1ch* pCurrLocalWord = bFoundUninitd ? m_pLocalWordListIter_1ch++ : (LocalWord_1ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + pCurrLocalWord->oFeature.anColor[0] = nSampleColor; + pCurrLocalWord->oFeature.anDesc[0] = nSampleIntraDesc; + pCurrLocalWord->nOccurrences = nBaseOccCount; + pCurrLocalWord->nFirstOcc = m_nFrameIndex; + pCurrLocalWord->nLastOcc = m_nFrameIndex; + m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx] = pCurrLocalWord; + } + while (nLocalWordIdx > 0 && (!m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx - 1] || GetLocalWordWeight(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx], m_nFrameIndex, m_nLocalWordWeightOffset) > GetLocalWordWeight(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx - 1], m_nFrameIndex, m_nLocalWordWeightOffset))) { + std::swap(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx], m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx - 1]); + --nLocalWordIdx; + } + } + } + CV_Assert(m_apLocalWordDict[nLocalDictIdx]); + for (size_t nLocalWordIdx = 1; nLocalWordIdx < m_nCurrLocalWords; ++nLocalWordIdx) { + // == refresh: local random resampling + LocalWord_1ch* pCurrLocalWord = (LocalWord_1ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + if (!pCurrLocalWord) { + const size_t nRandLocalWordIdx = (rand() % nLocalWordIdx); + const LocalWord_1ch* pRefLocalWord = (LocalWord_1ch*)m_apLocalWordDict[nLocalDictIdx + nRandLocalWordIdx]; + const int nRandColorOffset = (rand() % (nCurrColorDistThreshold + 1)) - (int)nCurrColorDistThreshold / 2; + pCurrLocalWord = m_pLocalWordListIter_1ch++; + pCurrLocalWord->oFeature.anColor[0] = cv::saturate_cast((int)pRefLocalWord->oFeature.anColor[0] + nRandColorOffset); + pCurrLocalWord->oFeature.anDesc[0] = pRefLocalWord->oFeature.anDesc[0]; + pCurrLocalWord->nOccurrences = std::max((size_t)(pRefLocalWord->nOccurrences*((float)(m_nCurrLocalWords - nLocalWordIdx) / m_nCurrLocalWords)), (size_t)1); + pCurrLocalWord->nFirstOcc = m_nFrameIndex; + pCurrLocalWord->nLastOcc = m_nFrameIndex; + m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx] = pCurrLocalWord; + } + } + } + } + CV_Assert(m_aLocalWordList_1ch == (m_pLocalWordListIter_1ch - m_nTotRelevantPxCount*m_nCurrLocalWords)); + cv::Mat oGlobalDictPresenceLookupMap(m_oImgSize, CV_8UC1, cv::Scalar_(0)); + size_t nPxIterIncr = std::max(m_nTotPxCount / m_nCurrGlobalWords, (size_t)1); + for (size_t nSamplingPasses = 0; nSamplingPasses < GWORD_DEFAULT_NB_INIT_SAMPL_PASSES; ++nSamplingPasses) { + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + // == refresh: global resampling + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + if ((nPxIter%nPxIterIncr) == 0) { // <=(m_nCurrGlobalWords) gwords from (m_nCurrGlobalWords) equally spaced pixels + if (bForceFGUpdate || !m_oLastFGMask_dilated.data[nPxIter]) { + const size_t nLocalDictIdx = nModelIter*m_nCurrLocalWords; + const size_t nGlobalWordMapLookupIdx = m_aPxInfoLUT_PAWCS[nPxIter].nGlobalWordMapLookupIdx; + const size_t nFloatIter = nPxIter * 4; + uchar& bCurrRegionIsUnstable = m_oUnstableRegionMask.data[nPxIter]; + const float fCurrDistThresholdFactor = *(float*)(m_oDistThresholdFrame.data + nFloatIter); + const size_t nCurrColorDistThreshold = (size_t)(sqrt(fCurrDistThresholdFactor)*m_nMinColorDistThreshold) / 2; + const size_t nCurrDescDistThreshold = ((size_t)1 << ((size_t)floor(fCurrDistThresholdFactor + 0.5f))) + m_nDescDistThresholdOffset + (bCurrRegionIsUnstable*UNSTAB_DESC_DIST_OFFSET); + CV_Assert(m_apLocalWordDict[nLocalDictIdx]); + const LocalWord_1ch* pRefBestLocalWord = (LocalWord_1ch*)m_apLocalWordDict[nLocalDictIdx]; + const float fRefBestLocalWordWeight = GetLocalWordWeight(pRefBestLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + const uchar nRefBestLocalWordDescBITS = (uchar)popcount(pRefBestLocalWord->oFeature.anDesc[0]); + bool bFoundUninitd = false; + size_t nGlobalWordIdx; + for (nGlobalWordIdx = 0; nGlobalWordIdx < m_nCurrGlobalWords; ++nGlobalWordIdx) { + GlobalWord_1ch* pCurrGlobalWord = (GlobalWord_1ch*)m_apGlobalWordDict[nGlobalWordIdx]; + if (pCurrGlobalWord + && L1dist(pCurrGlobalWord->oFeature.anColor[0], pRefBestLocalWord->oFeature.anColor[0]) <= nCurrColorDistThreshold + && L1dist(nRefBestLocalWordDescBITS, pCurrGlobalWord->nDescBITS) <= nCurrDescDistThreshold / GWORD_DESC_THRES_BITS_MATCH_FACTOR) + break; + else if (!pCurrGlobalWord) + bFoundUninitd = true; + } + if (nGlobalWordIdx == m_nCurrGlobalWords) { + nGlobalWordIdx = m_nCurrGlobalWords - 1; + GlobalWord_1ch* pCurrGlobalWord = bFoundUninitd ? m_pGlobalWordListIter_1ch++ : (GlobalWord_1ch*)m_apGlobalWordDict[nGlobalWordIdx]; + pCurrGlobalWord->oFeature.anColor[0] = pRefBestLocalWord->oFeature.anColor[0]; + pCurrGlobalWord->oFeature.anDesc[0] = pRefBestLocalWord->oFeature.anDesc[0]; + pCurrGlobalWord->nDescBITS = nRefBestLocalWordDescBITS; + pCurrGlobalWord->oSpatioOccMap.create(m_oDownSampledFrameSize_GlobalWordLookup, CV_32FC1); + pCurrGlobalWord->oSpatioOccMap = cv::Scalar(0.0f); + pCurrGlobalWord->fLatestWeight = 0.0f; + m_apGlobalWordDict[nGlobalWordIdx] = pCurrGlobalWord; + } + float& fCurrGlobalWordLocalWeight = *(float*)(m_apGlobalWordDict[nGlobalWordIdx]->oSpatioOccMap.data + nGlobalWordMapLookupIdx); + if (fCurrGlobalWordLocalWeight < fRefBestLocalWordWeight) { + m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight += fRefBestLocalWordWeight; + fCurrGlobalWordLocalWeight += fRefBestLocalWordWeight; + } + oGlobalDictPresenceLookupMap.data[nPxIter] = UCHAR_MAX; + while (nGlobalWordIdx > 0 && (!m_apGlobalWordDict[nGlobalWordIdx - 1] || m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight > m_apGlobalWordDict[nGlobalWordIdx - 1]->fLatestWeight)) { + std::swap(m_apGlobalWordDict[nGlobalWordIdx], m_apGlobalWordDict[nGlobalWordIdx - 1]); + --nGlobalWordIdx; + } + } + } + } + nPxIterIncr = std::max(nPxIterIncr / 3, (size_t)1); + } + for (size_t nGlobalWordIdx = 0; nGlobalWordIdx < m_nCurrGlobalWords; ++nGlobalWordIdx) { + GlobalWord_1ch* pCurrGlobalWord = (GlobalWord_1ch*)m_apGlobalWordDict[nGlobalWordIdx]; + if (!pCurrGlobalWord) { + pCurrGlobalWord = m_pGlobalWordListIter_1ch++; + pCurrGlobalWord->oFeature.anColor[0] = 0; + pCurrGlobalWord->oFeature.anDesc[0] = 0; + pCurrGlobalWord->nDescBITS = 0; + pCurrGlobalWord->oSpatioOccMap.create(m_oDownSampledFrameSize_GlobalWordLookup, CV_32FC1); + pCurrGlobalWord->oSpatioOccMap = cv::Scalar(0.0f); + pCurrGlobalWord->fLatestWeight = 0.0f; + m_apGlobalWordDict[nGlobalWordIdx] = pCurrGlobalWord; + } + } + CV_Assert((size_t)(m_pGlobalWordListIter_1ch - m_aGlobalWordList_1ch) == m_nCurrGlobalWords && m_aGlobalWordList_1ch == (m_pGlobalWordListIter_1ch - m_nCurrGlobalWords)); + } + else { //m_nImgChannels==3 + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + if (bForceFGUpdate || !m_oLastFGMask_dilated.data[nPxIter]) { + const size_t nLocalDictIdx = nModelIter*m_nCurrLocalWords; + const size_t nFloatIter = nPxIter * 4; + uchar& bCurrRegionIsUnstable = m_oUnstableRegionMask.data[nPxIter]; + const float fCurrDistThresholdFactor = *(float*)(m_oDistThresholdFrame.data + nFloatIter); + const size_t nCurrTotColorDistThreshold = (size_t)(sqrt(fCurrDistThresholdFactor)*m_nMinColorDistThreshold) * 3; + const size_t nCurrTotDescDistThreshold = (((size_t)1 << ((size_t)floor(fCurrDistThresholdFactor + 0.5f))) + m_nDescDistThresholdOffset + (bCurrRegionIsUnstable*UNSTAB_DESC_DIST_OFFSET)) * 3; + // == refresh: local decr + if (fOccDecrFrac > 0.0f) { + for (size_t nLocalWordIdx = 0; nLocalWordIdx < m_nCurrLocalWords; ++nLocalWordIdx) { + LocalWord_3ch* pCurrLocalWord = (LocalWord_3ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + if (pCurrLocalWord) + pCurrLocalWord->nOccurrences -= (size_t)(fOccDecrFrac*pCurrLocalWord->nOccurrences); + } + } + const size_t nCurrWordOccIncr = DEFAULT_LWORD_OCC_INCR; + const size_t nTotLocalSamplingIterCount = (s_nSamplesInitPatternWidth*s_nSamplesInitPatternHeight) * 2; + for (size_t nLocalSamplingIter = 0; nLocalSamplingIter < nTotLocalSamplingIterCount; ++nLocalSamplingIter) { + // == refresh: local resampling + int nSampleImgCoord_Y, nSampleImgCoord_X; + getRandSamplePosition(nSampleImgCoord_X, nSampleImgCoord_Y, m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X, m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y, LBSP_::PATCH_SIZE / 2, m_oImgSize); + const size_t nSamplePxIdx = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; + if (bForceFGUpdate || !m_oLastFGMask_dilated.data[nSamplePxIdx]) { + const size_t nSamplePxRGBIdx = nSamplePxIdx * 3; + const size_t nSampleDescRGBIdx = nSamplePxRGBIdx * 2; + const uchar* const anSampleColor = m_oLastColorFrame.data + nSamplePxRGBIdx; + const ushort* const anSampleIntraDesc = ((ushort*)(m_oLastDescFrame.data + nSampleDescRGBIdx)); + bool bFoundUninitd = false; + size_t nLocalWordIdx; + for (nLocalWordIdx = 0; nLocalWordIdx < m_nCurrLocalWords; ++nLocalWordIdx) { + LocalWord_3ch* pCurrLocalWord = (LocalWord_3ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + if (pCurrLocalWord + && cmixdist<3>(anSampleColor, pCurrLocalWord->oFeature.anColor) <= nCurrTotColorDistThreshold + && hdist<3>(anSampleIntraDesc, pCurrLocalWord->oFeature.anDesc) <= nCurrTotDescDistThreshold) { + pCurrLocalWord->nOccurrences += nCurrWordOccIncr; + pCurrLocalWord->nLastOcc = m_nFrameIndex; + break; + } + else if (!pCurrLocalWord) + bFoundUninitd = true; + } + if (nLocalWordIdx == m_nCurrLocalWords) { + nLocalWordIdx = m_nCurrLocalWords - 1; + LocalWord_3ch* pCurrLocalWord = bFoundUninitd ? m_pLocalWordListIter_3ch++ : (LocalWord_3ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + for (size_t c = 0; c < 3; ++c) { + pCurrLocalWord->oFeature.anColor[c] = anSampleColor[c]; + pCurrLocalWord->oFeature.anDesc[c] = anSampleIntraDesc[c]; + } + pCurrLocalWord->nOccurrences = nBaseOccCount; + pCurrLocalWord->nFirstOcc = m_nFrameIndex; + pCurrLocalWord->nLastOcc = m_nFrameIndex; + m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx] = pCurrLocalWord; + } + while (nLocalWordIdx > 0 && (!m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx - 1] || GetLocalWordWeight(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx], m_nFrameIndex, m_nLocalWordWeightOffset) > GetLocalWordWeight(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx - 1], m_nFrameIndex, m_nLocalWordWeightOffset))) { + std::swap(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx], m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx - 1]); + --nLocalWordIdx; + } + } + } + CV_Assert(m_apLocalWordDict[nLocalDictIdx]); + for (size_t nLocalWordIdx = 1; nLocalWordIdx < m_nCurrLocalWords; ++nLocalWordIdx) { + // == refresh: local random resampling + LocalWord_3ch* pCurrLocalWord = (LocalWord_3ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + if (!pCurrLocalWord) { + const size_t nRandLocalWordIdx = (rand() % nLocalWordIdx); + const LocalWord_3ch* pRefLocalWord = (LocalWord_3ch*)m_apLocalWordDict[nLocalDictIdx + nRandLocalWordIdx]; + const int nRandColorOffset = (rand() % (nCurrTotColorDistThreshold / 3 + 1)) - (int)(nCurrTotColorDistThreshold / 6); + pCurrLocalWord = m_pLocalWordListIter_3ch++; + for (size_t c = 0; c < 3; ++c) { + pCurrLocalWord->oFeature.anColor[c] = cv::saturate_cast((int)pRefLocalWord->oFeature.anColor[c] + nRandColorOffset); + pCurrLocalWord->oFeature.anDesc[c] = pRefLocalWord->oFeature.anDesc[c]; + } + pCurrLocalWord->nOccurrences = std::max((size_t)(pRefLocalWord->nOccurrences*((float)(m_nCurrLocalWords - nLocalWordIdx) / m_nCurrLocalWords)), (size_t)1); + pCurrLocalWord->nFirstOcc = m_nFrameIndex; + pCurrLocalWord->nLastOcc = m_nFrameIndex; + m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx] = pCurrLocalWord; + } + } + } + } + CV_Assert(m_aLocalWordList_3ch == (m_pLocalWordListIter_3ch - m_nTotRelevantPxCount*m_nCurrLocalWords)); + cv::Mat oGlobalDictPresenceLookupMap(m_oImgSize, CV_8UC1, cv::Scalar_(0)); + size_t nPxIterIncr = std::max(m_nTotPxCount / m_nCurrGlobalWords, (size_t)1); + for (size_t nSamplingPasses = 0; nSamplingPasses < GWORD_DEFAULT_NB_INIT_SAMPL_PASSES; ++nSamplingPasses) { + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + // == refresh: global resampling + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + if ((nPxIter%nPxIterIncr) == 0) { // <=(m_nCurrGlobalWords) gwords from (m_nCurrGlobalWords) equally spaced pixels + if (bForceFGUpdate || !m_oLastFGMask_dilated.data[nPxIter]) { + const size_t nLocalDictIdx = nModelIter*m_nCurrLocalWords; + const size_t nGlobalWordMapLookupIdx = m_aPxInfoLUT_PAWCS[nPxIter].nGlobalWordMapLookupIdx; + const size_t nFloatIter = nPxIter * 4; + uchar& bCurrRegionIsUnstable = m_oUnstableRegionMask.data[nPxIter]; + const float fCurrDistThresholdFactor = *(float*)(m_oDistThresholdFrame.data + nFloatIter); + const size_t nCurrTotColorDistThreshold = (size_t)(sqrt(fCurrDistThresholdFactor)*m_nMinColorDistThreshold) * 3; + const size_t nCurrTotDescDistThreshold = (((size_t)1 << ((size_t)floor(fCurrDistThresholdFactor + 0.5f))) + m_nDescDistThresholdOffset + (bCurrRegionIsUnstable*UNSTAB_DESC_DIST_OFFSET)) * 3; + CV_Assert(m_apLocalWordDict[nLocalDictIdx]); + const LocalWord_3ch* pRefBestLocalWord = (LocalWord_3ch*)m_apLocalWordDict[nLocalDictIdx]; + const float fRefBestLocalWordWeight = GetLocalWordWeight(pRefBestLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + const uchar nRefBestLocalWordDescBITS = (uchar)popcount<3>(pRefBestLocalWord->oFeature.anDesc); + bool bFoundUninitd = false; + size_t nGlobalWordIdx; + for (nGlobalWordIdx = 0; nGlobalWordIdx < m_nCurrGlobalWords; ++nGlobalWordIdx) { + GlobalWord_3ch* pCurrGlobalWord = (GlobalWord_3ch*)m_apGlobalWordDict[nGlobalWordIdx]; + if (pCurrGlobalWord + && L1dist(nRefBestLocalWordDescBITS, pCurrGlobalWord->nDescBITS) <= nCurrTotDescDistThreshold / GWORD_DESC_THRES_BITS_MATCH_FACTOR + && cmixdist<3>(pRefBestLocalWord->oFeature.anColor, pCurrGlobalWord->oFeature.anColor) <= nCurrTotColorDistThreshold) + break; + else if (!pCurrGlobalWord) + bFoundUninitd = true; + } + if (nGlobalWordIdx == m_nCurrGlobalWords) { + nGlobalWordIdx = m_nCurrGlobalWords - 1; + GlobalWord_3ch* pCurrGlobalWord = bFoundUninitd ? m_pGlobalWordListIter_3ch++ : (GlobalWord_3ch*)m_apGlobalWordDict[nGlobalWordIdx]; + for (size_t c = 0; c < 3; ++c) { + pCurrGlobalWord->oFeature.anColor[c] = pRefBestLocalWord->oFeature.anColor[c]; + pCurrGlobalWord->oFeature.anDesc[c] = pRefBestLocalWord->oFeature.anDesc[c]; + } + pCurrGlobalWord->nDescBITS = nRefBestLocalWordDescBITS; + pCurrGlobalWord->oSpatioOccMap.create(m_oDownSampledFrameSize_GlobalWordLookup, CV_32FC1); + pCurrGlobalWord->oSpatioOccMap = cv::Scalar(0.0f); + pCurrGlobalWord->fLatestWeight = 0.0f; + m_apGlobalWordDict[nGlobalWordIdx] = pCurrGlobalWord; + } + float& fCurrGlobalWordLocalWeight = *(float*)(m_apGlobalWordDict[nGlobalWordIdx]->oSpatioOccMap.data + nGlobalWordMapLookupIdx); + if (fCurrGlobalWordLocalWeight < fRefBestLocalWordWeight) { + m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight += fRefBestLocalWordWeight; + fCurrGlobalWordLocalWeight += fRefBestLocalWordWeight; + } + oGlobalDictPresenceLookupMap.data[nPxIter] = UCHAR_MAX; + while (nGlobalWordIdx > 0 && (!m_apGlobalWordDict[nGlobalWordIdx - 1] || m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight > m_apGlobalWordDict[nGlobalWordIdx - 1]->fLatestWeight)) { + std::swap(m_apGlobalWordDict[nGlobalWordIdx], m_apGlobalWordDict[nGlobalWordIdx - 1]); + --nGlobalWordIdx; + } + } + } + } + nPxIterIncr = std::max(nPxIterIncr / 3, (size_t)1); + } + for (size_t nGlobalWordIdx = 0; nGlobalWordIdx < m_nCurrGlobalWords; ++nGlobalWordIdx) { + GlobalWord_3ch* pCurrGlobalWord = (GlobalWord_3ch*)m_apGlobalWordDict[nGlobalWordIdx]; + if (!pCurrGlobalWord) { + pCurrGlobalWord = m_pGlobalWordListIter_3ch++; + for (size_t c = 0; c < 3; ++c) { + pCurrGlobalWord->oFeature.anColor[c] = 0; + pCurrGlobalWord->oFeature.anDesc[c] = 0; + } + pCurrGlobalWord->nDescBITS = 0; + pCurrGlobalWord->oSpatioOccMap.create(m_oDownSampledFrameSize_GlobalWordLookup, CV_32FC1); + pCurrGlobalWord->oSpatioOccMap = cv::Scalar(0.0f); + pCurrGlobalWord->fLatestWeight = 0.0f; + m_apGlobalWordDict[nGlobalWordIdx] = pCurrGlobalWord; + } + } + CV_Assert((size_t)(m_pGlobalWordListIter_3ch - m_aGlobalWordList_3ch) == m_nCurrGlobalWords && m_aGlobalWordList_3ch == (m_pGlobalWordListIter_3ch - m_nCurrGlobalWords)); + } + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + // == refresh: per-px global word sort + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + const size_t nGlobalWordMapLookupIdx = m_aPxInfoLUT_PAWCS[nPxIter].nGlobalWordMapLookupIdx; + float fLastGlobalWordLocalWeight = *(float*)(m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[0]->oSpatioOccMap.data + nGlobalWordMapLookupIdx); + for (size_t nGlobalWordLUTIdx = 1; nGlobalWordLUTIdx < m_nCurrGlobalWords; ++nGlobalWordLUTIdx) { + const float fCurrGlobalWordLocalWeight = *(float*)(m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordLUTIdx]->oSpatioOccMap.data + nGlobalWordMapLookupIdx); + if (fCurrGlobalWordLocalWeight > fLastGlobalWordLocalWeight) + std::swap(m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordLUTIdx], m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordLUTIdx - 1]); + else + fLastGlobalWordLocalWeight = fCurrGlobalWordLocalWeight; + } + } +} + +void BackgroundSubtractorPAWCS::apply(cv::InputArray _image, cv::OutputArray _fgmask, double learningRateOverride) { + // == process + CV_Assert(m_bInitialized); + cv::Mat oInputImg = _image.getMat(); + CV_Assert(oInputImg.type() == m_nImgType && oInputImg.size() == m_oImgSize); + CV_Assert(oInputImg.isContinuous()); + _fgmask.create(m_oImgSize, CV_8UC1); + cv::Mat oCurrFGMask = _fgmask.getMat(); + memset(oCurrFGMask.data, 0, oCurrFGMask.cols*oCurrFGMask.rows); + const bool bBootstrapping = ++m_nFrameIndex <= DEFAULT_BOOTSTRAP_WIN_SIZE; + const size_t nCurrSamplesForMovingAvg_LT = bBootstrapping ? m_nSamplesForMovingAvgs / 2 : m_nSamplesForMovingAvgs; + const size_t nCurrSamplesForMovingAvg_ST = nCurrSamplesForMovingAvg_LT / 4; + const float fRollAvgFactor_LT = 1.0f / std::min(m_nFrameIndex, nCurrSamplesForMovingAvg_LT); + const float fRollAvgFactor_ST = 1.0f / std::min(m_nFrameIndex, nCurrSamplesForMovingAvg_ST); + const size_t nCurrGlobalWordUpdateRate = bBootstrapping ? DEFAULT_RESAMPLING_RATE / 2 : DEFAULT_RESAMPLING_RATE; + size_t nFlatRegionCount = 0; + if (m_nImgChannels == 1) { + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + const size_t nDescIter = nPxIter * 2; + const size_t nFloatIter = nPxIter * 4; + const size_t nLocalDictIdx = nModelIter*m_nCurrLocalWords; + const size_t nGlobalWordMapLookupIdx = m_aPxInfoLUT_PAWCS[nPxIter].nGlobalWordMapLookupIdx; + const uchar nCurrColor = oInputImg.data[nPxIter]; + uchar& nLastColor = m_oLastColorFrame.data[nPxIter]; + ushort& nLastIntraDesc = *((ushort*)(m_oLastDescFrame.data + nDescIter)); + size_t nMinColorDist = s_nColorMaxDataRange_1ch; + size_t nMinDescDist = s_nDescMaxDataRange_1ch; + float& fCurrMeanRawSegmRes_LT = *(float*)(m_oMeanRawSegmResFrame_LT.data + nFloatIter); + float& fCurrMeanRawSegmRes_ST = *(float*)(m_oMeanRawSegmResFrame_ST.data + nFloatIter); + float& fCurrMeanFinalSegmRes_LT = *(float*)(m_oMeanFinalSegmResFrame_LT.data + nFloatIter); + float& fCurrMeanFinalSegmRes_ST = *(float*)(m_oMeanFinalSegmResFrame_ST.data + nFloatIter); + float& fCurrDistThresholdFactor = *(float*)(m_oDistThresholdFrame.data + nFloatIter); + float& fCurrDistThresholdVariationFactor = *(float*)(m_oDistThresholdVariationFrame.data + nFloatIter); + float& fCurrLearningRate = *(float*)(m_oUpdateRateFrame.data + nFloatIter); + float& fCurrMeanMinDist_LT = *(float*)(m_oMeanMinDistFrame_LT.data + nFloatIter); + float& fCurrMeanMinDist_ST = *(float*)(m_oMeanMinDistFrame_ST.data + nFloatIter); + const float fBestLocalWordWeight = GetLocalWordWeight(m_apLocalWordDict[nLocalDictIdx], m_nFrameIndex, m_nLocalWordWeightOffset); + const float fLocalWordsWeightSumThreshold = fBestLocalWordWeight / (fCurrDistThresholdFactor * 2); + uchar& bCurrRegionIsUnstable = m_oUnstableRegionMask.data[nPxIter]; + uchar& nCurrRegionIllumUpdtVal = m_oIllumUpdtRegionMask.data[nPxIter]; + uchar& nCurrRegionSegmVal = oCurrFGMask.data[nPxIter]; + const bool bCurrRegionIsROIBorder = m_oROI.data[nPxIter] < UCHAR_MAX; + const int nCurrImgCoord_X = m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X; + const int nCurrImgCoord_Y = m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y; + ushort nCurrInterDesc, nCurrIntraDesc; + LBSP_::computeGrayscaleDescriptor(oInputImg, nCurrColor, nCurrImgCoord_X, nCurrImgCoord_Y, m_anLBSPThreshold_8bitLUT[nCurrColor], nCurrIntraDesc); + const uchar nCurrIntraDescBITS = (uchar)popcount(nCurrIntraDesc); + const bool bCurrRegionIsFlat = nCurrIntraDescBITS < FLAT_REGION_BIT_COUNT; + if (bCurrRegionIsFlat) + ++nFlatRegionCount; + const size_t nCurrWordOccIncr = (DEFAULT_LWORD_OCC_INCR + m_nModelResetCooldown) << int(bCurrRegionIsFlat || bBootstrapping); + const size_t nCurrLocalWordUpdateRate = learningRateOverride > 0 ? (size_t)ceil(learningRateOverride) : bCurrRegionIsFlat ? (size_t)ceil(fCurrLearningRate + FEEDBACK_T_LOWER) / 2 : (size_t)ceil(fCurrLearningRate); + const size_t nCurrColorDistThreshold = (size_t)(sqrt(fCurrDistThresholdFactor)*m_nMinColorDistThreshold) / 2; + const size_t nCurrDescDistThreshold = ((size_t)1 << ((size_t)floor(fCurrDistThresholdFactor + 0.5f))) + m_nDescDistThresholdOffset + (bCurrRegionIsUnstable*UNSTAB_DESC_DIST_OFFSET); + size_t nLocalWordIdx = 0; + float fPotentialLocalWordsWeightSum = 0.0f; + float fLastLocalWordWeight = FLT_MAX; + while (nLocalWordIdx < m_nCurrLocalWords && fPotentialLocalWordsWeightSum < fLocalWordsWeightSumThreshold) { + LocalWord_1ch* pCurrLocalWord = (LocalWord_1ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + const float fCurrLocalWordWeight = GetLocalWordWeight(pCurrLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + { + const size_t nColorDist = L1dist(nCurrColor, pCurrLocalWord->oFeature.anColor[0]); + const size_t nIntraDescDist = hdist(nCurrIntraDesc, pCurrLocalWord->oFeature.anDesc[0]); + LBSP_::computeGrayscaleDescriptor(oInputImg, pCurrLocalWord->oFeature.anColor[0], nCurrImgCoord_X, nCurrImgCoord_Y, m_anLBSPThreshold_8bitLUT[pCurrLocalWord->oFeature.anColor[0]], nCurrInterDesc); + const size_t nInterDescDist = hdist(nCurrInterDesc, pCurrLocalWord->oFeature.anDesc[0]); + const size_t nDescDist = (nIntraDescDist + nInterDescDist) / 2; + if ((!bCurrRegionIsUnstable || bCurrRegionIsFlat || bCurrRegionIsROIBorder) + && nColorDist <= nCurrColorDistThreshold + && nColorDist >= nCurrColorDistThreshold / 2 + && nIntraDescDist <= nCurrDescDistThreshold / 2 + && (rand() % (nCurrRegionIllumUpdtVal ? (nCurrLocalWordUpdateRate / 2 + 1) : nCurrLocalWordUpdateRate)) == 0) { + // == illum updt + pCurrLocalWord->oFeature.anColor[0] = nCurrColor; + pCurrLocalWord->oFeature.anDesc[0] = nCurrIntraDesc; + m_oIllumUpdtRegionMask.data[nPxIter - 1] = 1 & m_oROI.data[nPxIter - 1]; + m_oIllumUpdtRegionMask.data[nPxIter + 1] = 1 & m_oROI.data[nPxIter + 1]; + m_oIllumUpdtRegionMask.data[nPxIter] = 2; + } + if (nDescDist <= nCurrDescDistThreshold && nColorDist <= nCurrColorDistThreshold) { + fPotentialLocalWordsWeightSum += fCurrLocalWordWeight; + pCurrLocalWord->nLastOcc = m_nFrameIndex; + if ((!m_oLastFGMask.data[nPxIter] || m_bUsingMovingCamera) && fCurrLocalWordWeight < DEFAULT_LWORD_MAX_WEIGHT) + pCurrLocalWord->nOccurrences += nCurrWordOccIncr; + nMinColorDist = std::min(nMinColorDist, nColorDist); + nMinDescDist = std::min(nMinDescDist, nDescDist); + } + } + if (fCurrLocalWordWeight > fLastLocalWordWeight) { + std::swap(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx], m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx - 1]); + } + else + fLastLocalWordWeight = fCurrLocalWordWeight; + ++nLocalWordIdx; + } + while (nLocalWordIdx < m_nCurrLocalWords) { + const float fCurrLocalWordWeight = GetLocalWordWeight(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx], m_nFrameIndex, m_nLocalWordWeightOffset); + if (fCurrLocalWordWeight > fLastLocalWordWeight) { + std::swap(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx], m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx - 1]); + } + else + fLastLocalWordWeight = fCurrLocalWordWeight; + ++nLocalWordIdx; + } + if (fPotentialLocalWordsWeightSum >= fLocalWordsWeightSumThreshold || bCurrRegionIsROIBorder) { + // == background + const float fNormalizedMinDist = std::max((float)nMinColorDist / s_nColorMaxDataRange_1ch, (float)nMinDescDist / s_nDescMaxDataRange_1ch); + fCurrMeanMinDist_LT = fCurrMeanMinDist_LT*(1.0f - fRollAvgFactor_LT) + fNormalizedMinDist*fRollAvgFactor_LT; + fCurrMeanMinDist_ST = fCurrMeanMinDist_ST*(1.0f - fRollAvgFactor_ST) + fNormalizedMinDist*fRollAvgFactor_ST; + fCurrMeanRawSegmRes_LT = fCurrMeanRawSegmRes_LT*(1.0f - fRollAvgFactor_LT); + fCurrMeanRawSegmRes_ST = fCurrMeanRawSegmRes_ST*(1.0f - fRollAvgFactor_ST); + if ((rand() % nCurrLocalWordUpdateRate) == 0) { + size_t nGlobalWordLUTIdx; + GlobalWord_1ch* pCurrGlobalWord = nullptr; + for (nGlobalWordLUTIdx = 0; nGlobalWordLUTIdx < m_nCurrGlobalWords; ++nGlobalWordLUTIdx) { + pCurrGlobalWord = (GlobalWord_1ch*)m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordLUTIdx]; + if (L1dist(pCurrGlobalWord->oFeature.anColor[0], nCurrColor) <= nCurrColorDistThreshold + && L1dist(nCurrIntraDescBITS, pCurrGlobalWord->nDescBITS) <= nCurrDescDistThreshold / GWORD_DESC_THRES_BITS_MATCH_FACTOR) + break; + } + if (nGlobalWordLUTIdx != m_nCurrGlobalWords || (rand() % (nCurrLocalWordUpdateRate * 2)) == 0) { + if (nGlobalWordLUTIdx == m_nCurrGlobalWords) { + pCurrGlobalWord = (GlobalWord_1ch*)m_apGlobalWordDict[m_nCurrGlobalWords - 1]; + pCurrGlobalWord->oFeature.anColor[0] = nCurrColor; + pCurrGlobalWord->oFeature.anDesc[0] = nCurrIntraDesc; + pCurrGlobalWord->nDescBITS = nCurrIntraDescBITS; + pCurrGlobalWord->oSpatioOccMap = cv::Scalar(0.0f); + pCurrGlobalWord->fLatestWeight = 0.0f; + } + float& fCurrGlobalWordLocalWeight = *(float*)(pCurrGlobalWord->oSpatioOccMap.data + nGlobalWordMapLookupIdx); + if (fCurrGlobalWordLocalWeight < fPotentialLocalWordsWeightSum) { + pCurrGlobalWord->fLatestWeight += fPotentialLocalWordsWeightSum; + fCurrGlobalWordLocalWeight += fPotentialLocalWordsWeightSum; + } + } + } + } + else { + // == foreground + const float fNormalizedMinDist = std::max(std::max((float)nMinColorDist / s_nColorMaxDataRange_1ch, (float)nMinDescDist / s_nDescMaxDataRange_1ch), (fLocalWordsWeightSumThreshold - fPotentialLocalWordsWeightSum) / fLocalWordsWeightSumThreshold); + fCurrMeanMinDist_LT = fCurrMeanMinDist_LT*(1.0f - fRollAvgFactor_LT) + fNormalizedMinDist*fRollAvgFactor_LT; + fCurrMeanMinDist_ST = fCurrMeanMinDist_ST*(1.0f - fRollAvgFactor_ST) + fNormalizedMinDist*fRollAvgFactor_ST; + fCurrMeanRawSegmRes_LT = fCurrMeanRawSegmRes_LT*(1.0f - fRollAvgFactor_LT) + fRollAvgFactor_LT; + fCurrMeanRawSegmRes_ST = fCurrMeanRawSegmRes_ST*(1.0f - fRollAvgFactor_ST) + fRollAvgFactor_ST; + if (bCurrRegionIsFlat || (rand() % nCurrLocalWordUpdateRate) == 0) { + size_t nGlobalWordLUTIdx; + GlobalWord_1ch* pCurrGlobalWord; + for (nGlobalWordLUTIdx = 0; nGlobalWordLUTIdx < m_nCurrGlobalWords; ++nGlobalWordLUTIdx) { + pCurrGlobalWord = (GlobalWord_1ch*)m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordLUTIdx]; + if (L1dist(pCurrGlobalWord->oFeature.anColor[0], nCurrColor) <= nCurrColorDistThreshold + && L1dist(nCurrIntraDescBITS, pCurrGlobalWord->nDescBITS) <= nCurrDescDistThreshold / GWORD_DESC_THRES_BITS_MATCH_FACTOR) + break; + } + if (nGlobalWordLUTIdx == m_nCurrGlobalWords) + nCurrRegionSegmVal = UCHAR_MAX; + else { + const float fGlobalWordLocalizedWeight = *(float*)(pCurrGlobalWord->oSpatioOccMap.data + nGlobalWordMapLookupIdx); + if (fPotentialLocalWordsWeightSum + fGlobalWordLocalizedWeight / (bCurrRegionIsFlat ? 2 : 4) < fLocalWordsWeightSumThreshold) + nCurrRegionSegmVal = UCHAR_MAX; + } + } + else + nCurrRegionSegmVal = UCHAR_MAX; + if (fPotentialLocalWordsWeightSum < DEFAULT_LWORD_INIT_WEIGHT) { + const size_t nNewLocalWordIdx = m_nCurrLocalWords - 1; + LocalWord_1ch* pNewLocalWord = (LocalWord_1ch*)m_apLocalWordDict[nLocalDictIdx + nNewLocalWordIdx]; + pNewLocalWord->oFeature.anColor[0] = nCurrColor; + pNewLocalWord->oFeature.anDesc[0] = nCurrIntraDesc; + pNewLocalWord->nOccurrences = nCurrWordOccIncr; + pNewLocalWord->nFirstOcc = m_nFrameIndex; + pNewLocalWord->nLastOcc = m_nFrameIndex; + } + } + // == neighb updt + if ((!nCurrRegionSegmVal && (rand() % nCurrLocalWordUpdateRate) == 0) || bCurrRegionIsROIBorder || m_bUsingMovingCamera) { + //if((!nCurrRegionSegmVal && (rand()%(nCurrRegionIllumUpdtVal?(nCurrLocalWordUpdateRate/2+1):nCurrLocalWordUpdateRate))==0) || bCurrRegionIsROIBorder) { + int nSampleImgCoord_Y, nSampleImgCoord_X; + if (bCurrRegionIsFlat || bCurrRegionIsROIBorder || m_bUsingMovingCamera) + getRandNeighborPosition_5x5(nSampleImgCoord_X, nSampleImgCoord_Y, nCurrImgCoord_X, nCurrImgCoord_Y, LBSP_::PATCH_SIZE / 2, m_oImgSize); + else + getRandNeighborPosition_3x3(nSampleImgCoord_X, nSampleImgCoord_Y, nCurrImgCoord_X, nCurrImgCoord_Y, LBSP_::PATCH_SIZE / 2, m_oImgSize); + const size_t nSamplePxIdx = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; + if (m_oROI.data[nSamplePxIdx]) { + const size_t nNeighborLocalDictIdx = m_aPxInfoLUT_PAWCS[nSamplePxIdx].nModelIdx*m_nCurrLocalWords; + size_t nNeighborLocalWordIdx = 0; + float fNeighborPotentialLocalWordsWeightSum = 0.0f; + while (nNeighborLocalWordIdx < m_nCurrLocalWords && fNeighborPotentialLocalWordsWeightSum < fLocalWordsWeightSumThreshold) { + LocalWord_1ch* pNeighborLocalWord = (LocalWord_1ch*)m_apLocalWordDict[nNeighborLocalDictIdx + nNeighborLocalWordIdx]; + const size_t nNeighborColorDist = L1dist(nCurrColor, pNeighborLocalWord->oFeature.anColor[0]); + const size_t nNeighborIntraDescDist = hdist(nCurrIntraDesc, pNeighborLocalWord->oFeature.anDesc[0]); + const bool bNeighborRegionIsFlat = popcount(pNeighborLocalWord->oFeature.anDesc[0]) < FLAT_REGION_BIT_COUNT; + const size_t nNeighborWordOccIncr = bNeighborRegionIsFlat ? nCurrWordOccIncr * 2 : nCurrWordOccIncr; + if (nNeighborColorDist <= nCurrColorDistThreshold && nNeighborIntraDescDist <= nCurrDescDistThreshold) { + const float fNeighborLocalWordWeight = GetLocalWordWeight(pNeighborLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + fNeighborPotentialLocalWordsWeightSum += fNeighborLocalWordWeight; + pNeighborLocalWord->nLastOcc = m_nFrameIndex; + if (fNeighborLocalWordWeight < DEFAULT_LWORD_MAX_WEIGHT) + pNeighborLocalWord->nOccurrences += nNeighborWordOccIncr; + } + else if (!oCurrFGMask.data[nSamplePxIdx] && bCurrRegionIsFlat && (bBootstrapping || (rand() % nCurrLocalWordUpdateRate) == 0)) { + const size_t nSampleDescIdx = nSamplePxIdx * 2; + ushort& nNeighborLastIntraDesc = *((ushort*)(m_oLastDescFrame.data + nSampleDescIdx)); + const size_t nNeighborLastIntraDescDist = hdist(nCurrIntraDesc, nNeighborLastIntraDesc); + if (nNeighborColorDist <= nCurrColorDistThreshold && nNeighborLastIntraDescDist <= nCurrDescDistThreshold / 2) { + const float fNeighborLocalWordWeight = GetLocalWordWeight(pNeighborLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + fNeighborPotentialLocalWordsWeightSum += fNeighborLocalWordWeight; + pNeighborLocalWord->nLastOcc = m_nFrameIndex; + if (fNeighborLocalWordWeight < DEFAULT_LWORD_MAX_WEIGHT) + pNeighborLocalWord->nOccurrences += nNeighborWordOccIncr; + pNeighborLocalWord->oFeature.anDesc[0] = nCurrIntraDesc; + } + } + ++nNeighborLocalWordIdx; + } + if (fNeighborPotentialLocalWordsWeightSum < DEFAULT_LWORD_INIT_WEIGHT) { + nNeighborLocalWordIdx = m_nCurrLocalWords - 1; + LocalWord_1ch* pNeighborLocalWord = (LocalWord_1ch*)m_apLocalWordDict[nNeighborLocalDictIdx + nNeighborLocalWordIdx]; + pNeighborLocalWord->oFeature.anColor[0] = nCurrColor; + pNeighborLocalWord->oFeature.anDesc[0] = nCurrIntraDesc; + pNeighborLocalWord->nOccurrences = nCurrWordOccIncr; + pNeighborLocalWord->nFirstOcc = m_nFrameIndex; + pNeighborLocalWord->nLastOcc = m_nFrameIndex; + } + } + } + if (nCurrRegionIllumUpdtVal) + nCurrRegionIllumUpdtVal -= 1; + // == feedback adj + bCurrRegionIsUnstable = fCurrDistThresholdFactor > UNSTABLE_REG_RDIST_MIN || (fCurrMeanRawSegmRes_LT - fCurrMeanFinalSegmRes_LT) > UNSTABLE_REG_RATIO_MIN || (fCurrMeanRawSegmRes_ST - fCurrMeanFinalSegmRes_ST) > UNSTABLE_REG_RATIO_MIN; + if (m_oLastFGMask.data[nPxIter] || (std::min(fCurrMeanMinDist_LT, fCurrMeanMinDist_ST) < UNSTABLE_REG_RATIO_MIN && nCurrRegionSegmVal)) + fCurrLearningRate = std::min(fCurrLearningRate + FEEDBACK_T_INCR / (std::max(fCurrMeanMinDist_LT, fCurrMeanMinDist_ST)*fCurrDistThresholdVariationFactor), FEEDBACK_T_UPPER); + else + fCurrLearningRate = std::max(fCurrLearningRate - FEEDBACK_T_DECR*fCurrDistThresholdVariationFactor / std::max(fCurrMeanMinDist_LT, fCurrMeanMinDist_ST), FEEDBACK_T_LOWER); + if (std::max(fCurrMeanMinDist_LT, fCurrMeanMinDist_ST) > UNSTABLE_REG_RATIO_MIN && m_oBlinksFrame.data[nPxIter]) + (fCurrDistThresholdVariationFactor) += bBootstrapping ? FEEDBACK_V_INCR * 2 : FEEDBACK_V_INCR; + else + fCurrDistThresholdVariationFactor = std::max(fCurrDistThresholdVariationFactor - FEEDBACK_V_DECR*((bBootstrapping || bCurrRegionIsFlat) ? 2 : m_oLastFGMask.data[nPxIter] ? 0.5f : 1), FEEDBACK_V_DECR); + if (fCurrDistThresholdFactor < std::pow(1.0f + std::min(fCurrMeanMinDist_LT, fCurrMeanMinDist_ST) * 2, 2)) + fCurrDistThresholdFactor += FEEDBACK_R_VAR*(fCurrDistThresholdVariationFactor - FEEDBACK_V_DECR); + else + fCurrDistThresholdFactor = std::max(fCurrDistThresholdFactor - FEEDBACK_R_VAR / fCurrDistThresholdVariationFactor, 1.0f); + nLastIntraDesc = nCurrIntraDesc; + nLastColor = nCurrColor; + } + } + else { //m_nImgChannels==3 + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + const size_t nPxRGBIter = nPxIter * 3; + const size_t nDescRGBIter = nPxRGBIter * 2; + const size_t nFloatIter = nPxIter * 4; + const size_t nLocalDictIdx = nModelIter*m_nCurrLocalWords; + const size_t nGlobalWordMapLookupIdx = m_aPxInfoLUT_PAWCS[nPxIter].nGlobalWordMapLookupIdx; + const uchar* const anCurrColor = oInputImg.data + nPxRGBIter; + uchar* anLastColor = m_oLastColorFrame.data + nPxRGBIter; + ushort* anLastIntraDesc = ((ushort*)(m_oLastDescFrame.data + nDescRGBIter)); + size_t nMinTotColorDist = s_nColorMaxDataRange_3ch; + size_t nMinTotDescDist = s_nDescMaxDataRange_3ch; + float& fCurrMeanRawSegmRes_LT = *(float*)(m_oMeanRawSegmResFrame_LT.data + nFloatIter); + float& fCurrMeanRawSegmRes_ST = *(float*)(m_oMeanRawSegmResFrame_ST.data + nFloatIter); + float& fCurrMeanFinalSegmRes_LT = *(float*)(m_oMeanFinalSegmResFrame_LT.data + nFloatIter); + float& fCurrMeanFinalSegmRes_ST = *(float*)(m_oMeanFinalSegmResFrame_ST.data + nFloatIter); + float& fCurrDistThresholdFactor = *(float*)(m_oDistThresholdFrame.data + nFloatIter); + float& fCurrDistThresholdVariationFactor = *(float*)(m_oDistThresholdVariationFrame.data + nFloatIter); + float& fCurrLearningRate = *(float*)(m_oUpdateRateFrame.data + nFloatIter); + float& fCurrMeanMinDist_LT = *(float*)(m_oMeanMinDistFrame_LT.data + nFloatIter); + float& fCurrMeanMinDist_ST = *(float*)(m_oMeanMinDistFrame_ST.data + nFloatIter); + const float fBestLocalWordWeight = GetLocalWordWeight(m_apLocalWordDict[nLocalDictIdx], m_nFrameIndex, m_nLocalWordWeightOffset); + const float fLocalWordsWeightSumThreshold = fBestLocalWordWeight / (fCurrDistThresholdFactor * 2); + uchar& bCurrRegionIsUnstable = m_oUnstableRegionMask.data[nPxIter]; + uchar& nCurrRegionIllumUpdtVal = m_oIllumUpdtRegionMask.data[nPxIter]; + uchar& nCurrRegionSegmVal = oCurrFGMask.data[nPxIter]; + const bool bCurrRegionIsROIBorder = m_oROI.data[nPxIter] < UCHAR_MAX; + const int nCurrImgCoord_X = m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X; + const int nCurrImgCoord_Y = m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y; + ushort anCurrInterDesc[3], anCurrIntraDesc[3]; + const size_t anCurrIntraLBSPThresholds[3] = { m_anLBSPThreshold_8bitLUT[anCurrColor[0]],m_anLBSPThreshold_8bitLUT[anCurrColor[1]],m_anLBSPThreshold_8bitLUT[anCurrColor[2]] }; + LBSP_::computeRGBDescriptor(oInputImg, anCurrColor, nCurrImgCoord_X, nCurrImgCoord_Y, anCurrIntraLBSPThresholds, anCurrIntraDesc); + const uchar nCurrIntraDescBITS = (uchar)popcount<3>(anCurrIntraDesc); + const bool bCurrRegionIsFlat = nCurrIntraDescBITS < FLAT_REGION_BIT_COUNT * 2; + if (bCurrRegionIsFlat) + ++nFlatRegionCount; + const size_t nCurrWordOccIncr = (DEFAULT_LWORD_OCC_INCR + m_nModelResetCooldown) << int(bCurrRegionIsFlat || bBootstrapping); + const size_t nCurrLocalWordUpdateRate = learningRateOverride > 0 ? (size_t)ceil(learningRateOverride) : bCurrRegionIsFlat ? (size_t)ceil(fCurrLearningRate + FEEDBACK_T_LOWER) / 2 : (size_t)ceil(fCurrLearningRate); + const size_t nCurrTotColorDistThreshold = (size_t)(sqrt(fCurrDistThresholdFactor)*m_nMinColorDistThreshold) * 3; + const size_t nCurrTotDescDistThreshold = (((size_t)1 << ((size_t)floor(fCurrDistThresholdFactor + 0.5f))) + m_nDescDistThresholdOffset + (bCurrRegionIsUnstable*UNSTAB_DESC_DIST_OFFSET)) * 3; + size_t nLocalWordIdx = 0; + float fPotentialLocalWordsWeightSum = 0.0f; + float fLastLocalWordWeight = FLT_MAX; + while (nLocalWordIdx < m_nCurrLocalWords && fPotentialLocalWordsWeightSum < fLocalWordsWeightSumThreshold) { + LocalWord_3ch* pCurrLocalWord = (LocalWord_3ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + const float fCurrLocalWordWeight = GetLocalWordWeight(pCurrLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + { + const size_t nTotColorL1Dist = L1dist<3>(anCurrColor, pCurrLocalWord->oFeature.anColor); + const size_t nColorDistortion = cdist<3>(anCurrColor, pCurrLocalWord->oFeature.anColor); + const size_t nTotColorMixDist = cmixdist(nTotColorL1Dist, nColorDistortion); + const size_t nTotIntraDescDist = hdist<3>(anCurrIntraDesc, pCurrLocalWord->oFeature.anDesc); + const size_t anCurrInterLBSPThresholds[3] = { m_anLBSPThreshold_8bitLUT[pCurrLocalWord->oFeature.anColor[0]],m_anLBSPThreshold_8bitLUT[pCurrLocalWord->oFeature.anColor[1]],m_anLBSPThreshold_8bitLUT[pCurrLocalWord->oFeature.anColor[2]] }; + LBSP_::computeRGBDescriptor(oInputImg, pCurrLocalWord->oFeature.anColor, nCurrImgCoord_X, nCurrImgCoord_Y, anCurrInterLBSPThresholds, anCurrInterDesc); + const size_t nTotInterDescDist = hdist<3>(anCurrInterDesc, pCurrLocalWord->oFeature.anDesc); + const size_t nTotDescDist = (nTotIntraDescDist + nTotInterDescDist) / 2; + if ((!bCurrRegionIsUnstable || bCurrRegionIsFlat || bCurrRegionIsROIBorder) + && nTotColorMixDist <= nCurrTotColorDistThreshold + && nTotColorL1Dist >= nCurrTotColorDistThreshold / 2 + && nTotIntraDescDist <= nCurrTotDescDistThreshold / 2 + && (rand() % (nCurrRegionIllumUpdtVal ? (nCurrLocalWordUpdateRate / 2 + 1) : nCurrLocalWordUpdateRate)) == 0) { + // == illum updt + for (size_t c = 0; c < 3; ++c) { + pCurrLocalWord->oFeature.anColor[c] = anCurrColor[c]; + pCurrLocalWord->oFeature.anDesc[c] = anCurrIntraDesc[c]; + } + m_oIllumUpdtRegionMask.data[nPxIter - 1] = 1 & m_oROI.data[nPxIter - 1]; + m_oIllumUpdtRegionMask.data[nPxIter + 1] = 1 & m_oROI.data[nPxIter + 1]; + m_oIllumUpdtRegionMask.data[nPxIter] = 2; + } + if (nTotDescDist <= nCurrTotDescDistThreshold && nTotColorMixDist <= nCurrTotColorDistThreshold) { + fPotentialLocalWordsWeightSum += fCurrLocalWordWeight; + pCurrLocalWord->nLastOcc = m_nFrameIndex; + if ((!m_oLastFGMask.data[nPxIter] || m_bUsingMovingCamera) && fCurrLocalWordWeight < DEFAULT_LWORD_MAX_WEIGHT) + pCurrLocalWord->nOccurrences += nCurrWordOccIncr; + nMinTotColorDist = std::min(nMinTotColorDist, nTotColorMixDist); + nMinTotDescDist = std::min(nMinTotDescDist, nTotDescDist); + } + } + if (fCurrLocalWordWeight > fLastLocalWordWeight) { + std::swap(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx], m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx - 1]); + } + else + fLastLocalWordWeight = fCurrLocalWordWeight; + ++nLocalWordIdx; + } + while (nLocalWordIdx < m_nCurrLocalWords) { + const float fCurrLocalWordWeight = GetLocalWordWeight(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx], m_nFrameIndex, m_nLocalWordWeightOffset); + if (fCurrLocalWordWeight > fLastLocalWordWeight) { + std::swap(m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx], m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx - 1]); + } + else + fLastLocalWordWeight = fCurrLocalWordWeight; + ++nLocalWordIdx; + } + if (fPotentialLocalWordsWeightSum >= fLocalWordsWeightSumThreshold || bCurrRegionIsROIBorder) { + // == background + const float fNormalizedMinDist = std::max((float)nMinTotColorDist / s_nColorMaxDataRange_3ch, (float)nMinTotDescDist / s_nDescMaxDataRange_3ch); + fCurrMeanMinDist_LT = fCurrMeanMinDist_LT*(1.0f - fRollAvgFactor_LT) + fNormalizedMinDist*fRollAvgFactor_LT; + fCurrMeanMinDist_ST = fCurrMeanMinDist_ST*(1.0f - fRollAvgFactor_ST) + fNormalizedMinDist*fRollAvgFactor_ST; + fCurrMeanRawSegmRes_LT = fCurrMeanRawSegmRes_LT*(1.0f - fRollAvgFactor_LT); + fCurrMeanRawSegmRes_ST = fCurrMeanRawSegmRes_ST*(1.0f - fRollAvgFactor_ST); + if ((rand() % nCurrLocalWordUpdateRate) == 0) { + size_t nGlobalWordLUTIdx; + GlobalWord_3ch* pCurrGlobalWord = nullptr; + for (nGlobalWordLUTIdx = 0; nGlobalWordLUTIdx < m_nCurrGlobalWords; ++nGlobalWordLUTIdx) { + pCurrGlobalWord = (GlobalWord_3ch*)m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordLUTIdx]; + if (L1dist(nCurrIntraDescBITS, pCurrGlobalWord->nDescBITS) <= nCurrTotDescDistThreshold / GWORD_DESC_THRES_BITS_MATCH_FACTOR + && cmixdist<3>(anCurrColor, pCurrGlobalWord->oFeature.anColor) <= nCurrTotColorDistThreshold) + break; + } + if (nGlobalWordLUTIdx != m_nCurrGlobalWords || (rand() % (nCurrLocalWordUpdateRate * 2)) == 0) { + if (nGlobalWordLUTIdx == m_nCurrGlobalWords) { + pCurrGlobalWord = (GlobalWord_3ch*)m_apGlobalWordDict[m_nCurrGlobalWords - 1]; + for (size_t c = 0; c < 3; ++c) { + pCurrGlobalWord->oFeature.anColor[c] = anCurrColor[c]; + pCurrGlobalWord->oFeature.anDesc[c] = anCurrIntraDesc[c]; + } + pCurrGlobalWord->nDescBITS = nCurrIntraDescBITS; + pCurrGlobalWord->oSpatioOccMap = cv::Scalar(0.0f); + pCurrGlobalWord->fLatestWeight = 0.0f; + } + float& fCurrGlobalWordLocalWeight = *(float*)(pCurrGlobalWord->oSpatioOccMap.data + nGlobalWordMapLookupIdx); + if (fCurrGlobalWordLocalWeight < fPotentialLocalWordsWeightSum) { + pCurrGlobalWord->fLatestWeight += fPotentialLocalWordsWeightSum; + fCurrGlobalWordLocalWeight += fPotentialLocalWordsWeightSum; + } + } + } + } + else { + // == foreground + const float fNormalizedMinDist = std::max(std::max((float)nMinTotColorDist / s_nColorMaxDataRange_3ch, (float)nMinTotDescDist / s_nDescMaxDataRange_3ch), (fLocalWordsWeightSumThreshold - fPotentialLocalWordsWeightSum) / fLocalWordsWeightSumThreshold); + fCurrMeanMinDist_LT = fCurrMeanMinDist_LT*(1.0f - fRollAvgFactor_LT) + fNormalizedMinDist*fRollAvgFactor_LT; + fCurrMeanMinDist_ST = fCurrMeanMinDist_ST*(1.0f - fRollAvgFactor_ST) + fNormalizedMinDist*fRollAvgFactor_ST; + fCurrMeanRawSegmRes_LT = fCurrMeanRawSegmRes_LT*(1.0f - fRollAvgFactor_LT) + fRollAvgFactor_LT; + fCurrMeanRawSegmRes_ST = fCurrMeanRawSegmRes_ST*(1.0f - fRollAvgFactor_ST) + fRollAvgFactor_ST; + if (bCurrRegionIsFlat || (rand() % nCurrLocalWordUpdateRate) == 0) { + size_t nGlobalWordLUTIdx; + GlobalWord_3ch* pCurrGlobalWord; + for (nGlobalWordLUTIdx = 0; nGlobalWordLUTIdx < m_nCurrGlobalWords; ++nGlobalWordLUTIdx) { + pCurrGlobalWord = (GlobalWord_3ch*)m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordLUTIdx]; + if (L1dist(nCurrIntraDescBITS, pCurrGlobalWord->nDescBITS) <= nCurrTotDescDistThreshold / GWORD_DESC_THRES_BITS_MATCH_FACTOR + && cmixdist<3>(anCurrColor, pCurrGlobalWord->oFeature.anColor) <= nCurrTotColorDistThreshold) + break; + } + if (nGlobalWordLUTIdx == m_nCurrGlobalWords) + nCurrRegionSegmVal = UCHAR_MAX; + else { + const float fGlobalWordLocalizedWeight = *(float*)(pCurrGlobalWord->oSpatioOccMap.data + nGlobalWordMapLookupIdx); + if (fPotentialLocalWordsWeightSum + fGlobalWordLocalizedWeight / (bCurrRegionIsFlat ? 2 : 4) < fLocalWordsWeightSumThreshold) + nCurrRegionSegmVal = UCHAR_MAX; + } + } + else + nCurrRegionSegmVal = UCHAR_MAX; + if (fPotentialLocalWordsWeightSum < DEFAULT_LWORD_INIT_WEIGHT) { + const size_t nNewLocalWordIdx = m_nCurrLocalWords - 1; + LocalWord_3ch* pNewLocalWord = (LocalWord_3ch*)m_apLocalWordDict[nLocalDictIdx + nNewLocalWordIdx]; + for (size_t c = 0; c < 3; ++c) { + pNewLocalWord->oFeature.anColor[c] = anCurrColor[c]; + pNewLocalWord->oFeature.anDesc[c] = anCurrIntraDesc[c]; + } + pNewLocalWord->nOccurrences = nCurrWordOccIncr; + pNewLocalWord->nFirstOcc = m_nFrameIndex; + pNewLocalWord->nLastOcc = m_nFrameIndex; + } + } + // == neighb updt + if ((!nCurrRegionSegmVal && (rand() % nCurrLocalWordUpdateRate) == 0) || bCurrRegionIsROIBorder || m_bUsingMovingCamera) { + //if((!nCurrRegionSegmVal && (rand()%(nCurrRegionIllumUpdtVal?(nCurrLocalWordUpdateRate/2+1):nCurrLocalWordUpdateRate))==0) || bCurrRegionIsROIBorder) { + int nSampleImgCoord_Y, nSampleImgCoord_X; + if (bCurrRegionIsFlat || bCurrRegionIsROIBorder || m_bUsingMovingCamera) + getRandNeighborPosition_5x5(nSampleImgCoord_X, nSampleImgCoord_Y, nCurrImgCoord_X, nCurrImgCoord_Y, LBSP_::PATCH_SIZE / 2, m_oImgSize); + else + getRandNeighborPosition_3x3(nSampleImgCoord_X, nSampleImgCoord_Y, nCurrImgCoord_X, nCurrImgCoord_Y, LBSP_::PATCH_SIZE / 2, m_oImgSize); + const size_t nSamplePxIdx = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; + if (m_oROI.data[nSamplePxIdx]) { + const size_t nNeighborLocalDictIdx = m_aPxInfoLUT_PAWCS[nSamplePxIdx].nModelIdx*m_nCurrLocalWords; + size_t nNeighborLocalWordIdx = 0; + float fNeighborPotentialLocalWordsWeightSum = 0.0f; + while (nNeighborLocalWordIdx < m_nCurrLocalWords && fNeighborPotentialLocalWordsWeightSum < fLocalWordsWeightSumThreshold) { + LocalWord_3ch* pNeighborLocalWord = (LocalWord_3ch*)m_apLocalWordDict[nNeighborLocalDictIdx + nNeighborLocalWordIdx]; + const size_t nNeighborTotColorL1Dist = L1dist<3>(anCurrColor, pNeighborLocalWord->oFeature.anColor); + const size_t nNeighborColorDistortion = cdist<3>(anCurrColor, pNeighborLocalWord->oFeature.anColor); + const size_t nNeighborTotColorMixDist = cmixdist(nNeighborTotColorL1Dist, nNeighborColorDistortion); + const size_t nNeighborTotIntraDescDist = hdist<3>(anCurrIntraDesc, pNeighborLocalWord->oFeature.anDesc); + const bool bNeighborRegionIsFlat = popcount<3>(pNeighborLocalWord->oFeature.anDesc) < FLAT_REGION_BIT_COUNT * 2; + const size_t nNeighborWordOccIncr = bNeighborRegionIsFlat ? nCurrWordOccIncr * 2 : nCurrWordOccIncr; + if (nNeighborTotColorMixDist <= nCurrTotColorDistThreshold && nNeighborTotIntraDescDist <= nCurrTotDescDistThreshold) { + const float fNeighborLocalWordWeight = GetLocalWordWeight(pNeighborLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + fNeighborPotentialLocalWordsWeightSum += fNeighborLocalWordWeight; + pNeighborLocalWord->nLastOcc = m_nFrameIndex; + if (fNeighborLocalWordWeight < DEFAULT_LWORD_MAX_WEIGHT) + pNeighborLocalWord->nOccurrences += nNeighborWordOccIncr; + } + else if (!oCurrFGMask.data[nSamplePxIdx] && bCurrRegionIsFlat && (bBootstrapping || (rand() % nCurrLocalWordUpdateRate) == 0)) { + const size_t nSamplePxRGBIdx = nSamplePxIdx * 3; + const size_t nSampleDescRGBIdx = nSamplePxRGBIdx * 2; + ushort* anNeighborLastIntraDesc = ((ushort*)(m_oLastDescFrame.data + nSampleDescRGBIdx)); + const size_t nNeighborTotLastIntraDescDist = hdist<3>(anCurrIntraDesc, anNeighborLastIntraDesc); + if (nNeighborTotColorMixDist <= nCurrTotColorDistThreshold && nNeighborTotLastIntraDescDist <= nCurrTotDescDistThreshold / 2) { + const float fNeighborLocalWordWeight = GetLocalWordWeight(pNeighborLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + fNeighborPotentialLocalWordsWeightSum += fNeighborLocalWordWeight; + pNeighborLocalWord->nLastOcc = m_nFrameIndex; + if (fNeighborLocalWordWeight < DEFAULT_LWORD_MAX_WEIGHT) + pNeighborLocalWord->nOccurrences += nNeighborWordOccIncr; + for (size_t c = 0; c < 3; ++c) + pNeighborLocalWord->oFeature.anDesc[c] = anCurrIntraDesc[c]; + } + else { + const bool bNeighborLastRegionIsFlat = popcount<3>(anNeighborLastIntraDesc) < FLAT_REGION_BIT_COUNT * 2; + if (bNeighborLastRegionIsFlat && bCurrRegionIsFlat && + nNeighborTotLastIntraDescDist + nNeighborTotIntraDescDist <= nCurrTotDescDistThreshold && + nNeighborColorDistortion <= nCurrTotColorDistThreshold / 4) { + const float fNeighborLocalWordWeight = GetLocalWordWeight(pNeighborLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + fNeighborPotentialLocalWordsWeightSum += fNeighborLocalWordWeight; + pNeighborLocalWord->nLastOcc = m_nFrameIndex; + if (fNeighborLocalWordWeight < DEFAULT_LWORD_MAX_WEIGHT) + pNeighborLocalWord->nOccurrences += nNeighborWordOccIncr; + for (size_t c = 0; c < 3; ++c) + pNeighborLocalWord->oFeature.anColor[c] = anCurrColor[c]; + } + } + } + ++nNeighborLocalWordIdx; + } + if (fNeighborPotentialLocalWordsWeightSum < DEFAULT_LWORD_INIT_WEIGHT) { + nNeighborLocalWordIdx = m_nCurrLocalWords - 1; + LocalWord_3ch* pNeighborLocalWord = (LocalWord_3ch*)m_apLocalWordDict[nNeighborLocalDictIdx + nNeighborLocalWordIdx]; + for (size_t c = 0; c < 3; ++c) { + pNeighborLocalWord->oFeature.anColor[c] = anCurrColor[c]; + pNeighborLocalWord->oFeature.anDesc[c] = anCurrIntraDesc[c]; + } + pNeighborLocalWord->nOccurrences = nCurrWordOccIncr; + pNeighborLocalWord->nFirstOcc = m_nFrameIndex; + pNeighborLocalWord->nLastOcc = m_nFrameIndex; + } + } + } + if (nCurrRegionIllumUpdtVal) + nCurrRegionIllumUpdtVal -= 1; + // == feedback adj + bCurrRegionIsUnstable = fCurrDistThresholdFactor > UNSTABLE_REG_RDIST_MIN || (fCurrMeanRawSegmRes_LT - fCurrMeanFinalSegmRes_LT) > UNSTABLE_REG_RATIO_MIN || (fCurrMeanRawSegmRes_ST - fCurrMeanFinalSegmRes_ST) > UNSTABLE_REG_RATIO_MIN; + if (m_oLastFGMask.data[nPxIter] || (std::min(fCurrMeanMinDist_LT, fCurrMeanMinDist_ST) < UNSTABLE_REG_RATIO_MIN && nCurrRegionSegmVal)) + fCurrLearningRate = std::min(fCurrLearningRate + FEEDBACK_T_INCR / (std::max(fCurrMeanMinDist_LT, fCurrMeanMinDist_ST)*fCurrDistThresholdVariationFactor), FEEDBACK_T_UPPER); + else + fCurrLearningRate = std::max(fCurrLearningRate - FEEDBACK_T_DECR*fCurrDistThresholdVariationFactor / std::max(fCurrMeanMinDist_LT, fCurrMeanMinDist_ST), FEEDBACK_T_LOWER); + if (std::max(fCurrMeanMinDist_LT, fCurrMeanMinDist_ST) > UNSTABLE_REG_RATIO_MIN && m_oBlinksFrame.data[nPxIter]) + (fCurrDistThresholdVariationFactor) += bBootstrapping ? FEEDBACK_V_INCR * 2 : FEEDBACK_V_INCR; + else + fCurrDistThresholdVariationFactor = std::max(fCurrDistThresholdVariationFactor - FEEDBACK_V_DECR*((bBootstrapping || bCurrRegionIsFlat) ? 2 : m_oLastFGMask.data[nPxIter] ? 0.5f : 1), FEEDBACK_V_DECR); + if (fCurrDistThresholdFactor < std::pow(1.0f + std::min(fCurrMeanMinDist_LT, fCurrMeanMinDist_ST) * 2, 2)) + fCurrDistThresholdFactor += FEEDBACK_R_VAR*(fCurrDistThresholdVariationFactor - FEEDBACK_V_DECR); + else + fCurrDistThresholdFactor = std::max(fCurrDistThresholdFactor - FEEDBACK_R_VAR / fCurrDistThresholdVariationFactor, 1.0f); + for (size_t c = 0; c < 3; ++c) { + anLastIntraDesc[c] = anCurrIntraDesc[c]; + anLastColor[c] = anCurrColor[c]; + } + } + } + const bool bRecalcGlobalWords = !(m_nFrameIndex % (nCurrGlobalWordUpdateRate << 5)); + const bool bUpdateGlobalWords = !(m_nFrameIndex % (nCurrGlobalWordUpdateRate)); + cv::Mat oLastFGMask_dilated_inverted_downscaled; + if (bUpdateGlobalWords) + cv::resize(m_oLastFGMask_dilated_inverted, oLastFGMask_dilated_inverted_downscaled, m_oDownSampledFrameSize_GlobalWordLookup, 0, 0, cv::INTER_NEAREST); + for (size_t nGlobalWordIdx = 0; nGlobalWordIdx < m_nCurrGlobalWords; ++nGlobalWordIdx) { + if (bRecalcGlobalWords && m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight > 0.0f) { + m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight = GetGlobalWordWeight(m_apGlobalWordDict[nGlobalWordIdx]); + if (m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight < 1.0f) { + m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight = 0.0f; + m_apGlobalWordDict[nGlobalWordIdx]->oSpatioOccMap = cv::Scalar(0.0f); + } + } + if (bUpdateGlobalWords && m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight > 0.0f) { + cv::accumulateProduct(m_apGlobalWordDict[nGlobalWordIdx]->oSpatioOccMap, m_oTempGlobalWordWeightDiffFactor, m_apGlobalWordDict[nGlobalWordIdx]->oSpatioOccMap, oLastFGMask_dilated_inverted_downscaled); + m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight *= 0.9f; + cv::blur(m_apGlobalWordDict[nGlobalWordIdx]->oSpatioOccMap, m_apGlobalWordDict[nGlobalWordIdx]->oSpatioOccMap, cv::Size(3, 3), cv::Point(-1, -1), cv::BORDER_REPLICATE); + } + if (nGlobalWordIdx > 0 && m_apGlobalWordDict[nGlobalWordIdx]->fLatestWeight > m_apGlobalWordDict[nGlobalWordIdx - 1]->fLatestWeight) + std::swap(m_apGlobalWordDict[nGlobalWordIdx], m_apGlobalWordDict[nGlobalWordIdx - 1]); + } + if (bUpdateGlobalWords) { + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + const size_t nGlobalWordMapLookupIdx = m_aPxInfoLUT_PAWCS[nPxIter].nGlobalWordMapLookupIdx; + float fLastGlobalWordLocalWeight = *(float*)(m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[0]->oSpatioOccMap.data + nGlobalWordMapLookupIdx); + for (size_t nGlobalWordLUTIdx = 1; nGlobalWordLUTIdx < m_nCurrGlobalWords; ++nGlobalWordLUTIdx) { + const float fCurrGlobalWordLocalWeight = *(float*)(m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordLUTIdx]->oSpatioOccMap.data + nGlobalWordMapLookupIdx); + if (fCurrGlobalWordLocalWeight > fLastGlobalWordLocalWeight) + std::swap(m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordLUTIdx], m_aPxInfoLUT_PAWCS[nPxIter].apGlobalDictSortLUT[nGlobalWordLUTIdx - 1]); + else + fLastGlobalWordLocalWeight = fCurrGlobalWordLocalWeight; + } + } + } + cv::bitwise_xor(oCurrFGMask, m_oLastRawFGMask, m_oCurrRawFGBlinkMask); + cv::bitwise_or(m_oCurrRawFGBlinkMask, m_oLastRawFGBlinkMask, m_oBlinksFrame); + m_oCurrRawFGBlinkMask.copyTo(m_oLastRawFGBlinkMask); + oCurrFGMask.copyTo(m_oLastRawFGMask); + cv::morphologyEx(oCurrFGMask, m_oFGMask_PreFlood, cv::MORPH_CLOSE, m_oMorphExStructElement); + m_oFGMask_PreFlood.copyTo(m_oFGMask_FloodedHoles); + cv::floodFill(m_oFGMask_FloodedHoles, cv::Point(0, 0), UCHAR_MAX); + cv::bitwise_not(m_oFGMask_FloodedHoles, m_oFGMask_FloodedHoles); + cv::erode(m_oFGMask_PreFlood, m_oFGMask_PreFlood, cv::Mat(), cv::Point(-1, -1), 3); + cv::bitwise_or(oCurrFGMask, m_oFGMask_FloodedHoles, oCurrFGMask); + cv::bitwise_or(oCurrFGMask, m_oFGMask_PreFlood, oCurrFGMask); + cv::medianBlur(oCurrFGMask, m_oLastFGMask, m_nMedianBlurKernelSize); + cv::dilate(m_oLastFGMask, m_oLastFGMask_dilated, cv::Mat(), cv::Point(-1, -1), 3); + cv::bitwise_and(m_oBlinksFrame, m_oLastFGMask_dilated_inverted, m_oBlinksFrame); + cv::bitwise_not(m_oLastFGMask_dilated, m_oLastFGMask_dilated_inverted); + cv::bitwise_and(m_oBlinksFrame, m_oLastFGMask_dilated_inverted, m_oBlinksFrame); + m_oLastFGMask.copyTo(oCurrFGMask); + cv::addWeighted(m_oMeanFinalSegmResFrame_LT, (1.0f - fRollAvgFactor_LT), m_oLastFGMask, (1.0 / UCHAR_MAX)*fRollAvgFactor_LT, 0, m_oMeanFinalSegmResFrame_LT, CV_32F); + cv::addWeighted(m_oMeanFinalSegmResFrame_ST, (1.0f - fRollAvgFactor_ST), m_oLastFGMask, (1.0 / UCHAR_MAX)*fRollAvgFactor_ST, 0, m_oMeanFinalSegmResFrame_ST, CV_32F); + const float fCurrNonFlatRegionRatio = (float)(m_nTotRelevantPxCount - nFlatRegionCount) / m_nTotRelevantPxCount; + if (fCurrNonFlatRegionRatio < LBSPDESC_RATIO_MIN && m_fLastNonFlatRegionRatio < LBSPDESC_RATIO_MIN) { + for (size_t t = 0; t <= UCHAR_MAX; ++t) + if (m_anLBSPThreshold_8bitLUT[t] > cv::saturate_cast((m_nLBSPThresholdOffset + t*m_fRelLBSPThreshold) / 4)) + --m_anLBSPThreshold_8bitLUT[t]; + } + else if (fCurrNonFlatRegionRatio > LBSPDESC_RATIO_MAX && m_fLastNonFlatRegionRatio > LBSPDESC_RATIO_MAX) { + for (size_t t = 0; t <= UCHAR_MAX; ++t) + if (m_anLBSPThreshold_8bitLUT[t] < cv::saturate_cast(m_nLBSPThresholdOffset + UCHAR_MAX*m_fRelLBSPThreshold)) + ++m_anLBSPThreshold_8bitLUT[t]; + } + m_fLastNonFlatRegionRatio = fCurrNonFlatRegionRatio; + cv::resize(oInputImg, m_oDownSampledFrame_MotionAnalysis, m_oDownSampledFrameSize_MotionAnalysis, 0, 0, cv::INTER_AREA); + cv::accumulateWeighted(m_oDownSampledFrame_MotionAnalysis, m_oMeanDownSampledLastDistFrame_LT, fRollAvgFactor_LT); + cv::accumulateWeighted(m_oDownSampledFrame_MotionAnalysis, m_oMeanDownSampledLastDistFrame_ST, fRollAvgFactor_ST); + const float fCurrMeanL1DistRatio = L1dist((float*)m_oMeanDownSampledLastDistFrame_LT.data, (float*)m_oMeanDownSampledLastDistFrame_ST.data, m_oMeanDownSampledLastDistFrame_LT.total(), m_nImgChannels, m_oDownSampledROI_MotionAnalysis.data) / m_nDownSampledROIPxCount; + if (!m_bAutoModelResetEnabled && fCurrMeanL1DistRatio >= FRAMELEVEL_MIN_L1DIST_THRES * 2) + m_bAutoModelResetEnabled = true; + if (m_bAutoModelResetEnabled || m_bUsingMovingCamera) { + if ((m_nFrameIndex%DEFAULT_BOOTSTRAP_WIN_SIZE) == 0) { + cv::Mat oCurrBackgroundImg, oDownSampledBackgroundImg; + getBackgroundImage(oCurrBackgroundImg); + cv::resize(oCurrBackgroundImg, oDownSampledBackgroundImg, m_oDownSampledFrameSize_MotionAnalysis, 0, 0, cv::INTER_AREA); + cv::Mat oDownSampledBackgroundImg_32F; oDownSampledBackgroundImg.convertTo(oDownSampledBackgroundImg_32F, CV_32F); + const float fCurrModelL1DistRatio = L1dist((float*)m_oMeanDownSampledLastDistFrame_LT.data, (float*)oDownSampledBackgroundImg_32F.data, m_oMeanDownSampledLastDistFrame_LT.total(), m_nImgChannels, cv::Mat(m_oDownSampledROI_MotionAnalysis == UCHAR_MAX).data) / m_nDownSampledROIPxCount; + const float fCurrModelCDistRatio = cdist((float*)m_oMeanDownSampledLastDistFrame_LT.data, (float*)oDownSampledBackgroundImg_32F.data, m_oMeanDownSampledLastDistFrame_LT.total(), m_nImgChannels, cv::Mat(m_oDownSampledROI_MotionAnalysis == UCHAR_MAX).data) / m_nDownSampledROIPxCount; + if (m_bUsingMovingCamera && fCurrModelL1DistRatio < FRAMELEVEL_MIN_L1DIST_THRES / 4 && fCurrModelCDistRatio < FRAMELEVEL_MIN_CDIST_THRES / 4) { + m_nLocalWordWeightOffset = DEFAULT_LWORD_WEIGHT_OFFSET; + m_bUsingMovingCamera = false; + refreshModel(1, 1, true); + } + else if (bBootstrapping && !m_bUsingMovingCamera && (fCurrModelL1DistRatio >= FRAMELEVEL_MIN_L1DIST_THRES || fCurrModelCDistRatio >= FRAMELEVEL_MIN_CDIST_THRES)) { + m_nLocalWordWeightOffset = 5; + m_bUsingMovingCamera = true; + refreshModel(1, 1, true); + } + } + if (m_nFramesSinceLastReset > DEFAULT_BOOTSTRAP_WIN_SIZE * 2) + m_bAutoModelResetEnabled = false; + else if (fCurrMeanL1DistRatio >= FRAMELEVEL_MIN_L1DIST_THRES && m_nModelResetCooldown == 0) { + m_nFramesSinceLastReset = 0; + refreshModel(m_nLocalWordWeightOffset / 8, 0, true); + m_nModelResetCooldown = nCurrSamplesForMovingAvg_ST; + m_oUpdateRateFrame = cv::Scalar(1.0f); + } + else if (!bBootstrapping) + ++m_nFramesSinceLastReset; + } + if (m_nModelResetCooldown > 0) + --m_nModelResetCooldown; +} + +void BackgroundSubtractorPAWCS::getBackgroundImage(cv::OutputArray backgroundImage) const { // @@@ add option to reconstruct from gwords? + CV_Assert(m_bInitialized); + cv::Mat oAvgBGImg = cv::Mat::zeros(m_oImgSize, CV_32FC((int)m_nImgChannels)); + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + const size_t nLocalDictIdx = nModelIter*m_nCurrLocalWords; + const int nCurrImgCoord_X = m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_X; + const int nCurrImgCoord_Y = m_aPxInfoLUT_PAWCS[nPxIter].nImgCoord_Y; + if (m_nImgChannels == 1) { + float fTotWeight = 0.0f; + float fTotColor = 0.0f; + for (size_t nLocalWordIdx = 0; nLocalWordIdx < m_nCurrLocalWords; ++nLocalWordIdx) { + LocalWord_1ch* pCurrLocalWord = (LocalWord_1ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + float fCurrWeight = GetLocalWordWeight(pCurrLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + fTotColor += (float)pCurrLocalWord->oFeature.anColor[0] * fCurrWeight; + fTotWeight += fCurrWeight; + } + oAvgBGImg.at(nCurrImgCoord_Y, nCurrImgCoord_X) = fTotColor / fTotWeight; + } + else { //m_nImgChannels==3 + float fTotWeight = 0.0f; + float fTotColor[3] = { 0.0f,0.0f,0.0f }; + for (size_t nLocalWordIdx = 0; nLocalWordIdx < m_nCurrLocalWords; ++nLocalWordIdx) { + LocalWord_3ch* pCurrLocalWord = (LocalWord_3ch*)m_apLocalWordDict[nLocalDictIdx + nLocalWordIdx]; + float fCurrWeight = GetLocalWordWeight(pCurrLocalWord, m_nFrameIndex, m_nLocalWordWeightOffset); + for (size_t c = 0; c < 3; ++c) + fTotColor[c] += (float)pCurrLocalWord->oFeature.anColor[c] * fCurrWeight; + fTotWeight += fCurrWeight; + } + oAvgBGImg.at(nCurrImgCoord_Y, nCurrImgCoord_X) = cv::Vec3f(fTotColor[0] / fTotWeight, fTotColor[1] / fTotWeight, fTotColor[2] / fTotWeight); + } + } + oAvgBGImg.convertTo(backgroundImage, CV_8U); +} + +void BackgroundSubtractorPAWCS::getBackgroundDescriptorsImage(cv::OutputArray backgroundDescImage) const { + CV_Assert(LBSP_::DESC_SIZE == 2); + CV_Assert(m_bInitialized); + cv::Mat oAvgBGDesc = cv::Mat::zeros(m_oImgSize, CV_32FC((int)m_nImgChannels)); + // @@@@@@ TO BE REWRITTEN FOR WORD-BASED RECONSTRUCTION + /*for(size_t n=0; nnOccurrences) / ((w->nLastOcc - w->nFirstOcc) + (nCurrFrame - w->nLastOcc) * 2 + nOffset); +} + +float BackgroundSubtractorPAWCS::GetGlobalWordWeight(const GlobalWordBase* w) { + return (float)cv::sum(w->oSpatioOccMap).val[0]; +} diff --git a/package_bgs/LBSP/BackgroundSubtractorPAWCS.h b/package_bgs/LBSP/BackgroundSubtractorPAWCS.h new file mode 100644 index 0000000..cafa48c --- /dev/null +++ b/package_bgs/LBSP/BackgroundSubtractorPAWCS.h @@ -0,0 +1,169 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "BackgroundSubtractorLBSP_.h" + +//! defines the default value for BackgroundSubtractorLBSP_::m_fRelLBSPThreshold +#define BGSPAWCS_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD (0.333f) +//! defines the default value for BackgroundSubtractorPAWCS::m_nDescDistThresholdOffset +#define BGSPAWCS_DEFAULT_DESC_DIST_THRESHOLD_OFFSET (2) +//! defines the default value for BackgroundSubtractorPAWCS::m_nMinColorDistThreshold +#define BGSPAWCS_DEFAULT_MIN_COLOR_DIST_THRESHOLD (20) +//! defines the default value for BackgroundSubtractorPAWCS::m_nMaxLocalWords and m_nMaxGlobalWords +#define BGSPAWCS_DEFAULT_MAX_NB_WORDS (50) +//! defines the default value for BackgroundSubtractorPAWCS::m_nSamplesForMovingAvgs +#define BGSPAWCS_DEFAULT_N_SAMPLES_FOR_MV_AVGS (100) + +/*! + Pixel-based Adaptive Word Consensus Segmenter (PAWCS) change detection algorithm. + + Note: both grayscale and RGB/BGR images may be used with this extractor (parameters are adjusted automatically). + For optimal grayscale results, use CV_8UC1 frames instead of CV_8UC3. + + For more details on the different parameters or on the algorithm itself, see P.-L. St-Charles et al., + "A Self-Adjusting Approach to Change Detection Based on Background Word Consensus", in WACV 2015. + + This algorithm is currently NOT thread-safe. + */ +class BackgroundSubtractorPAWCS : public BackgroundSubtractorLBSP_ { +public: + //! full constructor + BackgroundSubtractorPAWCS(float fRelLBSPThreshold = BGSPAWCS_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD, + size_t nDescDistThresholdOffset = BGSPAWCS_DEFAULT_DESC_DIST_THRESHOLD_OFFSET, + size_t nMinColorDistThreshold = BGSPAWCS_DEFAULT_MIN_COLOR_DIST_THRESHOLD, + size_t nMaxNbWords = BGSPAWCS_DEFAULT_MAX_NB_WORDS, + size_t nSamplesForMovingAvgs = BGSPAWCS_DEFAULT_N_SAMPLES_FOR_MV_AVGS); + //! default destructor + virtual ~BackgroundSubtractorPAWCS(); + //! (re)initiaization method; needs to be called before starting background subtraction + virtual void initialize(const cv::Mat& oInitImg, const cv::Mat& oROI); + //! refreshes all local (+ global) dictionaries based on the last analyzed frame + virtual void refreshModel(size_t nBaseOccCount, float fOccDecrFrac, bool bForceFGUpdate = false); + //! primary model update function; the learning param is used to override the internal learning speed (ignored when <= 0) + virtual void apply(cv::InputArray image, cv::OutputArray fgmask, double learningRateOverride = 0); + //! returns a copy of the latest reconstructed background image + virtual void getBackgroundImage(cv::OutputArray backgroundImage) const; + //! returns a copy of the latest reconstructed background descriptors image + virtual void getBackgroundDescriptorsImage(cv::OutputArray backgroundDescImage) const; + +protected: + template + struct ColorLBSPFeature { + uchar anColor[nChannels]; + ushort anDesc[nChannels]; + }; + struct LocalWordBase { + size_t nFirstOcc; + size_t nLastOcc; + size_t nOccurrences; + }; + template + struct LocalWord : LocalWordBase { + T oFeature; + }; + struct GlobalWordBase { + float fLatestWeight; + cv::Mat oSpatioOccMap; + uchar nDescBITS; + }; + template + struct GlobalWord : GlobalWordBase { + T oFeature; + }; + typedef LocalWord> LocalWord_1ch; + typedef LocalWord> LocalWord_3ch; + typedef GlobalWord> GlobalWord_1ch; + typedef GlobalWord> GlobalWord_3ch; + struct PxInfo_PAWCS : PxInfoBase { + size_t nGlobalWordMapLookupIdx; + GlobalWordBase** apGlobalDictSortLUT; + }; + //! absolute minimal color distance threshold ('R' or 'radius' in the original ViBe paper, used as the default/initial 'R(x)' value here) + const size_t m_nMinColorDistThreshold; + //! absolute descriptor distance threshold offset + const size_t m_nDescDistThresholdOffset; + //! max/curr number of local words used to build background submodels (for a single pixel, similar to 'N' in ViBe/PBAS, may vary based on img/channel size) + size_t m_nMaxLocalWords, m_nCurrLocalWords; + //! max/curr number of global words used to build the global background model (may vary based on img/channel size) + size_t m_nMaxGlobalWords, m_nCurrGlobalWords; + //! number of samples to use to compute the learning rate of moving averages + const size_t m_nSamplesForMovingAvgs; + //! last calculated non-flat region ratio + float m_fLastNonFlatRegionRatio; + //! current kernel size for median blur post-proc filtering + int m_nMedianBlurKernelSize; + //! specifies the downsampled frame size used for cam motion analysis & gword lookup maps + cv::Size m_oDownSampledFrameSize_MotionAnalysis, m_oDownSampledFrameSize_GlobalWordLookup; + //! downsampled version of the ROI used for cam motion analysis + cv::Mat m_oDownSampledROI_MotionAnalysis; + //! total pixel count for the downsampled ROIs + size_t m_nDownSampledROIPxCount; + //! current local word weight offset + size_t m_nLocalWordWeightOffset; + + //! word lists & dictionaries + LocalWordBase** m_apLocalWordDict; + LocalWord_1ch* m_aLocalWordList_1ch, *m_pLocalWordListIter_1ch; + LocalWord_3ch* m_aLocalWordList_3ch, *m_pLocalWordListIter_3ch; + GlobalWordBase** m_apGlobalWordDict; + GlobalWord_1ch* m_aGlobalWordList_1ch, *m_pGlobalWordListIter_1ch; + GlobalWord_3ch* m_aGlobalWordList_3ch, *m_pGlobalWordListIter_3ch; + PxInfo_PAWCS* m_aPxInfoLUT_PAWCS; + + //! a lookup map used to keep track of regions where illumination recently changed + cv::Mat m_oIllumUpdtRegionMask; + //! per-pixel update rates ('T(x)' in PBAS, which contains pixel-level 'sigmas', as referred to in ViBe) + cv::Mat m_oUpdateRateFrame; + //! per-pixel distance thresholds (equivalent to 'R(x)' in PBAS, but used as a relative value to determine both intensity and descriptor variation thresholds) + cv::Mat m_oDistThresholdFrame; + //! per-pixel distance threshold variation modulators ('v(x)', relative value used to modulate 'R(x)' and 'T(x)' variations) + cv::Mat m_oDistThresholdVariationFrame; + //! per-pixel mean minimal distances from the model ('D_min(x)' in PBAS, used to control variation magnitude and direction of 'T(x)' and 'R(x)') + cv::Mat m_oMeanMinDistFrame_LT, m_oMeanMinDistFrame_ST; + //! per-pixel mean downsampled distances between consecutive frames (used to analyze camera movement and force global model resets automatically) + cv::Mat m_oMeanDownSampledLastDistFrame_LT, m_oMeanDownSampledLastDistFrame_ST; + //! per-pixel mean raw segmentation results (used to detect unstable segmentation regions) + cv::Mat m_oMeanRawSegmResFrame_LT, m_oMeanRawSegmResFrame_ST; + //! per-pixel mean raw segmentation results (used to detect unstable segmentation regions) + cv::Mat m_oMeanFinalSegmResFrame_LT, m_oMeanFinalSegmResFrame_ST; + //! a lookup map used to keep track of unstable regions (based on segm. noise & local dist. thresholds) + cv::Mat m_oUnstableRegionMask; + //! per-pixel blink detection map ('Z(x)') + cv::Mat m_oBlinksFrame; + //! pre-allocated matrix used to downsample the input frame when needed + cv::Mat m_oDownSampledFrame_MotionAnalysis; + //! the foreground mask generated by the method at [t-1] (without post-proc, used for blinking px detection) + cv::Mat m_oLastRawFGMask; + + //! pre-allocated CV_8UC1 matrices used to speed up morph ops + cv::Mat m_oFGMask_PreFlood; + cv::Mat m_oFGMask_FloodedHoles; + cv::Mat m_oLastFGMask_dilated; + cv::Mat m_oLastFGMask_dilated_inverted; + cv::Mat m_oCurrRawFGBlinkMask; + cv::Mat m_oLastRawFGBlinkMask; + cv::Mat m_oTempGlobalWordWeightDiffFactor; + cv::Mat m_oMorphExStructElement; + + //! internal cleanup function for the dictionary structures + void CleanupDictionaries(); + //! internal weight lookup function for local words + static float GetLocalWordWeight(const LocalWordBase* w, size_t nCurrFrame, size_t nOffset); + //! internal weight lookup function for global words + static float GetGlobalWordWeight(const GlobalWordBase* w); +}; diff --git a/package_bgs/LBSP/BackgroundSubtractorSuBSENSE.cpp b/package_bgs/LBSP/BackgroundSubtractorSuBSENSE.cpp new file mode 100644 index 0000000..3dcc1b8 --- /dev/null +++ b/package_bgs/LBSP/BackgroundSubtractorSuBSENSE.cpp @@ -0,0 +1,753 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "BackgroundSubtractorSuBSENSE.h" +#include "DistanceUtils.h" +#include "RandUtils.h" +#include +#include +#include +#include + +/* + * + * Intrinsic parameters for our method are defined here; tuning these for better + * performance should not be required in most cases -- although improvements in + * very specific scenarios are always possible. + * + */ + //! defines the threshold value(s) used to detect long-term ghosting and trigger the fast edge-based absorption heuristic +#define GHOSTDET_D_MAX (0.010f) // defines 'negligible' change here +#define GHOSTDET_S_MIN (0.995f) // defines the required minimum local foreground saturation value +//! parameter used to scale dynamic distance threshold adjustments ('R(x)') +#define FEEDBACK_R_VAR (0.01f) +//! parameters used to adjust the variation step size of 'v(x)' +#define FEEDBACK_V_INCR (1.000f) +#define FEEDBACK_V_DECR (0.100f) +//! parameters used to scale dynamic learning rate adjustments ('T(x)') +#define FEEDBACK_T_DECR (0.2500f) +#define FEEDBACK_T_INCR (0.5000f) +#define FEEDBACK_T_LOWER (2.0000f) +#define FEEDBACK_T_UPPER (256.00f) +//! parameters used to define 'unstable' regions, based on segm noise/bg dynamics and local dist threshold values +#define UNSTABLE_REG_RATIO_MIN (0.100f) +#define UNSTABLE_REG_RDIST_MIN (3.000f) +//! parameters used to scale the relative LBSP intensity threshold used for internal comparisons +#define LBSPDESC_NONZERO_RATIO_MIN (0.100f) +#define LBSPDESC_NONZERO_RATIO_MAX (0.500f) +//! parameters used to define model reset/learning rate boosts in our frame-level component +#define FRAMELEVEL_MIN_COLOR_DIFF_THRESHOLD (m_nMinColorDistThreshold/2) +#define FRAMELEVEL_ANALYSIS_DOWNSAMPLE_RATIO (8) + +// local define used to display debug information +#define DISPLAY_SUBSENSE_DEBUG_INFO 0 +// local define used to specify the default frame size (320x240 = QVGA) +#define DEFAULT_FRAME_SIZE cv::Size(320,240) +// local define used to specify the color dist threshold offset used for unstable regions +#define STAB_COLOR_DIST_OFFSET (m_nMinColorDistThreshold/5) +// local define used to specify the desc dist threshold offset used for unstable regions +#define UNSTAB_DESC_DIST_OFFSET (m_nDescDistThresholdOffset) + +static const size_t s_nColorMaxDataRange_1ch = UCHAR_MAX; +static const size_t s_nDescMaxDataRange_1ch = LBSP::DESC_SIZE * 8; +static const size_t s_nColorMaxDataRange_3ch = s_nColorMaxDataRange_1ch * 3; +static const size_t s_nDescMaxDataRange_3ch = s_nDescMaxDataRange_1ch * 3; + +BackgroundSubtractorSuBSENSE::BackgroundSubtractorSuBSENSE(float fRelLBSPThreshold + , size_t nDescDistThresholdOffset + , size_t nMinColorDistThreshold + , size_t nBGSamples + , size_t nRequiredBGSamples + , size_t nSamplesForMovingAvgs) + : BackgroundSubtractorLBSP(fRelLBSPThreshold) + , m_nMinColorDistThreshold(nMinColorDistThreshold) + , m_nDescDistThresholdOffset(nDescDistThresholdOffset) + , m_nBGSamples(nBGSamples) + , m_nRequiredBGSamples(nRequiredBGSamples) + , m_nSamplesForMovingAvgs(nSamplesForMovingAvgs) + , m_fLastNonZeroDescRatio(0.0f) + , m_bLearningRateScalingEnabled(true) + , m_fCurrLearningRateLowerCap(FEEDBACK_T_LOWER) + , m_fCurrLearningRateUpperCap(FEEDBACK_T_UPPER) + , m_nMedianBlurKernelSize(m_nDefaultMedianBlurKernelSize) + , m_bUse3x3Spread(true) { + CV_Assert(m_nBGSamples > 0 && m_nRequiredBGSamples <= m_nBGSamples); + CV_Assert(m_nMinColorDistThreshold >= STAB_COLOR_DIST_OFFSET); +} + +BackgroundSubtractorSuBSENSE::~BackgroundSubtractorSuBSENSE() { + if (m_aPxIdxLUT) + delete[] m_aPxIdxLUT; + if (m_aPxInfoLUT) + delete[] m_aPxInfoLUT; +} + +void BackgroundSubtractorSuBSENSE::initialize(const cv::Mat& oInitImg, const cv::Mat& oROI) { + // == init + CV_Assert(!oInitImg.empty() && oInitImg.cols > 0 && oInitImg.rows > 0); + CV_Assert(oInitImg.isContinuous()); + CV_Assert(oInitImg.type() == CV_8UC3 || oInitImg.type() == CV_8UC1); + if (oInitImg.type() == CV_8UC3) { + std::vector voInitImgChannels; + cv::split(oInitImg, voInitImgChannels); + if (!cv::countNonZero((voInitImgChannels[0] != voInitImgChannels[1]) | (voInitImgChannels[2] != voInitImgChannels[1]))) + std::cout << std::endl << "\tBackgroundSubtractorSuBSENSE : Warning, grayscale images should always be passed in CV_8UC1 format for optimal performance." << std::endl; + } + cv::Mat oNewBGROI; + if (oROI.empty() && (m_oROI.empty() || oROI.size() != oInitImg.size())) { + oNewBGROI.create(oInitImg.size(), CV_8UC1); + oNewBGROI = cv::Scalar_(UCHAR_MAX); + } + else if (oROI.empty()) + oNewBGROI = m_oROI; + else { + CV_Assert(oROI.size() == oInitImg.size() && oROI.type() == CV_8UC1); + CV_Assert(cv::countNonZero((oROI < UCHAR_MAX)&(oROI > 0)) == 0); + oNewBGROI = oROI.clone(); + cv::Mat oTempROI; + cv::dilate(oNewBGROI, oTempROI, cv::Mat(), cv::Point(-1, -1), LBSP::PATCH_SIZE / 2); + cv::bitwise_or(oNewBGROI, oTempROI / 2, oNewBGROI); + } + const size_t nOrigROIPxCount = (size_t)cv::countNonZero(oNewBGROI); + CV_Assert(nOrigROIPxCount > 0); + LBSP::validateROI(oNewBGROI); + const size_t nFinalROIPxCount = (size_t)cv::countNonZero(oNewBGROI); + CV_Assert(nFinalROIPxCount > 0); + m_oROI = oNewBGROI; + m_oImgSize = oInitImg.size(); + m_nImgType = oInitImg.type(); + m_nImgChannels = oInitImg.channels(); + m_nTotPxCount = m_oImgSize.area(); + m_nTotRelevantPxCount = nFinalROIPxCount; + m_nFrameIndex = 0; + m_nFramesSinceLastReset = 0; + m_nModelResetCooldown = 0; + m_fLastNonZeroDescRatio = 0.0f; + const int nTotImgPixels = m_oImgSize.height*m_oImgSize.width; + if (nOrigROIPxCount >= m_nTotPxCount / 2 && (int)m_nTotPxCount >= DEFAULT_FRAME_SIZE.area()) { + m_bLearningRateScalingEnabled = true; + m_bAutoModelResetEnabled = true; + m_bUse3x3Spread = !(nTotImgPixels > DEFAULT_FRAME_SIZE.area() * 2); + const int nRawMedianBlurKernelSize = std::min((int)floor((float)nTotImgPixels / DEFAULT_FRAME_SIZE.area() + 0.5f) + m_nDefaultMedianBlurKernelSize, 14); + m_nMedianBlurKernelSize = (nRawMedianBlurKernelSize % 2) ? nRawMedianBlurKernelSize : nRawMedianBlurKernelSize - 1; + m_fCurrLearningRateLowerCap = FEEDBACK_T_LOWER; + m_fCurrLearningRateUpperCap = FEEDBACK_T_UPPER; + } + else { + m_bLearningRateScalingEnabled = false; + m_bAutoModelResetEnabled = false; + m_bUse3x3Spread = true; + m_nMedianBlurKernelSize = m_nDefaultMedianBlurKernelSize; + m_fCurrLearningRateLowerCap = FEEDBACK_T_LOWER * 2; + m_fCurrLearningRateUpperCap = FEEDBACK_T_UPPER * 2; + } + m_oUpdateRateFrame.create(m_oImgSize, CV_32FC1); + m_oUpdateRateFrame = cv::Scalar(m_fCurrLearningRateLowerCap); + m_oDistThresholdFrame.create(m_oImgSize, CV_32FC1); + m_oDistThresholdFrame = cv::Scalar(1.0f); + m_oVariationModulatorFrame.create(m_oImgSize, CV_32FC1); + m_oVariationModulatorFrame = cv::Scalar(10.0f); // should always be >= FEEDBACK_V_DECR + m_oMeanLastDistFrame.create(m_oImgSize, CV_32FC1); + m_oMeanLastDistFrame = cv::Scalar(0.0f); + m_oMeanMinDistFrame_LT.create(m_oImgSize, CV_32FC1); + m_oMeanMinDistFrame_LT = cv::Scalar(0.0f); + m_oMeanMinDistFrame_ST.create(m_oImgSize, CV_32FC1); + m_oMeanMinDistFrame_ST = cv::Scalar(0.0f); + m_oDownSampledFrameSize = cv::Size(m_oImgSize.width / FRAMELEVEL_ANALYSIS_DOWNSAMPLE_RATIO, m_oImgSize.height / FRAMELEVEL_ANALYSIS_DOWNSAMPLE_RATIO); + m_oMeanDownSampledLastDistFrame_LT.create(m_oDownSampledFrameSize, CV_32FC((int)m_nImgChannels)); + m_oMeanDownSampledLastDistFrame_LT = cv::Scalar(0.0f); + m_oMeanDownSampledLastDistFrame_ST.create(m_oDownSampledFrameSize, CV_32FC((int)m_nImgChannels)); + m_oMeanDownSampledLastDistFrame_ST = cv::Scalar(0.0f); + m_oMeanRawSegmResFrame_LT.create(m_oImgSize, CV_32FC1); + m_oMeanRawSegmResFrame_LT = cv::Scalar(0.0f); + m_oMeanRawSegmResFrame_ST.create(m_oImgSize, CV_32FC1); + m_oMeanRawSegmResFrame_ST = cv::Scalar(0.0f); + m_oMeanFinalSegmResFrame_LT.create(m_oImgSize, CV_32FC1); + m_oMeanFinalSegmResFrame_LT = cv::Scalar(0.0f); + m_oMeanFinalSegmResFrame_ST.create(m_oImgSize, CV_32FC1); + m_oMeanFinalSegmResFrame_ST = cv::Scalar(0.0f); + m_oUnstableRegionMask.create(m_oImgSize, CV_8UC1); + m_oUnstableRegionMask = cv::Scalar_(0); + m_oBlinksFrame.create(m_oImgSize, CV_8UC1); + m_oBlinksFrame = cv::Scalar_(0); + m_oDownSampledFrame_MotionAnalysis.create(m_oDownSampledFrameSize, CV_8UC((int)m_nImgChannels)); + m_oDownSampledFrame_MotionAnalysis = cv::Scalar_::all(0); + m_oLastColorFrame.create(m_oImgSize, CV_8UC((int)m_nImgChannels)); + m_oLastColorFrame = cv::Scalar_::all(0); + m_oLastDescFrame.create(m_oImgSize, CV_16UC((int)m_nImgChannels)); + m_oLastDescFrame = cv::Scalar_::all(0); + m_oLastRawFGMask.create(m_oImgSize, CV_8UC1); + m_oLastRawFGMask = cv::Scalar_(0); + m_oLastFGMask.create(m_oImgSize, CV_8UC1); + m_oLastFGMask = cv::Scalar_(0); + m_oLastFGMask_dilated.create(m_oImgSize, CV_8UC1); + m_oLastFGMask_dilated = cv::Scalar_(0); + m_oLastFGMask_dilated_inverted.create(m_oImgSize, CV_8UC1); + m_oLastFGMask_dilated_inverted = cv::Scalar_(0); + m_oFGMask_FloodedHoles.create(m_oImgSize, CV_8UC1); + m_oFGMask_FloodedHoles = cv::Scalar_(0); + m_oFGMask_PreFlood.create(m_oImgSize, CV_8UC1); + m_oFGMask_PreFlood = cv::Scalar_(0); + m_oCurrRawFGBlinkMask.create(m_oImgSize, CV_8UC1); + m_oCurrRawFGBlinkMask = cv::Scalar_(0); + m_oLastRawFGBlinkMask.create(m_oImgSize, CV_8UC1); + m_oLastRawFGBlinkMask = cv::Scalar_(0); + m_voBGColorSamples.resize(m_nBGSamples); + m_voBGDescSamples.resize(m_nBGSamples); + for (size_t s = 0; s < m_nBGSamples; ++s) { + m_voBGColorSamples[s].create(m_oImgSize, CV_8UC((int)m_nImgChannels)); + m_voBGColorSamples[s] = cv::Scalar_::all(0); + m_voBGDescSamples[s].create(m_oImgSize, CV_16UC((int)m_nImgChannels)); + m_voBGDescSamples[s] = cv::Scalar_::all(0); + } + if (m_aPxIdxLUT) + delete[] m_aPxIdxLUT; + if (m_aPxInfoLUT) + delete[] m_aPxInfoLUT; + m_aPxIdxLUT = new size_t[m_nTotRelevantPxCount]; + m_aPxInfoLUT = new PxInfoBase[m_nTotPxCount]; + if (m_nImgChannels == 1) { + CV_Assert(m_oLastColorFrame.step.p[0] == (size_t)m_oImgSize.width && m_oLastColorFrame.step.p[1] == 1); + CV_Assert(m_oLastDescFrame.step.p[0] == m_oLastColorFrame.step.p[0] * 2 && m_oLastDescFrame.step.p[1] == m_oLastColorFrame.step.p[1] * 2); + for (size_t t = 0; t <= UCHAR_MAX; ++t) + m_anLBSPThreshold_8bitLUT[t] = cv::saturate_cast((m_nLBSPThresholdOffset + t*m_fRelLBSPThreshold) / 3); + for (size_t nPxIter = 0, nModelIter = 0; nPxIter < m_nTotPxCount; ++nPxIter) { + if (m_oROI.data[nPxIter]) { + m_aPxIdxLUT[nModelIter] = nPxIter; + m_aPxInfoLUT[nPxIter].nImgCoord_Y = (int)nPxIter / m_oImgSize.width; + m_aPxInfoLUT[nPxIter].nImgCoord_X = (int)nPxIter%m_oImgSize.width; + m_aPxInfoLUT[nPxIter].nModelIdx = nModelIter; + m_oLastColorFrame.data[nPxIter] = oInitImg.data[nPxIter]; + const size_t nDescIter = nPxIter * 2; + LBSP::computeGrayscaleDescriptor(oInitImg, oInitImg.data[nPxIter], m_aPxInfoLUT[nPxIter].nImgCoord_X, m_aPxInfoLUT[nPxIter].nImgCoord_Y, m_anLBSPThreshold_8bitLUT[oInitImg.data[nPxIter]], *((ushort*)(m_oLastDescFrame.data + nDescIter))); + ++nModelIter; + } + } + } + else { //m_nImgChannels==3 + CV_Assert(m_oLastColorFrame.step.p[0] == (size_t)m_oImgSize.width * 3 && m_oLastColorFrame.step.p[1] == 3); + CV_Assert(m_oLastDescFrame.step.p[0] == m_oLastColorFrame.step.p[0] * 2 && m_oLastDescFrame.step.p[1] == m_oLastColorFrame.step.p[1] * 2); + for (size_t t = 0; t <= UCHAR_MAX; ++t) + m_anLBSPThreshold_8bitLUT[t] = cv::saturate_cast(m_nLBSPThresholdOffset + t*m_fRelLBSPThreshold); + for (size_t nPxIter = 0, nModelIter = 0; nPxIter < m_nTotPxCount; ++nPxIter) { + if (m_oROI.data[nPxIter]) { + m_aPxIdxLUT[nModelIter] = nPxIter; + m_aPxInfoLUT[nPxIter].nImgCoord_Y = (int)nPxIter / m_oImgSize.width; + m_aPxInfoLUT[nPxIter].nImgCoord_X = (int)nPxIter%m_oImgSize.width; + m_aPxInfoLUT[nPxIter].nModelIdx = nModelIter; + const size_t nPxRGBIter = nPxIter * 3; + const size_t nDescRGBIter = nPxRGBIter * 2; + for (size_t c = 0; c < 3; ++c) { + m_oLastColorFrame.data[nPxRGBIter + c] = oInitImg.data[nPxRGBIter + c]; + LBSP::computeSingleRGBDescriptor(oInitImg, oInitImg.data[nPxRGBIter + c], m_aPxInfoLUT[nPxIter].nImgCoord_X, m_aPxInfoLUT[nPxIter].nImgCoord_Y, c, m_anLBSPThreshold_8bitLUT[oInitImg.data[nPxRGBIter + c]], ((ushort*)(m_oLastDescFrame.data + nDescRGBIter))[c]); + } + ++nModelIter; + } + } + } + m_bInitialized = true; + refreshModel(1.0f); +} + +void BackgroundSubtractorSuBSENSE::refreshModel(float fSamplesRefreshFrac, bool bForceFGUpdate) { + // == refresh + CV_Assert(m_bInitialized); + CV_Assert(fSamplesRefreshFrac > 0.0f && fSamplesRefreshFrac <= 1.0f); + const size_t nModelsToRefresh = fSamplesRefreshFrac < 1.0f ? (size_t)(fSamplesRefreshFrac*m_nBGSamples) : m_nBGSamples; + const size_t nRefreshStartPos = fSamplesRefreshFrac < 1.0f ? rand() % m_nBGSamples : 0; + if (m_nImgChannels == 1) { + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + if (bForceFGUpdate || !m_oLastFGMask.data[nPxIter]) { + for (size_t nCurrModelIdx = nRefreshStartPos; nCurrModelIdx < nRefreshStartPos + nModelsToRefresh; ++nCurrModelIdx) { + int nSampleImgCoord_Y, nSampleImgCoord_X; + getRandSamplePosition(nSampleImgCoord_X, nSampleImgCoord_Y, m_aPxInfoLUT[nPxIter].nImgCoord_X, m_aPxInfoLUT[nPxIter].nImgCoord_Y, LBSP::PATCH_SIZE / 2, m_oImgSize); + const size_t nSamplePxIdx = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; + if (bForceFGUpdate || !m_oLastFGMask.data[nSamplePxIdx]) { + const size_t nCurrRealModelIdx = nCurrModelIdx%m_nBGSamples; + m_voBGColorSamples[nCurrRealModelIdx].data[nPxIter] = m_oLastColorFrame.data[nSamplePxIdx]; + *((ushort*)(m_voBGDescSamples[nCurrRealModelIdx].data + nPxIter * 2)) = *((ushort*)(m_oLastDescFrame.data + nSamplePxIdx * 2)); + } + } + } + } + } + else { //m_nImgChannels==3 + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + if (bForceFGUpdate || !m_oLastFGMask.data[nPxIter]) { + for (size_t nCurrModelIdx = nRefreshStartPos; nCurrModelIdx < nRefreshStartPos + nModelsToRefresh; ++nCurrModelIdx) { + int nSampleImgCoord_Y, nSampleImgCoord_X; + getRandSamplePosition(nSampleImgCoord_X, nSampleImgCoord_Y, m_aPxInfoLUT[nPxIter].nImgCoord_X, m_aPxInfoLUT[nPxIter].nImgCoord_Y, LBSP::PATCH_SIZE / 2, m_oImgSize); + const size_t nSamplePxIdx = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; + if (bForceFGUpdate || !m_oLastFGMask.data[nSamplePxIdx]) { + const size_t nCurrRealModelIdx = nCurrModelIdx%m_nBGSamples; + for (size_t c = 0; c < 3; ++c) { + m_voBGColorSamples[nCurrRealModelIdx].data[nPxIter * 3 + c] = m_oLastColorFrame.data[nSamplePxIdx * 3 + c]; + *((ushort*)(m_voBGDescSamples[nCurrRealModelIdx].data + (nPxIter * 3 + c) * 2)) = *((ushort*)(m_oLastDescFrame.data + (nSamplePxIdx * 3 + c) * 2)); + } + } + } + } + } + } +} + +void BackgroundSubtractorSuBSENSE::apply(cv::InputArray _image, cv::OutputArray _fgmask, double learningRateOverride) { + // == process + CV_Assert(m_bInitialized); + cv::Mat oInputImg = _image.getMat(); + CV_Assert(oInputImg.type() == m_nImgType && oInputImg.size() == m_oImgSize); + CV_Assert(oInputImg.isContinuous()); + _fgmask.create(m_oImgSize, CV_8UC1); + cv::Mat oCurrFGMask = _fgmask.getMat(); + memset(oCurrFGMask.data, 0, oCurrFGMask.cols*oCurrFGMask.rows); + size_t nNonZeroDescCount = 0; + const float fRollAvgFactor_LT = 1.0f / std::min(++m_nFrameIndex, m_nSamplesForMovingAvgs); + const float fRollAvgFactor_ST = 1.0f / std::min(m_nFrameIndex, m_nSamplesForMovingAvgs / 4); + if (m_nImgChannels == 1) { + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + const size_t nDescIter = nPxIter * 2; + const size_t nFloatIter = nPxIter * 4; + const int nCurrImgCoord_X = m_aPxInfoLUT[nPxIter].nImgCoord_X; + const int nCurrImgCoord_Y = m_aPxInfoLUT[nPxIter].nImgCoord_Y; + const uchar nCurrColor = oInputImg.data[nPxIter]; + size_t nMinDescDist = s_nDescMaxDataRange_1ch; + size_t nMinSumDist = s_nColorMaxDataRange_1ch; + float* pfCurrDistThresholdFactor = (float*)(m_oDistThresholdFrame.data + nFloatIter); + float* pfCurrVariationFactor = (float*)(m_oVariationModulatorFrame.data + nFloatIter); + float* pfCurrLearningRate = ((float*)(m_oUpdateRateFrame.data + nFloatIter)); + float* pfCurrMeanLastDist = ((float*)(m_oMeanLastDistFrame.data + nFloatIter)); + float* pfCurrMeanMinDist_LT = ((float*)(m_oMeanMinDistFrame_LT.data + nFloatIter)); + float* pfCurrMeanMinDist_ST = ((float*)(m_oMeanMinDistFrame_ST.data + nFloatIter)); + float* pfCurrMeanRawSegmRes_LT = ((float*)(m_oMeanRawSegmResFrame_LT.data + nFloatIter)); + float* pfCurrMeanRawSegmRes_ST = ((float*)(m_oMeanRawSegmResFrame_ST.data + nFloatIter)); + float* pfCurrMeanFinalSegmRes_LT = ((float*)(m_oMeanFinalSegmResFrame_LT.data + nFloatIter)); + float* pfCurrMeanFinalSegmRes_ST = ((float*)(m_oMeanFinalSegmResFrame_ST.data + nFloatIter)); + ushort& nLastIntraDesc = *((ushort*)(m_oLastDescFrame.data + nDescIter)); + uchar& nLastColor = m_oLastColorFrame.data[nPxIter]; + const size_t nCurrColorDistThreshold = (size_t)(((*pfCurrDistThresholdFactor)*m_nMinColorDistThreshold) - ((!m_oUnstableRegionMask.data[nPxIter])*STAB_COLOR_DIST_OFFSET)) / 2; + const size_t nCurrDescDistThreshold = ((size_t)1 << ((size_t)floor(*pfCurrDistThresholdFactor + 0.5f))) + m_nDescDistThresholdOffset + (m_oUnstableRegionMask.data[nPxIter] * UNSTAB_DESC_DIST_OFFSET); + ushort nCurrInterDesc, nCurrIntraDesc; + LBSP::computeGrayscaleDescriptor(oInputImg, nCurrColor, nCurrImgCoord_X, nCurrImgCoord_Y, m_anLBSPThreshold_8bitLUT[nCurrColor], nCurrIntraDesc); + m_oUnstableRegionMask.data[nPxIter] = ((*pfCurrDistThresholdFactor) > UNSTABLE_REG_RDIST_MIN || (*pfCurrMeanRawSegmRes_LT - *pfCurrMeanFinalSegmRes_LT) > UNSTABLE_REG_RATIO_MIN || (*pfCurrMeanRawSegmRes_ST - *pfCurrMeanFinalSegmRes_ST) > UNSTABLE_REG_RATIO_MIN) ? 1 : 0; + size_t nGoodSamplesCount = 0, nSampleIdx = 0; + while (nGoodSamplesCount < m_nRequiredBGSamples && nSampleIdx < m_nBGSamples) { + const uchar& nBGColor = m_voBGColorSamples[nSampleIdx].data[nPxIter]; + { + const size_t nColorDist = L1dist(nCurrColor, nBGColor); + if (nColorDist > nCurrColorDistThreshold) + goto failedcheck1ch; + const ushort& nBGIntraDesc = *((ushort*)(m_voBGDescSamples[nSampleIdx].data + nDescIter)); + const size_t nIntraDescDist = hdist(nCurrIntraDesc, nBGIntraDesc); + LBSP::computeGrayscaleDescriptor(oInputImg, nBGColor, nCurrImgCoord_X, nCurrImgCoord_Y, m_anLBSPThreshold_8bitLUT[nBGColor], nCurrInterDesc); + const size_t nInterDescDist = hdist(nCurrInterDesc, nBGIntraDesc); + const size_t nDescDist = (nIntraDescDist + nInterDescDist) / 2; + if (nDescDist > nCurrDescDistThreshold) + goto failedcheck1ch; + const size_t nSumDist = std::min((nDescDist / 4)*(s_nColorMaxDataRange_1ch / s_nDescMaxDataRange_1ch) + nColorDist, s_nColorMaxDataRange_1ch); + if (nSumDist > nCurrColorDistThreshold) + goto failedcheck1ch; + if (nMinDescDist > nDescDist) + nMinDescDist = nDescDist; + if (nMinSumDist > nSumDist) + nMinSumDist = nSumDist; + nGoodSamplesCount++; + } + failedcheck1ch: + nSampleIdx++; + } + const float fNormalizedLastDist = ((float)L1dist(nLastColor, nCurrColor) / s_nColorMaxDataRange_1ch + (float)hdist(nLastIntraDesc, nCurrIntraDesc) / s_nDescMaxDataRange_1ch) / 2; + *pfCurrMeanLastDist = (*pfCurrMeanLastDist)*(1.0f - fRollAvgFactor_ST) + fNormalizedLastDist*fRollAvgFactor_ST; + if (nGoodSamplesCount < m_nRequiredBGSamples) { + // == foreground + const float fNormalizedMinDist = std::min(1.0f, ((float)nMinSumDist / s_nColorMaxDataRange_1ch + (float)nMinDescDist / s_nDescMaxDataRange_1ch) / 2 + (float)(m_nRequiredBGSamples - nGoodSamplesCount) / m_nRequiredBGSamples); + *pfCurrMeanMinDist_LT = (*pfCurrMeanMinDist_LT)*(1.0f - fRollAvgFactor_LT) + fNormalizedMinDist*fRollAvgFactor_LT; + *pfCurrMeanMinDist_ST = (*pfCurrMeanMinDist_ST)*(1.0f - fRollAvgFactor_ST) + fNormalizedMinDist*fRollAvgFactor_ST; + *pfCurrMeanRawSegmRes_LT = (*pfCurrMeanRawSegmRes_LT)*(1.0f - fRollAvgFactor_LT) + fRollAvgFactor_LT; + *pfCurrMeanRawSegmRes_ST = (*pfCurrMeanRawSegmRes_ST)*(1.0f - fRollAvgFactor_ST) + fRollAvgFactor_ST; + oCurrFGMask.data[nPxIter] = UCHAR_MAX; + if (m_nModelResetCooldown && (rand() % (size_t)FEEDBACK_T_LOWER) == 0) { + const size_t s_rand = rand() % m_nBGSamples; + *((ushort*)(m_voBGDescSamples[s_rand].data + nDescIter)) = nCurrIntraDesc; + m_voBGColorSamples[s_rand].data[nPxIter] = nCurrColor; + } + } + else { + // == background + const float fNormalizedMinDist = ((float)nMinSumDist / s_nColorMaxDataRange_1ch + (float)nMinDescDist / s_nDescMaxDataRange_1ch) / 2; + *pfCurrMeanMinDist_LT = (*pfCurrMeanMinDist_LT)*(1.0f - fRollAvgFactor_LT) + fNormalizedMinDist*fRollAvgFactor_LT; + *pfCurrMeanMinDist_ST = (*pfCurrMeanMinDist_ST)*(1.0f - fRollAvgFactor_ST) + fNormalizedMinDist*fRollAvgFactor_ST; + *pfCurrMeanRawSegmRes_LT = (*pfCurrMeanRawSegmRes_LT)*(1.0f - fRollAvgFactor_LT); + *pfCurrMeanRawSegmRes_ST = (*pfCurrMeanRawSegmRes_ST)*(1.0f - fRollAvgFactor_ST); + const size_t nLearningRate = learningRateOverride > 0 ? (size_t)ceil(learningRateOverride) : (size_t)ceil(*pfCurrLearningRate); + if ((rand() % nLearningRate) == 0) { + const size_t s_rand = rand() % m_nBGSamples; + *((ushort*)(m_voBGDescSamples[s_rand].data + nDescIter)) = nCurrIntraDesc; + m_voBGColorSamples[s_rand].data[nPxIter] = nCurrColor; + } + int nSampleImgCoord_Y, nSampleImgCoord_X; + const bool bCurrUsing3x3Spread = m_bUse3x3Spread && !m_oUnstableRegionMask.data[nPxIter]; + if (bCurrUsing3x3Spread) + getRandNeighborPosition_3x3(nSampleImgCoord_X, nSampleImgCoord_Y, nCurrImgCoord_X, nCurrImgCoord_Y, LBSP::PATCH_SIZE / 2, m_oImgSize); + else + getRandNeighborPosition_5x5(nSampleImgCoord_X, nSampleImgCoord_Y, nCurrImgCoord_X, nCurrImgCoord_Y, LBSP::PATCH_SIZE / 2, m_oImgSize); + const size_t n_rand = rand(); + const size_t idx_rand_uchar = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; + const size_t idx_rand_flt32 = idx_rand_uchar * 4; + const float fRandMeanLastDist = *((float*)(m_oMeanLastDistFrame.data + idx_rand_flt32)); + const float fRandMeanRawSegmRes = *((float*)(m_oMeanRawSegmResFrame_ST.data + idx_rand_flt32)); + if ((n_rand % (bCurrUsing3x3Spread ? nLearningRate : (nLearningRate / 2 + 1))) == 0 + || (fRandMeanRawSegmRes > GHOSTDET_S_MIN && fRandMeanLastDist < GHOSTDET_D_MAX && (n_rand % ((size_t)m_fCurrLearningRateLowerCap)) == 0)) { + const size_t idx_rand_ushrt = idx_rand_uchar * 2; + const size_t s_rand = rand() % m_nBGSamples; + *((ushort*)(m_voBGDescSamples[s_rand].data + idx_rand_ushrt)) = nCurrIntraDesc; + m_voBGColorSamples[s_rand].data[idx_rand_uchar] = nCurrColor; + } + } + if (m_oLastFGMask.data[nPxIter] || (std::min(*pfCurrMeanMinDist_LT, *pfCurrMeanMinDist_ST) < UNSTABLE_REG_RATIO_MIN && oCurrFGMask.data[nPxIter])) { + if ((*pfCurrLearningRate) < m_fCurrLearningRateUpperCap) + *pfCurrLearningRate += FEEDBACK_T_INCR / (std::max(*pfCurrMeanMinDist_LT, *pfCurrMeanMinDist_ST)*(*pfCurrVariationFactor)); + } + else if ((*pfCurrLearningRate) > m_fCurrLearningRateLowerCap) + *pfCurrLearningRate -= FEEDBACK_T_DECR*(*pfCurrVariationFactor) / std::max(*pfCurrMeanMinDist_LT, *pfCurrMeanMinDist_ST); + if ((*pfCurrLearningRate) < m_fCurrLearningRateLowerCap) + *pfCurrLearningRate = m_fCurrLearningRateLowerCap; + else if ((*pfCurrLearningRate) > m_fCurrLearningRateUpperCap) + *pfCurrLearningRate = m_fCurrLearningRateUpperCap; + if (std::max(*pfCurrMeanMinDist_LT, *pfCurrMeanMinDist_ST) > UNSTABLE_REG_RATIO_MIN && m_oBlinksFrame.data[nPxIter]) + (*pfCurrVariationFactor) += FEEDBACK_V_INCR; + else if ((*pfCurrVariationFactor) > FEEDBACK_V_DECR) { + (*pfCurrVariationFactor) -= m_oLastFGMask.data[nPxIter] ? FEEDBACK_V_DECR / 4 : m_oUnstableRegionMask.data[nPxIter] ? FEEDBACK_V_DECR / 2 : FEEDBACK_V_DECR; + if ((*pfCurrVariationFactor) < FEEDBACK_V_DECR) + (*pfCurrVariationFactor) = FEEDBACK_V_DECR; + } + if ((*pfCurrDistThresholdFactor) < std::pow(1.0f + std::min(*pfCurrMeanMinDist_LT, *pfCurrMeanMinDist_ST) * 2, 2)) + (*pfCurrDistThresholdFactor) += FEEDBACK_R_VAR*(*pfCurrVariationFactor - FEEDBACK_V_DECR); + else { + (*pfCurrDistThresholdFactor) -= FEEDBACK_R_VAR / (*pfCurrVariationFactor); + if ((*pfCurrDistThresholdFactor) < 1.0f) + (*pfCurrDistThresholdFactor) = 1.0f; + } + if (popcount(nCurrIntraDesc) >= 2) + ++nNonZeroDescCount; + nLastIntraDesc = nCurrIntraDesc; + nLastColor = nCurrColor; + } + } + else { //m_nImgChannels==3 + for (size_t nModelIter = 0; nModelIter < m_nTotRelevantPxCount; ++nModelIter) { + const size_t nPxIter = m_aPxIdxLUT[nModelIter]; + const int nCurrImgCoord_X = m_aPxInfoLUT[nPxIter].nImgCoord_X; + const int nCurrImgCoord_Y = m_aPxInfoLUT[nPxIter].nImgCoord_Y; + const size_t nPxIterRGB = nPxIter * 3; + const size_t nDescIterRGB = nPxIterRGB * 2; + const size_t nFloatIter = nPxIter * 4; + const uchar* const anCurrColor = oInputImg.data + nPxIterRGB; + size_t nMinTotDescDist = s_nDescMaxDataRange_3ch; + size_t nMinTotSumDist = s_nColorMaxDataRange_3ch; + float* pfCurrDistThresholdFactor = (float*)(m_oDistThresholdFrame.data + nFloatIter); + float* pfCurrVariationFactor = (float*)(m_oVariationModulatorFrame.data + nFloatIter); + float* pfCurrLearningRate = ((float*)(m_oUpdateRateFrame.data + nFloatIter)); + float* pfCurrMeanLastDist = ((float*)(m_oMeanLastDistFrame.data + nFloatIter)); + float* pfCurrMeanMinDist_LT = ((float*)(m_oMeanMinDistFrame_LT.data + nFloatIter)); + float* pfCurrMeanMinDist_ST = ((float*)(m_oMeanMinDistFrame_ST.data + nFloatIter)); + float* pfCurrMeanRawSegmRes_LT = ((float*)(m_oMeanRawSegmResFrame_LT.data + nFloatIter)); + float* pfCurrMeanRawSegmRes_ST = ((float*)(m_oMeanRawSegmResFrame_ST.data + nFloatIter)); + float* pfCurrMeanFinalSegmRes_LT = ((float*)(m_oMeanFinalSegmResFrame_LT.data + nFloatIter)); + float* pfCurrMeanFinalSegmRes_ST = ((float*)(m_oMeanFinalSegmResFrame_ST.data + nFloatIter)); + ushort* anLastIntraDesc = ((ushort*)(m_oLastDescFrame.data + nDescIterRGB)); + uchar* anLastColor = m_oLastColorFrame.data + nPxIterRGB; + const size_t nCurrColorDistThreshold = (size_t)(((*pfCurrDistThresholdFactor)*m_nMinColorDistThreshold) - ((!m_oUnstableRegionMask.data[nPxIter])*STAB_COLOR_DIST_OFFSET)); + const size_t nCurrDescDistThreshold = ((size_t)1 << ((size_t)floor(*pfCurrDistThresholdFactor + 0.5f))) + m_nDescDistThresholdOffset + (m_oUnstableRegionMask.data[nPxIter] * UNSTAB_DESC_DIST_OFFSET); + const size_t nCurrTotColorDistThreshold = nCurrColorDistThreshold * 3; + const size_t nCurrTotDescDistThreshold = nCurrDescDistThreshold * 3; + const size_t nCurrSCColorDistThreshold = nCurrTotColorDistThreshold / 2; + ushort anCurrInterDesc[3], anCurrIntraDesc[3]; + const size_t anCurrIntraLBSPThresholds[3] = { m_anLBSPThreshold_8bitLUT[anCurrColor[0]],m_anLBSPThreshold_8bitLUT[anCurrColor[1]],m_anLBSPThreshold_8bitLUT[anCurrColor[2]] }; + LBSP::computeRGBDescriptor(oInputImg, anCurrColor, nCurrImgCoord_X, nCurrImgCoord_Y, anCurrIntraLBSPThresholds, anCurrIntraDesc); + m_oUnstableRegionMask.data[nPxIter] = ((*pfCurrDistThresholdFactor) > UNSTABLE_REG_RDIST_MIN || (*pfCurrMeanRawSegmRes_LT - *pfCurrMeanFinalSegmRes_LT) > UNSTABLE_REG_RATIO_MIN || (*pfCurrMeanRawSegmRes_ST - *pfCurrMeanFinalSegmRes_ST) > UNSTABLE_REG_RATIO_MIN) ? 1 : 0; + size_t nGoodSamplesCount = 0, nSampleIdx = 0; + while (nGoodSamplesCount < m_nRequiredBGSamples && nSampleIdx < m_nBGSamples) { + const ushort* const anBGIntraDesc = (ushort*)(m_voBGDescSamples[nSampleIdx].data + nDescIterRGB); + const uchar* const anBGColor = m_voBGColorSamples[nSampleIdx].data + nPxIterRGB; + size_t nTotDescDist = 0; + size_t nTotSumDist = 0; + for (size_t c = 0; c < 3; ++c) { + const size_t nColorDist = L1dist(anCurrColor[c], anBGColor[c]); + if (nColorDist > nCurrSCColorDistThreshold) + goto failedcheck3ch; + const size_t nIntraDescDist = hdist(anCurrIntraDesc[c], anBGIntraDesc[c]); + LBSP::computeSingleRGBDescriptor(oInputImg, anBGColor[c], nCurrImgCoord_X, nCurrImgCoord_Y, c, m_anLBSPThreshold_8bitLUT[anBGColor[c]], anCurrInterDesc[c]); + const size_t nInterDescDist = hdist(anCurrInterDesc[c], anBGIntraDesc[c]); + const size_t nDescDist = (nIntraDescDist + nInterDescDist) / 2; + const size_t nSumDist = std::min((nDescDist / 2)*(s_nColorMaxDataRange_1ch / s_nDescMaxDataRange_1ch) + nColorDist, s_nColorMaxDataRange_1ch); + if (nSumDist > nCurrSCColorDistThreshold) + goto failedcheck3ch; + nTotDescDist += nDescDist; + nTotSumDist += nSumDist; + } + if (nTotDescDist > nCurrTotDescDistThreshold || nTotSumDist > nCurrTotColorDistThreshold) + goto failedcheck3ch; + if (nMinTotDescDist > nTotDescDist) + nMinTotDescDist = nTotDescDist; + if (nMinTotSumDist > nTotSumDist) + nMinTotSumDist = nTotSumDist; + nGoodSamplesCount++; + failedcheck3ch: + nSampleIdx++; + } + const float fNormalizedLastDist = ((float)L1dist<3>(anLastColor, anCurrColor) / s_nColorMaxDataRange_3ch + (float)hdist<3>(anLastIntraDesc, anCurrIntraDesc) / s_nDescMaxDataRange_3ch) / 2; + *pfCurrMeanLastDist = (*pfCurrMeanLastDist)*(1.0f - fRollAvgFactor_ST) + fNormalizedLastDist*fRollAvgFactor_ST; + if (nGoodSamplesCount < m_nRequiredBGSamples) { + // == foreground + const float fNormalizedMinDist = std::min(1.0f, ((float)nMinTotSumDist / s_nColorMaxDataRange_3ch + (float)nMinTotDescDist / s_nDescMaxDataRange_3ch) / 2 + (float)(m_nRequiredBGSamples - nGoodSamplesCount) / m_nRequiredBGSamples); + *pfCurrMeanMinDist_LT = (*pfCurrMeanMinDist_LT)*(1.0f - fRollAvgFactor_LT) + fNormalizedMinDist*fRollAvgFactor_LT; + *pfCurrMeanMinDist_ST = (*pfCurrMeanMinDist_ST)*(1.0f - fRollAvgFactor_ST) + fNormalizedMinDist*fRollAvgFactor_ST; + *pfCurrMeanRawSegmRes_LT = (*pfCurrMeanRawSegmRes_LT)*(1.0f - fRollAvgFactor_LT) + fRollAvgFactor_LT; + *pfCurrMeanRawSegmRes_ST = (*pfCurrMeanRawSegmRes_ST)*(1.0f - fRollAvgFactor_ST) + fRollAvgFactor_ST; + oCurrFGMask.data[nPxIter] = UCHAR_MAX; + if (m_nModelResetCooldown && (rand() % (size_t)FEEDBACK_T_LOWER) == 0) { + const size_t s_rand = rand() % m_nBGSamples; + for (size_t c = 0; c < 3; ++c) { + *((ushort*)(m_voBGDescSamples[s_rand].data + nDescIterRGB + 2 * c)) = anCurrIntraDesc[c]; + *(m_voBGColorSamples[s_rand].data + nPxIterRGB + c) = anCurrColor[c]; + } + } + } + else { + // == background + const float fNormalizedMinDist = ((float)nMinTotSumDist / s_nColorMaxDataRange_3ch + (float)nMinTotDescDist / s_nDescMaxDataRange_3ch) / 2; + *pfCurrMeanMinDist_LT = (*pfCurrMeanMinDist_LT)*(1.0f - fRollAvgFactor_LT) + fNormalizedMinDist*fRollAvgFactor_LT; + *pfCurrMeanMinDist_ST = (*pfCurrMeanMinDist_ST)*(1.0f - fRollAvgFactor_ST) + fNormalizedMinDist*fRollAvgFactor_ST; + *pfCurrMeanRawSegmRes_LT = (*pfCurrMeanRawSegmRes_LT)*(1.0f - fRollAvgFactor_LT); + *pfCurrMeanRawSegmRes_ST = (*pfCurrMeanRawSegmRes_ST)*(1.0f - fRollAvgFactor_ST); + const size_t nLearningRate = learningRateOverride > 0 ? (size_t)ceil(learningRateOverride) : (size_t)ceil(*pfCurrLearningRate); + if ((rand() % nLearningRate) == 0) { + const size_t s_rand = rand() % m_nBGSamples; + for (size_t c = 0; c < 3; ++c) { + *((ushort*)(m_voBGDescSamples[s_rand].data + nDescIterRGB + 2 * c)) = anCurrIntraDesc[c]; + *(m_voBGColorSamples[s_rand].data + nPxIterRGB + c) = anCurrColor[c]; + } + } + int nSampleImgCoord_Y, nSampleImgCoord_X; + const bool bCurrUsing3x3Spread = m_bUse3x3Spread && !m_oUnstableRegionMask.data[nPxIter]; + if (bCurrUsing3x3Spread) + getRandNeighborPosition_3x3(nSampleImgCoord_X, nSampleImgCoord_Y, nCurrImgCoord_X, nCurrImgCoord_Y, LBSP::PATCH_SIZE / 2, m_oImgSize); + else + getRandNeighborPosition_5x5(nSampleImgCoord_X, nSampleImgCoord_Y, nCurrImgCoord_X, nCurrImgCoord_Y, LBSP::PATCH_SIZE / 2, m_oImgSize); + const size_t n_rand = rand(); + const size_t idx_rand_uchar = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; + const size_t idx_rand_flt32 = idx_rand_uchar * 4; + const float fRandMeanLastDist = *((float*)(m_oMeanLastDistFrame.data + idx_rand_flt32)); + const float fRandMeanRawSegmRes = *((float*)(m_oMeanRawSegmResFrame_ST.data + idx_rand_flt32)); + if ((n_rand % (bCurrUsing3x3Spread ? nLearningRate : (nLearningRate / 2 + 1))) == 0 + || (fRandMeanRawSegmRes > GHOSTDET_S_MIN && fRandMeanLastDist < GHOSTDET_D_MAX && (n_rand % ((size_t)m_fCurrLearningRateLowerCap)) == 0)) { + const size_t idx_rand_uchar_rgb = idx_rand_uchar * 3; + const size_t idx_rand_ushrt_rgb = idx_rand_uchar_rgb * 2; + const size_t s_rand = rand() % m_nBGSamples; + for (size_t c = 0; c < 3; ++c) { + *((ushort*)(m_voBGDescSamples[s_rand].data + idx_rand_ushrt_rgb + 2 * c)) = anCurrIntraDesc[c]; + *(m_voBGColorSamples[s_rand].data + idx_rand_uchar_rgb + c) = anCurrColor[c]; + } + } + } + if (m_oLastFGMask.data[nPxIter] || (std::min(*pfCurrMeanMinDist_LT, *pfCurrMeanMinDist_ST) < UNSTABLE_REG_RATIO_MIN && oCurrFGMask.data[nPxIter])) { + if ((*pfCurrLearningRate) < m_fCurrLearningRateUpperCap) + *pfCurrLearningRate += FEEDBACK_T_INCR / (std::max(*pfCurrMeanMinDist_LT, *pfCurrMeanMinDist_ST)*(*pfCurrVariationFactor)); + } + else if ((*pfCurrLearningRate) > m_fCurrLearningRateLowerCap) + *pfCurrLearningRate -= FEEDBACK_T_DECR*(*pfCurrVariationFactor) / std::max(*pfCurrMeanMinDist_LT, *pfCurrMeanMinDist_ST); + if ((*pfCurrLearningRate) < m_fCurrLearningRateLowerCap) + *pfCurrLearningRate = m_fCurrLearningRateLowerCap; + else if ((*pfCurrLearningRate) > m_fCurrLearningRateUpperCap) + *pfCurrLearningRate = m_fCurrLearningRateUpperCap; + if (std::max(*pfCurrMeanMinDist_LT, *pfCurrMeanMinDist_ST) > UNSTABLE_REG_RATIO_MIN && m_oBlinksFrame.data[nPxIter]) + (*pfCurrVariationFactor) += FEEDBACK_V_INCR; + else if ((*pfCurrVariationFactor) > FEEDBACK_V_DECR) { + (*pfCurrVariationFactor) -= m_oLastFGMask.data[nPxIter] ? FEEDBACK_V_DECR / 4 : m_oUnstableRegionMask.data[nPxIter] ? FEEDBACK_V_DECR / 2 : FEEDBACK_V_DECR; + if ((*pfCurrVariationFactor) < FEEDBACK_V_DECR) + (*pfCurrVariationFactor) = FEEDBACK_V_DECR; + } + if ((*pfCurrDistThresholdFactor) < std::pow(1.0f + std::min(*pfCurrMeanMinDist_LT, *pfCurrMeanMinDist_ST) * 2, 2)) + (*pfCurrDistThresholdFactor) += FEEDBACK_R_VAR*(*pfCurrVariationFactor - FEEDBACK_V_DECR); + else { + (*pfCurrDistThresholdFactor) -= FEEDBACK_R_VAR / (*pfCurrVariationFactor); + if ((*pfCurrDistThresholdFactor) < 1.0f) + (*pfCurrDistThresholdFactor) = 1.0f; + } + if (popcount<3>(anCurrIntraDesc) >= 4) + ++nNonZeroDescCount; + for (size_t c = 0; c < 3; ++c) { + anLastIntraDesc[c] = anCurrIntraDesc[c]; + anLastColor[c] = anCurrColor[c]; + } + } + } +#if DISPLAY_SUBSENSE_DEBUG_INFO + std::cout << std::endl; + cv::Point dbgpt(nDebugCoordX, nDebugCoordY); + cv::Mat oMeanMinDistFrameNormalized; m_oMeanMinDistFrame_ST.copyTo(oMeanMinDistFrameNormalized); + cv::circle(oMeanMinDistFrameNormalized, dbgpt, 5, cv::Scalar(1.0f)); + cv::resize(oMeanMinDistFrameNormalized, oMeanMinDistFrameNormalized, DEFAULT_FRAME_SIZE); + cv::imshow("d_min(x)", oMeanMinDistFrameNormalized); + std::cout << std::fixed << std::setprecision(5) << " d_min(" << dbgpt << ") = " << m_oMeanMinDistFrame_ST.at(dbgpt) << std::endl; + cv::Mat oMeanLastDistFrameNormalized; m_oMeanLastDistFrame.copyTo(oMeanLastDistFrameNormalized); + cv::circle(oMeanLastDistFrameNormalized, dbgpt, 5, cv::Scalar(1.0f)); + cv::resize(oMeanLastDistFrameNormalized, oMeanLastDistFrameNormalized, DEFAULT_FRAME_SIZE); + cv::imshow("d_last(x)", oMeanLastDistFrameNormalized); + std::cout << std::fixed << std::setprecision(5) << " d_last(" << dbgpt << ") = " << m_oMeanLastDistFrame.at(dbgpt) << std::endl; + cv::Mat oMeanRawSegmResFrameNormalized; m_oMeanRawSegmResFrame_ST.copyTo(oMeanRawSegmResFrameNormalized); + cv::circle(oMeanRawSegmResFrameNormalized, dbgpt, 5, cv::Scalar(1.0f)); + cv::resize(oMeanRawSegmResFrameNormalized, oMeanRawSegmResFrameNormalized, DEFAULT_FRAME_SIZE); + cv::imshow("s_avg(x)", oMeanRawSegmResFrameNormalized); + std::cout << std::fixed << std::setprecision(5) << " s_avg(" << dbgpt << ") = " << m_oMeanRawSegmResFrame_ST.at(dbgpt) << std::endl; + cv::Mat oMeanFinalSegmResFrameNormalized; m_oMeanFinalSegmResFrame_ST.copyTo(oMeanFinalSegmResFrameNormalized); + cv::circle(oMeanFinalSegmResFrameNormalized, dbgpt, 5, cv::Scalar(1.0f)); + cv::resize(oMeanFinalSegmResFrameNormalized, oMeanFinalSegmResFrameNormalized, DEFAULT_FRAME_SIZE); + cv::imshow("z_avg(x)", oMeanFinalSegmResFrameNormalized); + std::cout << std::fixed << std::setprecision(5) << " z_avg(" << dbgpt << ") = " << m_oMeanFinalSegmResFrame_ST.at(dbgpt) << std::endl; + cv::Mat oDistThresholdFrameNormalized; m_oDistThresholdFrame.convertTo(oDistThresholdFrameNormalized, CV_32FC1, 0.25f, -0.25f); + cv::circle(oDistThresholdFrameNormalized, dbgpt, 5, cv::Scalar(1.0f)); + cv::resize(oDistThresholdFrameNormalized, oDistThresholdFrameNormalized, DEFAULT_FRAME_SIZE); + cv::imshow("r(x)", oDistThresholdFrameNormalized); + std::cout << std::fixed << std::setprecision(5) << " r(" << dbgpt << ") = " << m_oDistThresholdFrame.at(dbgpt) << std::endl; + cv::Mat oVariationModulatorFrameNormalized; cv::normalize(m_oVariationModulatorFrame, oVariationModulatorFrameNormalized, 0, 255, cv::NORM_MINMAX, CV_8UC1); + cv::circle(oVariationModulatorFrameNormalized, dbgpt, 5, cv::Scalar(255)); + cv::resize(oVariationModulatorFrameNormalized, oVariationModulatorFrameNormalized, DEFAULT_FRAME_SIZE); + cv::imshow("v(x)", oVariationModulatorFrameNormalized); + std::cout << std::fixed << std::setprecision(5) << " v(" << dbgpt << ") = " << m_oVariationModulatorFrame.at(dbgpt) << std::endl; + cv::Mat oUpdateRateFrameNormalized; m_oUpdateRateFrame.convertTo(oUpdateRateFrameNormalized, CV_32FC1, 1.0f / FEEDBACK_T_UPPER, -FEEDBACK_T_LOWER / FEEDBACK_T_UPPER); + cv::circle(oUpdateRateFrameNormalized, dbgpt, 5, cv::Scalar(1.0f)); + cv::resize(oUpdateRateFrameNormalized, oUpdateRateFrameNormalized, DEFAULT_FRAME_SIZE); + cv::imshow("t(x)", oUpdateRateFrameNormalized); + std::cout << std::fixed << std::setprecision(5) << " t(" << dbgpt << ") = " << m_oUpdateRateFrame.at(dbgpt) << std::endl; +#endif //DISPLAY_SUBSENSE_DEBUG_INFO + cv::bitwise_xor(oCurrFGMask, m_oLastRawFGMask, m_oCurrRawFGBlinkMask); + cv::bitwise_or(m_oCurrRawFGBlinkMask, m_oLastRawFGBlinkMask, m_oBlinksFrame); + m_oCurrRawFGBlinkMask.copyTo(m_oLastRawFGBlinkMask); + oCurrFGMask.copyTo(m_oLastRawFGMask); + cv::morphologyEx(oCurrFGMask, m_oFGMask_PreFlood, cv::MORPH_CLOSE, cv::Mat()); + m_oFGMask_PreFlood.copyTo(m_oFGMask_FloodedHoles); + cv::floodFill(m_oFGMask_FloodedHoles, cv::Point(0, 0), UCHAR_MAX); + cv::bitwise_not(m_oFGMask_FloodedHoles, m_oFGMask_FloodedHoles); + cv::erode(m_oFGMask_PreFlood, m_oFGMask_PreFlood, cv::Mat(), cv::Point(-1, -1), 3); + cv::bitwise_or(oCurrFGMask, m_oFGMask_FloodedHoles, oCurrFGMask); + cv::bitwise_or(oCurrFGMask, m_oFGMask_PreFlood, oCurrFGMask); + cv::medianBlur(oCurrFGMask, m_oLastFGMask, m_nMedianBlurKernelSize); + cv::dilate(m_oLastFGMask, m_oLastFGMask_dilated, cv::Mat(), cv::Point(-1, -1), 3); + cv::bitwise_and(m_oBlinksFrame, m_oLastFGMask_dilated_inverted, m_oBlinksFrame); + cv::bitwise_not(m_oLastFGMask_dilated, m_oLastFGMask_dilated_inverted); + cv::bitwise_and(m_oBlinksFrame, m_oLastFGMask_dilated_inverted, m_oBlinksFrame); + m_oLastFGMask.copyTo(oCurrFGMask); + cv::addWeighted(m_oMeanFinalSegmResFrame_LT, (1.0f - fRollAvgFactor_LT), m_oLastFGMask, (1.0 / UCHAR_MAX)*fRollAvgFactor_LT, 0, m_oMeanFinalSegmResFrame_LT, CV_32F); + cv::addWeighted(m_oMeanFinalSegmResFrame_ST, (1.0f - fRollAvgFactor_ST), m_oLastFGMask, (1.0 / UCHAR_MAX)*fRollAvgFactor_ST, 0, m_oMeanFinalSegmResFrame_ST, CV_32F); + const float fCurrNonZeroDescRatio = (float)nNonZeroDescCount / m_nTotRelevantPxCount; + if (fCurrNonZeroDescRatio < LBSPDESC_NONZERO_RATIO_MIN && m_fLastNonZeroDescRatio < LBSPDESC_NONZERO_RATIO_MIN) { + for (size_t t = 0; t <= UCHAR_MAX; ++t) + if (m_anLBSPThreshold_8bitLUT[t] > cv::saturate_cast(m_nLBSPThresholdOffset + ceil(t*m_fRelLBSPThreshold / 4))) + --m_anLBSPThreshold_8bitLUT[t]; + } + else if (fCurrNonZeroDescRatio > LBSPDESC_NONZERO_RATIO_MAX && m_fLastNonZeroDescRatio > LBSPDESC_NONZERO_RATIO_MAX) { + for (size_t t = 0; t <= UCHAR_MAX; ++t) + if (m_anLBSPThreshold_8bitLUT[t] < cv::saturate_cast(m_nLBSPThresholdOffset + UCHAR_MAX*m_fRelLBSPThreshold)) + ++m_anLBSPThreshold_8bitLUT[t]; + } + m_fLastNonZeroDescRatio = fCurrNonZeroDescRatio; + if (m_bLearningRateScalingEnabled) { + cv::resize(oInputImg, m_oDownSampledFrame_MotionAnalysis, m_oDownSampledFrameSize, 0, 0, cv::INTER_AREA); + cv::accumulateWeighted(m_oDownSampledFrame_MotionAnalysis, m_oMeanDownSampledLastDistFrame_LT, fRollAvgFactor_LT); + cv::accumulateWeighted(m_oDownSampledFrame_MotionAnalysis, m_oMeanDownSampledLastDistFrame_ST, fRollAvgFactor_ST); + size_t nTotColorDiff = 0; + for (int i = 0; i < m_oMeanDownSampledLastDistFrame_ST.rows; ++i) { + const size_t idx1 = m_oMeanDownSampledLastDistFrame_ST.step.p[0] * i; + for (int j = 0; j < m_oMeanDownSampledLastDistFrame_ST.cols; ++j) { + const size_t idx2 = idx1 + m_oMeanDownSampledLastDistFrame_ST.step.p[1] * j; + nTotColorDiff += (m_nImgChannels == 1) ? + (size_t)fabs((*(float*)(m_oMeanDownSampledLastDistFrame_ST.data + idx2)) - (*(float*)(m_oMeanDownSampledLastDistFrame_LT.data + idx2))) / 2 + : //(m_nImgChannels==3) + std::max((size_t)fabs((*(float*)(m_oMeanDownSampledLastDistFrame_ST.data + idx2)) - (*(float*)(m_oMeanDownSampledLastDistFrame_LT.data + idx2))), + std::max((size_t)fabs((*(float*)(m_oMeanDownSampledLastDistFrame_ST.data + idx2 + 4)) - (*(float*)(m_oMeanDownSampledLastDistFrame_LT.data + idx2 + 4))), + (size_t)fabs((*(float*)(m_oMeanDownSampledLastDistFrame_ST.data + idx2 + 8)) - (*(float*)(m_oMeanDownSampledLastDistFrame_LT.data + idx2 + 8))))); + } + } + const float fCurrColorDiffRatio = (float)nTotColorDiff / (m_oMeanDownSampledLastDistFrame_ST.rows*m_oMeanDownSampledLastDistFrame_ST.cols); + if (m_bAutoModelResetEnabled) { + if (m_nFramesSinceLastReset > 1000) + m_bAutoModelResetEnabled = false; + else if (fCurrColorDiffRatio >= FRAMELEVEL_MIN_COLOR_DIFF_THRESHOLD && m_nModelResetCooldown == 0) { + m_nFramesSinceLastReset = 0; + refreshModel(0.1f); // reset 10% of the bg model + m_nModelResetCooldown = m_nSamplesForMovingAvgs / 4; + m_oUpdateRateFrame = cv::Scalar(1.0f); + } + else + ++m_nFramesSinceLastReset; + } + else if (fCurrColorDiffRatio >= FRAMELEVEL_MIN_COLOR_DIFF_THRESHOLD * 2) { + m_nFramesSinceLastReset = 0; + m_bAutoModelResetEnabled = true; + } + if (fCurrColorDiffRatio >= FRAMELEVEL_MIN_COLOR_DIFF_THRESHOLD / 2) { + m_fCurrLearningRateLowerCap = (float)std::max((int)FEEDBACK_T_LOWER >> (int)(fCurrColorDiffRatio / 2), 1); + m_fCurrLearningRateUpperCap = (float)std::max((int)FEEDBACK_T_UPPER >> (int)(fCurrColorDiffRatio / 2), 1); + } + else { + m_fCurrLearningRateLowerCap = FEEDBACK_T_LOWER; + m_fCurrLearningRateUpperCap = FEEDBACK_T_UPPER; + } + if (m_nModelResetCooldown > 0) + --m_nModelResetCooldown; + } +} + +void BackgroundSubtractorSuBSENSE::getBackgroundImage(cv::OutputArray backgroundImage) const { + CV_Assert(m_bInitialized); + cv::Mat oAvgBGImg = cv::Mat::zeros(m_oImgSize, CV_32FC((int)m_nImgChannels)); + for (size_t s = 0; s < m_nBGSamples; ++s) { + for (int y = 0; y < m_oImgSize.height; ++y) { + for (int x = 0; x < m_oImgSize.width; ++x) { + const size_t idx_nimg = m_voBGColorSamples[s].step.p[0] * y + m_voBGColorSamples[s].step.p[1] * x; + const size_t nFloatIter = idx_nimg * 4; + float* oAvgBgImgPtr = (float*)(oAvgBGImg.data + nFloatIter); + const uchar* const oBGImgPtr = m_voBGColorSamples[s].data + idx_nimg; + for (size_t c = 0; c < m_nImgChannels; ++c) + oAvgBgImgPtr[c] += ((float)oBGImgPtr[c]) / m_nBGSamples; + } + } + } + oAvgBGImg.convertTo(backgroundImage, CV_8U); +} + +void BackgroundSubtractorSuBSENSE::getBackgroundDescriptorsImage(cv::OutputArray backgroundDescImage) const { + CV_Assert(LBSP::DESC_SIZE == 2); + CV_Assert(m_bInitialized); + cv::Mat oAvgBGDesc = cv::Mat::zeros(m_oImgSize, CV_32FC((int)m_nImgChannels)); + for (size_t n = 0; n < m_voBGDescSamples.size(); ++n) { + for (int y = 0; y < m_oImgSize.height; ++y) { + for (int x = 0; x < m_oImgSize.width; ++x) { + const size_t idx_ndesc = m_voBGDescSamples[n].step.p[0] * y + m_voBGDescSamples[n].step.p[1] * x; + const size_t nFloatIter = idx_ndesc * 2; + float* oAvgBgDescPtr = (float*)(oAvgBGDesc.data + nFloatIter); + const ushort* const oBGDescPtr = (ushort*)(m_voBGDescSamples[n].data + idx_ndesc); + for (size_t c = 0; c < m_nImgChannels; ++c) + oAvgBgDescPtr[c] += ((float)oBGDescPtr[c]) / m_voBGDescSamples.size(); + } + } + } + oAvgBGDesc.convertTo(backgroundDescImage, CV_16U); +} diff --git a/package_bgs/LBSP/BackgroundSubtractorSuBSENSE.h b/package_bgs/LBSP/BackgroundSubtractorSuBSENSE.h new file mode 100644 index 0000000..9950ea4 --- /dev/null +++ b/package_bgs/LBSP/BackgroundSubtractorSuBSENSE.h @@ -0,0 +1,129 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "BackgroundSubtractorLBSP.h" + +//! defines the default value for BackgroundSubtractorLBSP::m_fRelLBSPThreshold +#define BGSSUBSENSE_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD (0.333f) +//! defines the default value for BackgroundSubtractorSuBSENSE::m_nDescDistThresholdOffset +#define BGSSUBSENSE_DEFAULT_DESC_DIST_THRESHOLD_OFFSET (3) +//! defines the default value for BackgroundSubtractorSuBSENSE::m_nMinColorDistThreshold +#define BGSSUBSENSE_DEFAULT_MIN_COLOR_DIST_THRESHOLD (30) +//! defines the default value for BackgroundSubtractorSuBSENSE::m_nBGSamples +#define BGSSUBSENSE_DEFAULT_NB_BG_SAMPLES (50) +//! defines the default value for BackgroundSubtractorSuBSENSE::m_nRequiredBGSamples +#define BGSSUBSENSE_DEFAULT_REQUIRED_NB_BG_SAMPLES (2) +//! defines the default value for BackgroundSubtractorSuBSENSE::m_nSamplesForMovingAvgs +#define BGSSUBSENSE_DEFAULT_N_SAMPLES_FOR_MV_AVGS (100) + +/*! + Self-Balanced Sensitivity segmenTER (SuBSENSE) change detection algorithm. + + Note: both grayscale and RGB/BGR images may be used with this extractor (parameters are adjusted automatically). + For optimal grayscale results, use CV_8UC1 frames instead of CV_8UC3. + + For more details on the different parameters or on the algorithm itself, see P.-L. St-Charles et al., + "Flexible Background Subtraction With Self-Balanced Local Sensitivity", in CVPRW 2014. + + This algorithm is currently NOT thread-safe. + */ +class BackgroundSubtractorSuBSENSE : public BackgroundSubtractorLBSP { +public: + //! full constructor + BackgroundSubtractorSuBSENSE(float fRelLBSPThreshold = BGSSUBSENSE_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD, + size_t nDescDistThresholdOffset = BGSSUBSENSE_DEFAULT_DESC_DIST_THRESHOLD_OFFSET, + size_t nMinColorDistThreshold = BGSSUBSENSE_DEFAULT_MIN_COLOR_DIST_THRESHOLD, + size_t nBGSamples = BGSSUBSENSE_DEFAULT_NB_BG_SAMPLES, + size_t nRequiredBGSamples = BGSSUBSENSE_DEFAULT_REQUIRED_NB_BG_SAMPLES, + size_t nSamplesForMovingAvgs = BGSSUBSENSE_DEFAULT_N_SAMPLES_FOR_MV_AVGS); + //! default destructor + virtual ~BackgroundSubtractorSuBSENSE(); + //! (re)initiaization method; needs to be called before starting background subtraction + virtual void initialize(const cv::Mat& oInitImg, const cv::Mat& oROI); + //! refreshes all samples based on the last analyzed frame + virtual void refreshModel(float fSamplesRefreshFrac, bool bForceFGUpdate = false); + //! primary model update function; the learning param is used to override the internal learning thresholds (ignored when <= 0) + virtual void apply(cv::InputArray image, cv::OutputArray fgmask, double learningRateOverride = 0); + //! returns a copy of the latest reconstructed background image + void getBackgroundImage(cv::OutputArray backgroundImage) const; + //! returns a copy of the latest reconstructed background descriptors image + void getBackgroundDescriptorsImage(cv::OutputArray backgroundDescImage) const; + +protected: + //! absolute minimal color distance threshold ('R' or 'radius' in the original ViBe paper, used as the default/initial 'R(x)' value here) + const size_t m_nMinColorDistThreshold; + //! absolute descriptor distance threshold offset + const size_t m_nDescDistThresholdOffset; + //! number of different samples per pixel/block to be taken from input frames to build the background model (same as 'N' in ViBe/PBAS) + const size_t m_nBGSamples; + //! number of similar samples needed to consider the current pixel/block as 'background' (same as '#_min' in ViBe/PBAS) + const size_t m_nRequiredBGSamples; + //! number of samples to use to compute the learning rate of moving averages + const size_t m_nSamplesForMovingAvgs; + //! last calculated non-zero desc ratio + float m_fLastNonZeroDescRatio; + //! specifies whether Tmin/Tmax scaling is enabled or not + bool m_bLearningRateScalingEnabled; + //! current learning rate caps + float m_fCurrLearningRateLowerCap, m_fCurrLearningRateUpperCap; + //! current kernel size for median blur post-proc filtering + int m_nMedianBlurKernelSize; + //! specifies the px update spread range + bool m_bUse3x3Spread; + //! specifies the downsampled frame size used for cam motion analysis + cv::Size m_oDownSampledFrameSize; + + //! background model pixel color intensity samples (equivalent to 'B(x)' in PBAS) + std::vector m_voBGColorSamples; + //! background model descriptors samples + std::vector m_voBGDescSamples; + + //! per-pixel update rates ('T(x)' in PBAS, which contains pixel-level 'sigmas', as referred to in ViBe) + cv::Mat m_oUpdateRateFrame; + //! per-pixel distance thresholds (equivalent to 'R(x)' in PBAS, but used as a relative value to determine both intensity and descriptor variation thresholds) + cv::Mat m_oDistThresholdFrame; + //! per-pixel distance variation modulators ('v(x)', relative value used to modulate 'R(x)' and 'T(x)' variations) + cv::Mat m_oVariationModulatorFrame; + //! per-pixel mean distances between consecutive frames ('D_last(x)', used to detect ghosts and high variation regions in the sequence) + cv::Mat m_oMeanLastDistFrame; + //! per-pixel mean minimal distances from the model ('D_min(x)' in PBAS, used to control variation magnitude and direction of 'T(x)' and 'R(x)') + cv::Mat m_oMeanMinDistFrame_LT, m_oMeanMinDistFrame_ST; + //! per-pixel mean downsampled distances between consecutive frames (used to analyze camera movement and control max learning rates globally) + cv::Mat m_oMeanDownSampledLastDistFrame_LT, m_oMeanDownSampledLastDistFrame_ST; + //! per-pixel mean raw segmentation results (used to detect unstable segmentation regions) + cv::Mat m_oMeanRawSegmResFrame_LT, m_oMeanRawSegmResFrame_ST; + //! per-pixel mean raw segmentation results (used to detect unstable segmentation regions) + cv::Mat m_oMeanFinalSegmResFrame_LT, m_oMeanFinalSegmResFrame_ST; + //! a lookup map used to keep track of unstable regions (based on segm. noise & local dist. thresholds) + cv::Mat m_oUnstableRegionMask; + //! per-pixel blink detection map ('Z(x)') + cv::Mat m_oBlinksFrame; + //! pre-allocated matrix used to downsample the input frame when needed + cv::Mat m_oDownSampledFrame_MotionAnalysis; + //! the foreground mask generated by the method at [t-1] (without post-proc, used for blinking px detection) + cv::Mat m_oLastRawFGMask; + + //! pre-allocated CV_8UC1 matrices used to speed up morph ops + cv::Mat m_oFGMask_PreFlood; + cv::Mat m_oFGMask_FloodedHoles; + cv::Mat m_oLastFGMask_dilated; + cv::Mat m_oLastFGMask_dilated_inverted; + cv::Mat m_oCurrRawFGBlinkMask; + cv::Mat m_oLastRawFGBlinkMask; +}; + diff --git a/package_bgs/LBSP/DistanceUtils.h b/package_bgs/LBSP/DistanceUtils.h new file mode 100644 index 0000000..9eabca4 --- /dev/null +++ b/package_bgs/LBSP/DistanceUtils.h @@ -0,0 +1,332 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include + +//! computes the L1 distance between two integer values +template static inline typename std::enable_if::value,size_t>::type L1dist(T a, T b) { + return (size_t)abs((int)a-b); +} + +//! computes the L1 distance between two float values +template static inline typename std::enable_if::value,float>::type L1dist(T a, T b) { + return fabs((float)a-(float)b); +} + +//! computes the L1 distance between two generic arrays +template static inline auto L1dist(const T* a, const T* b) -> decltype(L1dist(*a,*b)) { + decltype(L1dist(*a,*b)) oResult = 0; + for(size_t c=0; c static inline auto L1dist(const T* a, const T* b, size_t nElements, const uchar* m=NULL) -> decltype(L1dist(a,b)) { + decltype(L1dist(a,b)) oResult = 0; + size_t nTotElements = nElements*nChannels; + if(m) { + for(size_t n=0,i=0; n(a+n,b+n); + } + else { + for(size_t n=0; n(a+n,b+n); + } + return oResult; +} + +//! computes the L1 distance between two generic arrays +template static inline auto L1dist(const T* a, const T* b, size_t nElements, size_t nChannels, const uchar* m=NULL) -> decltype(L1dist<3>(a,b,nElements,m)) { + CV_Assert(nChannels>0 && nChannels<=4); + switch(nChannels) { + case 1: return L1dist<1>(a,b,nElements,m); + case 2: return L1dist<2>(a,b,nElements,m); + case 3: return L1dist<3>(a,b,nElements,m); + case 4: return L1dist<4>(a,b,nElements,m); + default: return 0; + } +} + +//! computes the L1 distance between two opencv vectors +template static inline auto L1dist_(const cv::Vec& a, const cv::Vec& b) -> decltype(L1dist((T*)(0),(T*)(0))) { + T a_array[nChannels], b_array[nChannels]; + for(size_t c=0; c(a_array,b_array); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//! computes the squared L2 distance between two generic variables +template static inline auto L2sqrdist(T a, T b) -> decltype(L1dist(a,b)) { + auto oResult = L1dist(a,b); + return oResult*oResult; +} + +//! computes the squared L2 distance between two generic arrays +template static inline auto L2sqrdist(const T* a, const T* b) -> decltype(L2sqrdist(*a,*b)) { + decltype(L2sqrdist(*a,*b)) oResult = 0; + for(size_t c=0; c static inline auto L2sqrdist(const T* a, const T* b, size_t nElements, const uchar* m=NULL) -> decltype(L2sqrdist(a,b)) { + decltype(L2sqrdist(a,b)) oResult = 0; + size_t nTotElements = nElements*nChannels; + if(m) { + for(size_t n=0,i=0; n(a+n,b+n); + } + else { + for(size_t n=0; n(a+n,b+n); + } + return oResult; +} + +//! computes the squared L2 distance between two generic arrays +template static inline auto L2sqrdist(const T* a, const T* b, size_t nElements, size_t nChannels, const uchar* m=NULL) -> decltype(L2sqrdist<3>(a,b,nElements,m)) { + CV_Assert(nChannels>0 && nChannels<=4); + switch(nChannels) { + case 1: return L2sqrdist<1>(a,b,nElements,m); + case 2: return L2sqrdist<2>(a,b,nElements,m); + case 3: return L2sqrdist<3>(a,b,nElements,m); + case 4: return L2sqrdist<4>(a,b,nElements,m); + default: return 0; + } +} + +//! computes the squared L2 distance between two opencv vectors +template static inline auto L2sqrdist_(const cv::Vec& a, const cv::Vec& b) -> decltype(L2sqrdist((T*)(0),(T*)(0))) { + T a_array[nChannels], b_array[nChannels]; + for(size_t c=0; c(a_array,b_array); +} + +//! computes the L2 distance between two generic arrays +template static inline float L2dist(const T* a, const T* b) { + decltype(L2sqrdist(*a,*b)) oResult = 0; + for(size_t c=0; c static inline float L2dist(const T* a, const T* b, size_t nElements, const uchar* m=NULL) { + decltype(L2sqrdist(a,b)) oResult = 0; + size_t nTotElements = nElements*nChannels; + if(m) { + for(size_t n=0,i=0; n(a+n,b+n); + } + else { + for(size_t n=0; n(a+n,b+n); + } + return sqrt((float)oResult); +} + +//! computes the squared L2 distance between two generic arrays +template static inline float L2dist(const T* a, const T* b, size_t nElements, size_t nChannels, const uchar* m=NULL) { + CV_Assert(nChannels>0 && nChannels<=4); + switch(nChannels) { + case 1: return L2dist<1>(a,b,nElements,m); + case 2: return L2dist<2>(a,b,nElements,m); + case 3: return L2dist<3>(a,b,nElements,m); + case 4: return L2dist<4>(a,b,nElements,m); + default: return 0; + } +} + +//! computes the L2 distance between two opencv vectors +template static inline float L2dist_(const cv::Vec& a, const cv::Vec& b) { + T a_array[nChannels], b_array[nChannels]; + for(size_t c=0; c(a_array,b_array); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//! computes the color distortion between two integer arrays +template static inline typename std::enable_if::value,size_t>::type cdist(const T* curr, const T* bg) { + static_assert(nChannels>1,"cdist: requires more than one channel"); + size_t curr_sqr = 0; + bool bSkip = true; + for(size_t c=0; c static inline typename std::enable_if::value,float>::type cdist(const T* curr, const T* bg) { + static_assert(nChannels>1,"cdist: requires more than one channel"); + float curr_sqr = 0; + bool bSkip = true; + for(size_t c=0; c static inline auto cdist(const T* a, const T* b, size_t nElements, const uchar* m=NULL) -> decltype(cdist(a,b)) { + decltype(cdist(a,b)) oResult = 0; + size_t nTotElements = nElements*nChannels; + if(m) { + for(size_t n=0,i=0; n(a+n,b+n); + } + else { + for(size_t n=0; n(a+n,b+n); + } + return oResult; +} + +//! computes the color distortion between two generic arrays +template static inline auto cdist(const T* a, const T* b, size_t nElements, size_t nChannels, const uchar* m=NULL) -> decltype(cdist<3>(a,b,nElements,m)) { + CV_Assert(nChannels>1 && nChannels<=4); + switch(nChannels) { + case 2: return cdist<2>(a,b,nElements,m); + case 3: return cdist<3>(a,b,nElements,m); + case 4: return cdist<4>(a,b,nElements,m); + default: return 0; + } +} + +//! computes the color distortion between two opencv vectors +template static inline auto cdist_(const cv::Vec& a, const cv::Vec& b) -> decltype(cdist((T*)(0),(T*)(0))) { + T a_array[nChannels], b_array[nChannels]; + for(size_t c=0; c(a_array,b_array); +} + +//! computes a color distortion-distance mix using two generic distances +template static inline T cmixdist(T oL1Distance, T oCDistortion) { + return (oL1Distance/2+oCDistortion*4); +} + +//! computes a color distoirtion-distance mix using two generic arrays +template static inline typename std::enable_if::value,size_t>::type cmixdist(const T* curr, const T* bg) { + return cmixdist(L1dist(curr,bg),cdist(curr,bg)); +} + +template static inline typename std::enable_if::value,float>::type cmixdist(const T* curr, const T* bg) { + return cmixdist(L1dist(curr,bg),cdist(curr,bg)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//! popcount LUT for 8-bit vectors +static const uchar popcount_LUT8[256] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, +}; + +//! computes the population count of an N-byte vector using an 8-bit popcount LUT +template static inline size_t popcount(T x) { + size_t nBytes = sizeof(T); + size_t nResult = 0; + for(size_t l=0; l>l*8)]; + return nResult; +} + +//! computes the hamming distance between two N-byte vectors using an 8-bit popcount LUT +template static inline size_t hdist(T a, T b) { + return popcount(a^b); +} + +//! computes the gradient magnitude distance between two N-byte vectors using an 8-bit popcount LUT +template static inline size_t gdist(T a, T b) { + return L1dist(popcount(a),popcount(b)); +} + +//! computes the population count of a (nChannels*N)-byte vector using an 8-bit popcount LUT +template static inline size_t popcount(const T* x) { + size_t nBytes = sizeof(T); + size_t nResult = 0; + for(size_t c=0; c>l*8)]; + return nResult; +} + +//! computes the hamming distance between two (nChannels*N)-byte vectors using an 8-bit popcount LUT +template static inline size_t hdist(const T* a, const T* b) { + T xor_array[nChannels]; + for(size_t c=0; c(xor_array); +} + +//! computes the gradient magnitude distance between two (nChannels*N)-byte vectors using an 8-bit popcount LUT +template static inline size_t gdist(const T* a, const T* b) { + return L1dist(popcount(a),popcount(b)); +} diff --git a/package_bgs/LBSP/LBSP.cpp b/package_bgs/LBSP/LBSP.cpp new file mode 100644 index 0000000..4ec17b9 --- /dev/null +++ b/package_bgs/LBSP/LBSP.cpp @@ -0,0 +1,334 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "LBSP.h" + +LBSP::LBSP(size_t nThreshold) + : m_bOnlyUsingAbsThreshold(true) + , m_fRelThreshold(0) // unused + , m_nThreshold(nThreshold) + , m_oRefImage() {} + +LBSP::LBSP(float fRelThreshold, size_t nThresholdOffset) + : m_bOnlyUsingAbsThreshold(false) + , m_fRelThreshold(fRelThreshold) + , m_nThreshold(nThresholdOffset) + , m_oRefImage() { + CV_Assert(m_fRelThreshold >= 0); +} + +LBSP::~LBSP() {} + +void LBSP::read(const cv::FileNode& /*fn*/) { + // ... = fn["..."]; +} + +void LBSP::write(cv::FileStorage& /*fs*/) const { + //fs << "..." << ...; +} + +void LBSP::setReference(const cv::Mat& img) { + CV_DbgAssert(img.empty() || img.type() == CV_8UC1 || img.type() == CV_8UC3); + m_oRefImage = img; +} + +int LBSP::descriptorSize() const { + return DESC_SIZE; +} + +int LBSP::descriptorType() const { + return CV_16U; +} + +bool LBSP::isUsingRelThreshold() const { + return !m_bOnlyUsingAbsThreshold; +} + +float LBSP::getRelThreshold() const { + return m_fRelThreshold; +} + +size_t LBSP::getAbsThreshold() const { + return m_nThreshold; +} + +static inline void lbsp_computeImpl(const cv::Mat& oInputImg, + const cv::Mat& oRefImg, + const std::vector& voKeyPoints, + cv::Mat& oDesc, + size_t _t) { + CV_DbgAssert(oRefImg.empty() || (oRefImg.size == oInputImg.size && oRefImg.type() == oInputImg.type())); + CV_DbgAssert(oInputImg.type() == CV_8UC1 || oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP::DESC_SIZE == 2); // @@@ also relies on a constant desc size + const size_t nChannels = (size_t)oInputImg.channels(); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* _data = oInputImg.data; + const uchar* _refdata = oRefImg.empty() ? oInputImg.data : oRefImg.data; + const size_t nKeyPoints = voKeyPoints.size(); + if (nChannels == 1) { + oDesc.create((int)nKeyPoints, 1, CV_16UC1); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar _ref = _refdata[_step_row*(_y)+_x]; + ushort& _res = oDesc.at((int)k); +#include "LBSP_16bits_dbcross_1ch.i" + } + } + else { //nChannels==3 + oDesc.create((int)nKeyPoints, 1, CV_16UC3); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar* _ref = _refdata + _step_row*(_y)+3 * (_x); + ushort* _res = ((ushort*)(oDesc.data + oDesc.step.p[0] * k)); +#include "LBSP_16bits_dbcross_3ch1t.i" + } + } +} + +static inline void lbsp_computeImpl(const cv::Mat& oInputImg, + const cv::Mat& oRefImg, + const std::vector& voKeyPoints, + cv::Mat& oDesc, + float fThreshold, + size_t nThresholdOffset) { + CV_DbgAssert(oRefImg.empty() || (oRefImg.size == oInputImg.size && oRefImg.type() == oInputImg.type())); + CV_DbgAssert(oInputImg.type() == CV_8UC1 || oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(fThreshold >= 0); + const size_t nChannels = (size_t)oInputImg.channels(); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* _data = oInputImg.data; + const uchar* _refdata = oRefImg.empty() ? oInputImg.data : oRefImg.data; + const size_t nKeyPoints = voKeyPoints.size(); + if (nChannels == 1) { + oDesc.create((int)nKeyPoints, 1, CV_16UC1); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar _ref = _refdata[_step_row*(_y)+_x]; + ushort& _res = oDesc.at((int)k); + const size_t _t = (size_t)(_ref*fThreshold) + nThresholdOffset; +#include "LBSP_16bits_dbcross_1ch.i" + } + } + else { //nChannels==3 + oDesc.create((int)nKeyPoints, 1, CV_16UC3); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar* _ref = _refdata + _step_row*(_y)+3 * (_x); + ushort* _res = ((ushort*)(oDesc.data + oDesc.step.p[0] * k)); + const size_t _t[3] = { (size_t)(_ref[0] * fThreshold) + nThresholdOffset,(size_t)(_ref[1] * fThreshold) + nThresholdOffset,(size_t)(_ref[2] * fThreshold) + nThresholdOffset }; +#include "LBSP_16bits_dbcross_3ch3t.i" + } + } +} + +static inline void lbsp_computeImpl2(const cv::Mat& oInputImg, + const cv::Mat& oRefImg, + const std::vector& voKeyPoints, + cv::Mat& oDesc, + size_t _t) { + CV_DbgAssert(oRefImg.empty() || (oRefImg.size == oInputImg.size && oRefImg.type() == oInputImg.type())); + CV_DbgAssert(oInputImg.type() == CV_8UC1 || oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP::DESC_SIZE == 2); // @@@ also relies on a constant desc size + const size_t nChannels = (size_t)oInputImg.channels(); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* _data = oInputImg.data; + const uchar* _refdata = oRefImg.empty() ? oInputImg.data : oRefImg.data; + const size_t nKeyPoints = voKeyPoints.size(); + if (nChannels == 1) { + oDesc.create(oInputImg.size(), CV_16UC1); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar _ref = _refdata[_step_row*(_y)+_x]; + ushort& _res = oDesc.at(_y, _x); +#include "LBSP_16bits_dbcross_1ch.i" + } + } + else { //nChannels==3 + oDesc.create(oInputImg.size(), CV_16UC3); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar* _ref = _refdata + _step_row*(_y)+3 * (_x); + ushort* _res = ((ushort*)(oDesc.data + oDesc.step.p[0] * _y + oDesc.step.p[1] * _x)); +#include "LBSP_16bits_dbcross_3ch1t.i" + } + } +} + +static inline void lbsp_computeImpl2(const cv::Mat& oInputImg, + const cv::Mat& oRefImg, + const std::vector& voKeyPoints, + cv::Mat& oDesc, + float fThreshold, + size_t nThresholdOffset) { + CV_DbgAssert(oRefImg.empty() || (oRefImg.size == oInputImg.size && oRefImg.type() == oInputImg.type())); + CV_DbgAssert(oInputImg.type() == CV_8UC1 || oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(fThreshold >= 0); + const size_t nChannels = (size_t)oInputImg.channels(); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* _data = oInputImg.data; + const uchar* _refdata = oRefImg.empty() ? oInputImg.data : oRefImg.data; + const size_t nKeyPoints = voKeyPoints.size(); + if (nChannels == 1) { + oDesc.create(oInputImg.size(), CV_16UC1); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar _ref = _refdata[_step_row*(_y)+_x]; + ushort& _res = oDesc.at(_y, _x); + const size_t _t = (size_t)(_ref*fThreshold) + nThresholdOffset; +#include "LBSP_16bits_dbcross_1ch.i" + } + } + else { //nChannels==3 + oDesc.create(oInputImg.size(), CV_16UC3); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar* _ref = _refdata + _step_row*(_y)+3 * (_x); + ushort* _res = ((ushort*)(oDesc.data + oDesc.step.p[0] * _y + oDesc.step.p[1] * _x)); + const size_t _t[3] = { (size_t)(_ref[0] * fThreshold) + nThresholdOffset,(size_t)(_ref[1] * fThreshold) + nThresholdOffset,(size_t)(_ref[2] * fThreshold) + nThresholdOffset }; +#include "LBSP_16bits_dbcross_3ch3t.i" + } + } +} + +void LBSP::compute2(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const { + CV_Assert(!oImage.empty()); + cv::KeyPointsFilter::runByImageBorder(voKeypoints, oImage.size(), PATCH_SIZE / 2); + cv::KeyPointsFilter::runByKeypointSize(voKeypoints, std::numeric_limits::epsilon()); + if (voKeypoints.empty()) { + oDescriptors.release(); + return; + } + if (m_bOnlyUsingAbsThreshold) + lbsp_computeImpl2(oImage, m_oRefImage, voKeypoints, oDescriptors, m_nThreshold); + else + lbsp_computeImpl2(oImage, m_oRefImage, voKeypoints, oDescriptors, m_fRelThreshold, m_nThreshold); +} + +void LBSP::compute2(const std::vector& voImageCollection, std::vector >& vvoPointCollection, std::vector& voDescCollection) const { + CV_Assert(voImageCollection.size() == vvoPointCollection.size()); + voDescCollection.resize(voImageCollection.size()); + for (size_t i = 0; i < voImageCollection.size(); i++) + compute2(voImageCollection[i], vvoPointCollection[i], voDescCollection[i]); +} + +void LBSP::computeImpl(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const { + CV_Assert(!oImage.empty()); + cv::KeyPointsFilter::runByImageBorder(voKeypoints, oImage.size(), PATCH_SIZE / 2); + cv::KeyPointsFilter::runByKeypointSize(voKeypoints, std::numeric_limits::epsilon()); + if (voKeypoints.empty()) { + oDescriptors.release(); + return; + } + if (m_bOnlyUsingAbsThreshold) + lbsp_computeImpl(oImage, m_oRefImage, voKeypoints, oDescriptors, m_nThreshold); + else + lbsp_computeImpl(oImage, m_oRefImage, voKeypoints, oDescriptors, m_fRelThreshold, m_nThreshold); +} + +void LBSP::reshapeDesc(cv::Size oSize, const std::vector& voKeypoints, const cv::Mat& oDescriptors, cv::Mat& oOutput) { + CV_DbgAssert(!voKeypoints.empty()); + CV_DbgAssert(!oDescriptors.empty() && oDescriptors.cols == 1); + CV_DbgAssert(oSize.width > 0 && oSize.height > 0); + CV_DbgAssert(DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(oDescriptors.type() == CV_16UC1 || oDescriptors.type() == CV_16UC3); + const size_t nChannels = (size_t)oDescriptors.channels(); + const size_t nKeyPoints = voKeypoints.size(); + if (nChannels == 1) { + oOutput.create(oSize, CV_16UC1); + oOutput = cv::Scalar_(0); + for (size_t k = 0; k < nKeyPoints; ++k) + oOutput.at(voKeypoints[k].pt) = oDescriptors.at((int)k); + } + else { //nChannels==3 + oOutput.create(oSize, CV_16UC3); + oOutput = cv::Scalar_(0, 0, 0); + for (size_t k = 0; k < nKeyPoints; ++k) { + ushort* output_ptr = (ushort*)(oOutput.data + oOutput.step.p[0] * (int)voKeypoints[k].pt.y); + const ushort* const desc_ptr = (ushort*)(oDescriptors.data + oDescriptors.step.p[0] * k); + const size_t idx = 3 * (int)voKeypoints[k].pt.x; + for (size_t n = 0; n < 3; ++n) + output_ptr[idx + n] = desc_ptr[n]; + } + } +} + +void LBSP::calcDescImgDiff(const cv::Mat& oDesc1, const cv::Mat& oDesc2, cv::Mat& oOutput, bool bForceMergeChannels) { + CV_DbgAssert(oDesc1.size() == oDesc2.size() && oDesc1.type() == oDesc2.type()); + CV_DbgAssert(DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(oDesc1.type() == CV_16UC1 || oDesc1.type() == CV_16UC3); + CV_DbgAssert(CV_MAT_DEPTH(oDesc1.type()) == CV_16U); + CV_DbgAssert(DESC_SIZE * 8 <= UCHAR_MAX); + CV_DbgAssert(oDesc1.step.p[0] == oDesc2.step.p[0] && oDesc1.step.p[1] == oDesc2.step.p[1]); + const float fScaleFactor = (float)UCHAR_MAX / (DESC_SIZE * 8); + const size_t nChannels = CV_MAT_CN(oDesc1.type()); + const size_t _step_row = oDesc1.step.p[0]; + if (nChannels == 1) { + oOutput.create(oDesc1.size(), CV_8UC1); + oOutput = cv::Scalar(0); + for (int i = 0; i < oDesc1.rows; ++i) { + const size_t idx = _step_row*i; + const ushort* const desc1_ptr = (ushort*)(oDesc1.data + idx); + const ushort* const desc2_ptr = (ushort*)(oDesc2.data + idx); + for (int j = 0; j < oDesc1.cols; ++j) + oOutput.at(i, j) = (uchar)(fScaleFactor*hdist(desc1_ptr[j], desc2_ptr[j])); + } + } + else { //nChannels==3 + if (bForceMergeChannels) + oOutput.create(oDesc1.size(), CV_8UC1); + else + oOutput.create(oDesc1.size(), CV_8UC3); + oOutput = cv::Scalar::all(0); + for (int i = 0; i < oDesc1.rows; ++i) { + const size_t idx = _step_row*i; + const ushort* const desc1_ptr = (ushort*)(oDesc1.data + idx); + const ushort* const desc2_ptr = (ushort*)(oDesc2.data + idx); + uchar* output_ptr = oOutput.data + oOutput.step.p[0] * i; + for (int j = 0; j < oDesc1.cols; ++j) { + for (size_t n = 0; n < 3; ++n) { + const size_t idx2 = 3 * j + n; + if (bForceMergeChannels) + output_ptr[j] += (uchar)((fScaleFactor*hdist(desc1_ptr[idx2], desc2_ptr[idx2])) / 3); + else + output_ptr[idx2] = (uchar)(fScaleFactor*hdist(desc1_ptr[idx2], desc2_ptr[idx2])); + } + } + } + } +} + +void LBSP::validateKeyPoints(std::vector& voKeypoints, cv::Size oImgSize) { + cv::KeyPointsFilter::runByImageBorder(voKeypoints, oImgSize, PATCH_SIZE / 2); +} + +void LBSP::validateROI(cv::Mat& oROI) { + CV_Assert(!oROI.empty() && oROI.type() == CV_8UC1); + cv::Mat oROI_new(oROI.size(), CV_8UC1, cv::Scalar_(0)); + const size_t nBorderSize = PATCH_SIZE / 2; + const cv::Rect nROI_inner(nBorderSize, nBorderSize, oROI.cols - nBorderSize * 2, oROI.rows - nBorderSize * 2); + cv::Mat(oROI, nROI_inner).copyTo(cv::Mat(oROI_new, nROI_inner)); + oROI = oROI_new; +} diff --git a/package_bgs/LBSP/LBSP.h b/package_bgs/LBSP/LBSP.h new file mode 100644 index 0000000..c908eaa --- /dev/null +++ b/package_bgs/LBSP/LBSP.h @@ -0,0 +1,134 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include +#include +#include +#include "DistanceUtils.h" + +/*! + Local Binary Similarity Pattern (LBSP) feature extractor + + Note 1: both grayscale and RGB/BGR images may be used with this extractor. + Note 2: using LBSP::compute2(...) is logically equivalent to using LBSP::compute(...) followed by LBSP::reshapeDesc(...). + + For more details on the different parameters, see G.-A. Bilodeau et al, "Change Detection in Feature Space Using Local + Binary Similarity Patterns", in CRV 2013. + + This algorithm is currently NOT thread-safe. + */ +class LBSP : public cv::DescriptorExtractor { +public: + //! constructor 1, threshold = absolute intensity 'similarity' threshold used when computing comparisons + LBSP(size_t nThreshold); + //! constructor 2, threshold = relative intensity 'similarity' threshold used when computing comparisons + LBSP(float fRelThreshold, size_t nThresholdOffset = 0); + //! default destructor + virtual ~LBSP(); + //! loads extractor params from the specified file node @@@@ not impl + virtual void read(const cv::FileNode&); + //! writes extractor params to the specified file storage @@@@ not impl + virtual void write(cv::FileStorage&) const; + //! sets the 'reference' image to be used for inter-frame comparisons (note: if no image is set or if the image is empty, the algorithm will default back to intra-frame comparisons) + virtual void setReference(const cv::Mat&); + //! returns the current descriptor size, in bytes + virtual int descriptorSize() const; + //! returns the current descriptor data type + virtual int descriptorType() const; + //! returns whether this extractor is using a relative threshold or not + virtual bool isUsingRelThreshold() const; + //! returns the current relative threshold used for comparisons (-1 = invalid/not used) + virtual float getRelThreshold() const; + //! returns the current absolute threshold used for comparisons (-1 = invalid/not used) + virtual size_t getAbsThreshold() const; + + //! similar to DescriptorExtractor::compute(const cv::Mat& image, ...), but in this case, the descriptors matrix has the same shape as the input matrix (possibly slower, but the result can be displayed) + void compute2(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const; + //! batch version of LBSP::compute2(const cv::Mat& image, ...), also similar to DescriptorExtractor::compute(const std::vector& imageCollection, ...) + void compute2(const std::vector& voImageCollection, std::vector >& vvoPointCollection, std::vector& voDescCollection) const; + + //! utility function, shortcut/lightweight/direct single-point LBSP computation function for extra flexibility (1-channel version) + inline static void computeGrayscaleDescriptor(const cv::Mat& oInputImg, const uchar _ref, const int _x, const int _y, const size_t _t, ushort& _res) { + CV_DbgAssert(!oInputImg.empty()); + CV_DbgAssert(oInputImg.type() == CV_8UC1); + CV_DbgAssert(LBSP::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(_x >= (int)LBSP::PATCH_SIZE / 2 && _y >= (int)LBSP::PATCH_SIZE / 2); + CV_DbgAssert(_x < oInputImg.cols - (int)LBSP::PATCH_SIZE / 2 && _y < oInputImg.rows - (int)LBSP::PATCH_SIZE / 2); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* const _data = oInputImg.data; +#include "LBSP_16bits_dbcross_1ch.i" + } + + //! utility function, shortcut/lightweight/direct single-point LBSP computation function for extra flexibility (3-channels version) + inline static void computeRGBDescriptor(const cv::Mat& oInputImg, const uchar* const _ref, const int _x, const int _y, const size_t* const _t, ushort* _res) { + CV_DbgAssert(!oInputImg.empty()); + CV_DbgAssert(oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(_x >= (int)LBSP::PATCH_SIZE / 2 && _y >= (int)LBSP::PATCH_SIZE / 2); + CV_DbgAssert(_x < oInputImg.cols - (int)LBSP::PATCH_SIZE / 2 && _y < oInputImg.rows - (int)LBSP::PATCH_SIZE / 2); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* const _data = oInputImg.data; +#include "LBSP_16bits_dbcross_3ch3t.i" + } + + //! utility function, shortcut/lightweight/direct single-point LBSP computation function for extra flexibility (3-channels version) + inline static void computeRGBDescriptor(const cv::Mat& oInputImg, const uchar* const _ref, const int _x, const int _y, const size_t _t, ushort* _res) { + CV_DbgAssert(!oInputImg.empty()); + CV_DbgAssert(oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(_x >= (int)LBSP::PATCH_SIZE / 2 && _y >= (int)LBSP::PATCH_SIZE / 2); + CV_DbgAssert(_x < oInputImg.cols - (int)LBSP::PATCH_SIZE / 2 && _y < oInputImg.rows - (int)LBSP::PATCH_SIZE / 2); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* const _data = oInputImg.data; +#include "LBSP_16bits_dbcross_3ch1t.i" + } + + //! utility function, shortcut/lightweight/direct single-point LBSP computation function for extra flexibility (1-channel-RGB version) + inline static void computeSingleRGBDescriptor(const cv::Mat& oInputImg, const uchar _ref, const int _x, const int _y, const size_t _c, const size_t _t, ushort& _res) { + CV_DbgAssert(!oInputImg.empty()); + CV_DbgAssert(oInputImg.type() == CV_8UC3 && _c < 3); + CV_DbgAssert(LBSP::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(_x >= (int)LBSP::PATCH_SIZE / 2 && _y >= (int)LBSP::PATCH_SIZE / 2); + CV_DbgAssert(_x < oInputImg.cols - (int)LBSP::PATCH_SIZE / 2 && _y < oInputImg.rows - (int)LBSP::PATCH_SIZE / 2); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* const _data = oInputImg.data; +#include "LBSP_16bits_dbcross_s3ch.i" + } + + //! utility function, used to reshape a descriptors matrix to its input image size via their keypoint locations + static void reshapeDesc(cv::Size oSize, const std::vector& voKeypoints, const cv::Mat& oDescriptors, cv::Mat& oOutput); + //! utility function, used to illustrate the difference between two descriptor images + static void calcDescImgDiff(const cv::Mat& oDesc1, const cv::Mat& oDesc2, cv::Mat& oOutput, bool bForceMergeChannels = false); + //! utility function, used to filter out bad keypoints that would trigger out of bounds error because they're too close to the image border + static void validateKeyPoints(std::vector& voKeypoints, cv::Size oImgSize); + //! utility function, used to filter out bad pixels in a ROI that would trigger out of bounds error because they're too close to the image border + static void validateROI(cv::Mat& oROI); + //! utility, specifies the pixel size of the pattern used (width and height) + static const size_t PATCH_SIZE = 5; + //! utility, specifies the number of bytes per descriptor (should be the same as calling 'descriptorSize()') + static const size_t DESC_SIZE = 2; + +protected: + //! classic 'compute' implementation, based on the regular DescriptorExtractor::computeImpl arguments & expected output + virtual void computeImpl(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const; + + const bool m_bOnlyUsingAbsThreshold; + const float m_fRelThreshold; + const size_t m_nThreshold; + cv::Mat m_oRefImage; +}; diff --git a/package_bgs/LBSP/LBSP_.cpp b/package_bgs/LBSP/LBSP_.cpp new file mode 100644 index 0000000..ff5c8e8 --- /dev/null +++ b/package_bgs/LBSP/LBSP_.cpp @@ -0,0 +1,334 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "LBSP_.h" + +LBSP_::LBSP_(size_t nThreshold) + : m_bOnlyUsingAbsThreshold(true) + , m_fRelThreshold(0) // unused + , m_nThreshold(nThreshold) + , m_oRefImage() {} + +LBSP_::LBSP_(float fRelThreshold, size_t nThresholdOffset) + : m_bOnlyUsingAbsThreshold(false) + , m_fRelThreshold(fRelThreshold) + , m_nThreshold(nThresholdOffset) + , m_oRefImage() { + CV_Assert(m_fRelThreshold >= 0); +} + +LBSP_::~LBSP_() {} + +void LBSP_::read(const cv::FileNode& /*fn*/) { + // ... = fn["..."]; +} + +void LBSP_::write(cv::FileStorage& /*fs*/) const { + //fs << "..." << ...; +} + +void LBSP_::setReference(const cv::Mat& img) { + CV_DbgAssert(img.empty() || img.type() == CV_8UC1 || img.type() == CV_8UC3); + m_oRefImage = img; +} + +int LBSP_::descriptorSize() const { + return DESC_SIZE; +} + +int LBSP_::descriptorType() const { + return CV_16U; +} + +bool LBSP_::isUsingRelThreshold() const { + return !m_bOnlyUsingAbsThreshold; +} + +float LBSP_::getRelThreshold() const { + return m_fRelThreshold; +} + +size_t LBSP_::getAbsThreshold() const { + return m_nThreshold; +} + +static inline void LBSP__computeImpl(const cv::Mat& oInputImg, + const cv::Mat& oRefImg, + const std::vector& voKeyPoints, + cv::Mat& oDesc, + size_t _t) { + CV_DbgAssert(oRefImg.empty() || (oRefImg.size == oInputImg.size && oRefImg.type() == oInputImg.type())); + CV_DbgAssert(oInputImg.type() == CV_8UC1 || oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP_::DESC_SIZE == 2); // @@@ also relies on a constant desc size + const size_t nChannels = (size_t)oInputImg.channels(); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* _data = oInputImg.data; + const uchar* _refdata = oRefImg.empty() ? oInputImg.data : oRefImg.data; + const size_t nKeyPoints = voKeyPoints.size(); + if (nChannels == 1) { + oDesc.create((int)nKeyPoints, 1, CV_16UC1); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar _ref = _refdata[_step_row*(_y)+_x]; + ushort& _res = oDesc.at((int)k); +#include "LBSP_16bits_dbcross_1ch.i" + } + } + else { //nChannels==3 + oDesc.create((int)nKeyPoints, 1, CV_16UC3); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar* _ref = _refdata + _step_row*(_y)+3 * (_x); + ushort* _res = ((ushort*)(oDesc.data + oDesc.step.p[0] * k)); +#include "LBSP_16bits_dbcross_3ch1t.i" + } + } +} + +static inline void LBSP__computeImpl(const cv::Mat& oInputImg, + const cv::Mat& oRefImg, + const std::vector& voKeyPoints, + cv::Mat& oDesc, + float fThreshold, + size_t nThresholdOffset) { + CV_DbgAssert(oRefImg.empty() || (oRefImg.size == oInputImg.size && oRefImg.type() == oInputImg.type())); + CV_DbgAssert(oInputImg.type() == CV_8UC1 || oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP_::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(fThreshold >= 0); + const size_t nChannels = (size_t)oInputImg.channels(); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* _data = oInputImg.data; + const uchar* _refdata = oRefImg.empty() ? oInputImg.data : oRefImg.data; + const size_t nKeyPoints = voKeyPoints.size(); + if (nChannels == 1) { + oDesc.create((int)nKeyPoints, 1, CV_16UC1); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar _ref = _refdata[_step_row*(_y)+_x]; + ushort& _res = oDesc.at((int)k); + const size_t _t = (size_t)(_ref*fThreshold) + nThresholdOffset; +#include "LBSP_16bits_dbcross_1ch.i" + } + } + else { //nChannels==3 + oDesc.create((int)nKeyPoints, 1, CV_16UC3); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar* _ref = _refdata + _step_row*(_y)+3 * (_x); + ushort* _res = ((ushort*)(oDesc.data + oDesc.step.p[0] * k)); + const size_t _t[3] = { (size_t)(_ref[0] * fThreshold) + nThresholdOffset,(size_t)(_ref[1] * fThreshold) + nThresholdOffset,(size_t)(_ref[2] * fThreshold) + nThresholdOffset }; +#include "LBSP_16bits_dbcross_3ch3t.i" + } + } +} + +static inline void LBSP__computeImpl2(const cv::Mat& oInputImg, + const cv::Mat& oRefImg, + const std::vector& voKeyPoints, + cv::Mat& oDesc, + size_t _t) { + CV_DbgAssert(oRefImg.empty() || (oRefImg.size == oInputImg.size && oRefImg.type() == oInputImg.type())); + CV_DbgAssert(oInputImg.type() == CV_8UC1 || oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP_::DESC_SIZE == 2); // @@@ also relies on a constant desc size + const size_t nChannels = (size_t)oInputImg.channels(); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* _data = oInputImg.data; + const uchar* _refdata = oRefImg.empty() ? oInputImg.data : oRefImg.data; + const size_t nKeyPoints = voKeyPoints.size(); + if (nChannels == 1) { + oDesc.create(oInputImg.size(), CV_16UC1); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar _ref = _refdata[_step_row*(_y)+_x]; + ushort& _res = oDesc.at(_y, _x); +#include "LBSP_16bits_dbcross_1ch.i" + } + } + else { //nChannels==3 + oDesc.create(oInputImg.size(), CV_16UC3); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar* _ref = _refdata + _step_row*(_y)+3 * (_x); + ushort* _res = ((ushort*)(oDesc.data + oDesc.step.p[0] * _y + oDesc.step.p[1] * _x)); +#include "LBSP_16bits_dbcross_3ch1t.i" + } + } +} + +static inline void LBSP__computeImpl2(const cv::Mat& oInputImg, + const cv::Mat& oRefImg, + const std::vector& voKeyPoints, + cv::Mat& oDesc, + float fThreshold, + size_t nThresholdOffset) { + CV_DbgAssert(oRefImg.empty() || (oRefImg.size == oInputImg.size && oRefImg.type() == oInputImg.type())); + CV_DbgAssert(oInputImg.type() == CV_8UC1 || oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP_::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(fThreshold >= 0); + const size_t nChannels = (size_t)oInputImg.channels(); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* _data = oInputImg.data; + const uchar* _refdata = oRefImg.empty() ? oInputImg.data : oRefImg.data; + const size_t nKeyPoints = voKeyPoints.size(); + if (nChannels == 1) { + oDesc.create(oInputImg.size(), CV_16UC1); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar _ref = _refdata[_step_row*(_y)+_x]; + ushort& _res = oDesc.at(_y, _x); + const size_t _t = (size_t)(_ref*fThreshold) + nThresholdOffset; +#include "LBSP_16bits_dbcross_1ch.i" + } + } + else { //nChannels==3 + oDesc.create(oInputImg.size(), CV_16UC3); + for (size_t k = 0; k < nKeyPoints; ++k) { + const int _x = (int)voKeyPoints[k].pt.x; + const int _y = (int)voKeyPoints[k].pt.y; + const uchar* _ref = _refdata + _step_row*(_y)+3 * (_x); + ushort* _res = ((ushort*)(oDesc.data + oDesc.step.p[0] * _y + oDesc.step.p[1] * _x)); + const size_t _t[3] = { (size_t)(_ref[0] * fThreshold) + nThresholdOffset,(size_t)(_ref[1] * fThreshold) + nThresholdOffset,(size_t)(_ref[2] * fThreshold) + nThresholdOffset }; +#include "LBSP_16bits_dbcross_3ch3t.i" + } + } +} + +void LBSP_::compute2(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const { + CV_Assert(!oImage.empty()); + cv::KeyPointsFilter::runByImageBorder(voKeypoints, oImage.size(), PATCH_SIZE / 2); + cv::KeyPointsFilter::runByKeypointSize(voKeypoints, std::numeric_limits::epsilon()); + if (voKeypoints.empty()) { + oDescriptors.release(); + return; + } + if (m_bOnlyUsingAbsThreshold) + LBSP__computeImpl2(oImage, m_oRefImage, voKeypoints, oDescriptors, m_nThreshold); + else + LBSP__computeImpl2(oImage, m_oRefImage, voKeypoints, oDescriptors, m_fRelThreshold, m_nThreshold); +} + +void LBSP_::compute2(const std::vector& voImageCollection, std::vector >& vvoPointCollection, std::vector& voDescCollection) const { + CV_Assert(voImageCollection.size() == vvoPointCollection.size()); + voDescCollection.resize(voImageCollection.size()); + for (size_t i = 0; i < voImageCollection.size(); i++) + compute2(voImageCollection[i], vvoPointCollection[i], voDescCollection[i]); +} + +void LBSP_::computeImpl(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const { + CV_Assert(!oImage.empty()); + cv::KeyPointsFilter::runByImageBorder(voKeypoints, oImage.size(), PATCH_SIZE / 2); + cv::KeyPointsFilter::runByKeypointSize(voKeypoints, std::numeric_limits::epsilon()); + if (voKeypoints.empty()) { + oDescriptors.release(); + return; + } + if (m_bOnlyUsingAbsThreshold) + LBSP__computeImpl(oImage, m_oRefImage, voKeypoints, oDescriptors, m_nThreshold); + else + LBSP__computeImpl(oImage, m_oRefImage, voKeypoints, oDescriptors, m_fRelThreshold, m_nThreshold); +} + +void LBSP_::reshapeDesc(cv::Size oSize, const std::vector& voKeypoints, const cv::Mat& oDescriptors, cv::Mat& oOutput) { + CV_DbgAssert(!voKeypoints.empty()); + CV_DbgAssert(!oDescriptors.empty() && oDescriptors.cols == 1); + CV_DbgAssert(oSize.width > 0 && oSize.height > 0); + CV_DbgAssert(DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(oDescriptors.type() == CV_16UC1 || oDescriptors.type() == CV_16UC3); + const size_t nChannels = (size_t)oDescriptors.channels(); + const size_t nKeyPoints = voKeypoints.size(); + if (nChannels == 1) { + oOutput.create(oSize, CV_16UC1); + oOutput = cv::Scalar_(0); + for (size_t k = 0; k < nKeyPoints; ++k) + oOutput.at(voKeypoints[k].pt) = oDescriptors.at((int)k); + } + else { //nChannels==3 + oOutput.create(oSize, CV_16UC3); + oOutput = cv::Scalar_(0, 0, 0); + for (size_t k = 0; k < nKeyPoints; ++k) { + ushort* output_ptr = (ushort*)(oOutput.data + oOutput.step.p[0] * (int)voKeypoints[k].pt.y); + const ushort* const desc_ptr = (ushort*)(oDescriptors.data + oDescriptors.step.p[0] * k); + const size_t idx = 3 * (int)voKeypoints[k].pt.x; + for (size_t n = 0; n < 3; ++n) + output_ptr[idx + n] = desc_ptr[n]; + } + } +} + +void LBSP_::calcDescImgDiff(const cv::Mat& oDesc1, const cv::Mat& oDesc2, cv::Mat& oOutput, bool bForceMergeChannels) { + CV_DbgAssert(oDesc1.size() == oDesc2.size() && oDesc1.type() == oDesc2.type()); + CV_DbgAssert(DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(oDesc1.type() == CV_16UC1 || oDesc1.type() == CV_16UC3); + CV_DbgAssert(CV_MAT_DEPTH(oDesc1.type()) == CV_16U); + CV_DbgAssert(DESC_SIZE * 8 <= UCHAR_MAX); + CV_DbgAssert(oDesc1.step.p[0] == oDesc2.step.p[0] && oDesc1.step.p[1] == oDesc2.step.p[1]); + const float fScaleFactor = (float)UCHAR_MAX / (DESC_SIZE * 8); + const size_t nChannels = CV_MAT_CN(oDesc1.type()); + const size_t _step_row = oDesc1.step.p[0]; + if (nChannels == 1) { + oOutput.create(oDesc1.size(), CV_8UC1); + oOutput = cv::Scalar(0); + for (int i = 0; i < oDesc1.rows; ++i) { + const size_t idx = _step_row*i; + const ushort* const desc1_ptr = (ushort*)(oDesc1.data + idx); + const ushort* const desc2_ptr = (ushort*)(oDesc2.data + idx); + for (int j = 0; j < oDesc1.cols; ++j) + oOutput.at(i, j) = (uchar)(fScaleFactor*hdist(desc1_ptr[j], desc2_ptr[j])); + } + } + else { //nChannels==3 + if (bForceMergeChannels) + oOutput.create(oDesc1.size(), CV_8UC1); + else + oOutput.create(oDesc1.size(), CV_8UC3); + oOutput = cv::Scalar::all(0); + for (int i = 0; i < oDesc1.rows; ++i) { + const size_t idx = _step_row*i; + const ushort* const desc1_ptr = (ushort*)(oDesc1.data + idx); + const ushort* const desc2_ptr = (ushort*)(oDesc2.data + idx); + uchar* output_ptr = oOutput.data + oOutput.step.p[0] * i; + for (int j = 0; j < oDesc1.cols; ++j) { + for (size_t n = 0; n < 3; ++n) { + const size_t idx2 = 3 * j + n; + if (bForceMergeChannels) + output_ptr[j] += (uchar)((fScaleFactor*hdist(desc1_ptr[idx2], desc2_ptr[idx2])) / 3); + else + output_ptr[idx2] = (uchar)(fScaleFactor*hdist(desc1_ptr[idx2], desc2_ptr[idx2])); + } + } + } + } +} + +void LBSP_::validateKeyPoints(std::vector& voKeypoints, cv::Size oImgSize) { + cv::KeyPointsFilter::runByImageBorder(voKeypoints, oImgSize, PATCH_SIZE / 2); +} + +void LBSP_::validateROI(cv::Mat& oROI) { + CV_Assert(!oROI.empty() && oROI.type() == CV_8UC1); + cv::Mat oROI_new(oROI.size(), CV_8UC1, cv::Scalar_(0)); + const size_t nBorderSize = PATCH_SIZE / 2; + const cv::Rect nROI_inner(nBorderSize, nBorderSize, oROI.cols - nBorderSize * 2, oROI.rows - nBorderSize * 2); + cv::Mat(oROI, nROI_inner).copyTo(cv::Mat(oROI_new, nROI_inner)); + oROI = oROI_new; +} diff --git a/package_bgs/LBSP/LBSP_.h b/package_bgs/LBSP/LBSP_.h new file mode 100644 index 0000000..819174a --- /dev/null +++ b/package_bgs/LBSP/LBSP_.h @@ -0,0 +1,134 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include +#include +#include +#include "DistanceUtils.h" + +/*! + Local Binary Similarity Pattern (LBSP) feature extractor + + Note 1: both grayscale and RGB/BGR images may be used with this extractor. + Note 2: using LBSP_::compute2(...) is logically equivalent to using LBSP_::compute(...) followed by LBSP_::reshapeDesc(...). + + For more details on the different parameters, see G.-A. Bilodeau et al, "Change Detection in Feature Space Using Local + Binary Similarity Patterns", in CRV 2013. + + This algorithm is currently NOT thread-safe. + */ +class LBSP_ : public cv::Feature2D { +public: + //! constructor 1, threshold = absolute intensity 'similarity' threshold used when computing comparisons + LBSP_(size_t nThreshold); + //! constructor 2, threshold = relative intensity 'similarity' threshold used when computing comparisons + LBSP_(float fRelThreshold, size_t nThresholdOffset = 0); + //! default destructor + virtual ~LBSP_(); + //! loads extractor params from the specified file node @@@@ not impl + virtual void read(const cv::FileNode&); + //! writes extractor params to the specified file storage @@@@ not impl + virtual void write(cv::FileStorage&) const; + //! sets the 'reference' image to be used for inter-frame comparisons (note: if no image is set or if the image is empty, the algorithm will default back to intra-frame comparisons) + virtual void setReference(const cv::Mat&); + //! returns the current descriptor size, in bytes + virtual int descriptorSize() const; + //! returns the current descriptor data type + virtual int descriptorType() const; + //! returns whether this extractor is using a relative threshold or not + virtual bool isUsingRelThreshold() const; + //! returns the current relative threshold used for comparisons (-1 = invalid/not used) + virtual float getRelThreshold() const; + //! returns the current absolute threshold used for comparisons (-1 = invalid/not used) + virtual size_t getAbsThreshold() const; + + //! similar to DescriptorExtractor::compute(const cv::Mat& image, ...), but in this case, the descriptors matrix has the same shape as the input matrix (possibly slower, but the result can be displayed) + void compute2(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const; + //! batch version of LBSP_::compute2(const cv::Mat& image, ...), also similar to DescriptorExtractor::compute(const std::vector& imageCollection, ...) + void compute2(const std::vector& voImageCollection, std::vector >& vvoPointCollection, std::vector& voDescCollection) const; + + //! utility function, shortcut/lightweight/direct single-point LBSP computation function for extra flexibility (1-channel version) + inline static void computeGrayscaleDescriptor(const cv::Mat& oInputImg, const uchar _ref, const int _x, const int _y, const size_t _t, ushort& _res) { + CV_DbgAssert(!oInputImg.empty()); + CV_DbgAssert(oInputImg.type() == CV_8UC1); + CV_DbgAssert(LBSP_::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(_x >= (int)LBSP_::PATCH_SIZE / 2 && _y >= (int)LBSP_::PATCH_SIZE / 2); + CV_DbgAssert(_x < oInputImg.cols - (int)LBSP_::PATCH_SIZE / 2 && _y < oInputImg.rows - (int)LBSP_::PATCH_SIZE / 2); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* const _data = oInputImg.data; +#include "LBSP_16bits_dbcross_1ch.i" + } + + //! utility function, shortcut/lightweight/direct single-point LBSP computation function for extra flexibility (3-channels version) + inline static void computeRGBDescriptor(const cv::Mat& oInputImg, const uchar* const _ref, const int _x, const int _y, const size_t* const _t, ushort* _res) { + CV_DbgAssert(!oInputImg.empty()); + CV_DbgAssert(oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP_::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(_x >= (int)LBSP_::PATCH_SIZE / 2 && _y >= (int)LBSP_::PATCH_SIZE / 2); + CV_DbgAssert(_x < oInputImg.cols - (int)LBSP_::PATCH_SIZE / 2 && _y < oInputImg.rows - (int)LBSP_::PATCH_SIZE / 2); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* const _data = oInputImg.data; +#include "LBSP_16bits_dbcross_3ch3t.i" + } + + //! utility function, shortcut/lightweight/direct single-point LBSP computation function for extra flexibility (3-channels version) + inline static void computeRGBDescriptor(const cv::Mat& oInputImg, const uchar* const _ref, const int _x, const int _y, const size_t _t, ushort* _res) { + CV_DbgAssert(!oInputImg.empty()); + CV_DbgAssert(oInputImg.type() == CV_8UC3); + CV_DbgAssert(LBSP_::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(_x >= (int)LBSP_::PATCH_SIZE / 2 && _y >= (int)LBSP_::PATCH_SIZE / 2); + CV_DbgAssert(_x < oInputImg.cols - (int)LBSP_::PATCH_SIZE / 2 && _y < oInputImg.rows - (int)LBSP_::PATCH_SIZE / 2); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* const _data = oInputImg.data; +#include "LBSP_16bits_dbcross_3ch1t.i" + } + + //! utility function, shortcut/lightweight/direct single-point LBSP computation function for extra flexibility (1-channel-RGB version) + inline static void computeSingleRGBDescriptor(const cv::Mat& oInputImg, const uchar _ref, const int _x, const int _y, const size_t _c, const size_t _t, ushort& _res) { + CV_DbgAssert(!oInputImg.empty()); + CV_DbgAssert(oInputImg.type() == CV_8UC3 && _c < 3); + CV_DbgAssert(LBSP_::DESC_SIZE == 2); // @@@ also relies on a constant desc size + CV_DbgAssert(_x >= (int)LBSP_::PATCH_SIZE / 2 && _y >= (int)LBSP_::PATCH_SIZE / 2); + CV_DbgAssert(_x < oInputImg.cols - (int)LBSP_::PATCH_SIZE / 2 && _y < oInputImg.rows - (int)LBSP_::PATCH_SIZE / 2); + const size_t _step_row = oInputImg.step.p[0]; + const uchar* const _data = oInputImg.data; +#include "LBSP_16bits_dbcross_s3ch.i" + } + + //! utility function, used to reshape a descriptors matrix to its input image size via their keypoint locations + static void reshapeDesc(cv::Size oSize, const std::vector& voKeypoints, const cv::Mat& oDescriptors, cv::Mat& oOutput); + //! utility function, used to illustrate the difference between two descriptor images + static void calcDescImgDiff(const cv::Mat& oDesc1, const cv::Mat& oDesc2, cv::Mat& oOutput, bool bForceMergeChannels = false); + //! utility function, used to filter out bad keypoints that would trigger out of bounds error because they're too close to the image border + static void validateKeyPoints(std::vector& voKeypoints, cv::Size oImgSize); + //! utility function, used to filter out bad pixels in a ROI that would trigger out of bounds error because they're too close to the image border + static void validateROI(cv::Mat& oROI); + //! utility, specifies the pixel size of the pattern used (width and height) + static const size_t PATCH_SIZE = 5; + //! utility, specifies the number of bytes per descriptor (should be the same as calling 'descriptorSize()') + static const size_t DESC_SIZE = 2; + +protected: + //! classic 'compute' implementation, based on the regular DescriptorExtractor::computeImpl arguments & expected output + virtual void computeImpl(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const; + + const bool m_bOnlyUsingAbsThreshold; + const float m_fRelThreshold; + const size_t m_nThreshold; + cv::Mat m_oRefImage; +}; diff --git a/package_bgs/pl/LBSP_16bits_dbcross_1ch.i b/package_bgs/LBSP/LBSP_16bits_dbcross_1ch.i similarity index 100% rename from package_bgs/pl/LBSP_16bits_dbcross_1ch.i rename to package_bgs/LBSP/LBSP_16bits_dbcross_1ch.i diff --git a/package_bgs/pl/LBSP_16bits_dbcross_3ch1t.i b/package_bgs/LBSP/LBSP_16bits_dbcross_3ch1t.i similarity index 100% rename from package_bgs/pl/LBSP_16bits_dbcross_3ch1t.i rename to package_bgs/LBSP/LBSP_16bits_dbcross_3ch1t.i diff --git a/package_bgs/pl/LBSP_16bits_dbcross_3ch3t.i b/package_bgs/LBSP/LBSP_16bits_dbcross_3ch3t.i similarity index 100% rename from package_bgs/pl/LBSP_16bits_dbcross_3ch3t.i rename to package_bgs/LBSP/LBSP_16bits_dbcross_3ch3t.i diff --git a/package_bgs/pl/LBSP_16bits_dbcross_s3ch.i b/package_bgs/LBSP/LBSP_16bits_dbcross_s3ch.i similarity index 100% rename from package_bgs/pl/LBSP_16bits_dbcross_s3ch.i rename to package_bgs/LBSP/LBSP_16bits_dbcross_s3ch.i diff --git a/package_bgs/LBSP/RandUtils.h b/package_bgs/LBSP/RandUtils.h new file mode 100644 index 0000000..f676ca0 --- /dev/null +++ b/package_bgs/LBSP/RandUtils.h @@ -0,0 +1,112 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +/*// gaussian 3x3 pattern, based on 'floor(fspecial('gaussian', 3, 1)*256)' +static const int s_nSamplesInitPatternWidth = 3; +static const int s_nSamplesInitPatternHeight = 3; +static const int s_nSamplesInitPatternTot = 256; +static const int s_anSamplesInitPattern[s_nSamplesInitPatternHeight][s_nSamplesInitPatternWidth] = { + {19, 32, 19,}, + {32, 52, 32,}, + {19, 32, 19,}, +};*/ + +// gaussian 7x7 pattern, based on 'floor(fspecial('gaussian',7,2)*512)' +static const int s_nSamplesInitPatternWidth = 7; +static const int s_nSamplesInitPatternHeight = 7; +static const int s_nSamplesInitPatternTot = 512; +static const int s_anSamplesInitPattern[s_nSamplesInitPatternHeight][s_nSamplesInitPatternWidth] = { + {2, 4, 6, 7, 6, 4, 2,}, + {4, 8, 12, 14, 12, 8, 4,}, + {6, 12, 21, 25, 21, 12, 6,}, + {7, 14, 25, 28, 25, 14, 7,}, + {6, 12, 21, 25, 21, 12, 6,}, + {4, 8, 12, 14, 12, 8, 4,}, + {2, 4, 6, 7, 6, 4, 2,}, +}; + +//! returns a random init/sampling position for the specified pixel position; also guards against out-of-bounds values via image/border size check. +static inline void getRandSamplePosition(int& x_sample, int& y_sample, const int x_orig, const int y_orig, const int border, const cv::Size& imgsize) { + int r = 1+rand()%s_nSamplesInitPatternTot; + for(x_sample=0; x_sample=imgsize.width-border) + x_sample = imgsize.width-border-1; + if(y_sample=imgsize.height-border) + y_sample = imgsize.height-border-1; +} + +// simple 8-connected (3x3) neighbors pattern +static const int s_anNeighborPatternSize_3x3 = 8; +static const int s_anNeighborPattern_3x3[8][2] = { + {-1, 1}, { 0, 1}, { 1, 1}, + {-1, 0}, { 1, 0}, + {-1,-1}, { 0,-1}, { 1,-1}, +}; + +//! returns a random neighbor position for the specified pixel position; also guards against out-of-bounds values via image/border size check. +static inline void getRandNeighborPosition_3x3(int& x_neighbor, int& y_neighbor, const int x_orig, const int y_orig, const int border, const cv::Size& imgsize) { + int r = rand()%s_anNeighborPatternSize_3x3; + x_neighbor = x_orig+s_anNeighborPattern_3x3[r][0]; + y_neighbor = y_orig+s_anNeighborPattern_3x3[r][1]; + if(x_neighbor=imgsize.width-border) + x_neighbor = imgsize.width-border-1; + if(y_neighbor=imgsize.height-border) + y_neighbor = imgsize.height-border-1; +} + +// 5x5 neighbors pattern +static const int s_anNeighborPatternSize_5x5 = 24; +static const int s_anNeighborPattern_5x5[24][2] = { + {-2, 2}, {-1, 2}, { 0, 2}, { 1, 2}, { 2, 2}, + {-2, 1}, {-1, 1}, { 0, 1}, { 1, 1}, { 2, 1}, + {-2, 0}, {-1, 0}, { 1, 0}, { 2, 0}, + {-2,-1}, {-1,-1}, { 0,-1}, { 1,-1}, { 2,-1}, + {-2,-2}, {-1,-2}, { 0,-2}, { 1,-2}, { 2,-2}, +}; + +//! returns a random neighbor position for the specified pixel position; also guards against out-of-bounds values via image/border size check. +static inline void getRandNeighborPosition_5x5(int& x_neighbor, int& y_neighbor, const int x_orig, const int y_orig, const int border, const cv::Size& imgsize) { + int r = rand()%s_anNeighborPatternSize_5x5; + x_neighbor = x_orig+s_anNeighborPattern_5x5[r][0]; + y_neighbor = y_orig+s_anNeighborPattern_5x5[r][1]; + if(x_neighbor=imgsize.width-border) + x_neighbor = imgsize.width-border-1; + if(y_neighbor=imgsize.height-border) + y_neighbor = imgsize.height-border-1; +} diff --git a/package_bgs/lb/LBSimpleGaussian.cpp b/package_bgs/LBSimpleGaussian.cpp similarity index 61% rename from package_bgs/lb/LBSimpleGaussian.cpp rename to package_bgs/LBSimpleGaussian.cpp index f0ce6fc..9ce071c 100644 --- a/package_bgs/lb/LBSimpleGaussian.cpp +++ b/package_bgs/LBSimpleGaussian.cpp @@ -16,9 +16,13 @@ along with BGSLibrary. If not, see . */ #include "LBSimpleGaussian.h" -LBSimpleGaussian::LBSimpleGaussian() : firstTime(true), showOutput(true), sensitivity(66), noiseVariance(162), learningRate(18) +using namespace bgslibrary::algorithms; + +LBSimpleGaussian::LBSimpleGaussian() : + sensitivity(66), noiseVariance(162), learningRate(18) { std::cout << "LBSimpleGaussian()" << std::endl; + setup("./config/LBSimpleGaussian.xml"); } LBSimpleGaussian::~LBSimpleGaussian() @@ -29,55 +33,47 @@ LBSimpleGaussian::~LBSimpleGaussian() void LBSimpleGaussian::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; + init(img_input, img_output, img_bgmodel); - loadConfig(); - IplImage *frame = new IplImage(img_input); - - if(firstTime) - { - saveConfig(); + if (firstTime) + { int w = cvGetSize(frame).width; int h = cvGetSize(frame).height; - m_pBGModel = new BGModelGauss(w,h); + m_pBGModel = new BGModelGauss(w, h); m_pBGModel->InitModel(frame); } - - m_pBGModel->setBGModelParameter(0,sensitivity); - m_pBGModel->setBGModelParameter(1,noiseVariance); - m_pBGModel->setBGModelParameter(2,learningRate); + + m_pBGModel->setBGModelParameter(0, sensitivity); + m_pBGModel->setBGModelParameter(1, noiseVariance); + m_pBGModel->setBGModelParameter(2, learningRate); m_pBGModel->UpdateModel(frame); - img_foreground = cv::Mat(m_pBGModel->GetFG()); - img_background = cv::Mat(m_pBGModel->GetBG()); - - if(showOutput) + img_foreground = cv::cvarrToMat(m_pBGModel->GetFG()); + img_background = cv::cvarrToMat(m_pBGModel->GetBG()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) { cv::imshow("SG Mask", img_foreground); cv::imshow("SG Model", img_background); } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); - + delete frame; - + firstTime = false; } -//void LBSimpleGaussian::finish(void) -//{ -// delete m_pBGModel; -//} - void LBSimpleGaussian::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LBSimpleGaussian.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "sensitivity", sensitivity); cvWriteInt(fs, "noiseVariance", noiseVariance); @@ -89,12 +85,12 @@ void LBSimpleGaussian::saveConfig() void LBSimpleGaussian::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/LBSimpleGaussian.xml", 0, CV_STORAGE_READ); - - sensitivity = cvReadIntByName(fs, 0, "sensitivity", 66); - noiseVariance = cvReadIntByName(fs, 0, "noiseVariance", 162); - learningRate = cvReadIntByName(fs, 0, "learningRate", 18); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + sensitivity = cvReadIntByName(fs, nullptr, "sensitivity", 66); + noiseVariance = cvReadIntByName(fs, nullptr, "noiseVariance", 162); + learningRate = cvReadIntByName(fs, nullptr, "learningRate", 18); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); -} \ No newline at end of file +} diff --git a/package_bgs/lb/LBSimpleGaussian.h b/package_bgs/LBSimpleGaussian.h similarity index 57% rename from package_bgs/lb/LBSimpleGaussian.h rename to package_bgs/LBSimpleGaussian.h index bfefd3e..5c82923 100644 --- a/package_bgs/lb/LBSimpleGaussian.h +++ b/package_bgs/LBSimpleGaussian.h @@ -16,39 +16,33 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "BGModelGauss.h" - -#include "../IBGS.h" +#include "IBGS.h" +#include "lb/BGModelGauss.h" using namespace lb_library; using namespace lb_library::SimpleGaussian; -class LBSimpleGaussian : public IBGS +namespace bgslibrary { -private: - bool firstTime; - bool showOutput; - - BGModel* m_pBGModel; - int sensitivity; - int noiseVariance; - int learningRate; - - cv::Mat img_foreground; - cv::Mat img_background; - -public: - LBSimpleGaussian(); - ~LBSimpleGaussian(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - //void finish(void); - -private: - void saveConfig(); - void loadConfig(); -}; \ No newline at end of file + namespace algorithms + { + class LBSimpleGaussian : public IBGS + { + private: + BGModel* m_pBGModel; + int sensitivity; + int noiseVariance; + int learningRate; + + public: + LBSimpleGaussian(); + ~LBSimpleGaussian(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/LOBSTER.cpp b/package_bgs/LOBSTER.cpp new file mode 100644 index 0000000..dc48a63 --- /dev/null +++ b/package_bgs/LOBSTER.cpp @@ -0,0 +1,98 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "LOBSTER.h" + +using namespace bgslibrary::algorithms; + +LOBSTER::LOBSTER() : + pLOBSTER(nullptr), + fRelLBSPThreshold(BGSLOBSTER_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD), + nLBSPThresholdOffset(BGSLOBSTER_DEFAULT_LBSP_OFFSET_SIMILARITY_THRESHOLD), + nDescDistThreshold(BGSLOBSTER_DEFAULT_DESC_DIST_THRESHOLD), + nColorDistThreshold(BGSLOBSTER_DEFAULT_COLOR_DIST_THRESHOLD), + nBGSamples(BGSLOBSTER_DEFAULT_NB_BG_SAMPLES), + nRequiredBGSamples(BGSLOBSTER_DEFAULT_REQUIRED_NB_BG_SAMPLES) +{ + std::cout << "LOBSTER()" << std::endl; + setup("./config/LOBSTER.xml"); +} + +LOBSTER::~LOBSTER() +{ + if (pLOBSTER) + delete pLOBSTER; + std::cout << "~LOBSTER()" << std::endl; +} + +void LOBSTER::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + if (firstTime) + { + pLOBSTER = new BackgroundSubtractorLOBSTER( + fRelLBSPThreshold, nLBSPThresholdOffset, nDescDistThreshold, + nColorDistThreshold, nBGSamples, nRequiredBGSamples); + + pLOBSTER->initialize(img_input, cv::Mat(img_input.size(), CV_8UC1, cv::Scalar_(255))); + firstTime = false; + } + + pLOBSTER->apply(img_input, img_foreground); + pLOBSTER->getBackgroundImage(img_background); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + { + imshow("LOBSTER FG", img_foreground); + imshow("LOBSTER BG", img_background); + } +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); +} + +void LOBSTER::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteReal(fs, "fRelLBSPThreshold", fRelLBSPThreshold); + cvWriteInt(fs, "nLBSPThresholdOffset", nLBSPThresholdOffset); + cvWriteInt(fs, "nDescDistThreshold", nDescDistThreshold); + cvWriteInt(fs, "nColorDistThreshold", nColorDistThreshold); + cvWriteInt(fs, "nBGSamples", nBGSamples); + cvWriteInt(fs, "nRequiredBGSamples", nRequiredBGSamples); + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void LOBSTER::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + fRelLBSPThreshold = cvReadRealByName(fs, nullptr, "fRelLBSPThreshold", BGSLOBSTER_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD); + nLBSPThresholdOffset = cvReadIntByName(fs, nullptr, "nLBSPThresholdOffset", BGSLOBSTER_DEFAULT_LBSP_OFFSET_SIMILARITY_THRESHOLD); + nDescDistThreshold = cvReadIntByName(fs, nullptr, "nDescDistThreshold", BGSLOBSTER_DEFAULT_DESC_DIST_THRESHOLD); + nColorDistThreshold = cvReadIntByName(fs, nullptr, "nColorDistThreshold", BGSLOBSTER_DEFAULT_COLOR_DIST_THRESHOLD); + nBGSamples = cvReadIntByName(fs, nullptr, "nBGSamples", BGSLOBSTER_DEFAULT_NB_BG_SAMPLES); + nRequiredBGSamples = cvReadIntByName(fs, nullptr, "nRequiredBGSamples", BGSLOBSTER_DEFAULT_REQUIRED_NB_BG_SAMPLES); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + + cvReleaseFileStorage(&fs); +} diff --git a/package_bgs/LOBSTER.h b/package_bgs/LOBSTER.h new file mode 100644 index 0000000..41ba882 --- /dev/null +++ b/package_bgs/LOBSTER.h @@ -0,0 +1,49 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "LBSP/BackgroundSubtractorLOBSTER.h" + +namespace bgslibrary +{ + namespace algorithms + { + class LOBSTER : public IBGS + { + private: + BackgroundSubtractorLOBSTER* pLOBSTER; + + float fRelLBSPThreshold; + size_t nLBSPThresholdOffset; + size_t nDescDistThreshold; + size_t nColorDistThreshold; + size_t nBGSamples; + size_t nRequiredBGSamples; + + public: + LOBSTER(); + ~LOBSTER(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/MixtureOfGaussianV1BGS.cpp b/package_bgs/MixtureOfGaussianV1.cpp similarity index 53% rename from package_bgs/MixtureOfGaussianV1BGS.cpp rename to package_bgs/MixtureOfGaussianV1.cpp index 51d41eb..e56609a 100644 --- a/package_bgs/MixtureOfGaussianV1BGS.cpp +++ b/package_bgs/MixtureOfGaussianV1.cpp @@ -14,27 +14,27 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "MixtureOfGaussianV1BGS.h" +#include "MixtureOfGaussianV1.h" -MixtureOfGaussianV1BGS::MixtureOfGaussianV1BGS() : firstTime(true), alpha(0.05), enableThreshold(true), threshold(15), showOutput(true) +#if CV_MAJOR_VERSION == 2 + +using namespace bgslibrary::algorithms; + +MixtureOfGaussianV1::MixtureOfGaussianV1() : + alpha(0.05), enableThreshold(true), threshold(15) { - std::cout << "MixtureOfGaussianV1BGS()" << std::endl; + std::cout << "MixtureOfGaussianV1()" << std::endl; + setup("./config/MixtureOfGaussianV1.xml"); } -MixtureOfGaussianV1BGS::~MixtureOfGaussianV1BGS() +MixtureOfGaussianV1::~MixtureOfGaussianV1() { - std::cout << "~MixtureOfGaussianV1BGS()" << std::endl; + std::cout << "~MixtureOfGaussianV1()" << std::endl; } -void MixtureOfGaussianV1BGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void MixtureOfGaussianV1::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); + init(img_input, img_output, img_bgmodel); //------------------------------------------------------------------ // BackgroundSubtractorMOG @@ -43,26 +43,30 @@ void MixtureOfGaussianV1BGS::process(const cv::Mat &img_input, cv::Mat &img_outp // Gaussian Mixture-based Backbround/Foreground Segmentation Algorithm. // // The class implements the algorithm described in: - // P. KadewTraKuPong and R. Bowden, - // An improved adaptive background mixture model for real-time tracking with shadow detection, + // P. KadewTraKuPong and R. Bowden, + // An improved adaptive background mixture model for real-time tracking with shadow detection, // Proc. 2nd European Workshp on Advanced Video-Based Surveillance Systems, 2001 //------------------------------------------------------------------ mog(img_input, img_foreground, alpha); - cv::Mat img_background; mog.getBackgroundImage(img_background); - if(enableThreshold) + if (enableThreshold) cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY); - if(showOutput) + if (img_foreground.empty()) + img_foreground = cv::Mat::zeros(img_input.size(), img_input.type()); + + if (img_background.empty()) + img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) { - if (!img_foreground.empty()) - cv::imshow("GMM FG (KadewTraKuPong&Bowden)", img_foreground); - - if (!img_background.empty()) - cv::imshow("GMM BG (KadewTraKuPong&Bowden)", img_background); + cv::imshow("GMM FG (KadewTraKuPong&Bowden)", img_foreground); + cv::imshow("GMM BG (KadewTraKuPong&Bowden)", img_background); } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); @@ -70,9 +74,9 @@ void MixtureOfGaussianV1BGS::process(const cv::Mat &img_input, cv::Mat &img_outp firstTime = false; } -void MixtureOfGaussianV1BGS::saveConfig() +void MixtureOfGaussianV1::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/MixtureOfGaussianV1BGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteReal(fs, "alpha", alpha); cvWriteInt(fs, "enableThreshold", enableThreshold); @@ -82,14 +86,15 @@ void MixtureOfGaussianV1BGS::saveConfig() cvReleaseFileStorage(&fs); } -void MixtureOfGaussianV1BGS::loadConfig() +void MixtureOfGaussianV1::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/MixtureOfGaussianV1BGS.xml", 0, CV_STORAGE_READ); - - alpha = cvReadRealByName(fs, 0, "alpha", 0.05); - enableThreshold = cvReadIntByName(fs, 0, "enableThreshold", true); - threshold = cvReadIntByName(fs, 0, "threshold", 15); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.05); + enableThreshold = cvReadIntByName(fs, nullptr, "enableThreshold", true); + threshold = cvReadIntByName(fs, nullptr, "threshold", 15); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } +#endif diff --git a/package_bgs/MixtureOfGaussianV2BGS.h b/package_bgs/MixtureOfGaussianV1.h similarity index 59% rename from package_bgs/MixtureOfGaussianV2BGS.h rename to package_bgs/MixtureOfGaussianV1.h index a14ff0b..e18dbdb 100644 --- a/package_bgs/MixtureOfGaussianV2BGS.h +++ b/package_bgs/MixtureOfGaussianV1.h @@ -16,32 +16,38 @@ along with BGSLibrary. If not, see . */ #pragma once +#include "opencv2/core/version.hpp" +#if CV_MAJOR_VERSION == 2 + #include #include - #include #include "IBGS.h" -class MixtureOfGaussianV2BGS : public IBGS +namespace bgslibrary { -private: - bool firstTime; - cv::BackgroundSubtractorMOG2 mog; - cv::Mat img_foreground; - double alpha; - bool enableThreshold; - int threshold; - bool showOutput; - -public: - MixtureOfGaussianV2BGS(); - ~MixtureOfGaussianV2BGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class MixtureOfGaussianV1 : public IBGS + { + private: + cv::BackgroundSubtractorMOG mog; + double alpha; + bool enableThreshold; + int threshold; + + public: + MixtureOfGaussianV1(); + ~MixtureOfGaussianV1(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} + +#endif diff --git a/package_bgs/MixtureOfGaussianV2BGS.cpp b/package_bgs/MixtureOfGaussianV2.cpp similarity index 60% rename from package_bgs/MixtureOfGaussianV2BGS.cpp rename to package_bgs/MixtureOfGaussianV2.cpp index 5ce33a3..085b1a9 100644 --- a/package_bgs/MixtureOfGaussianV2BGS.cpp +++ b/package_bgs/MixtureOfGaussianV2.cpp @@ -14,27 +14,31 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "MixtureOfGaussianV2BGS.h" +#include "MixtureOfGaussianV2.h" -MixtureOfGaussianV2BGS::MixtureOfGaussianV2BGS() : firstTime(true), alpha(0.05), enableThreshold(true), threshold(15), showOutput(true) +using namespace bgslibrary::algorithms; + +MixtureOfGaussianV2::MixtureOfGaussianV2() : + alpha(0.05), enableThreshold(true), threshold(15) { - std::cout << "MixtureOfGaussianV2BGS()" << std::endl; + std::cout << "MixtureOfGaussianV2()" << std::endl; + setup("./config/MixtureOfGaussianV2.xml"); } -MixtureOfGaussianV2BGS::~MixtureOfGaussianV2BGS() +MixtureOfGaussianV2::~MixtureOfGaussianV2() { - std::cout << "~MixtureOfGaussianV2BGS()" << std::endl; + std::cout << "~MixtureOfGaussianV2()" << std::endl; } -void MixtureOfGaussianV2BGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void MixtureOfGaussianV2::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); + init(img_input, img_output, img_bgmodel); - if(firstTime) - saveConfig(); + if (firstTime) { +#if CV_MAJOR_VERSION == 3 + mog = cv::createBackgroundSubtractorMOG2(); +#endif + } //------------------------------------------------------------------ // BackgroundSubtractorMOG2 @@ -43,29 +47,34 @@ void MixtureOfGaussianV2BGS::process(const cv::Mat &img_input, cv::Mat &img_outp // Gaussian Mixture-based Backbround/Foreground Segmentation Algorithm. // // The class implements the Gaussian mixture model background subtraction described in: - // (1) Z.Zivkovic, Improved adaptive Gausian mixture model for background subtraction, International Conference Pattern Recognition, UK, August, 2004, + // (1) Z.Zivkovic, Improved adaptive Gausian mixture model for background subtraction, International Conference Pattern Recognition, UK, August, 2004, // The code is very fast and performs also shadow detection. Number of Gausssian components is adapted per pixel. // - // (2) Z.Zivkovic, F. van der Heijden, Efficient Adaptive Density Estimation per Image Pixel for the Task of Background Subtraction, - // Pattern Recognition Letters, vol. 27, no. 7, pages 773-780, 2006. - // The algorithm similar to the standard Stauffer&Grimson algorithm with additional selection of the number of the Gaussian components based on: - // Z.Zivkovic, F.van der Heijden, Recursive unsupervised learning of finite mixture models, IEEE Trans. on Pattern Analysis and Machine Intelligence, + // (2) Z.Zivkovic, F. van der Heijden, Efficient Adaptive Density Estimation per Image Pixel for the Task of Background Subtraction, + // Pattern Recognition Letters, vol. 27, no. 7, pages 773-780, 2006. + // The algorithm similar to the standard Stauffer&Grimson algorithm with additional selection of the number of the Gaussian components based on: + // Z.Zivkovic, F.van der Heijden, Recursive unsupervised learning of finite mixture models, IEEE Trans. on Pattern Analysis and Machine Intelligence, // vol.26, no.5, pages 651-656, 2004. //------------------------------------------------------------------ +#if CV_MAJOR_VERSION == 2 mog(img_input, img_foreground, alpha); - - cv::Mat img_background; mog.getBackgroundImage(img_background); +#elif CV_MAJOR_VERSION == 3 + mog->apply(img_input, img_foreground, alpha); + mog->getBackgroundImage(img_background); +#endif - if(enableThreshold) + if (enableThreshold) cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY); - if(showOutput) +#ifndef MEX_COMPILE_FLAG + if (showOutput) { - cv::imshow("GMM (Zivkovic&Heijden)", img_foreground); - cv::imshow("GMM BKG (Zivkovic&Heijden)", img_background); + cv::imshow("GMM FG (Zivkovic&Heijden)", img_foreground); + cv::imshow("GMM BG (Zivkovic&Heijden)", img_background); } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); @@ -73,9 +82,9 @@ void MixtureOfGaussianV2BGS::process(const cv::Mat &img_input, cv::Mat &img_outp firstTime = false; } -void MixtureOfGaussianV2BGS::saveConfig() +void MixtureOfGaussianV2::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/MixtureOfGaussianV2BGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteReal(fs, "alpha", alpha); cvWriteInt(fs, "enableThreshold", enableThreshold); @@ -85,14 +94,14 @@ void MixtureOfGaussianV2BGS::saveConfig() cvReleaseFileStorage(&fs); } -void MixtureOfGaussianV2BGS::loadConfig() +void MixtureOfGaussianV2::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/MixtureOfGaussianV2BGS.xml", 0, CV_STORAGE_READ); - - alpha = cvReadRealByName(fs, 0, "alpha", 0.05); - enableThreshold = cvReadIntByName(fs, 0, "enableThreshold", true); - threshold = cvReadIntByName(fs, 0, "threshold", 15); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.05); + enableThreshold = cvReadIntByName(fs, nullptr, "enableThreshold", true); + threshold = cvReadIntByName(fs, nullptr, "threshold", 15); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/MixtureOfGaussianV1BGS.h b/package_bgs/MixtureOfGaussianV2.h similarity index 57% rename from package_bgs/MixtureOfGaussianV1BGS.h rename to package_bgs/MixtureOfGaussianV2.h index f735a13..edc8add 100644 --- a/package_bgs/MixtureOfGaussianV1BGS.h +++ b/package_bgs/MixtureOfGaussianV2.h @@ -18,30 +18,35 @@ along with BGSLibrary. If not, see . #include #include - #include #include "IBGS.h" -class MixtureOfGaussianV1BGS : public IBGS +namespace bgslibrary { -private: - bool firstTime; - cv::BackgroundSubtractorMOG mog; - cv::Mat img_foreground; - double alpha; - bool enableThreshold; - int threshold; - bool showOutput; - -public: - MixtureOfGaussianV1BGS(); - ~MixtureOfGaussianV1BGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class MixtureOfGaussianV2 : public IBGS + { + private: +#if CV_MAJOR_VERSION == 2 + cv::BackgroundSubtractorMOG2 mog; +#elif CV_MAJOR_VERSION == 3 + cv::Ptr mog; +#endif + double alpha; + bool enableThreshold; + int threshold; + + public: + MixtureOfGaussianV2(); + ~MixtureOfGaussianV2(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/sjn/SJN_MultiCueBGS.cpp b/package_bgs/MultiCue.cpp similarity index 83% rename from package_bgs/sjn/SJN_MultiCueBGS.cpp rename to package_bgs/MultiCue.cpp index 83db6ec..7717842 100644 --- a/package_bgs/sjn/SJN_MultiCueBGS.cpp +++ b/package_bgs/MultiCue.cpp @@ -21,12 +21,16 @@ along with BGSLibrary. If not, see . // - Code by: SeungJon Noh // //------------------------------------------------------------------------------------------------------------------------------------// //#include "StdAfx.h" -#include "SJN_MultiCueBGS.h" -SJN_MultiCueBGS::SJN_MultiCueBGS() : firstTime(true), showOutput(true) +#include "MultiCue.h" + +using namespace bgslibrary::algorithms::libMultiCue; +using namespace bgslibrary::algorithms; + +MultiCue::MultiCue() { //---------------------------------- // User adjustable parameters - //---------------------------------- + //---------------------------------- g_iTrainingPeriod = 20; //the training period (The parameter t in the paper) g_iT_ModelThreshold = 1; //the threshold for texture-model based BGS. (The parameter tau_T in the paper) g_iC_ModelThreshold = 10; //the threshold for appearance based verification. (The parameter tau_A in the paper) @@ -37,15 +41,15 @@ SJN_MultiCueBGS::SJN_MultiCueBGS() : firstTime(true), showOutput(true) g_nColorTrainVolRange = 20; //the codebook size factor for color models. (The parameter eta_1 in the paper) g_bAbsorptionEnable = TRUE; //If TRUE, cache-book is also modeled for ghost region removal. - g_iAbsortionPeriod = 200; //the period to absorb static ghost regions + g_iAbsortionPeriod = 200; //the period to absorb static ghost regions g_iRWidth = 160, g_iRHeight = 120; //Frames are precessed after reduced in this size . //------------------------------------ // For codebook maintenance //------------------------------------ - g_iBackClearPeriod = 300; //the period to clear background models - g_iCacheClearPeriod = 30; //the period to clear cache-book models + g_iBackClearPeriod = 300; //the period to clear background models + g_iCacheClearPeriod = 30; //the period to clear cache-book models //------------------------------------ // Initialization of other parameters @@ -57,38 +61,37 @@ SJN_MultiCueBGS::SJN_MultiCueBGS() : firstTime(true), showOutput(true) g_bForegroundMapEnable = FALSE; //TRUE only when BGS is successful g_bModelMemAllocated = FALSE; //To handle memory.. g_bNonModelMemAllocated = FALSE; //To handle memory.. + + std::cout << "MultiCue()" << std::endl; + setup("./config/MultiCue.xml"); } -SJN_MultiCueBGS::~SJN_MultiCueBGS(void){ +MultiCue::~MultiCue(void) +{ Destroy(); + std::cout << "~MultiCue()" << std::endl; } //-----------------------------------------------------------------------------------------------------------------------------------------// // the main function to background modeling and subtraction // // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel){ - - if (img_input.empty()) - return; - - loadConfig(); - - if (firstTime) - saveConfig(); +void MultiCue::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); //--STep1: Background Modeling--// //IplImage* frame = &IplImage(img_input); IplImage* frame = new IplImage(img_input); IplImage* result_image = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 3); cvSetZero(result_image); - - if (g_iFrameCount <= g_iTrainingPeriod){ + if (g_iFrameCount <= g_iTrainingPeriod) + { BackgroundModeling_Par(frame); g_iFrameCount++; } - //--Step2: Background Subtraction--// - else{ + else + { g_bForegroundMapEnable = FALSE; ForegroundExtraction(frame); @@ -99,33 +102,35 @@ void SJN_MultiCueBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv: } delete frame; - cv::Mat temp(result_image, TRUE); - temp.copyTo(img_output); - + img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + img_foreground = cv::cvarrToMat(result_image, TRUE); cvReleaseImage(&result_image); +#ifndef MEX_COMPILE_FLAG if (showOutput) - { - cv::imshow("MultiCueBGS FG", img_output); - } + cv::imshow("MultiCue FG", img_foreground); +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); firstTime = false; } -void SJN_MultiCueBGS::saveConfig() +void MultiCue::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/MultiCueBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "showOutput", showOutput); cvReleaseFileStorage(&fs); } -void SJN_MultiCueBGS::loadConfig() +void MultiCue::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/MultiCueBGS.xml", 0, CV_STORAGE_READ); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } @@ -133,7 +138,7 @@ void SJN_MultiCueBGS::loadConfig() //-----------------------------------------------------------------------------------------------------------------------------------------// // the system initialization function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::Initialize(IplImage* frame) +void MultiCue::Initialize(IplImage* frame) { int i, j; @@ -148,15 +153,15 @@ void SJN_MultiCueBGS::Initialize(IplImage* frame) g_ResizedFrame = cvCreateImage(cvSize(g_iRWidth, g_iRHeight), IPL_DEPTH_8U, 3); g_aGaussFilteredFrame = (uchar***)malloc(sizeof(uchar**)*g_iRHeight); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { g_aGaussFilteredFrame[i] = (uchar**)malloc(sizeof(uchar*)*g_iRWidth); - for (j = 0; j < g_iRWidth; j++) g_aGaussFilteredFrame[i][j] = (uchar*)malloc(sizeof(uchar)* 3); + for (j = 0; j < g_iRWidth; j++) g_aGaussFilteredFrame[i][j] = (uchar*)malloc(sizeof(uchar) * 3); } g_aXYZFrame = (uchar***)malloc(sizeof(uchar**)*g_iRHeight); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { g_aXYZFrame[i] = (uchar**)malloc(sizeof(uchar*)*g_iRWidth); - for (j = 0; j < g_iRWidth; j++) g_aXYZFrame[i][j] = (uchar*)malloc(sizeof(uchar)* 3); + for (j = 0; j < g_iRWidth; j++) g_aXYZFrame[i][j] = (uchar*)malloc(sizeof(uchar) * 3); } g_aLandmarkArray = (uchar**)malloc(sizeof(uchar*)*g_iRHeight); @@ -203,30 +208,30 @@ void SJN_MultiCueBGS::Initialize(IplImage* frame) //-----------------------------------------------------------------------------------------------------------------------------------------// // the function to release allocated memories // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::Destroy() +void MultiCue::Destroy() { if (g_bModelMemAllocated == FALSE && g_bNonModelMemAllocated == FALSE) return; short nNeighborNum = g_nNeighborNum; - if (g_bModelMemAllocated == TRUE){ + if (g_bModelMemAllocated == TRUE) { T_ReleaseTextureModelRelatedMemory(); C_ReleaseColorModelRelatedMemory(); g_bModelMemAllocated = FALSE; } - if (g_bNonModelMemAllocated == TRUE){ + if (g_bNonModelMemAllocated == TRUE) { cvReleaseImage(&g_ResizedFrame); - for (int i = 0; i < g_iRHeight; i++){ + for (int i = 0; i < g_iRHeight; i++) { for (int j = 0; j < g_iRWidth; j++) free(g_aGaussFilteredFrame[i][j]); free(g_aGaussFilteredFrame[i]); } free(g_aGaussFilteredFrame); - for (int i = 0; i < g_iRHeight; i++){ + for (int i = 0; i < g_iRHeight; i++) { for (int j = 0; j < g_iRWidth; j++) free(g_aXYZFrame[i][j]); free(g_aXYZFrame[i]); } @@ -256,7 +261,7 @@ void SJN_MultiCueBGS::Destroy() //-----------------------------------------------------------------------------------------------------------------------------------------// // the preprocessing function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::PreProcessing(IplImage* frame){ +void MultiCue::PreProcessing(IplImage* frame) { //image resize ReduceImageSize(frame, g_ResizedFrame); @@ -271,7 +276,7 @@ void SJN_MultiCueBGS::PreProcessing(IplImage* frame){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the background modeling function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::BackgroundModeling_Par(IplImage* frame){ +void MultiCue::BackgroundModeling_Par(IplImage* frame) { //initialization if (g_iFrameCount == 0) Initialize(frame); @@ -285,8 +290,8 @@ void SJN_MultiCueBGS::BackgroundModeling_Par(IplImage* frame){ float fLearningRate = g_fLearningRate * 4; //Step2: background modeling - for (int i = iH_Start; i < iH_end; i++){ - for (int j = iW_Start; j < iW_end; j++){ + for (int i = iH_Start; i < iH_end; i++) { + for (int j = iW_Start; j < iW_end; j++) { point center; center.m_nX = j; center.m_nY = i; @@ -297,9 +302,9 @@ void SJN_MultiCueBGS::BackgroundModeling_Par(IplImage* frame){ } //Step3: Clear non-essential codewords - if (g_iFrameCount == g_iTrainingPeriod){ - for (int i = 0; i < g_iRHeight; i++){ - for (int j = 0; j < g_iRWidth; j++){ + if (g_iFrameCount == g_iTrainingPeriod) { + for (int i = 0; i < g_iRHeight; i++) { + for (int j = 0; j < g_iRWidth; j++) { T_ClearNonEssentialEntries(g_iTrainingPeriod, g_TextureModel[i][j]); C_ClearNonEssentialEntries(g_iTrainingPeriod, g_ColorModel[i][j]); @@ -312,7 +317,7 @@ void SJN_MultiCueBGS::BackgroundModeling_Par(IplImage* frame){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the background subtraction function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::ForegroundExtraction(IplImage* frame){ +void MultiCue::ForegroundExtraction(IplImage* frame) { //Step1:pre-processing PreProcessing(frame); @@ -332,9 +337,9 @@ void SJN_MultiCueBGS::ForegroundExtraction(IplImage* frame){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the post-processing function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::PostProcessing(IplImage* frame){ +void MultiCue::PostProcessing(IplImage* frame) { - //Step1: morphological operation + //Step1: morphological operation MorphologicalOpearions(g_aLandmarkArray, g_aResizedForeMap, 0.5, 5, g_iRWidth, g_iRHeight); g_bForegroundMapEnable = TRUE; @@ -361,20 +366,20 @@ void SJN_MultiCueBGS::PostProcessing(IplImage* frame){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the background-model update function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::UpdateModel_Par(){ +void MultiCue::UpdateModel_Par() { short nNeighborNum = g_nNeighborNum; //Step1: update map construction - for (int i = 0; i < g_iRHeight; i++){ - for (int j = 0; j < g_iRWidth; j++){ + for (int i = 0; i < g_iRHeight; i++) { + for (int j = 0; j < g_iRWidth; j++) { g_aUpdateMap[i][j] = TRUE; } } - for (int k = 0; k < g_BoundBoxInfo->m_iBoundBoxNum; k++){ - if (g_BoundBoxInfo->m_ValidBox[k] == TRUE){ - for (int i = g_BoundBoxInfo->m_aRUpper[k]; i <= g_BoundBoxInfo->m_aRBottom[k]; i++){ - for (int j = g_BoundBoxInfo->m_aRLeft[k]; j <= g_BoundBoxInfo->m_aRRight[k]; j++){ + for (int k = 0; k < g_BoundBoxInfo->m_iBoundBoxNum; k++) { + if (g_BoundBoxInfo->m_ValidBox[k] == TRUE) { + for (int i = g_BoundBoxInfo->m_aRUpper[k]; i <= g_BoundBoxInfo->m_aRBottom[k]; i++) { + for (int j = g_BoundBoxInfo->m_aRLeft[k]; j <= g_BoundBoxInfo->m_aRRight[k]; j++) { g_aUpdateMap[i][j] = FALSE; } } @@ -387,16 +392,16 @@ void SJN_MultiCueBGS::UpdateModel_Par(){ float fLearningRate = (float)g_fLearningRate; - for (int i = iH_Start; i < iH_End; i++){ - for (int j = iW_Start; j < iW_End; j++){ + for (int i = iH_Start; i < iH_End; i++) { + for (int j = iW_Start; j < iW_End; j++) { point center; center.m_nX = j; center.m_nY = i; - if (g_aUpdateMap[i][j] == TRUE){ - //model update + if (g_aUpdateMap[i][j] == TRUE) { + //model update T_ModelConstruction(g_nTextureTrainVolRange, fLearningRate, g_aXYZFrame, center, g_aNeighborDirection[i][j], g_TextureModel[i][j]); C_CodebookConstruction(g_aXYZFrame[i][j], j, i, g_nColorTrainVolRange, fLearningRate, g_ColorModel[i][j]); @@ -406,7 +411,7 @@ void SJN_MultiCueBGS::UpdateModel_Par(){ } else { - if (g_bAbsorptionEnable == TRUE){ + if (g_bAbsorptionEnable == TRUE) { //model update T_ModelConstruction(g_nTextureTrainVolRange, fLearningRate, g_aXYZFrame, center, g_aNeighborDirection[i][j], g_TCacheBook[i][j]); C_CodebookConstruction(g_aXYZFrame[i][j], j, i, g_nColorTrainVolRange, fLearningRate, g_CCacheBook[i][j]); @@ -419,7 +424,7 @@ void SJN_MultiCueBGS::UpdateModel_Par(){ } //clearing non-essential codewords for cache-books - if (g_bAbsorptionEnable == TRUE){ + if (g_bAbsorptionEnable == TRUE) { T_ClearNonEssentialEntriesForCachebook(g_aLandmarkArray[i][j], g_aTReferredIndex[i][j], 10, g_TCacheBook[i][j]); C_ClearNonEssentialEntriesForCachebook(g_aLandmarkArray[i][j], g_aCReferredIndex[i][j], 10, g_CCacheBook[i][j]); } @@ -431,16 +436,16 @@ void SJN_MultiCueBGS::UpdateModel_Par(){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the color based verification function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::CreateLandmarkArray_Par(float fConfThre, short nTrainVolRange, float**aConfMap, int iNehborNum, uchar*** aXYZ, - point*** aNeiDir, TextureModel**** TModel, ColorModel*** CModel, uchar**aLandmarkArr){ +void MultiCue::CreateLandmarkArray_Par(float fConfThre, short nTrainVolRange, float**aConfMap, int iNehborNum, uchar*** aXYZ, + point*** aNeiDir, TextureModel**** TModel, ColorModel*** CModel, uchar**aLandmarkArr) { int iBound_w = g_iRWidth - g_nRadius; int iBound_h = g_iRHeight - g_nRadius; - for (int i = 0; i < g_iRHeight; i++){ - for (int j = 0; j < g_iRWidth; j++){ + for (int i = 0; i < g_iRHeight; i++) { + for (int j = 0; j < g_iRWidth; j++) { - if (i < g_nRadius || i >= iBound_h || j= iBound_w) { + if (i < g_nRadius || i >= iBound_h || j < g_nRadius || j >= iBound_w) { aLandmarkArr[i][j] = 0; continue; } @@ -448,14 +453,14 @@ void SJN_MultiCueBGS::CreateLandmarkArray_Par(float fConfThre, short nTrainVolRa double tmp = aConfMap[i][j]; if (tmp > fConfThre) aLandmarkArr[i][j] = 255; - else{ + else { aLandmarkArr[i][j] = 0; //Calculating texture amount in the background double dBackAmt, dCnt; dBackAmt = dCnt = 0; - for (int m = 0; m < iNehborNum; m++){ - for (int n = 0; n < TModel[i][j][m]->m_iNumEntries; n++){ + for (int m = 0; m < iNehborNum; m++) { + for (int n = 0; n < TModel[i][j][m]->m_iNumEntries; n++) { dBackAmt += TModel[i][j][m]->m_Codewords[n]->m_fMean; dCnt++; } @@ -464,7 +469,7 @@ void SJN_MultiCueBGS::CreateLandmarkArray_Par(float fConfThre, short nTrainVolRa //Calculating texture amount in the input image double dTemp, dInputAmt = 0; - for (int m = 0; m < iNehborNum; m++){ + for (int m = 0; m < iNehborNum; m++) { dTemp = aXYZ[i][j][2] - aXYZ[aNeiDir[i][j][m].m_nY][aNeiDir[i][j][m].m_nX][2]; if (dTemp >= 0) dInputAmt += dTemp; @@ -473,13 +478,13 @@ void SJN_MultiCueBGS::CreateLandmarkArray_Par(float fConfThre, short nTrainVolRa } //If there are only few textures in both background and input image - if (dBackAmt < 50 && dInputAmt < 50){ + if (dBackAmt < 50 && dInputAmt < 50) { //Conduct color codebook matching BOOL bMatched = FALSE; - for (int m = 0; m < CModel[i][j]->m_iNumEntries; m++){ + for (int m = 0; m < CModel[i][j]->m_iNumEntries; m++) { int iMatchedCount = 0; - for (int n = 0; n < 3; n++){ + for (int n = 0; n < 3; n++) { double dLowThre = CModel[i][j]->m_Codewords[m]->m_dMean[n] - nTrainVolRange - 10; double dHighThre = CModel[i][j]->m_Codewords[m]->m_dMean[n] + nTrainVolRange + 10; @@ -505,13 +510,13 @@ void SJN_MultiCueBGS::CreateLandmarkArray_Par(float fConfThre, short nTrainVolRa //-----------------------------------------------------------------------------------------------------------------------------------------// // the Gaussian filtering function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::GaussianFiltering(IplImage* frame, uchar*** aFilteredFrame){ +void MultiCue::GaussianFiltering(IplImage* frame, uchar*** aFilteredFrame) { double dSigma = 0.7; - if (dSigma == 0){ - for (int i = 0; i < g_iRHeight; i++){ - for (int j = 0; j < g_iRWidth; j++){ + if (dSigma == 0) { + for (int i = 0; i < g_iRHeight; i++) { + for (int j = 0; j < g_iRWidth; j++) { aFilteredFrame[i][j][0] = frame->imageData[i*frame->widthStep + j * 3]; aFilteredFrame[i][j][1] = frame->imageData[i*frame->widthStep + j * 3 + 1]; aFilteredFrame[i][j][2] = frame->imageData[i*frame->widthStep + j * 3 + 2]; @@ -520,16 +525,16 @@ void SJN_MultiCueBGS::GaussianFiltering(IplImage* frame, uchar*** aFilteredFrame } else { - cv::Mat temp_img(frame, TRUE); + cv::Mat temp_img = cv::cvarrToMat(frame, TRUE); cv::GaussianBlur(temp_img, temp_img, cv::Size(7, 7), dSigma); //Store results into aFilteredFrame[][][] //IplImage* img = &IplImage(temp_img); IplImage* img = new IplImage(temp_img); - int iWidthStep = img->widthStep; + //int iWidthStep = img->widthStep; - for (int i = 0; i < g_iRHeight; i++){ - for (int j = 0; j < g_iRWidth; j++){ + for (int i = 0; i < g_iRHeight; i++) { + for (int j = 0; j < g_iRWidth; j++) { aFilteredFrame[i][j][0] = img->imageData[i*img->widthStep + j * 3]; aFilteredFrame[i][j][1] = img->imageData[i*img->widthStep + j * 3 + 1]; aFilteredFrame[i][j][2] = img->imageData[i*img->widthStep + j * 3 + 2]; @@ -542,7 +547,7 @@ void SJN_MultiCueBGS::GaussianFiltering(IplImage* frame, uchar*** aFilteredFrame //------------------------------------------------------------------------------------------------------------------------------------// // the image resize function // //------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::ReduceImageSize(IplImage* SrcImage, IplImage* DstImage){ +void MultiCue::ReduceImageSize(IplImage* SrcImage, IplImage* DstImage) { int iChannel = 3; @@ -550,8 +555,8 @@ void SJN_MultiCueBGS::ReduceImageSize(IplImage* SrcImage, IplImage* DstImage){ double dResizeFactor_h = (double)g_iHeight / (double)g_iRHeight; - for (int i = 0; i < g_iRHeight; i++){ - for (int j = 0; j < g_iRWidth; j++){ + for (int i = 0; i < g_iRHeight; i++) { + for (int j = 0; j < g_iRWidth; j++) { int iSrcY = (int)(i*dResizeFactor_h); int iSrcX = (int)(j*dResizeFactor_w); @@ -565,18 +570,18 @@ void SJN_MultiCueBGS::ReduceImageSize(IplImage* SrcImage, IplImage* DstImage){ //------------------------------------------------------------------------------------------------------------------------------------// // the color space conversion function // //------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::BGR2HSVxyz_Par(uchar*** aBGR, uchar*** aXYZ){ +void MultiCue::BGR2HSVxyz_Par(uchar*** aBGR, uchar*** aXYZ) { double dH_ratio = (2 * PI) / 360; - for (int i = 0; i < g_iRHeight; i++){ + for (int i = 0; i < g_iRHeight; i++) { double dR, dG, dB; double dMax, dMin; double dH, dS, dV; - for (int j = 0; j < g_iRWidth; j++){ + for (int j = 0; j < g_iRWidth; j++) { dB = (double)(aBGR[i][j][0]) / 255; dG = (double)(aBGR[i][j][1]) / 255; @@ -593,13 +598,13 @@ void SJN_MultiCueBGS::BGR2HSVxyz_Par(uchar*** aBGR, uchar*** aXYZ){ //Get S, H if (dV == 0) dS = dH = 0; - else{ + else { //S value dS = (dMax - dMin) / dMax; if (dS == 0) dH = 0; - else{ + else { //H value if (dMax == dR) { dH = 60 * (dG - dB) / dS; @@ -622,7 +627,7 @@ void SJN_MultiCueBGS::BGR2HSVxyz_Par(uchar*** aBGR, uchar*** aXYZ){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the function to get enlarged confidence map // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::GetEnlargedMap(float** aOriginMap, float** aEnlargedMap){ +void MultiCue::GetEnlargedMap(float** aOriginMap, float** aEnlargedMap) { int i, j; short nSrcX; @@ -639,8 +644,8 @@ void SJN_MultiCueBGS::GetEnlargedMap(float** aOriginMap, float** aEnlargedMap){ double dScaleFactor_w = ((double)g_iWidth) / ((double)g_iRWidth); double dScaleFactor_h = ((double)g_iHeight) / ((double)g_iRHeight); - for (i = 0; i < g_iHeight; i++){ - for (j = 0; j < g_iWidth; j++){ + for (i = 0; i < g_iHeight; i++) { + for (j = 0; j < g_iWidth; j++) { //backward mapping nSrcY = (int)(i / dScaleFactor_h); nSrcX = (int)(j / dScaleFactor_w); @@ -668,7 +673,7 @@ void SJN_MultiCueBGS::GetEnlargedMap(float** aOriginMap, float** aEnlargedMap){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the morphological operation function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::MorphologicalOpearions(uchar** aInput, uchar** aOutput, double dThresholdRatio, int iMaskSize, int iWidth, int iHeight){ +void MultiCue::MorphologicalOpearions(uchar** aInput, uchar** aOutput, double dThresholdRatio, int iMaskSize, int iWidth, int iHeight) { int iOffset = (int)(iMaskSize / 2); @@ -676,28 +681,28 @@ void SJN_MultiCueBGS::MorphologicalOpearions(uchar** aInput, uchar** aOutput, do int iBound_h = iHeight - iOffset; uchar** aTemp = (uchar**)malloc(sizeof(uchar*)*iHeight); - for (int i = 0; i < iHeight; i++){ + for (int i = 0; i < iHeight; i++) { aTemp[i] = (uchar*)malloc(sizeof(uchar)*iWidth); } - for (int i = 0; i < iHeight; i++){ - for (int j = 0; j < iWidth; j++){ + for (int i = 0; i < iHeight; i++) { + for (int j = 0; j < iWidth; j++) { aTemp[i][j] = aInput[i][j]; } } int iThreshold = (int)(iMaskSize*iMaskSize*dThresholdRatio); - for (int i = 0; i < iHeight; i++){ - for (int j = 0; j < iWidth; j++){ + for (int i = 0; i < iHeight; i++) { + for (int j = 0; j < iWidth; j++) { - if (i < iOffset || i >= iBound_h || j < iOffset || j >= iBound_w){ + if (i < iOffset || i >= iBound_h || j < iOffset || j >= iBound_w) { aOutput[i][j] = 0; continue; } int iCnt = 0; - for (int m = -iOffset; m <= iOffset; m++){ - for (int n = -iOffset; n <= iOffset; n++){ + for (int m = -iOffset; m <= iOffset; m++) { + for (int n = -iOffset; n <= iOffset; n++) { if (aTemp[i + m][j + n] == 255) iCnt++; } } @@ -708,7 +713,7 @@ void SJN_MultiCueBGS::MorphologicalOpearions(uchar** aInput, uchar** aOutput, do } - for (int i = 0; i < iHeight; i++){ + for (int i = 0; i < iHeight; i++) { free(aTemp[i]); } free(aTemp); @@ -717,7 +722,7 @@ void SJN_MultiCueBGS::MorphologicalOpearions(uchar** aInput, uchar** aOutput, do //-----------------------------------------------------------------------------------------------------------------------------------------// // 2-raster scan pass based labeling function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::Labeling(uchar** aBinaryArray, int* pLabelCount, int** aLabelTable){ +void MultiCue::Labeling(uchar** aBinaryArray, int* pLabelCount, int** aLabelTable) { int x, y, i; // pass 1,2 int cnt = 0; // pass 1 int label = 0; // pass 2 @@ -730,45 +735,45 @@ void SJN_MultiCueBGS::Labeling(uchar** aBinaryArray, int* pLabelCount, int** aLa int* aTable1 = (int*)malloc(iSize / 2 * sizeof(int)); int* aTable2 = (int*)malloc(iSize / 2 * sizeof(int)); - memset(aPass1, 0, (iSize)* sizeof(int)); - for (y = 1; y < (g_iRHeight); y++){ - for (x = 1; x < (g_iRWidth); x++){ + memset(aPass1, 0, (iSize) * sizeof(int)); + for (y = 1; y < (g_iRHeight); y++) { + for (x = 1; x < (g_iRWidth); x++) { aLabelTable[y][x] = 0; } } - for (i = 0; i < iTableSize; i++){ + for (i = 0; i < iTableSize; i++) { aTable1[i] = i; } memset(aTable2, 0, iTableSize * sizeof(int)); // pass 1 - for (y = 1; y < (g_iRHeight); y++){ - for (x = 1; x < (g_iRWidth); x++){ + for (y = 1; y < (g_iRHeight); y++) { + for (x = 1; x < (g_iRWidth); x++) { - if (aBinaryArray[y][x] == 255){ // fore ground?? + if (aBinaryArray[y][x] == 255) { // fore ground?? int up, le; up = aPass1[(y - 1)*(g_iRWidth)+(x)]; // up index le = aPass1[(y)*(g_iRWidth)+(x - 1)]; // left index // case - if (up == 0 && le == 0){ + if (up == 0 && le == 0) { ++cnt; aPass1[y * g_iRWidth + x] = cnt; } - else if (up != 0 && le != 0){ - if (up > le){ + else if (up != 0 && le != 0) { + if (up > le) { aPass1[y *g_iRWidth + x] = le; aTable1[up] = aTable1[le]; // update table1 table1 } - else{ + else { aPass1[y * g_iRWidth + x] = up; aTable1[le] = aTable1[up]; // update table1 table1 } } - else{ + else { aPass1[y * g_iRWidth + x] = up + le; } @@ -778,13 +783,13 @@ void SJN_MultiCueBGS::Labeling(uchar** aBinaryArray, int* pLabelCount, int** aLa } // pass 2 - for (y = 1; y < (g_iRHeight); y++){ - for (x = 1; x < (g_iRWidth); x++){ + for (y = 1; y < (g_iRHeight); y++) { + for (x = 1; x < (g_iRWidth); x++) { - if (aPass1[y * g_iRWidth + x]){ + if (aPass1[y * g_iRWidth + x]) { int v = aTable1[aPass1[y * g_iRWidth + x]]; - if (aTable2[v] == 0){ + if (aTable2[v] == 0) { ++label; aTable2[v] = label; } @@ -804,12 +809,12 @@ void SJN_MultiCueBGS::Labeling(uchar** aBinaryArray, int* pLabelCount, int** aLa //-----------------------------------------------------------------------------------------------------------------------------------------// // the function to set bounding boxes for each candidate foreground regions // // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::SetBoundingBox(int iLabelCount, int** aLabelTable){ +void MultiCue::SetBoundingBox(int iLabelCount, int** aLabelTable) { int iBoundBoxIndex; g_BoundBoxInfo->m_iBoundBoxNum = iLabelCount; - for (int i = 0; i < g_BoundBoxInfo->m_iBoundBoxNum; i++){ + for (int i = 0; i < g_BoundBoxInfo->m_iBoundBoxNum; i++) { g_BoundBoxInfo->m_aRLeft[i] = 9999; //left g_BoundBoxInfo->m_aRUpper[i] = 9999; //top g_BoundBoxInfo->m_aRRight[i] = 0; //right @@ -817,8 +822,8 @@ void SJN_MultiCueBGS::SetBoundingBox(int iLabelCount, int** aLabelTable){ } //Step1: Set tight bounding boxes - for (int i = 1; im_iBoundBoxNum; i++){ + for (int i = 0; i < g_BoundBoxInfo->m_iBoundBoxNum; i++) { g_BoundBoxInfo->m_aRLeft[i] -= iBoundary_w; if (g_BoundBoxInfo->m_aRLeft[i] < g_nRadius) g_BoundBoxInfo->m_aRLeft[i] = g_nRadius; //left @@ -853,7 +858,7 @@ void SJN_MultiCueBGS::SetBoundingBox(int iLabelCount, int** aLabelTable){ double dH_ratio = (double)g_iHeight / (double)g_iRHeight; double dW_ratio = (double)g_iWidth / (double)g_iRWidth; - for (int i = 0; i < g_BoundBoxInfo->m_iBoundBoxNum; i++){ + for (int i = 0; i < g_BoundBoxInfo->m_iBoundBoxNum; i++) { g_BoundBoxInfo->m_aLeft[i] = (int)(g_BoundBoxInfo->m_aRLeft[i] * dW_ratio); g_BoundBoxInfo->m_aUpper[i] = (int)(g_BoundBoxInfo->m_aRUpper[i] * dH_ratio); g_BoundBoxInfo->m_aRight[i] = (int)(g_BoundBoxInfo->m_aRRight[i] * dW_ratio); @@ -865,7 +870,7 @@ void SJN_MultiCueBGS::SetBoundingBox(int iLabelCount, int** aLabelTable){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the box verification function // // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::BoundBoxVerification(IplImage* frame, uchar** aResForeMap, BoundingBoxInfo* BoundBoxInfo){ +void MultiCue::BoundBoxVerification(IplImage* frame, uchar** aResForeMap, BoundingBoxInfo* BoundBoxInfo) { //Step1: Verification by the bounding box size EvaluateBoxSize(BoundBoxInfo); @@ -875,7 +880,7 @@ void SJN_MultiCueBGS::BoundBoxVerification(IplImage* frame, uchar** aResForeMap, //Step3: Counting the # of valid box g_iForegroundNum = 0; - for (int i = 0; i < BoundBoxInfo->m_iBoundBoxNum; i++){ + for (int i = 0; i < BoundBoxInfo->m_iBoundBoxNum; i++) { if (BoundBoxInfo->m_ValidBox[i] == TRUE) g_iForegroundNum++; } } @@ -883,7 +888,7 @@ void SJN_MultiCueBGS::BoundBoxVerification(IplImage* frame, uchar** aResForeMap, //-----------------------------------------------------------------------------------------------------------------------------------------// // the size based verification // // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::EvaluateBoxSize(BoundingBoxInfo* BoundBoxInfo){ +void MultiCue::EvaluateBoxSize(BoundingBoxInfo* BoundBoxInfo) { //Set thresholds int iLowThreshold_w, iHighThreshold_w; @@ -897,7 +902,7 @@ void SJN_MultiCueBGS::EvaluateBoxSize(BoundingBoxInfo* BoundBoxInfo){ int iBoxWidth, iBoxHeight; //Perform verification. - for (int i = 0; i < BoundBoxInfo->m_iBoundBoxNum; i++){ + for (int i = 0; i < BoundBoxInfo->m_iBoundBoxNum; i++) { iBoxWidth = BoundBoxInfo->m_aRRight[i] - BoundBoxInfo->m_aRLeft[i]; iBoxHeight = BoundBoxInfo->m_aRBottom[i] - BoundBoxInfo->m_aRUpper[i]; @@ -912,7 +917,7 @@ void SJN_MultiCueBGS::EvaluateBoxSize(BoundingBoxInfo* BoundBoxInfo){ //------------------------------------------------------------------------------------------------------------------------------------// // overlapped region removal // //------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::EvaluateOverlapRegionSize(BoundingBoxInfo* SrcBoxInfo){ +void MultiCue::EvaluateOverlapRegionSize(BoundingBoxInfo* SrcBoxInfo) { BOOL *aValidBoxFlag = new BOOL[SrcBoxInfo->m_iBoundBoxNum]; for (int i = 0; i < SrcBoxInfo->m_iBoundBoxNum; i++) aValidBoxFlag[i] = TRUE; @@ -924,7 +929,7 @@ void SJN_MultiCueBGS::EvaluateOverlapRegionSize(BoundingBoxInfo* SrcBoxInfo){ int iThreshold, iCount, iSmall_Idx, iLarge_Idx; double dThreRatio = 0.7; - for (int i = 0; i < SrcBoxInfo->m_iBoundBoxNum; i++){ + for (int i = 0; i < SrcBoxInfo->m_iBoundBoxNum; i++) { if (SrcBoxInfo->m_ValidBox[i] == FALSE) { aValidBoxFlag[i] = FALSE; @@ -933,7 +938,7 @@ void SJN_MultiCueBGS::EvaluateOverlapRegionSize(BoundingBoxInfo* SrcBoxInfo){ size1 = (aRight[i] - aLeft[i]) * (aBottom[i] - aTop[i]); - for (int j = i; j < SrcBoxInfo->m_iBoundBoxNum; j++){ + for (int j = i; j < SrcBoxInfo->m_iBoundBoxNum; j++) { if ((i == j) || (SrcBoxInfo->m_ValidBox[j] == FALSE)) continue; //Setting threshold for checking overlapped region size @@ -950,8 +955,8 @@ void SJN_MultiCueBGS::EvaluateOverlapRegionSize(BoundingBoxInfo* SrcBoxInfo){ //Calculating overlapped region size iCount = 0; - for (int m = aLeft[iSmall_Idx]; m < aRight[iSmall_Idx]; m++){ - for (int n = aTop[iSmall_Idx]; nm_iBoundBoxNum; i++){ - if (BoundBoxInfo->m_ValidBox[i] == TRUE){ + for (int i = 0; i < BoundBoxInfo->m_iBoundBoxNum; i++) { + if (BoundBoxInfo->m_ValidBox[i] == TRUE) { int iWin_w = BoundBoxInfo->m_aRRight[i] - BoundBoxInfo->m_aRLeft[i]; int iWin_h = BoundBoxInfo->m_aRBottom[i] - BoundBoxInfo->m_aRUpper[i]; @@ -999,8 +1004,8 @@ void SJN_MultiCueBGS::EvaluateGhostRegion(IplImage* frame, uchar** aResForeMap, //Generating edge image from aResForeMap IplImage* edge_fore = cvCreateImage(cvSize(iWin_w, iWin_h), IPL_DEPTH_8U, 1); - for (int m = BoundBoxInfo->m_aRUpper[i]; m < BoundBoxInfo->m_aRBottom[i]; m++){ - for (int n = BoundBoxInfo->m_aRLeft[i]; nm_aRRight[i]; n++){ + for (int m = BoundBoxInfo->m_aRUpper[i]; m < BoundBoxInfo->m_aRBottom[i]; m++) { + for (int n = BoundBoxInfo->m_aRLeft[i]; n < BoundBoxInfo->m_aRRight[i]; n++) { edge_fore->imageData[(m - BoundBoxInfo->m_aRUpper[i])*edge_fore->widthStep + (n - BoundBoxInfo->m_aRLeft[i])] = (char)aResForeMap[m][n]; } } @@ -1011,8 +1016,8 @@ void SJN_MultiCueBGS::EvaluateGhostRegion(IplImage* frame, uchar** aResForeMap, //Recording evaluation result if (distance > dThreshold) { - for (int m = BoundBoxInfo->m_aRUpper[i]; m < BoundBoxInfo->m_aRBottom[i]; m++){ - for (int n = BoundBoxInfo->m_aRLeft[i]; n < BoundBoxInfo->m_aRRight[i]; n++){ + for (int m = BoundBoxInfo->m_aRUpper[i]; m < BoundBoxInfo->m_aRBottom[i]; m++) { + for (int n = BoundBoxInfo->m_aRLeft[i]; n < BoundBoxInfo->m_aRRight[i]; n++) { aUpdateMap[m][n] = TRUE; } } @@ -1029,9 +1034,9 @@ void SJN_MultiCueBGS::EvaluateGhostRegion(IplImage* frame, uchar** aResForeMap, //Step2: Adding information fo ghost region pixels to background model float fLearningRate = g_fLearningRate; - for (int i = 0; i < g_iRHeight; i++){ - for (int j = 0; j < g_iRWidth; j++){ - if (aUpdateMap[i][j] == TRUE){ + for (int i = 0; i < g_iRHeight; i++) { + for (int j = 0; j < g_iRWidth; j++) { + if (aUpdateMap[i][j] == TRUE) { point center; center.m_nX = j; center.m_nY = i; @@ -1053,16 +1058,16 @@ void SJN_MultiCueBGS::EvaluateGhostRegion(IplImage* frame, uchar** aResForeMap, //-----------------------------------------------------------------------------------------------------------------------------------------// // the function to calculate partial undirected Hausdorff distance(forward distance) // // //-----------------------------------------------------------------------------------------------------------------------------------------// -double SJN_MultiCueBGS::CalculateHausdorffDist(IplImage* input_image, IplImage* model_image){ +double MultiCue::CalculateHausdorffDist(IplImage* input_image, IplImage* model_image) { //Step1: Generating imag vectors //For reduce errors, points at the image boundary are excluded - vector vInput, vModel; + std::vector vInput, vModel; point temp; - //input image --> input vector - for (int i = 0; i < input_image->height; i++){ - for (int j = 0; j < input_image->width; j++){ + //input image --> input vector + for (int i = 0; i < input_image->height; i++) { + for (int j = 0; j < input_image->width; j++) { if ((uchar)input_image->imageData[i*input_image->widthStep + j] == 0) continue; @@ -1071,8 +1076,8 @@ double SJN_MultiCueBGS::CalculateHausdorffDist(IplImage* input_image, IplImage* } } //model image --> model vector - for (int i = 0; i < model_image->height; i++){ - for (int j = 0; j < model_image->width; j++){ + for (int i = 0; i < model_image->height; i++) { + for (int j = 0; j < model_image->width; j++) { if ((uchar)model_image->imageData[i*model_image->widthStep + j] == 0) continue; temp.m_nX = j; temp.m_nY = i; @@ -1086,12 +1091,12 @@ double SJN_MultiCueBGS::CalculateHausdorffDist(IplImage* input_image, IplImage* //Step2: Calculating forward distance h(Model,Image) double dDist, temp1, temp2, dMinDist; - vector vTempDist; + std::vector vTempDist; - for (auto iter_m = vModel.begin(); iter_m < vModel.end(); iter_m++){ + for (auto iter_m = vModel.begin(); iter_m < vModel.end(); iter_m++) { dMinDist = 9999999; - for (auto iter_i = vInput.begin(); iter_i < vInput.end(); iter_i++){ + for (auto iter_i = vInput.begin(); iter_i < vInput.end(); iter_i++) { temp1 = (*iter_m).m_nX - (*iter_i).m_nX; temp2 = (*iter_m).m_nY - (*iter_i).m_nY; dDist = temp1*temp1 + temp2*temp2; @@ -1114,15 +1119,15 @@ double SJN_MultiCueBGS::CalculateHausdorffDist(IplImage* input_image, IplImage* //-----------------------------------------------------------------------------------------------------------------------------------------// // function to remove non-valid bounding boxes fore fore-candidates // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::RemovingInvalidForeRegions(uchar** aResForeMap, BoundingBoxInfo* BoundBoxInfo){ +void MultiCue::RemovingInvalidForeRegions(uchar** aResForeMap, BoundingBoxInfo* BoundBoxInfo) { int iBoxNum = BoundBoxInfo->m_iBoundBoxNum; - for (int k = 0; k < iBoxNum; k++){ + for (int k = 0; k < iBoxNum; k++) { - if (BoundBoxInfo->m_ValidBox[k] == FALSE){ - for (int i = BoundBoxInfo->m_aRUpper[k]; i < BoundBoxInfo->m_aRBottom[k]; i++){ - for (int j = BoundBoxInfo->m_aRLeft[k]; j < BoundBoxInfo->m_aRRight[k]; j++){ + if (BoundBoxInfo->m_ValidBox[k] == FALSE) { + for (int i = BoundBoxInfo->m_aRUpper[k]; i < BoundBoxInfo->m_aRBottom[k]; i++) { + for (int j = BoundBoxInfo->m_aRLeft[k]; j < BoundBoxInfo->m_aRRight[k]; j++) { if (aResForeMap[i][j] == 255) aResForeMap[i][j] = 0; } } @@ -1134,15 +1139,15 @@ void SJN_MultiCueBGS::RemovingInvalidForeRegions(uchar** aResForeMap, BoundingBo //-----------------------------------------------------------------------------------------------------------------------------------------// // the function returning a foreground binary-map // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::GetForegroundMap(IplImage* return_image, IplImage* input_frame){ +void MultiCue::GetForegroundMap(IplImage* return_image, IplImage* input_frame) { if (g_bForegroundMapEnable == FALSE) return; IplImage* temp_image = cvCreateImage(cvSize(g_iRWidth, g_iRHeight), IPL_DEPTH_8U, 3); - if (input_frame == NULL){ - for (int i = 0; i < g_iRHeight; i++){ - for (int j = 0; j < g_iRWidth; j++){ + if (input_frame == NULL) { + for (int i = 0; i < g_iRHeight; i++) { + for (int j = 0; j < g_iRWidth; j++) { temp_image->imageData[i*temp_image->widthStep + j * 3] = (char)g_aResizedForeMap[i][j]; temp_image->imageData[i*temp_image->widthStep + j * 3 + 1] = (char)g_aResizedForeMap[i][j]; temp_image->imageData[i*temp_image->widthStep + j * 3 + 2] = (char)g_aResizedForeMap[i][j]; @@ -1150,7 +1155,7 @@ void SJN_MultiCueBGS::GetForegroundMap(IplImage* return_image, IplImage* input_f } } - else{ + else { cvResize(input_frame, temp_image); CvScalar MixColor; @@ -1158,8 +1163,8 @@ void SJN_MultiCueBGS::GetForegroundMap(IplImage* return_image, IplImage* input_f MixColor.val[1] = 0; //G MixColor.val[2] = 255; //R - for (int i = 0; i < g_iRHeight; i++){ - for (int j = 0; j < g_iRWidth; j++){ + for (int i = 0; i < g_iRHeight; i++) { + for (int j = 0; j < g_iRWidth; j++) { if (g_aResizedForeMap[i][j] == 255) { @@ -1187,15 +1192,15 @@ void SJN_MultiCueBGS::GetForegroundMap(IplImage* return_image, IplImage* input_f //-----------------------------------------------------------------------------------------------------------------------------------------// // the initialization function for the texture-models // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::T_AllocateTextureModelRelatedMemory(){ +void MultiCue::T_AllocateTextureModelRelatedMemory() { int i, j, k; //neighborhood system related int iMaxNeighborArraySize = 8; g_aNeighborDirection = (point***)malloc(sizeof(point**)*g_iRHeight); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { g_aNeighborDirection[i] = (point**)malloc(sizeof(point*)*g_iRWidth); - for (j = 0; j < g_iRWidth; j++){ + for (j = 0; j < g_iRWidth; j++) { g_aNeighborDirection[i][j] = (point*)malloc(sizeof(point)*iMaxNeighborArraySize); } } @@ -1204,11 +1209,11 @@ void SJN_MultiCueBGS::T_AllocateTextureModelRelatedMemory(){ //texture-model related int iElementArraySize = 6; g_TextureModel = (TextureModel****)malloc(sizeof(TextureModel***)*g_iRHeight); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { g_TextureModel[i] = (TextureModel***)malloc(sizeof(TextureModel**)*g_iRWidth); - for (j = 0; j < g_iRWidth; j++){ + for (j = 0; j < g_iRWidth; j++) { g_TextureModel[i][j] = (TextureModel**)malloc(sizeof(TextureModel*)*g_nNeighborNum); - for (k = 0; k < g_nNeighborNum; k++){ + for (k = 0; k < g_nNeighborNum; k++) { g_TextureModel[i][j][k] = (TextureModel*)malloc(sizeof(TextureModel)); g_TextureModel[i][j][k]->m_Codewords = (TextureCodeword**)malloc(sizeof(TextureCodeword*)*iElementArraySize); g_TextureModel[i][j][k]->m_iElementArraySize = iElementArraySize; @@ -1223,16 +1228,16 @@ void SJN_MultiCueBGS::T_AllocateTextureModelRelatedMemory(){ for (i = 0; i < g_iRHeight; i++) g_aTextureConfMap[i] = (float*)malloc(sizeof(float)*g_iRWidth); //cache-book related - if (g_bAbsorptionEnable == TRUE){ + if (g_bAbsorptionEnable == TRUE) { iElementArraySize = iElementArraySize / 2; if (iElementArraySize < 3)iElementArraySize = 3; g_TCacheBook = (TextureModel****)malloc(sizeof(TextureModel***)*g_iRHeight); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { g_TCacheBook[i] = (TextureModel***)malloc(sizeof(TextureModel**)*g_iRWidth); - for (j = 0; j < g_iRWidth; j++){ + for (j = 0; j < g_iRWidth; j++) { g_TCacheBook[i][j] = (TextureModel**)malloc(sizeof(TextureModel*)*g_nNeighborNum); - for (k = 0; k < g_nNeighborNum; k++){ + for (k = 0; k < g_nNeighborNum; k++) { g_TCacheBook[i][j][k] = (TextureModel*)malloc(sizeof(TextureModel)); g_TCacheBook[i][j][k]->m_Codewords = (TextureCodeword**)malloc(sizeof(TextureCodeword*)*iElementArraySize); g_TCacheBook[i][j][k]->m_iElementArraySize = iElementArraySize; @@ -1245,13 +1250,13 @@ void SJN_MultiCueBGS::T_AllocateTextureModelRelatedMemory(){ g_aTReferredIndex = (short***)malloc(sizeof(short**)*g_iRHeight); g_aTContinuousCnt = (short***)malloc(sizeof(short**)*g_iRHeight); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { g_aTReferredIndex[i] = (short**)malloc(sizeof(short*)*g_iRWidth); g_aTContinuousCnt[i] = (short**)malloc(sizeof(short*)*g_iRWidth); for (j = 0; j < g_iRWidth; j++) { g_aTReferredIndex[i][j] = (short*)malloc(sizeof(short)*g_nNeighborNum); g_aTContinuousCnt[i][j] = (short*)malloc(sizeof(short)*g_nNeighborNum); - for (k = 0; k < g_nNeighborNum; k++){ + for (k = 0; k < g_nNeighborNum; k++) { g_aTReferredIndex[i][j][k] = -1; g_aTContinuousCnt[i][j][k] = 0; } @@ -1262,13 +1267,13 @@ void SJN_MultiCueBGS::T_AllocateTextureModelRelatedMemory(){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the memory release function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::T_ReleaseTextureModelRelatedMemory(){ +void MultiCue::T_ReleaseTextureModelRelatedMemory() { int i, j, k, m; short nNeighborNum = g_nNeighborNum; - for (i = 0; i < g_iRHeight; i++){ - for (j = 0; j < g_iRWidth; j++){ - for (k = 0; k < nNeighborNum; k++){ + for (i = 0; i < g_iRHeight; i++) { + for (j = 0; j < g_iRWidth; j++) { + for (k = 0; k < nNeighborNum; k++) { for (m = 0; m < g_TextureModel[i][j][k]->m_iNumEntries; m++) free(g_TextureModel[i][j][k]->m_Codewords[m]); free(g_TextureModel[i][j][k]->m_Codewords); free(g_TextureModel[i][j][k]); @@ -1278,7 +1283,7 @@ void SJN_MultiCueBGS::T_ReleaseTextureModelRelatedMemory(){ } free(g_TextureModel); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { for (j = 0; j < g_iRWidth; j++) free(g_aNeighborDirection[i][j]); free(g_aNeighborDirection[i]); } @@ -1287,10 +1292,10 @@ void SJN_MultiCueBGS::T_ReleaseTextureModelRelatedMemory(){ for (i = 0; i < g_iRHeight; i++) free(g_aTextureConfMap[i]); free(g_aTextureConfMap); - if (g_bAbsorptionEnable == TRUE){ - for (i = 0; i < g_iRHeight; i++){ - for (j = 0; j < g_iRWidth; j++){ - for (k = 0; k < nNeighborNum; k++){ + if (g_bAbsorptionEnable == TRUE) { + for (i = 0; i < g_iRHeight; i++) { + for (j = 0; j < g_iRWidth; j++) { + for (k = 0; k < nNeighborNum; k++) { for (m = 0; m < g_TCacheBook[i][j][k]->m_iNumEntries; m++) free(g_TCacheBook[i][j][k]->m_Codewords[m]); free(g_TCacheBook[i][j][k]->m_Codewords); free(g_TCacheBook[i][j][k]); @@ -1300,8 +1305,8 @@ void SJN_MultiCueBGS::T_ReleaseTextureModelRelatedMemory(){ } free(g_TCacheBook); - for (i = 0; i < g_iRHeight; i++){ - for (j = 0; j < g_iRWidth; j++){ + for (i = 0; i < g_iRHeight; i++) { + for (j = 0; j < g_iRWidth; j++) { free(g_aTReferredIndex[i][j]); free(g_aTContinuousCnt[i][j]); } @@ -1317,7 +1322,7 @@ void SJN_MultiCueBGS::T_ReleaseTextureModelRelatedMemory(){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the codebook construction function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::T_ModelConstruction(short nTrainVolRange, float fLearningRate, uchar*** aXYZ, point center, point* aNei, TextureModel** aModel){ +void MultiCue::T_ModelConstruction(short nTrainVolRange, float fLearningRate, uchar*** aXYZ, point center, point* aNei, TextureModel** aModel) { int i, j; int iMatchedIndex; @@ -1329,15 +1334,15 @@ void SJN_MultiCueBGS::T_ModelConstruction(short nTrainVolRange, float fLearningR float fNegLearningRate = 1 - fLearningRate; - //for all neighboring pairs - for (i = 0; i < nNeighborNum; i++){ + //for all neighboring pairs + for (i = 0; i < nNeighborNum; i++) { fDifference = (float)(aXYZ[center.m_nY][center.m_nX][2] - aXYZ[aNei[i].m_nY][aNei[i].m_nX][2]); //Step1: matching iMatchedIndex = -1; - for (j = 0; j < aModel[i]->m_iNumEntries; j++){ - if (aModel[i]->m_Codewords[j]->m_fLowThre <= fDifference && fDifference <= aModel[i]->m_Codewords[j]->m_fHighThre){ + for (j = 0; j < aModel[i]->m_iNumEntries; j++) { + if (aModel[i]->m_Codewords[j]->m_fLowThre <= fDifference && fDifference <= aModel[i]->m_Codewords[j]->m_fHighThre) { iMatchedIndex = j; break; } @@ -1345,12 +1350,12 @@ void SJN_MultiCueBGS::T_ModelConstruction(short nTrainVolRange, float fLearningR aModel[i]->m_iTotal++; //Step2: adding a new element - if (iMatchedIndex == -1){ + if (iMatchedIndex == -1) { //element array - if (aModel[i]->m_iElementArraySize == aModel[i]->m_iNumEntries){ + if (aModel[i]->m_iElementArraySize == aModel[i]->m_iNumEntries) { aModel[i]->m_iElementArraySize += 5; TextureCodeword **temp = (TextureCodeword**)malloc(sizeof(TextureCodeword*)*aModel[i]->m_iElementArraySize); - for (j = 0; j < aModel[i]->m_iNumEntries; j++){ + for (j = 0; j < aModel[i]->m_iNumEntries; j++) { temp[j] = aModel[i]->m_Codewords[j]; aModel[i]->m_Codewords[j] = NULL; } @@ -1370,7 +1375,7 @@ void SJN_MultiCueBGS::T_ModelConstruction(short nTrainVolRange, float fLearningR } //Step3: update - else{ + else { fDiffMean = aModel[i]->m_Codewords[iMatchedIndex]->m_fMean; aModel[i]->m_Codewords[iMatchedIndex]->m_fMean = fLearningRate*fDifference + fNegLearningRate*fDiffMean; @@ -1381,10 +1386,10 @@ void SJN_MultiCueBGS::T_ModelConstruction(short nTrainVolRange, float fLearningR } //cache-book handling - if (aModel[i]->m_bID == 1){ + if (aModel[i]->m_bID == 1) { //1. m_iMNRL update int negTime; - for (j = 0; j < aModel[i]->m_iNumEntries; j++){ + for (j = 0; j < aModel[i]->m_iNumEntries; j++) { //m_iMNRL update negTime = aModel[i]->m_iTotal - aModel[i]->m_Codewords[j]->m_iT_last_time + aModel[i]->m_Codewords[j]->m_iT_first_time - 1; if (aModel[i]->m_Codewords[j]->m_iMNRL < negTime) aModel[i]->m_Codewords[j]->m_iMNRL = negTime; @@ -1395,18 +1400,18 @@ void SJN_MultiCueBGS::T_ModelConstruction(short nTrainVolRange, float fLearningR if (g_bAbsorptionEnable == TRUE) g_aTReferredIndex[center.m_nY][center.m_nX][i] = -1; } - else{ + else { //1. m_iMNRL update if (iMatchedIndex == -1) aModel[i]->m_Codewords[aModel[i]->m_iNumEntries - 1]->m_iMNRL = 0; //2. g_aTReferredIndex[center.m_nY][center.m_nX][i] update - if (iMatchedIndex == -1){ + if (iMatchedIndex == -1) { g_aTReferredIndex[center.m_nY][center.m_nX][i] = aModel[i]->m_iNumEntries - 1; g_aTContinuousCnt[center.m_nY][center.m_nX][i] = 1; } - else{ + else { if (iMatchedIndex == g_aTReferredIndex[center.m_nY][center.m_nX][i]) g_aTContinuousCnt[center.m_nY][center.m_nX][i]++; - else{ + else { g_aTReferredIndex[center.m_nY][center.m_nX][i] = iMatchedIndex; g_aTContinuousCnt[center.m_nY][center.m_nX][i] = 1; } @@ -1420,7 +1425,7 @@ void SJN_MultiCueBGS::T_ModelConstruction(short nTrainVolRange, float fLearningR //-----------------------------------------------------------------------------------------------------------------------------------------// // Clear non-essential codewords of the given codebook // // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::T_ClearNonEssentialEntries(short nClearNum, TextureModel** aModel){ +void MultiCue::T_ClearNonEssentialEntries(short nClearNum, TextureModel** aModel) { int i, n; int iStaleThresh = (int)(nClearNum*0.5); int iKeepCnt; @@ -1430,7 +1435,7 @@ void SJN_MultiCueBGS::T_ClearNonEssentialEntries(short nClearNum, TextureModel** TextureModel* c; - for (n = 0; n < nNeighborNum; n++){ + for (n = 0; n < nNeighborNum; n++) { c = aModel[n]; if (c->m_iTotal < nClearNum) continue; //(being operated only when c[w][h]->m_iTotal == nClearNum) @@ -1441,7 +1446,7 @@ void SJN_MultiCueBGS::T_ClearNonEssentialEntries(short nClearNum, TextureModel** iKeepCnt = 0; //Step2: Find non-essential code-words - for (i = 0; im_iNumEntries; i++){ + for (i = 0; i < c->m_iNumEntries; i++) { if (c->m_Codewords[i]->m_iMNRL > iStaleThresh) { aKeep[i] = 0; //removal candidate } @@ -1452,20 +1457,20 @@ void SJN_MultiCueBGS::T_ClearNonEssentialEntries(short nClearNum, TextureModel** } //Step3: Perform removal - if (iKeepCnt == 0 || iKeepCnt == c->m_iNumEntries){ - for (i = 0; i < c->m_iNumEntries; i++){ + if (iKeepCnt == 0 || iKeepCnt == c->m_iNumEntries) { + for (i = 0; i < c->m_iNumEntries; i++) { c->m_Codewords[i]->m_iT_first_time = 1; c->m_Codewords[i]->m_iT_last_time = 1; c->m_Codewords[i]->m_iMNRL = 0; } } - else{ + else { iKeepCnt = 0; TextureCodeword** temp = (TextureCodeword**)malloc(sizeof(TextureCodeword*)*c->m_iNumEntries); - for (i = 0; i < c->m_iNumEntries; i++){ - if (aKeep[i] == 1){ + for (i = 0; i < c->m_iNumEntries; i++) { + if (aKeep[i] == 1) { temp[iKeepCnt] = c->m_Codewords[i]; temp[iKeepCnt]->m_iT_first_time = 1; temp[iKeepCnt]->m_iT_last_time = 1; @@ -1491,21 +1496,21 @@ void SJN_MultiCueBGS::T_ClearNonEssentialEntries(short nClearNum, TextureModel** //-----------------------------------------------------------------------------------------------------------------------------------------// // Clear non-essential codewords of the given codebook (only for the cache-book) // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::T_ClearNonEssentialEntriesForCachebook(uchar bLandmark, short* nReferredIdxArr, short nClearNum, TextureModel** pCachebook){ +void MultiCue::T_ClearNonEssentialEntriesForCachebook(uchar bLandmark, short* nReferredIdxArr, short nClearNum, TextureModel** pCachebook) { int i, n; short nNeighborNum = g_nNeighborNum; TextureModel* c; short nReferredIdx; - for (n = 0; n < nNeighborNum; n++){ + for (n = 0; n < nNeighborNum; n++) { c = pCachebook[n]; nReferredIdx = nReferredIdxArr[n]; //pCachebook->m_iTotal < nClearNum? --> MNRL update if (c->m_iTotal < nClearNum) { - for (i = 0; i < c->m_iNumEntries; i++){ + for (i = 0; i < c->m_iNumEntries; i++) { if (bLandmark == 255 && i == nReferredIdx) c->m_Codewords[i]->m_iMNRL = 0; else c->m_Codewords[i]->m_iMNRL++; } @@ -1514,7 +1519,7 @@ void SJN_MultiCueBGS::T_ClearNonEssentialEntriesForCachebook(uchar bLandmark, sh } //Perform clearing - else{ + else { int iStaleThreshold = 5; int* aKeep; @@ -1523,8 +1528,8 @@ void SJN_MultiCueBGS::T_ClearNonEssentialEntriesForCachebook(uchar bLandmark, sh aKeep = (int*)malloc(sizeof(int)*c->m_iNumEntries); nKeepCnt = 0; - for (i = 0; i < c->m_iNumEntries; i++){ - if (c->m_Codewords[i]->m_iMNRL < iStaleThreshold){ + for (i = 0; i < c->m_iNumEntries; i++) { + if (c->m_Codewords[i]->m_iMNRL < iStaleThreshold) { aKeep[i] = 1; nKeepCnt++; } @@ -1537,8 +1542,8 @@ void SJN_MultiCueBGS::T_ClearNonEssentialEntriesForCachebook(uchar bLandmark, sh TextureCodeword** temp = (TextureCodeword**)malloc(sizeof(TextureCodeword*)*c->m_iElementArraySize); nKeepCnt = 0; - for (i = 0; i < c->m_iNumEntries; i++){ - if (aKeep[i] == 1){ + for (i = 0; i < c->m_iNumEntries; i++) { + if (aKeep[i] == 1) { temp[nKeepCnt] = c->m_Codewords[i]; temp[nKeepCnt]->m_iMNRL = 0; nKeepCnt++; @@ -1564,7 +1569,7 @@ void SJN_MultiCueBGS::T_ClearNonEssentialEntriesForCachebook(uchar bLandmark, sh //-----------------------------------------------------------------------------------------------------------------------------------------// // the function to generate texture confidence maps // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::T_GetConfidenceMap_Par(uchar*** aXYZ, float** aTextureMap, point*** aNeiDirArr, TextureModel**** aModel){ +void MultiCue::T_GetConfidenceMap_Par(uchar*** aXYZ, float** aTextureMap, point*** aNeiDirArr, TextureModel**** aModel) { int iBound_w = g_iRWidth - g_nRadius; int iBound_h = g_iRHeight - g_nRadius; @@ -1572,10 +1577,10 @@ void SJN_MultiCueBGS::T_GetConfidenceMap_Par(uchar*** aXYZ, float** aTextureMap, short nNeighborNum = g_nNeighborNum; float fPadding = 5; - for (int h = 0; h < g_iRHeight; h++){ - for (int w = 0; w < g_iRWidth; w++){ + for (int h = 0; h < g_iRHeight; h++) { + for (int w = 0; w < g_iRWidth; w++) { - if (h < g_nRadius || h >= iBound_h || w < g_nRadius || w >= iBound_w){ + if (h < g_nRadius || h >= iBound_h || w < g_nRadius || w >= iBound_w) { aTextureMap[h][w] = 0; continue; } @@ -1585,7 +1590,7 @@ void SJN_MultiCueBGS::T_GetConfidenceMap_Par(uchar*** aXYZ, float** aTextureMap, float fDifference; point nei; - for (int i = 0; i < nNeighborNum; i++){ + for (int i = 0; i < nNeighborNum; i++) { nei.m_nX = aNeiDirArr[h][w][i].m_nX; nei.m_nY = aNeiDirArr[h][w][i].m_nY; @@ -1594,8 +1599,8 @@ void SJN_MultiCueBGS::T_GetConfidenceMap_Par(uchar*** aXYZ, float** aTextureMap, if (fDifference < 0) fDiffSum -= fDifference; else fDiffSum += fDifference; - for (int j = 0; j < aModel[h][w][i]->m_iNumEntries; j++){ - if (aModel[h][w][i]->m_Codewords[j]->m_fLowThre - fPadding <= fDifference && fDifference <= aModel[h][w][i]->m_Codewords[j]->m_fHighThre + fPadding){ + for (int j = 0; j < aModel[h][w][i]->m_iNumEntries; j++) { + if (aModel[h][w][i]->m_Codewords[j]->m_fLowThre - fPadding <= fDifference && fDifference <= aModel[h][w][i]->m_Codewords[j]->m_fHighThre + fPadding) { nMatchedCount++; break; } @@ -1609,21 +1614,21 @@ void SJN_MultiCueBGS::T_GetConfidenceMap_Par(uchar*** aXYZ, float** aTextureMap, //-----------------------------------------------------------------------------------------------------------------------------------------// // Absorbing Ghost Non-background Region Update // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::T_Absorption(int iAbsorbCnt, point pos, short*** aContinuCnt, short*** aRefferedIndex, TextureModel** pModel, TextureModel** pCache){ +void MultiCue::T_Absorption(int iAbsorbCnt, point pos, short*** aContinuCnt, short*** aRefferedIndex, TextureModel** pModel, TextureModel** pCache) { int i, j, k; int iLeavingIndex; - short g_nRadius = 2; + //short g_nRadius = 2; short nNeighborNum = g_nNeighborNum; - for (i = 0; i < nNeighborNum; i++){ + for (i = 0; i < nNeighborNum; i++) { //set iLeavingIndex if (aContinuCnt[pos.m_nY][pos.m_nX][i] < iAbsorbCnt) continue; iLeavingIndex = aRefferedIndex[pos.m_nY][pos.m_nX][i]; //array expansion - if (pModel[i]->m_iElementArraySize == pModel[i]->m_iNumEntries){ + if (pModel[i]->m_iElementArraySize == pModel[i]->m_iNumEntries) { pModel[i]->m_iElementArraySize = pModel[i]->m_iElementArraySize + 5; TextureCodeword** temp = (TextureCodeword**)malloc(sizeof(TextureCodeword*)*pModel[i]->m_iElementArraySize); for (j = 0; j < pModel[i]->m_iNumEntries; j++) temp[j] = pModel[i]->m_Codewords[j]; @@ -1643,9 +1648,9 @@ void SJN_MultiCueBGS::T_Absorption(int iAbsorbCnt, point pos, short*** aContinuC k = 0; TextureCodeword **temp_Cache = (TextureCodeword**)malloc(sizeof(TextureCodeword*)*pCache[i]->m_iElementArraySize); - for (j = 0; j < pCache[i]->m_iNumEntries; j++){ + for (j = 0; j < pCache[i]->m_iNumEntries; j++) { if (j == iLeavingIndex) continue; - else{ + else { temp_Cache[k] = pCache[i]->m_Codewords[j]; k++; } @@ -1659,7 +1664,7 @@ void SJN_MultiCueBGS::T_Absorption(int iAbsorbCnt, point pos, short*** aContinuC //-----------------------------------------------------------------------------------------------------------------------------------------// // the function to set neighborhood system // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::T_SetNeighborDirection(point*** aNeighborPos){ +void MultiCue::T_SetNeighborDirection(point*** aNeighborPos) { int i, j, k; point* aSearchDirection = (point*)malloc(sizeof(point)*g_nNeighborNum); @@ -1683,9 +1688,9 @@ void SJN_MultiCueBGS::T_SetNeighborDirection(point*** aNeighborPos){ point temp_pos; - for (i = 0; i < g_iRHeight; i++){ - for (j = 0; j < g_iRWidth; j++){ - for (k = 0; k < g_nNeighborNum; k++){ + for (i = 0; i < g_iRHeight; i++) { + for (j = 0; j < g_iRWidth; j++) { + for (k = 0; k < g_nNeighborNum; k++) { temp_pos.m_nX = j + aSearchDirection[k].m_nX; temp_pos.m_nY = i + aSearchDirection[k].m_nY; @@ -1707,16 +1712,16 @@ void SJN_MultiCueBGS::T_SetNeighborDirection(point*** aNeighborPos){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the color-model initialization function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::C_AllocateColorModelRelatedMemory(){ +void MultiCue::C_AllocateColorModelRelatedMemory() { int i, j; int iElementArraySize = 10; //codebook initialization g_ColorModel = (ColorModel***)malloc(sizeof(ColorModel**)*g_iRHeight); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { g_ColorModel[i] = (ColorModel**)malloc(sizeof(ColorModel*)*g_iRWidth); - for (j = 0; j < g_iRWidth; j++){ + for (j = 0; j < g_iRWidth; j++) { //initialization of each CodeBookArray. g_ColorModel[i][j] = (ColorModel*)malloc(sizeof(ColorModel)); g_ColorModel[i][j]->m_Codewords = (ColorCodeword**)malloc(sizeof(ColorCodeword*)*iElementArraySize); @@ -1728,13 +1733,13 @@ void SJN_MultiCueBGS::C_AllocateColorModelRelatedMemory(){ } //cache-book initialization - if (g_bAbsorptionEnable == TRUE){ + if (g_bAbsorptionEnable == TRUE) { iElementArraySize = 3; g_CCacheBook = (ColorModel***)malloc(sizeof(ColorModel**)*g_iRHeight); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { g_CCacheBook[i] = (ColorModel**)malloc(sizeof(ColorModel*)*g_iRWidth); - for (j = 0; j < g_iRWidth; j++){ + for (j = 0; j < g_iRWidth; j++) { //initialization of each CodeBookArray. g_CCacheBook[i][j] = (ColorModel*)malloc(sizeof(ColorModel)); g_CCacheBook[i][j]->m_Codewords = (ColorCodeword**)malloc(sizeof(ColorCodeword*)*iElementArraySize); @@ -1747,10 +1752,10 @@ void SJN_MultiCueBGS::C_AllocateColorModelRelatedMemory(){ g_aCReferredIndex = (short**)malloc(sizeof(short*)*g_iRHeight); g_aCContinuousCnt = (short**)malloc(sizeof(short*)*g_iRHeight); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { g_aCReferredIndex[i] = (short*)malloc(sizeof(short)*g_iRWidth); g_aCContinuousCnt[i] = (short*)malloc(sizeof(short)*g_iRWidth); - for (j = 0; j < g_iRWidth; j++){ + for (j = 0; j < g_iRWidth; j++) { g_aCReferredIndex[i][j] = -1; g_aCContinuousCnt[i][j] = 0; } @@ -1761,12 +1766,12 @@ void SJN_MultiCueBGS::C_AllocateColorModelRelatedMemory(){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the memory release function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::C_ReleaseColorModelRelatedMemory(){ +void MultiCue::C_ReleaseColorModelRelatedMemory() { int i, j, k; - for (i = 0; i < g_iRHeight; i++){ - for (j = 0; j < g_iRWidth; j++){ - for (k = 0; k < g_ColorModel[i][j]->m_iNumEntries; k++){ + for (i = 0; i < g_iRHeight; i++) { + for (j = 0; j < g_iRWidth; j++) { + for (k = 0; k < g_ColorModel[i][j]->m_iNumEntries; k++) { free(g_ColorModel[i][j]->m_Codewords[k]); } free(g_ColorModel[i][j]->m_Codewords); @@ -1776,10 +1781,10 @@ void SJN_MultiCueBGS::C_ReleaseColorModelRelatedMemory(){ } free(g_ColorModel); - if (g_bAbsorptionEnable == TRUE){ - for (i = 0; i < g_iRHeight; i++){ - for (j = 0; j < g_iRWidth; j++){ - for (k = 0; k < g_CCacheBook[i][j]->m_iNumEntries; k++){ + if (g_bAbsorptionEnable == TRUE) { + for (i = 0; i < g_iRHeight; i++) { + for (j = 0; j < g_iRWidth; j++) { + for (k = 0; k < g_CCacheBook[i][j]->m_iNumEntries; k++) { free(g_CCacheBook[i][j]->m_Codewords[k]); } free(g_CCacheBook[i][j]->m_Codewords); @@ -1789,7 +1794,7 @@ void SJN_MultiCueBGS::C_ReleaseColorModelRelatedMemory(){ } free(g_CCacheBook); - for (i = 0; i < g_iRHeight; i++){ + for (i = 0; i < g_iRHeight; i++) { free(g_aCReferredIndex[i]); free(g_aCContinuousCnt[i]); } @@ -1801,7 +1806,7 @@ void SJN_MultiCueBGS::C_ReleaseColorModelRelatedMemory(){ //-----------------------------------------------------------------------------------------------------------------------------------------// // the codebook construction function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::C_CodebookConstruction(uchar* aP, int iPosX, int iPosY, short nTrainVolRange, float fLearningRate, ColorModel* pC){ +void MultiCue::C_CodebookConstruction(uchar* aP, int iPosX, int iPosY, short nTrainVolRange, float fLearningRate, ColorModel* pC) { //Step1: matching short nMatchedIndex; @@ -1810,14 +1815,14 @@ void SJN_MultiCueBGS::C_CodebookConstruction(uchar* aP, int iPosX, int iPosY, sh nMatchedIndex = -1; - for (int i = 0; i < pC->m_iNumEntries; i++){ + for (int i = 0; i < pC->m_iNumEntries; i++) { //Checking X - if (pC->m_Codewords[i]->m_dMean[0] - nTrainVolRange <= aP[0] && aP[0] <= pC->m_Codewords[i]->m_dMean[0] + nTrainVolRange){ + if (pC->m_Codewords[i]->m_dMean[0] - nTrainVolRange <= aP[0] && aP[0] <= pC->m_Codewords[i]->m_dMean[0] + nTrainVolRange) { //Checking Y - if (pC->m_Codewords[i]->m_dMean[1] - nTrainVolRange <= aP[1] && aP[1] <= pC->m_Codewords[i]->m_dMean[1] + nTrainVolRange){ + if (pC->m_Codewords[i]->m_dMean[1] - nTrainVolRange <= aP[1] && aP[1] <= pC->m_Codewords[i]->m_dMean[1] + nTrainVolRange) { //Checking Z - if (pC->m_Codewords[i]->m_dMean[2] - nTrainVolRange <= aP[2] && aP[2] <= pC->m_Codewords[i]->m_dMean[2] + nTrainVolRange){ + if (pC->m_Codewords[i]->m_dMean[2] - nTrainVolRange <= aP[2] && aP[2] <= pC->m_Codewords[i]->m_dMean[2] + nTrainVolRange) { nMatchedIndex = i; break; } @@ -1828,11 +1833,11 @@ void SJN_MultiCueBGS::C_CodebookConstruction(uchar* aP, int iPosX, int iPosY, sh pC->m_iTotal = pC->m_iTotal + 1; //Step2 : adding a new element - if (nMatchedIndex == -1){ - if (pC->m_iElementArraySize == pC->m_iNumEntries){ + if (nMatchedIndex == -1) { + if (pC->m_iElementArraySize == pC->m_iNumEntries) { pC->m_iElementArraySize = pC->m_iElementArraySize + 5; ColorCodeword **temp = (ColorCodeword**)malloc(sizeof(ColorCodeword*)*pC->m_iElementArraySize); - for (int j = 0; j < pC->m_iNumEntries; j++){ + for (int j = 0; j < pC->m_iNumEntries; j++) { temp[j] = pC->m_Codewords[j]; pC->m_Codewords[j] = NULL; } @@ -1853,7 +1858,7 @@ void SJN_MultiCueBGS::C_CodebookConstruction(uchar* aP, int iPosX, int iPosY, sh } //Step3 : update - else{ + else { //m_dMean update pC->m_Codewords[nMatchedIndex]->m_dMean[0] = (fLearningRate*aP[0]) + fNegLearningRate*pC->m_Codewords[nMatchedIndex]->m_dMean[0];//X pC->m_Codewords[nMatchedIndex]->m_dMean[1] = (fLearningRate*aP[1]) + fNegLearningRate*pC->m_Codewords[nMatchedIndex]->m_dMean[1];//Y @@ -1863,10 +1868,10 @@ void SJN_MultiCueBGS::C_CodebookConstruction(uchar* aP, int iPosX, int iPosY, sh } //cache-book handling - if (pC->m_bID == 1){ + if (pC->m_bID == 1) { //1. m_iMNRL update int iNegTime; - for (int i = 0; i < pC->m_iNumEntries; i++){ + for (int i = 0; i < pC->m_iNumEntries; i++) { //m_iMNRL update iNegTime = pC->m_iTotal - pC->m_Codewords[i]->m_iT_last_time + pC->m_Codewords[i]->m_iT_first_time - 1; if (pC->m_Codewords[i]->m_iMNRL < iNegTime) pC->m_Codewords[i]->m_iMNRL = iNegTime; @@ -1876,18 +1881,18 @@ void SJN_MultiCueBGS::C_CodebookConstruction(uchar* aP, int iPosX, int iPosY, sh if (g_bAbsorptionEnable == TRUE) g_aCReferredIndex[iPosY][iPosX] = -1; } - else{ + else { //1. m_iMNRL update: if (nMatchedIndex == -1) pC->m_Codewords[pC->m_iNumEntries - 1]->m_iMNRL = 0; //2. g_aCReferredIndex[iPosY][iPosX] update - if (nMatchedIndex == -1){ + if (nMatchedIndex == -1) { g_aCReferredIndex[iPosY][iPosX] = pC->m_iNumEntries - 1; g_aCContinuousCnt[iPosY][iPosX] = 1; } - else{ + else { if (nMatchedIndex == g_aCReferredIndex[iPosY][iPosX]) g_aCContinuousCnt[iPosY][iPosX]++; - else{ + else { g_aCReferredIndex[iPosY][iPosX] = nMatchedIndex; g_aCContinuousCnt[iPosY][iPosX] = 1; } @@ -1898,7 +1903,7 @@ void SJN_MultiCueBGS::C_CodebookConstruction(uchar* aP, int iPosX, int iPosY, sh //-----------------------------------------------------------------------------------------------------------------------------------------// // Clear non-essential codewords of the given codebook // // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::C_ClearNonEssentialEntries(short nClearNum, ColorModel* pModel){ +void MultiCue::C_ClearNonEssentialEntries(short nClearNum, ColorModel* pModel) { int i; short nStaleThresh = (int)(nClearNum*0.5); short nKeepCnt; @@ -1914,7 +1919,7 @@ void SJN_MultiCueBGS::C_ClearNonEssentialEntries(short nClearNum, ColorModel* pM nKeepCnt = 0; //Step2: Find non-essential codewords - for (i = 0; im_iNumEntries; i++){ + for (i = 0; i < pC->m_iNumEntries; i++) { if (pC->m_Codewords[i]->m_iMNRL > nStaleThresh) { aKeep[i] = 0; //removal } @@ -1925,19 +1930,19 @@ void SJN_MultiCueBGS::C_ClearNonEssentialEntries(short nClearNum, ColorModel* pM } //Step3: Perform removal - if (nKeepCnt == 0 || nKeepCnt == pC->m_iNumEntries){ - for (i = 0; i < pC->m_iNumEntries; i++){ + if (nKeepCnt == 0 || nKeepCnt == pC->m_iNumEntries) { + for (i = 0; i < pC->m_iNumEntries; i++) { pC->m_Codewords[i]->m_iT_first_time = 1; pC->m_Codewords[i]->m_iT_last_time = 1; pC->m_Codewords[i]->m_iMNRL = 0; } } - else{ + else { nKeepCnt = 0; ColorCodeword** temp = (ColorCodeword**)malloc(sizeof(ColorCodeword*)*pC->m_iNumEntries); - for (i = 0; i < pC->m_iNumEntries; i++){ - if (aKeep[i] == 1){ + for (i = 0; i < pC->m_iNumEntries; i++) { + if (aKeep[i] == 1) { temp[nKeepCnt] = pC->m_Codewords[i]; temp[nKeepCnt]->m_iT_first_time = 1; temp[nKeepCnt]->m_iT_last_time = 1; @@ -1962,11 +1967,11 @@ void SJN_MultiCueBGS::C_ClearNonEssentialEntries(short nClearNum, ColorModel* pM //-----------------------------------------------------------------------------------------------------------------------------------------// // Clear non-essential codewords of the given codebook (for cache-book) // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::C_ClearNonEssentialEntriesForCachebook(uchar bLandmark, short nReferredIdx, short nClearNum, ColorModel* pCachebook){ +void MultiCue::C_ClearNonEssentialEntriesForCachebook(uchar bLandmark, short nReferredIdx, short nClearNum, ColorModel* pCachebook) { int i; if (pCachebook->m_iTotal < nClearNum) { - for (i = 0; i < pCachebook->m_iNumEntries; i++){ + for (i = 0; i < pCachebook->m_iNumEntries; i++) { if (bLandmark == 255 && i == nReferredIdx) pCachebook->m_Codewords[i]->m_iMNRL = 0; else pCachebook->m_Codewords[i]->m_iMNRL++; } @@ -1974,7 +1979,7 @@ void SJN_MultiCueBGS::C_ClearNonEssentialEntriesForCachebook(uchar bLandmark, sh pCachebook->m_iTotal++; } - else{ + else { int iStaleThreshold = 5; int* aKeep; @@ -1983,8 +1988,8 @@ void SJN_MultiCueBGS::C_ClearNonEssentialEntriesForCachebook(uchar bLandmark, sh aKeep = (int*)malloc(sizeof(int)*pCachebook->m_iNumEntries); nKeepCnt = 0; - for (i = 0; i < pCachebook->m_iNumEntries; i++){ - if (pCachebook->m_Codewords[i]->m_iMNRL < iStaleThreshold){ + for (i = 0; i < pCachebook->m_iNumEntries; i++) { + if (pCachebook->m_Codewords[i]->m_iMNRL < iStaleThreshold) { aKeep[i] = 1; nKeepCnt++; } @@ -1997,8 +2002,8 @@ void SJN_MultiCueBGS::C_ClearNonEssentialEntriesForCachebook(uchar bLandmark, sh ColorCodeword** temp = (ColorCodeword**)malloc(sizeof(ColorCodeword*)*pCachebook->m_iElementArraySize); nKeepCnt = 0; - for (i = 0; i < pCachebook->m_iNumEntries; i++){ - if (aKeep[i] == 1){ + for (i = 0; i < pCachebook->m_iNumEntries; i++) { + if (aKeep[i] == 1) { temp[nKeepCnt] = pCachebook->m_Codewords[i]; temp[nKeepCnt]->m_iMNRL = 0; nKeepCnt++; @@ -2022,7 +2027,7 @@ void SJN_MultiCueBGS::C_ClearNonEssentialEntriesForCachebook(uchar bLandmark, sh //-----------------------------------------------------------------------------------------------------------------------------------------// // the ghost-region absorption function // //-----------------------------------------------------------------------------------------------------------------------------------------// -void SJN_MultiCueBGS::C_Absorption(int iAbsorbCnt, point pos, short** aContinuCnt, short** aRefferedIndex, ColorModel* pModel, ColorModel* pCache){ +void MultiCue::C_Absorption(int iAbsorbCnt, point pos, short** aContinuCnt, short** aRefferedIndex, ColorModel* pModel, ColorModel* pCache) { //set iLeavingIndex if (aContinuCnt[pos.m_nY][pos.m_nX] < iAbsorbCnt) return; @@ -2030,7 +2035,7 @@ void SJN_MultiCueBGS::C_Absorption(int iAbsorbCnt, point pos, short** aContinuCn int iLeavingIndex = aRefferedIndex[pos.m_nY][pos.m_nX]; //array expansion - if (pModel->m_iElementArraySize == pModel->m_iNumEntries){ + if (pModel->m_iElementArraySize == pModel->m_iNumEntries) { pModel->m_iElementArraySize = pModel->m_iElementArraySize + 5; ColorCodeword** temp = (ColorCodeword**)malloc(sizeof(ColorCodeword*)*pModel->m_iElementArraySize); for (int i = 0; i < pModel->m_iNumEntries; i++) temp[i] = pModel->m_Codewords[i]; @@ -2051,9 +2056,9 @@ void SJN_MultiCueBGS::C_Absorption(int iAbsorbCnt, point pos, short** aContinuCn int k = 0; ColorCodeword **pTempCache = (ColorCodeword**)malloc(sizeof(ColorCodeword*)*pCache->m_iElementArraySize); - for (int i = 0; i < pCache->m_iNumEntries; i++){ + for (int i = 0; i < pCache->m_iNumEntries; i++) { if (i == iLeavingIndex) continue; - else{ + else { pTempCache[k] = pCache->m_Codewords[i]; k++; } diff --git a/package_bgs/MultiCue.h b/package_bgs/MultiCue.h new file mode 100644 index 0000000..44524e0 --- /dev/null +++ b/package_bgs/MultiCue.h @@ -0,0 +1,254 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#define MIN3(x,y,z) ((y) <= (z) ? ((x) <= (y) ? (x) : (y)) : ((x) <= (z) ? (x) : (z))) +#define MAX3(x,y,z) ((y) >= (z) ? ((x) >= (y) ? (x) : (y)) : ((x) >= (z) ? (x) : (z))) + +#ifndef PI +#define PI 3.14159 +#endif + +typedef int BOOL; + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#if !defined(__APPLE__) +#include +#endif +#include "math.h" + +#include +#include +#include + +#include "IBGS.h" + +//------------------------------------Structure Lists-------------------------------------// +namespace bgslibrary +{ + namespace algorithms + { + namespace libMultiCue + { + struct point { + short m_nX; + short m_nY; + }; + + struct neighbor_pos { + short m_nX; + short m_nY; + }; + //1) Bounding Box Structure + struct BoundingBoxInfo { + int m_iBoundBoxNum; //# of bounding boxes for all foreground and false-positive blobs + int m_iArraySize; //the size of the below arrays to store bounding box information + + short *m_aLeft, *m_aRight, *m_aUpper, *m_aBottom; //arrays to store bounding box information for (the original frame size) + short *m_aRLeft, *m_aRRight, *m_aRUpper, *m_aRBottom; //arrays to store bounding box information for (the reduced frame size) + BOOL* m_ValidBox; //If this value is true, the corresponding bounding box is for a foreground blob. + //Else, it is for a false-positive blob + }; + + //2) Texture Model Structure + struct TextureCodeword { + int m_iMNRL; //the maximum negative run-length + int m_iT_first_time; //the first access time + int m_iT_last_time; //the last access time + + float m_fLowThre; //a low threshold for the matching + float m_fHighThre; //a high threshold for the matching + float m_fMean; //mean of the codeword + }; + + struct TextureModel { + TextureCodeword** m_Codewords; //the texture-codeword Array + + int m_iTotal; //# of learned samples after the last clear process + int m_iElementArraySize; //the array size of m_Codewords + int m_iNumEntries; //# of codewords + + BOOL m_bID; //id=1 --> background model, id=0 --> cachebook + }; + + //3) Color Model Structure + struct ColorCodeword { + int m_iMNRL; //the maximum negative run-length + int m_iT_first_time; //the first access time + int m_iT_last_time; //the last access time + + double m_dMean[3]; //mean vector of the codeword + + }; + + struct ColorModel { + ColorCodeword** m_Codewords; //the color-codeword Array + + int m_iTotal; //# of learned samples after the last clear process + int m_iElementArraySize; //the array size of m_Codewords + int m_iNumEntries; //# of codewords + + BOOL m_bID; //id=1 --> background model, id=0 --> cachebookk + }; + } + } +} + +namespace bgslibrary +{ + namespace algorithms + { + using namespace bgslibrary::algorithms::libMultiCue; + + class MultiCue : public IBGS + { + private: + void saveConfig(); + void loadConfig(); + + public: + MultiCue(); + ~MultiCue(); + + public: + //---------------------------------------------------- + // APIs and User-Adjustable Parameters + //---------------------------------------------------- + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); //the main function to background modeling and subtraction + + void GetForegroundMap(IplImage* return_image, IplImage* input_frame = NULL); //the function returning a foreground binary-map + void Destroy(); //the function to release allocated memories + + int g_iTrainingPeriod; //the training period (The parameter t in the paper) + int g_iT_ModelThreshold; //the threshold for texture-model based BGS. (The parameter tau_T in the paper) + int g_iC_ModelThreshold; //the threshold for appearance based verification. (The parameter tau_A in the paper) + + float g_fLearningRate; //the learning rate for background models. (The parameter alpha in the paper) + + short g_nTextureTrainVolRange; //the codebook size factor for texture models. (The parameter k in the paper) + short g_nColorTrainVolRange; //the codebook size factor for color models. (The parameter eta_1 in the paper) + + public: + //---------------------------------------------------- + // Implemented Function Lists + //---------------------------------------------------- + + //--1) General Functions + void Initialize(IplImage* frame); + + void PreProcessing(IplImage* frame); + void ReduceImageSize(IplImage* SrcImage, IplImage* DstImage); + void GaussianFiltering(IplImage* frame, uchar*** aFilteredFrame); + void BGR2HSVxyz_Par(uchar*** aBGR, uchar*** aXYZ); + + void BackgroundModeling_Par(IplImage* frame); + void ForegroundExtraction(IplImage* frame); + void CreateLandmarkArray_Par(float fConfThre, short nTrainVolRange, float**aConfMap, int iNehborNum, uchar*** aXYZ, + point*** aNeiDir, TextureModel**** TModel, ColorModel*** CModel, uchar**aLandmarkArr); + + void PostProcessing(IplImage* frame); + void MorphologicalOpearions(uchar** aInput, uchar** aOutput, double dThresholdRatio, int iMaskSize, int iWidth, int iHeight); + void Labeling(uchar** aBinaryArray, int* pLabelCount, int** aLabelTable); + void SetBoundingBox(int iLabelCount, int** aLabelTable); + void BoundBoxVerification(IplImage* frame, uchar** aResForeMap, BoundingBoxInfo* BoundBoxInfo); + void EvaluateBoxSize(BoundingBoxInfo* BoundBoxInfo); + void EvaluateOverlapRegionSize(BoundingBoxInfo* SrcBoxInfo); + void EvaluateGhostRegion(IplImage* frame, uchar** aResForeMap, BoundingBoxInfo* BoundBoxInfo); + double CalculateHausdorffDist(IplImage* input_image, IplImage* model_image); + void RemovingInvalidForeRegions(uchar** aResForeMap, BoundingBoxInfo* BoundBoxInfo); + + void UpdateModel_Par(); + void GetEnlargedMap(float** aOriginMap, float** aEnlargedMap); + + //--2) Texture Model Related Functions + void T_AllocateTextureModelRelatedMemory(); + void T_ReleaseTextureModelRelatedMemory(); + void T_SetNeighborDirection(point*** aNeighborPos); + void T_ModelConstruction(short nTrainVolRange, float fLearningRate, uchar*** aXYZ, point center, point* aNei, TextureModel** aModel); + void T_ClearNonEssentialEntries(short nClearNum, TextureModel** aModel); + void T_ClearNonEssentialEntriesForCachebook(uchar bLandmark, short* nReferredIdxArr, short nClearNum, TextureModel** pCachebook); + void T_GetConfidenceMap_Par(uchar*** aXYZ, float** aTextureMap, point*** aNeiDirArr, TextureModel**** aModel); + void T_Absorption(int iAbsorbCnt, point pos, short*** aContinuCnt, short*** aRefferedIndex, TextureModel** pModel, TextureModel** pCache); + + //--3) Color Model Related Functions + void C_AllocateColorModelRelatedMemory(); + void C_ReleaseColorModelRelatedMemory(); + void C_CodebookConstruction(uchar* aP, int iPosX, int iPosY, short nTrainVolRange, float fLearningRate, ColorModel* pC); + void C_ClearNonEssentialEntries(short nClearNum, ColorModel* pModel); + void C_ClearNonEssentialEntriesForCachebook(uchar bLandmark, short nReferredIdx, short nClearNum, ColorModel* pCachebook); + void C_Absorption(int iAbsorbCnt, point pos, short** aContinuCnt, short** aRefferedIndex, ColorModel* pModel, ColorModel* pCache); + public: + //---------------------------------------------------- + // Implemented Variable Lists + //---------------------------------------------------- + + //--1) General Variables + int g_iFrameCount; //the counter of processed frames + + int g_iBackClearPeriod; //the period to clear background models + int g_iCacheClearPeriod; //the period to clear cache-book models + + int g_iAbsortionPeriod; //the period to absorb static ghost regions + BOOL g_bAbsorptionEnable; //If True, procedures for ghost region absorption are activated. + + BOOL g_bModelMemAllocated; //To handle memory.. + BOOL g_bNonModelMemAllocated; //To handle memory.. + + float g_fConfidenceThre; //the final decision threshold + + int g_iWidth, g_iHeight; //width and height of input frames + int g_iRWidth, g_iRHeight; //width and height of reduced frames (For efficiency, the reduced size of frames are processed) + int g_iForegroundNum; //# of detected foreground regions + BOOL g_bForegroundMapEnable; //TRUE only when BGS is successful + + IplImage* g_ResizedFrame; //reduced size of frame (For efficiency, the reduced size of frames are processed) + uchar*** g_aGaussFilteredFrame; + uchar*** g_aXYZFrame; + uchar** g_aLandmarkArray; //the landmark map + uchar** g_aResizedForeMap; //the resized foreground map + uchar** g_aForegroundMap; //the final foreground map + BOOL** g_aUpdateMap; //the location map of update candidate pixels + + BoundingBoxInfo* g_BoundBoxInfo; //the array of bounding boxes of each foreground blob + + //--2) Texture Model Related + TextureModel**** g_TextureModel; //the texture background model + TextureModel**** g_TCacheBook; //the texture cache-book + short*** g_aTReferredIndex; //To handle cache-book + short*** g_aTContinuousCnt; //To handle cache-book + point*** g_aNeighborDirection; + float**g_aTextureConfMap; //the texture confidence map + + short g_nNeighborNum; //# of neighborhoods + short g_nRadius; + short g_nBoundarySize; + + //--3) Texture Model Related + ColorModel*** g_ColorModel; //the color background model + ColorModel*** g_CCacheBook; //the color cache-book + short** g_aCReferredIndex; //To handle cache-book + short** g_aCContinuousCnt; //To handle cache-book + }; + } +} diff --git a/package_bgs/jmo/MultiLayerBGS.cpp b/package_bgs/MultiLayer.cpp similarity index 65% rename from package_bgs/jmo/MultiLayerBGS.cpp rename to package_bgs/MultiLayer.cpp index 03e223f..180d88d 100644 --- a/package_bgs/jmo/MultiLayerBGS.cpp +++ b/package_bgs/MultiLayer.cpp @@ -14,36 +14,40 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "MultiLayerBGS.h" +#include "MultiLayer.h" -MultiLayerBGS::MultiLayerBGS() : firstTime(true), frameNumber(0), showOutput(true), -saveModel(false), disableDetectMode(true), disableLearning(false), detectAfter(0), bg_model_preload(""), loadDefaultParams(true) +using namespace bgslibrary::algorithms; + +MultiLayer::MultiLayer() : + frameNumber(0), saveModel(false), disableDetectMode(true), disableLearning(false), + detectAfter(0), bg_model_preload(""), loadDefaultParams(true) { - std::cout << "MultiLayerBGS()" << std::endl; + std::cout << "MultiLayer()" << std::endl; + setup("./config/MultiLayer.xml"); } -MultiLayerBGS::~MultiLayerBGS() +MultiLayer::~MultiLayer() { finish(); - std::cout << "~MultiLayerBGS()" << std::endl; + std::cout << "~MultiLayer()" << std::endl; } -void MultiLayerBGS::setStatus(Status _status) +void MultiLayer::setStatus(Status _status) { status = _status; } -void MultiLayerBGS::finish(void) +void MultiLayer::finish() { if (bg_model_preload.empty()) { - bg_model_preload = "./models/MultiLayerBGSModel.yml"; + bg_model_preload = "./MultiLayerModel.yml"; saveConfig(); } if (status == MLBGS_LEARN && saveModel == true) { - std::cout << "MultiLayerBGS saving background model: " << bg_model_preload << std::endl; + std::cout << "MultiLayer saving background model: " << bg_model_preload << std::endl; BGS->Save(bg_model_preload.c_str()); } @@ -57,13 +61,9 @@ void MultiLayerBGS::finish(void) delete BGS; } -void MultiLayerBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void MultiLayer::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if (img_input.empty()) - return; - - loadConfig(); - + init(img_input, img_output, img_bgmodel); CvSize img_size = cvSize(cvCeil((double)img_input.size().width), cvCeil((double)img_input.size().height)); if (firstTime) @@ -72,10 +72,10 @@ void MultiLayerBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::M status = MLBGS_LEARN; if (status == MLBGS_LEARN) - std::cout << "MultiLayerBGS in LEARN mode" << std::endl; + std::cout << "MultiLayer in LEARN mode" << std::endl; if (status == MLBGS_DETECT) - std::cout << "MultiLayerBGS in DETECT mode" << std::endl; + std::cout << "MultiLayer in DETECT mode" << std::endl; org_img = new IplImage(img_input); @@ -93,7 +93,7 @@ void MultiLayerBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::M if (bg_model_preload.empty() == false) { - std::cout << "MultiLayerBGS loading background model: " << bg_model_preload << std::endl; + std::cout << "MultiLayer loading background model: " << bg_model_preload << std::endl; BGS->Load(bg_model_preload.c_str()); } @@ -102,14 +102,14 @@ void MultiLayerBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::M BGS->m_disableLearning = disableLearning; if (disableLearning) - std::cout << "MultiLayerBGS disabled learning in DETECT mode" << std::endl; + std::cout << "MultiLayer disabled learning in DETECT mode" << std::endl; else - std::cout << "MultiLayerBGS enabled learning in DETECT mode" << std::endl; + std::cout << "MultiLayer enabled learning in DETECT mode" << std::endl; } if (loadDefaultParams) { - std::cout << "MultiLayerBGS loading default params" << std::endl; + std::cout << "MultiLayer loading default params" << std::endl; max_mode_num = 5; weight_updating_constant = 5.0; @@ -127,7 +127,7 @@ void MultiLayerBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::M bilater_filter_sigma_r = 0.1f; } else - std::cout << "MultiLayerBGS loading config params" << std::endl; + std::cout << "MultiLayer loading config params" << std::endl; BGS->m_nMaxLBPModeNum = max_mode_num; BGS->m_fWeightUpdatingConstant = weight_updating_constant; @@ -186,9 +186,6 @@ void MultiLayerBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::M } BGS->SetParameters(max_mode_num, mode_learn_rate_per_second, weight_learn_rate_per_second, init_mode_weight); - - saveConfig(); - delete org_img; } @@ -199,7 +196,7 @@ void MultiLayerBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::M if (detectAfter > 0 && detectAfter == frameNumber) { - std::cout << "MultiLayerBGS in DETECT mode" << std::endl; + std::cout << "MultiLayer in DETECT mode" << std::endl; status = MLBGS_DETECT; @@ -212,9 +209,9 @@ void MultiLayerBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::M BGS->m_disableLearning = disableLearning; if (disableLearning) - std::cout << "MultiLayerBGS disabled learning in DETECT mode" << std::endl; + std::cout << "MultiLayer disabled learning in DETECT mode" << std::endl; else - std::cout << "MultiLayerBGS enabled learning in DETECT mode" << std::endl; + std::cout << "MultiLayer enabled learning in DETECT mode" << std::endl; } IplImage* img = new IplImage(img_input); @@ -228,15 +225,17 @@ void MultiLayerBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::M BGS->GetForegroundMaskImage(fg_mask_img); BGS->MergeImages(4, img, bg_img, fg_prob_img3, fg_img, merged_img); - img_merged = cv::Mat(merged_img); - img_foreground = cv::Mat(fg_mask_img); - img_background = cv::Mat(bg_img); + img_merged = cv::cvarrToMat(merged_img); + img_foreground = cv::cvarrToMat(fg_mask_img); + img_background = cv::cvarrToMat(bg_img); +#ifndef MEX_COMPILE_FLAG if (showOutput) { cv::imshow("MLBGS Layers", img_merged); cv::imshow("MLBGS FG Mask", img_foreground); } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); @@ -248,9 +247,9 @@ void MultiLayerBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::M frameNumber++; } -void MultiLayerBGS::saveConfig() +void MultiLayer::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/MultiLayerBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteString(fs, "preloadModel", bg_model_preload.c_str()); cvWriteInt(fs, "saveModel", saveModel); @@ -289,43 +288,43 @@ void MultiLayerBGS::saveConfig() cvReleaseFileStorage(&fs); } -void MultiLayerBGS::loadConfig() +void MultiLayer::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/MultiLayerBGS.xml", 0, CV_STORAGE_READ); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); - bg_model_preload = cvReadStringByName(fs, 0, "preloadModel", ""); - saveModel = cvReadIntByName(fs, 0, "saveModel", false); - detectAfter = cvReadIntByName(fs, 0, "detectAfter", 0); - disableDetectMode = cvReadIntByName(fs, 0, "disableDetectMode", true); - disableLearning = cvReadIntByName(fs, 0, "disableLearningInDetecMode", false); - loadDefaultParams = cvReadIntByName(fs, 0, "loadDefaultParams", true); + bg_model_preload = cvReadStringByName(fs, nullptr, "preloadModel", ""); + saveModel = cvReadIntByName(fs, nullptr, "saveModel", false); + detectAfter = cvReadIntByName(fs, nullptr, "detectAfter", 0); + disableDetectMode = cvReadIntByName(fs, nullptr, "disableDetectMode", true); + disableLearning = cvReadIntByName(fs, nullptr, "disableLearningInDetecMode", false); + loadDefaultParams = cvReadIntByName(fs, nullptr, "loadDefaultParams", true); - max_mode_num = cvReadIntByName(fs, 0, "max_mode_num", 5); + max_mode_num = cvReadIntByName(fs, nullptr, "max_mode_num", 5); weight_updating_constant = cvReadRealByName(fs, 0, "weight_updating_constant", 5.0); - texture_weight = cvReadRealByName(fs, 0, "texture_weight", 0.5); - bg_mode_percent = cvReadRealByName(fs, 0, "bg_mode_percent", 0.6); - pattern_neig_half_size = cvReadIntByName(fs, 0, "pattern_neig_half_size", 4); - pattern_neig_gaus_sigma = cvReadRealByName(fs, 0, "pattern_neig_gaus_sigma", 3.0); - bg_prob_threshold = cvReadRealByName(fs, 0, "bg_prob_threshold", 0.2); - bg_prob_updating_threshold = cvReadRealByName(fs, 0, "bg_prob_updating_threshold", 0.2); - robust_LBP_constant = cvReadIntByName(fs, 0, "robust_LBP_constant", 3); - min_noised_angle = cvReadRealByName(fs, 0, "min_noised_angle", 0.01768); - shadow_rate = cvReadRealByName(fs, 0, "shadow_rate", 0.6); - highlight_rate = cvReadRealByName(fs, 0, "highlight_rate", 1.2); - bilater_filter_sigma_s = cvReadRealByName(fs, 0, "bilater_filter_sigma_s", 3.0); - bilater_filter_sigma_r = cvReadRealByName(fs, 0, "bilater_filter_sigma_r", 0.1); - - frame_duration = cvReadRealByName(fs, 0, "frame_duration", 0.1); - - learn_mode_learn_rate_per_second = cvReadRealByName(fs, 0, "learn_mode_learn_rate_per_second", 0.5); - learn_weight_learn_rate_per_second = cvReadRealByName(fs, 0, "learn_weight_learn_rate_per_second", 0.5); - learn_init_mode_weight = cvReadRealByName(fs, 0, "learn_init_mode_weight", 0.05); - - detect_mode_learn_rate_per_second = cvReadRealByName(fs, 0, "detect_mode_learn_rate_per_second", 0.01); - detect_weight_learn_rate_per_second = cvReadRealByName(fs, 0, "detect_weight_learn_rate_per_second", 0.01); - detect_init_mode_weight = cvReadRealByName(fs, 0, "detect_init_mode_weight", 0.001); - - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + texture_weight = cvReadRealByName(fs, nullptr, "texture_weight", 0.5); + bg_mode_percent = cvReadRealByName(fs, nullptr, "bg_mode_percent", 0.6); + pattern_neig_half_size = cvReadIntByName(fs, nullptr, "pattern_neig_half_size", 4); + pattern_neig_gaus_sigma = cvReadRealByName(fs, nullptr, "pattern_neig_gaus_sigma", 3.0); + bg_prob_threshold = cvReadRealByName(fs, nullptr, "bg_prob_threshold", 0.2); + bg_prob_updating_threshold = cvReadRealByName(fs, nullptr, "bg_prob_updating_threshold", 0.2); + robust_LBP_constant = cvReadIntByName(fs, nullptr, "robust_LBP_constant", 3); + min_noised_angle = cvReadRealByName(fs, nullptr, "min_noised_angle", 0.01768); + shadow_rate = cvReadRealByName(fs, nullptr, "shadow_rate", 0.6); + highlight_rate = cvReadRealByName(fs, nullptr, "highlight_rate", 1.2); + bilater_filter_sigma_s = cvReadRealByName(fs, nullptr, "bilater_filter_sigma_s", 3.0); + bilater_filter_sigma_r = cvReadRealByName(fs, nullptr, "bilater_filter_sigma_r", 0.1); + + frame_duration = cvReadRealByName(fs, nullptr, "frame_duration", 0.1); + + learn_mode_learn_rate_per_second = cvReadRealByName(fs, nullptr, "learn_mode_learn_rate_per_second", 0.5); + learn_weight_learn_rate_per_second = cvReadRealByName(fs, nullptr, "learn_weight_learn_rate_per_second", 0.5); + learn_init_mode_weight = cvReadRealByName(fs, nullptr, "learn_init_mode_weight", 0.05); + + detect_mode_learn_rate_per_second = cvReadRealByName(fs, nullptr, "detect_mode_learn_rate_per_second", 0.01); + detect_weight_learn_rate_per_second = cvReadRealByName(fs, nullptr, "detect_weight_learn_rate_per_second", 0.01); + detect_init_mode_weight = cvReadRealByName(fs, nullptr, "detect_init_mode_weight", 0.001); + + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/MultiLayer.h b/package_bgs/MultiLayer.h new file mode 100644 index 0000000..af7e128 --- /dev/null +++ b/package_bgs/MultiLayer.h @@ -0,0 +1,99 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "MultiLayer/CMultiLayerBGS.h" + +namespace bgslibrary +{ + namespace algorithms + { + class MultiLayer : public IBGS + { + public: + enum Status + { + MLBGS_NONE = -1, + MLBGS_LEARN = 0, + MLBGS_DETECT = 1 + }; + + private: + long long frameNumber; + cv::Mat img_merged; + bool saveModel; + bool disableDetectMode; + bool disableLearning; + int detectAfter; + CMultiLayerBGS* BGS; + Status status; + IplImage* img; + IplImage* org_img; + IplImage* fg_img; + IplImage* bg_img; + IplImage* fg_prob_img; + IplImage* fg_mask_img; + IplImage* fg_prob_img3; + IplImage* merged_img; + std::string bg_model_preload; + + bool loadDefaultParams; + + int max_mode_num; + float weight_updating_constant; + float texture_weight; + float bg_mode_percent; + int pattern_neig_half_size; + float pattern_neig_gaus_sigma; + float bg_prob_threshold; + float bg_prob_updating_threshold; + int robust_LBP_constant; + float min_noised_angle; + float shadow_rate; + float highlight_rate; + float bilater_filter_sigma_s; + float bilater_filter_sigma_r; + + float frame_duration; + + float mode_learn_rate_per_second; + float weight_learn_rate_per_second; + float init_mode_weight; + + float learn_mode_learn_rate_per_second; + float learn_weight_learn_rate_per_second; + float learn_init_mode_weight; + + float detect_mode_learn_rate_per_second; + float detect_weight_learn_rate_per_second; + float detect_init_mode_weight; + + public: + MultiLayer(); + ~MultiLayer(); + + void setStatus(Status status); + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void finish(); + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/jmo/BGS.h b/package_bgs/MultiLayer/BGS.h similarity index 98% rename from package_bgs/jmo/BGS.h rename to package_bgs/MultiLayer/BGS.h index c506df2..ad2129b 100644 --- a/package_bgs/jmo/BGS.h +++ b/package_bgs/MultiLayer/BGS.h @@ -40,10 +40,9 @@ along with BGSLibrary. If not, see . * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#if !defined(_BGS_H_) -#define _BGS_H_ +#pragma once -#include +#include "OpenCvLegacyIncludes.h" // TODO check these defines are not used (or not redundant with real params) @@ -211,6 +210,3 @@ class IMAGE_BG_MODEL delete[] pixel_PATTERNs; } }; - - -#endif // !defined(_BGS_H_) diff --git a/package_bgs/jmo/BackgroundSubtractionAPI.h b/package_bgs/MultiLayer/BackgroundSubtractionAPI.h similarity index 89% rename from package_bgs/jmo/BackgroundSubtractionAPI.h rename to package_bgs/MultiLayer/BackgroundSubtractionAPI.h index bb39611..409fa40 100644 --- a/package_bgs/jmo/BackgroundSubtractionAPI.h +++ b/package_bgs/MultiLayer/BackgroundSubtractionAPI.h @@ -60,12 +60,9 @@ along with BGSLibrary. If not, see . // char image for the output // ////////////////////////////////////////////////////////////////////// +#pragma once - -#if !defined(_BACKGROUND_SUBTRACTION_API_H_) -#define _BACKGROUND_SUBTRACTION_API_H_ - -#include +#include "OpenCvLegacyIncludes.h" class CBackgroundSubtractionAPI { @@ -79,14 +76,14 @@ class CBackgroundSubtractionAPI void Init(int width, int height); //------------------------------------------------------------- - // PROVIDE A MASK TO DEFINE THE SET OF POINTS WHERE BACKGROUND + // PROVIDE A MASK TO DEFINE THE SET OF POINTS WHERE BACKGROUND // SUBTRACTION DOES NOT NEED TO BE PERFORMED - // - // mode is useful to specify if the points to remove from + // + // mode is useful to specify if the points to remove from // processing are in addition to the ones potentially // removed according to the configuration file, - // or if they are the only ones to be removed - // + // or if they are the only ones to be removed + // // mode=0 : provided points need to be removed // in addition to those already removed // mode=1 : the provided points are the only one to remove @@ -96,12 +93,12 @@ class CBackgroundSubtractionAPI void SetValidPointMask(IplImage* maskImage, int mode); //------------------------------------------------------------- - // + // // set the frame rate, to adjust the update parameters // to the actual frame rate. // Can be called only once at initialisation, // but in online cases, can be used to indicate - // the time interval during the last processed frame + // the time interval during the last processed frame // // frameDuration is in millisecond void SetFrameRate(float frameDuration); @@ -113,31 +110,31 @@ class CBackgroundSubtractionAPI // Here assumes that the input image will contain RGB images. // The memory of this image is handled by the caller. // - // The return value indicate whether the actual Background + // The return value indicate whether the actual Background // Subtraction algorithm handles RGB images (1) or not (0). - // + // int SetRGBInputImage(IplImage * inputImage); //------------------------------------------------------------- // PROVIDE A POINTER TO THE RESULT IMAGE // INDICATE WHERE THE BACKGROUND RESULT NEED TO BE STORED - // + // // The return value is 1 if correct image format is provided, // otherwise the return value is 0. // e.g. fg_mask_img = cvCreateImage(imgSize, IPL_DEPTH_8U, 1); int SetForegroundMaskImage(IplImage *fg_mask_img); - // The return value is 1 if the function is implemented + // The return value is 1 if the function is implemented // with correct format, otherwise the return value is 0 // e.g. fg_prob_img = cvCreateImage(imgSize, IPL_DEPTH_32F, 1); int SetForegroundProbImage(IplImage *fg_prob_img); //------------------------------------------------------------- - // This function should be called each time a new image is + // This function should be called each time a new image is // available in the input image. - // + // // The return value is 1 if everything goes well, - // otherwise the return value is 0. + // otherwise the return value is 0. // int Process(); @@ -150,9 +147,7 @@ class CBackgroundSubtractionAPI //------------------------------------------------------------- // this function should load the parameters necessary - // for the processing of the background subtraction or + // for the processing of the background subtraction or // load background model information void Load(char *bg_model_fn); }; - -#endif // !defined(_BACKGROUND_SUBTRACTION_API_H_) diff --git a/package_bgs/jmo/BlobExtraction.cpp b/package_bgs/MultiLayer/BlobExtraction.cpp similarity index 91% rename from package_bgs/jmo/BlobExtraction.cpp rename to package_bgs/MultiLayer/BlobExtraction.cpp index 81857eb..7aecd65 100644 --- a/package_bgs/jmo/BlobExtraction.cpp +++ b/package_bgs/MultiLayer/BlobExtraction.cpp @@ -55,47 +55,47 @@ along with BGSLibrary. If not, see . //! Indica si la connectivitat es a 8 (si es desactiva es a 4) #define B_CONNECTIVITAT_8 -//! si la imatge és cíclica verticalment (els blobs que toquen +//! si la imatge és cíclica verticalment (els blobs que toquen //! les vores superior i inferior no es consideren externs) #define IMATGE_CICLICA_VERTICAL 1 -//! si la imatge és cíclica horitzontalment (els blobs que toquen +//! si la imatge és cíclica horitzontalment (els blobs que toquen //! les vores dreta i esquerra no es consideren externs) #define IMATGE_CICLICA_HORITZONTAL 0 #define PERIMETRE_DIAGONAL (1.41421356237310 - 2) #define SQRT2 1.41421356237310 -// color dels píxels de la màscara per ser exteriors +// color dels píxels de la màscara per ser exteriors #define PIXEL_EXTERIOR 0 #include "BlobResult.h" #include "BlobExtraction.h" -#include +#include "OpenCvLegacyIncludes.h" namespace Blob { /** - - FUNCIÓ: BlobAnalysis - - FUNCIONALITAT: Extreu els blobs d'una imatge d'un sol canal - - PARÀMETRES: - - inputImage: Imatge d'entrada. Ha de ser d'un sol canal - - threshold: Nivell de gris per considerar un pixel blanc o negre - - maskImage: Imatge de màscara fora de la cual no es calculen els blobs. A més, - els blobs que toquen els pixels de la màscara a 0, són considerats - externs - - borderColor: Color del marc de la imatge (0=black or 1=white) - - findmoments: calcula els moments dels blobs o no - - RegionData: on es desarà el resultat - - RESULTAT: - - retorna true si tot ha anat bé, false si no. Deixa el resultat a blobs. - - RESTRICCIONS: - - La imatge d'entrada ha de ser d'un sol canal - - AUTOR: dgrossman@cdr.stanford.edu - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - - fpinyol@cvc.uab.es, rborras@cvc.uab.es: adaptació a les OpenCV - */ + - FUNCIÓ: BlobAnalysis + - FUNCIONALITAT: Extreu els blobs d'una imatge d'un sol canal + - PARÀMETRES: + - inputImage: Imatge d'entrada. Ha de ser d'un sol canal + - threshold: Nivell de gris per considerar un pixel blanc o negre + - maskImage: Imatge de màscara fora de la cual no es calculen els blobs. A més, + els blobs que toquen els pixels de la màscara a 0, són considerats + externs + - borderColor: Color del marc de la imatge (0=black or 1=white) + - findmoments: calcula els moments dels blobs o no + - RegionData: on es desarà el resultat + - RESULTAT: + - retorna true si tot ha anat bé, false si no. Deixa el resultat a blobs. + - RESTRICCIONS: + - La imatge d'entrada ha de ser d'un sol canal + - AUTOR: dgrossman@cdr.stanford.edu + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + - fpinyol@cvc.uab.es, rborras@cvc.uab.es: adaptació a les OpenCV + */ bool BlobAnalysis(IplImage* inputImage, uchar threshold, IplImage* maskImage, @@ -151,13 +151,13 @@ namespace Blob if (!CV_IS_IMAGE(inputImage) || !CV_IS_IMAGE(maskImage)) return false; - // comprova que la màscara tingui les mateixes dimensions que la imatge + // comprova que la màscara tingui les mateixes dimensions que la imatge if (inputImage->width != maskImage->width || inputImage->height != maskImage->height) { return false; } - // comprova que la màscara sigui una imatge d'un sol canal (grayscale) + // comprova que la màscara sigui una imatge d'un sol canal (grayscale) if (maskImage->nChannels != 1) { return false; @@ -167,16 +167,16 @@ namespace Blob // Initialize Transition array Transition = new int[(Rows + 2)*(Cols + 2)]; - memset(Transition, 0, (Rows + 2) * (Cols + 2)*sizeof(int)); + memset(Transition, 0, (Rows + 2) * (Cols + 2) * sizeof(int)); Transition[0] = Transition[(Rows + 1) * (Cols + 2)] = Cols + 2; // Start at the beginning of the image (startCol, startRow) pImage = inputImage->imageData + startCol - 1 + startRow * inputImage->widthStep; /* - Paral·lelització del càlcul de la matriu de transicions - Fem que cada iteració del for el faci un thread o l'altre ( tenim 2 possibles threads ) - */ + Paral·lelització del càlcul de la matriu de transicions + Fem que cada iteració del for el faci un thread o l'altre ( tenim 2 possibles threads ) + */ if (maskImage == NULL) { imatgePerimetreExtern = NULL; @@ -184,7 +184,7 @@ namespace Blob //Fill Transition array for (iRow = 1; iRow < Rows + 1; iRow++) // Choose a row of Bordered image { - TransitionOffset = iRow*(Cols + 2); //per a que sigui paral·litzable + TransitionOffset = iRow*(Cols + 2); //per a que sigui paral·litzable iTran = 0; // Index into Transition array Tran = 0; // No transitions at row start LastCell = borderColor; @@ -218,12 +218,12 @@ namespace Blob } else { - //maskImage not NULL: Cal recòrrer la màscara també per calcular la matriu de transicions + //maskImage not NULL: Cal recòrrer la màscara també per calcular la matriu de transicions char perimeter; char *pPerimetre; - // creem la imatge que contindrà el perimetre extern de cada pixel + // creem la imatge que contindrà el perimetre extern de cada pixel imatgePerimetreExtern = cvCreateImage(cvSize(maskImage->width, maskImage->height), IPL_DEPTH_8U, 1); cvSetZero(imatgePerimetreExtern); @@ -255,9 +255,9 @@ namespace Blob } /*//////////////////////////////////////////////////////////////////////// - Calcul de la imatge amb els pixels externs - ////////////////////////////////////////////////////////////////////////*/ - // pels pixels externs no cal calcular res pq no hi accedir-hem + Calcul de la imatge amb els pixels externs + ////////////////////////////////////////////////////////////////////////*/ + // pels pixels externs no cal calcular res pq no hi accedir-hem if ((iCol > 0) && (iCol < Cols)) { if (*pMask == PIXEL_EXTERIOR) @@ -277,7 +277,7 @@ namespace Blob // pixels a l'est i oest de l'actual if (iRow < imatgePerimetreExtern->height) { - if ((iCol>0) && (*(pMask - 1) == PIXEL_EXTERIOR)) perimeter++; + if ((iCol > 0) && (*(pMask - 1) == PIXEL_EXTERIOR)) perimeter++; if ((iCol < imatgePerimetreExtern->width - 1) && (*(pMask + 1) == PIXEL_EXTERIOR)) perimeter++; } @@ -323,7 +323,7 @@ namespace Blob // Last |xxx |xxxxoo |xxxxxxx|xxxxxxx|ooxxxxx|ooxxx |ooxxxxx| xxx| // This | yyy| yyy| yyyy | yyyyy|yyyyyyy|yyyyyyy|yyyy |yyyy | // Here o is optional - // + // // Here are the primitive tests to distinguish these 6 cases: // A) Last end < This start - 1 OR NOT Note: -1 // B) This end < Last start OR NOT @@ -409,7 +409,7 @@ namespace Blob uchar imagevalue; bool CandidatExterior = false; - // apuntadors als blobs de la regió actual i last + // apuntadors als blobs de la regió actual i last CBlob *regionDataThisRegion, *regionDataLastRegion; LastRegion = new int[Cols + 2]; @@ -431,7 +431,7 @@ namespace Blob ThisIndexCount = 1; ThisRegion[0] = 0; // Border region - // beginning of the image + // beginning of the image // en cada linia, pimage apunta al primer pixel de la fila pImage = inputImage->imageData - 1 + startCol + startRow * inputImage->widthStep; //the mask should be the same size as image Roi, so don't take into account the offset @@ -457,9 +457,9 @@ namespace Blob { int Index = ThisOffset + j; int TranVal = Transition[Index]; - if (TranVal > 0) ThisIndexCount = j + 1; // stop at highest + if (TranVal > 0) ThisIndexCount = j + 1; // stop at highest - if (ThisRegion[j] == -1) { EndLast = 1; } + if (ThisRegion[j] == -1) { EndLast = 1; } if (TranVal < 0) { EndThis = 1; } if (EndLast > 0 && EndThis > 0) { break; } @@ -505,7 +505,7 @@ namespace Blob #endif #if !IMATGE_CICLICA_HORITZONTAL ThisStart <= 1 || ThisEnd >= Cols || -#endif +#endif GetExternPerimeter(ThisStart, ThisEnd, ThisRow, inputImage->width, inputImage->height, imatgePerimetreExtern) ) { @@ -601,7 +601,7 @@ namespace Blob //afegim la cantonada a ThisRegion if (ThisRegionNum != -1) { - // afegim dos vertexs si són diferents, només + // afegim dos vertexs si són diferents, només if (ThisStart - 1 != ThisEnd) { actualedge.x = ThisStart - 1; @@ -615,7 +615,7 @@ namespace Blob //afegim la cantonada a ThisRegion if (LastRegionNum != -1 && LastRegionNum != ThisRegionNum) { - // afegim dos vertexs si són diferents, només + // afegim dos vertexs si són diferents, només if (ThisStart - 1 != ThisEnd) { actualedge.x = ThisStart - 1; @@ -662,8 +662,8 @@ namespace Blob SubsumedRegion = NewSubsume(SubsumedRegion, HighRegionNum); if (CandidatExterior) ThisExternPerimeter = GetExternPerimeter(ThisStart, ThisEnd, ThisRow, - inputImage->width, inputImage->height, - imatgePerimetreExtern); + inputImage->width, inputImage->height, + imatgePerimetreExtern); } @@ -728,8 +728,8 @@ namespace Blob SubsumedRegion = NewSubsume(SubsumedRegion, HighRegionNum); if (CandidatExterior) ThisExternPerimeter = GetExternPerimeter(ThisStart, ThisEnd, ThisRow, - inputImage->width, inputImage->height, - imatgePerimetreExtern); + inputImage->width, inputImage->height, + imatgePerimetreExtern); } @@ -789,8 +789,8 @@ namespace Blob SubsumedRegion = NewSubsume(SubsumedRegion, HighRegionNum); if (CandidatExterior) ThisExternPerimeter = GetExternPerimeter(ThisStart, ThisEnd, ThisRow, - inputImage->width, inputImage->height, - imatgePerimetreExtern); + inputImage->width, inputImage->height, + imatgePerimetreExtern); } else if (TestMatch && !TestKnown) // Same color and unknown @@ -986,8 +986,8 @@ namespace Blob SubsumedRegion = NewSubsume(SubsumedRegion, HighRegionNum); if (CandidatExterior) ThisExternPerimeter = GetExternPerimeter(ThisStart, ThisEnd, ThisRow, - inputImage->width, inputImage->height, - imatgePerimetreExtern); + inputImage->width, inputImage->height, + imatgePerimetreExtern); } else if (TestMatch && !TestKnown) @@ -1077,7 +1077,7 @@ namespace Blob //|yyyy | #ifdef B_CONNECTIVITAT_8 - // fusionem blobs + // fusionem blobs if (TestMatch) { if (ThisRegionNum > LastRegionNum) @@ -1159,7 +1159,7 @@ namespace Blob } - // compute the mean gray level and its std deviation + // compute the mean gray level and its std deviation if (ThisRow <= Rows) { pImageAux = pImage + ThisStart; @@ -1170,9 +1170,9 @@ namespace Blob { if (maskImage != NULL) { - // només es té en compte el valor del píxel de la - // imatge que queda dins de la màscara - // (de pas, comptem el nombre de píxels de la màscara) + // només es té en compte el valor del píxel de la + // imatge que queda dins de la màscara + // (de pas, comptem el nombre de píxels de la màscara) if (((unsigned char)*pMaskAux) != PIXEL_EXTERIOR) { imagevalue = (unsigned char)(*pImageAux); @@ -1200,7 +1200,7 @@ namespace Blob // compute the min and max values of X and Y if (ThisStart - 1 < (int)ThisMinX) ThisMinX = (float)(ThisStart - 1); if (ThisMinX < (float) 0.0) ThisMinX = (float) 0.0; - if (ThisEnd >(int) ThisMaxX) ThisMaxX = (float)ThisEnd; + if (ThisEnd > (int) ThisMaxX) ThisMaxX = (float)ThisEnd; if (ThisRow - 1 < ThisMinY) ThisMinY = ThisRow - 1; if (ThisMinY < (float) 0.0) ThisMinY = (float) 0.0; @@ -1242,10 +1242,10 @@ namespace Blob } // end Main loop if (ErrorFlag != 0) { - delete[] Transition; - delete[] ThisRegion; - delete[] LastRegion; - return false; + delete[] Transition; + delete[] ThisRegion; + delete[] LastRegion; + return false; } // ens situem al primer pixel de la seguent fila pImage = inputImage->imageData - 1 + startCol + (ThisRow + startRow) * inputImage->widthStep; @@ -1254,19 +1254,19 @@ namespace Blob pMask = maskImage->imageData - 1 + ThisRow * maskImage->widthStep; } // end Loop over all rows - // eliminem l'àrea del marc - // i també els píxels de la màscara + // eliminem l'àrea del marc + // i també els píxels de la màscara // ATENCIO: PERFER: el fet de restar el nombre_pixels_mascara del - // blob 0 només serà cert si la màscara té contacte amb el marc. - // Si no, s'haurà de trobar quin és el blob que conté més píxels del + // blob 0 només serà cert si la màscara té contacte amb el marc. + // Si no, s'haurà de trobar quin és el blob que conté més píxels del // compte. RegionData[0]->area -= (Rows + 1 + Cols + 1) * 2 + nombre_pixels_mascara; - // eliminem el perímetre de més: - // - sense marc: 2m+2n (perímetre extern) + // eliminem el perímetre de més: + // - sense marc: 2m+2n (perímetre extern) // - amb marc: 2(m+2)+2(n+2) = 2m+2n + 8 - // (segurament no és del tot acurat) - // (i amb les màscares encara menys...) + // (segurament no és del tot acurat) + // (i amb les màscares encara menys...) RegionData[0]->perimeter -= 8.0; // Condense the list @@ -1306,7 +1306,7 @@ namespace Blob if (findmoments) { iti = RegionData.begin(); - // Normalize summation fields into moments + // Normalize summation fields into moments for (ThisRegionNum = 0; ThisRegionNum <= HighRegionNum; ThisRegionNum++, iti++) { blobActual = *iti; @@ -1340,10 +1340,10 @@ namespace Blob blobActual->stddev = sqrt( ( - blobActual->stddev * blobActual->area - - blobActual->mean * blobActual->mean - ) / - (blobActual->area*(blobActual->area - 1)) + blobActual->stddev * blobActual->area - + blobActual->mean * blobActual->mean + ) / + (blobActual->area*(blobActual->area - 1)) ); } else @@ -1385,16 +1385,16 @@ namespace Blob } else { - subsumed = (int*)realloc(subsumed, (index_subsume + 1)*sizeof(int)); + subsumed = (int*)realloc(subsumed, (index_subsume + 1) * sizeof(int)); } subsumed[index_subsume] = 0; return subsumed; } /** - Fusiona dos blobs i afegeix el blob les característiques del blob RegionData[HiNum] - al blob RegionData[LoNum]. Al final allibera el blob de RegionData[HiNum] - */ + Fusiona dos blobs i afegeix el blob les característiques del blob RegionData[HiNum] + al blob RegionData[LoNum]. Al final allibera el blob de RegionData[HiNum] + */ void Subsume(blob_vector &RegionData, int HighRegionNum, int* SubsumedRegion, @@ -1440,28 +1440,28 @@ namespace Blob // marquem el blob com a lliure blobHi->etiqueta = -1; - // Atenció!!!! abans d'eliminar els edges + // Atenció!!!! abans d'eliminar els edges // s'han de traspassar del blob HiNum al blob LoNum blobHi->CopyEdges(*blobLo); blobHi->ClearEdges(); } /** - - FUNCIÓ: GetExternPerimeter - - FUNCIONALITAT: Retorna el perimetre extern d'una run lenght - - PARÀMETRES: - - start: columna d'inici del run - - end: columna final del run - - row: fila del run - - maskImage: màscara pels pixels externs - - RESULTAT: - - quantitat de perimetre extern d'un run, suposant que és un blob - d'una única fila d'alçada - - RESTRICCIONS: - - AUTOR: - - DATA DE CREACIÓ: 2006/02/27 - - MODIFICACIÓ: Data. Autor. Descripció. - */ + - FUNCIÓ: GetExternPerimeter + - FUNCIONALITAT: Retorna el perimetre extern d'una run lenght + - PARÀMETRES: + - start: columna d'inici del run + - end: columna final del run + - row: fila del run + - maskImage: màscara pels pixels externs + - RESULTAT: + - quantitat de perimetre extern d'un run, suposant que és un blob + d'una única fila d'alçada + - RESTRICCIONS: + - AUTOR: + - DATA DE CREACIÓ: 2006/02/27 + - MODIFICACIÓ: Data. Autor. Descripció. + */ double GetExternPerimeter(int start, int end, int row, int width, int height, IplImage *imatgePerimetreExtern) { double perimeter = 0.0f; @@ -1474,7 +1474,7 @@ namespace Blob if (row >= height - 1) perimeter += start - end; - // comprovem els pixels que toquen a la màscara (si s'escau) + // comprovem els pixels que toquen a la màscara (si s'escau) if (imatgePerimetreExtern != NULL) { if (row <= 0 || row >= height) return perimeter; diff --git a/package_bgs/jmo/BlobExtraction.h b/package_bgs/MultiLayer/BlobExtraction.h similarity index 96% rename from package_bgs/jmo/BlobExtraction.h rename to package_bgs/MultiLayer/BlobExtraction.h index 9f47b02..d8bd1af 100644 --- a/package_bgs/jmo/BlobExtraction.h +++ b/package_bgs/MultiLayer/BlobExtraction.h @@ -49,10 +49,7 @@ along with BGSLibrary. If not, see . //* Email: dgrossman@cdr.stanford.edu *// //* Acknowledgement: the algorithm has been around > 20 yrs *// //***********************************************************// - - -#if !defined(_CLASSE_BLOBEXTRACTION_INCLUDED) -#define _CLASSE_BLOBEXTRACTION_INCLUDED +#pragma once namespace Blob { @@ -71,6 +68,3 @@ namespace Blob //! Retorna el perimetre extern d'una run lenght double GetExternPerimeter(int start, int end, int row, int width, int height, IplImage *maskImage); } - -#endif //_CLASSE_BLOBEXTRACTION_INCLUDED - diff --git a/package_bgs/jmo/BlobLibraryConfiguration.h b/package_bgs/MultiLayer/BlobLibraryConfiguration.h similarity index 95% rename from package_bgs/jmo/BlobLibraryConfiguration.h rename to package_bgs/MultiLayer/BlobLibraryConfiguration.h index b864fc7..0ba18fc 100644 --- a/package_bgs/jmo/BlobLibraryConfiguration.h +++ b/package_bgs/MultiLayer/BlobLibraryConfiguration.h @@ -43,15 +43,16 @@ along with BGSLibrary. If not, see . /************************************************************************ BlobLibraryConfiguration.h -FUNCIONALITAT: Configuració del comportament global de la llibreria +FUNCIONALITAT: Configuració del comportament global de la llibreria AUTOR: Inspecta S.L. -MODIFICACIONS (Modificació, Autor, Data): +MODIFICACIONS (Modificació, Autor, Data): FUNCTIONALITY: Global configuration of the library AUTHOR: Inspecta S.L. MODIFICATIONS (Modification, Author, Date): **************************************************************************/ +#pragma once //! Indica si es volen fer servir les MatrixCV o no //! Use/Not use the MatrixCV class diff --git a/package_bgs/MultiLayer/BlobResult.cpp b/package_bgs/MultiLayer/BlobResult.cpp new file mode 100644 index 0000000..1ec60e3 --- /dev/null +++ b/package_bgs/MultiLayer/BlobResult.cpp @@ -0,0 +1,847 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* --- --- --- +* Copyright (C) 2008--2010 Idiap Research Institute (.....@idiap.ch) +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. 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. +* 3. The name of the author may not be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. +*/ +/************************************************************************ +BlobResult.cpp + +FUNCIONALITAT: Implementació de la classe CBlobResult +AUTOR: Inspecta S.L. +MODIFICACIONS (Modificació, Autor, Data): + +**************************************************************************/ + +#include +#include +#include +#include +#include "BlobResult.h" +#include "BlobExtraction.h" + +/************************************************************************** +Constructors / Destructors +**************************************************************************/ + +namespace Blob +{ + + /** + - FUNCIÓ: CBlobResult + - FUNCIONALITAT: Constructor estandard. + - PARÀMETRES: + - RESULTAT: + - Crea un CBlobResult sense cap blob + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 20-07-2004. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlobResult + - FUNCTIONALITY: Standard constructor + - PARAMETERS: + - RESULT: + - creates an empty set of blobs + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlobResult::CBlobResult() + { + m_blobs = blob_vector(); + } + + /** + - FUNCIÓ: CBlobResult + - FUNCIONALITAT: Constructor a partir d'una imatge. Inicialitza la seqüència de blobs + amb els blobs resultants de l'anàlisi de blobs de la imatge. + - PARÀMETRES: + - source: imatge d'on s'extreuran els blobs + - mask: màscara a aplicar. Només es calcularan els blobs on la màscara sigui + diferent de 0. Els blobs que toquin a un pixel 0 de la màscara seran + considerats exteriors. + - threshold: llindar que s'aplicarà a la imatge source abans de calcular els blobs + - findmoments: indica si s'han de calcular els moments de cada blob + - RESULTAT: + - objecte CBlobResult amb els blobs de la imatge source + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlob + - FUNCTIONALITY: Constructor from an image. Fills an object with all the blobs in + the image + - PARAMETERS: + - source: image to extract the blobs from + - mask: optional mask to apply. The blobs will be extracted where the mask is + not 0. All the neighbouring blobs where the mask is 0 will be extern blobs + - threshold: threshold level to apply to the image before computing blobs + - findmoments: true to calculate the blob moments (slower) + - RESULT: + - object with all the blobs in the image. It throws an EXCEPCIO_CALCUL_BLOBS + if some error appears in the BlobAnalysis function + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlobResult::CBlobResult(IplImage *source, IplImage *mask, int threshold, bool findmoments) + { + bool success; + + try + { + // cridem la funció amb el marc a true=1=blanc (així no unirà els blobs externs) + success = BlobAnalysis(source, (uchar)threshold, mask, true, findmoments, m_blobs); + } + catch (...) + { + success = false; + } + + if (!success) throw EXCEPCIO_CALCUL_BLOBS; + } + + /** + - FUNCIÓ: CBlobResult + - FUNCIONALITAT: Constructor de còpia. Inicialitza la seqüència de blobs + amb els blobs del paràmetre. + - PARÀMETRES: + - source: objecte que es copiarà + - RESULTAT: + - objecte CBlobResult amb els blobs de l'objecte source + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlobResult + - FUNCTIONALITY: Copy constructor + - PARAMETERS: + - source: object to copy + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlobResult::CBlobResult(const CBlobResult &source) + { + m_blobs = blob_vector(source.GetNumBlobs()); + + // creem el nou a partir del passat com a paràmetre + m_blobs = blob_vector(source.GetNumBlobs()); + // copiem els blobs de l'origen a l'actual + blob_vector::const_iterator pBlobsSrc = source.m_blobs.begin(); + blob_vector::iterator pBlobsDst = m_blobs.begin(); + + while (pBlobsSrc != source.m_blobs.end()) + { + // no podem cridar a l'operador = ja que blob_vector és un + // vector de CBlob*. Per tant, creem un blob nou a partir del + // blob original + *pBlobsDst = new CBlob(**pBlobsSrc); + ++pBlobsSrc; + ++pBlobsDst; + } + } + + + + /** + - FUNCIÓ: ~CBlobResult + - FUNCIONALITAT: Destructor estandard. + - PARÀMETRES: + - RESULTAT: + - Allibera la memòria reservada de cadascun dels blobs de la classe + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: ~CBlobResult + - FUNCTIONALITY: Destructor + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlobResult::~CBlobResult() + { + ClearBlobs(); + } + + /************************************************************************** + Operadors / Operators + **************************************************************************/ + + + /** + - FUNCIÓ: operador = + - FUNCIONALITAT: Assigna un objecte source a l'actual + - PARÀMETRES: + - source: objecte a assignar + - RESULTAT: + - Substitueix els blobs actuals per els de l'objecte source + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: Assigment operator + - FUNCTIONALITY: + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlobResult& CBlobResult::operator=(const CBlobResult& source) + { + // si ja són el mateix, no cal fer res + if (this != &source) + { + // alliberem el conjunt de blobs antic + for (int i = 0; i < GetNumBlobs(); i++) + { + delete m_blobs[i]; + } + m_blobs.clear(); + // creem el nou a partir del passat com a paràmetre + m_blobs = blob_vector(source.GetNumBlobs()); + // copiem els blobs de l'origen a l'actual + blob_vector::const_iterator pBlobsSrc = source.m_blobs.begin(); + blob_vector::iterator pBlobsDst = m_blobs.begin(); + + while (pBlobsSrc != source.m_blobs.end()) + { + // no podem cridar a l'operador = ja que blob_vector és un + // vector de CBlob*. Per tant, creem un blob nou a partir del + // blob original + *pBlobsDst = new CBlob(**pBlobsSrc); + ++pBlobsSrc; + ++pBlobsDst; + } + } + return *this; + } + + + /** + - FUNCIÓ: operador + + - FUNCIONALITAT: Concatena els blobs de dos CBlobResult + - PARÀMETRES: + - source: d'on s'agafaran els blobs afegits a l'actual + - RESULTAT: + - retorna un nou CBlobResult amb els dos CBlobResult concatenats + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - NOTA: per la implementació, els blobs del paràmetre es posen en ordre invers + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: + operator + - FUNCTIONALITY: Joins the blobs in source with the current ones + - PARAMETERS: + - source: object to copy the blobs + - RESULT: + - object with the actual blobs and the source blobs + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlobResult CBlobResult::operator+(const CBlobResult& source) + { + //creem el resultat a partir dels blobs actuals + CBlobResult resultat(*this); + + // reservem memòria per als nous blobs + resultat.m_blobs.resize(resultat.GetNumBlobs() + source.GetNumBlobs()); + + // declarem els iterador per recòrrer els blobs d'origen i desti + blob_vector::const_iterator pBlobsSrc = source.m_blobs.begin(); + blob_vector::iterator pBlobsDst = resultat.m_blobs.end(); + + // insertem els blobs de l'origen a l'actual + while (pBlobsSrc != source.m_blobs.end()) + { + --pBlobsDst; + *pBlobsDst = new CBlob(**pBlobsSrc); + ++pBlobsSrc; + } + + return resultat; + } + + /************************************************************************** + Operacions / Operations + **************************************************************************/ + + /** + - FUNCIÓ: AddBlob + - FUNCIONALITAT: Afegeix un blob al conjunt + - PARÀMETRES: + - blob: blob a afegir + - RESULTAT: + - modifica el conjunt de blobs actual + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 2006/03/01 + - MODIFICACIÓ: Data. Autor. Descripció. + */ + void CBlobResult::AddBlob(CBlob *blob) + { + if (blob != NULL) + m_blobs.push_back(new CBlob(blob)); + } + + + /** + - FUNCIÓ: GetSTLResult + - FUNCIONALITAT: Calcula el resultat especificat sobre tots els blobs de la classe + - PARÀMETRES: + - evaluador: Qualsevol objecte derivat de COperadorBlob + - RESULTAT: + - Retorna un array de double's STL amb el resultat per cada blob + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: GetResult + - FUNCTIONALITY: Computes the function evaluador on all the blobs of the class + and returns a vector with the result + - PARAMETERS: + - evaluador: function to apply to each blob (any object derived from the + COperadorBlob class ) + - RESULT: + - vector with all the results in the same order as the blobs + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double_stl_vector CBlobResult::GetSTLResult(funcio_calculBlob *evaluador) const + { + if (GetNumBlobs() <= 0) + { + return double_stl_vector(); + } + + // definim el resultat + double_stl_vector result = double_stl_vector(GetNumBlobs()); + // i iteradors sobre els blobs i el resultat + double_stl_vector::iterator itResult = result.begin(); + blob_vector::const_iterator itBlobs = m_blobs.begin(); + + // avaluem la funció en tots els blobs + while (itBlobs != m_blobs.end()) + { + *itResult = (*evaluador)(**itBlobs); + ++itBlobs; + ++itResult; + } + return result; + } + + /** + - FUNCIÓ: GetNumber + - FUNCIONALITAT: Calcula el resultat especificat sobre un únic blob de la classe + - PARÀMETRES: + - evaluador: Qualsevol objecte derivat de COperadorBlob + - indexblob: número de blob del que volem calcular el resultat. + - RESULTAT: + - Retorna un double amb el resultat + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: GetNumber + - FUNCTIONALITY: Computes the function evaluador on a blob of the class + - PARAMETERS: + - indexBlob: index of the blob to compute the function + - evaluador: function to apply to each blob (any object derived from the + COperadorBlob class ) + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobResult::GetNumber(int indexBlob, funcio_calculBlob *evaluador) const + { + if (indexBlob < 0 || indexBlob >= GetNumBlobs()) + RaiseError(EXCEPTION_BLOB_OUT_OF_BOUNDS); + return (*evaluador)(*m_blobs[indexBlob]); + } + + /////////////////////////// FILTRAT DE BLOBS //////////////////////////////////// + + /** + - FUNCIÓ: Filter + - FUNCIONALITAT: Filtra els blobs de la classe i deixa el resultat amb només + els blobs que han passat el filtre. + El filtrat es basa en especificar condicions sobre un resultat dels blobs + i seleccionar (o excloure) aquells blobs que no compleixen una determinada + condicio + - PARÀMETRES: + - dst: variable per deixar els blobs filtrats + - filterAction: acció de filtrat. Incloure els blobs trobats (B_INCLUDE), + o excloure els blobs trobats (B_EXCLUDE) + - evaluador: Funció per evaluar els blobs (qualsevol objecte derivat de COperadorBlob + - Condition: tipus de condició que ha de superar la mesura (FilterType) + sobre cada blob per a ser considerat. + B_EQUAL,B_NOT_EQUAL,B_GREATER,B_LESS,B_GREATER_OR_EQUAL, + B_LESS_OR_EQUAL,B_INSIDE,B_OUTSIDE + - LowLimit: valor numèric per a la comparació (Condition) de la mesura (FilterType) + - HighLimit: valor numèric per a la comparació (Condition) de la mesura (FilterType) + (només té sentit per a aquelles condicions que tenen dos valors + (B_INSIDE, per exemple). + - RESULTAT: + - Deixa els blobs resultants del filtrat a destination + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: Filter + - FUNCTIONALITY: Get some blobs from the class based on conditions on measures + of the blobs. + - PARAMETERS: + - dst: where to store the selected blobs + - filterAction: B_INCLUDE: include the blobs which pass the filter in the result + B_EXCLUDE: exclude the blobs which pass the filter in the result + - evaluador: Object to evaluate the blob + - Condition: How to decide if the result returned by evaluador on each blob + is included or not. It can be: + B_EQUAL,B_NOT_EQUAL,B_GREATER,B_LESS,B_GREATER_OR_EQUAL, + B_LESS_OR_EQUAL,B_INSIDE,B_OUTSIDE + - LowLimit: numerical value to evaluate the Condition on evaluador(blob) + - HighLimit: numerical value to evaluate the Condition on evaluador(blob). + Only useful for B_INSIDE and B_OUTSIDE + - RESULT: + - It returns on dst the blobs that accomplish (B_INCLUDE) or discards (B_EXCLUDE) + the Condition on the result returned by evaluador on each blob + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + void CBlobResult::Filter(CBlobResult &dst, + int filterAction, + funcio_calculBlob *evaluador, + int condition, + double lowLimit, double highLimit /*=0*/) + + { + int i, numBlobs; + bool resultavaluacio; + double_stl_vector avaluacioBlobs; + double_stl_vector::iterator itavaluacioBlobs; + + if (GetNumBlobs() <= 0) return; + if (!evaluador) return; + //avaluem els blobs amb la funció pertinent + avaluacioBlobs = GetSTLResult(evaluador); + itavaluacioBlobs = avaluacioBlobs.begin(); + numBlobs = GetNumBlobs(); + switch (condition) + { + case B_EQUAL: + for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) + { + resultavaluacio = *itavaluacioBlobs == lowLimit; + if ((resultavaluacio && filterAction == B_INCLUDE) || + (!resultavaluacio && filterAction == B_EXCLUDE)) + { + dst.m_blobs.push_back(new CBlob(GetBlob(i))); + } + } + break; + case B_NOT_EQUAL: + for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) + { + resultavaluacio = *itavaluacioBlobs != lowLimit; + if ((resultavaluacio && filterAction == B_INCLUDE) || + (!resultavaluacio && filterAction == B_EXCLUDE)) + { + dst.m_blobs.push_back(new CBlob(GetBlob(i))); + } + } + break; + case B_GREATER: + for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) + { + resultavaluacio = *itavaluacioBlobs > lowLimit; + if ((resultavaluacio && filterAction == B_INCLUDE) || + (!resultavaluacio && filterAction == B_EXCLUDE)) + { + dst.m_blobs.push_back(new CBlob(GetBlob(i))); + } + } + break; + case B_LESS: + for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) + { + resultavaluacio = *itavaluacioBlobs < lowLimit; + if ((resultavaluacio && filterAction == B_INCLUDE) || + (!resultavaluacio && filterAction == B_EXCLUDE)) + { + dst.m_blobs.push_back(new CBlob(GetBlob(i))); + } + } + break; + case B_GREATER_OR_EQUAL: + for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) + { + resultavaluacio = *itavaluacioBlobs >= lowLimit; + if ((resultavaluacio && filterAction == B_INCLUDE) || + (!resultavaluacio && filterAction == B_EXCLUDE)) + { + dst.m_blobs.push_back(new CBlob(GetBlob(i))); + } + } + break; + case B_LESS_OR_EQUAL: + for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) + { + resultavaluacio = *itavaluacioBlobs <= lowLimit; + if ((resultavaluacio && filterAction == B_INCLUDE) || + (!resultavaluacio && filterAction == B_EXCLUDE)) + { + dst.m_blobs.push_back(new CBlob(GetBlob(i))); + } + } + break; + case B_INSIDE: + for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) + { + resultavaluacio = (*itavaluacioBlobs >= lowLimit) && (*itavaluacioBlobs <= highLimit); + if ((resultavaluacio && filterAction == B_INCLUDE) || + (!resultavaluacio && filterAction == B_EXCLUDE)) + { + dst.m_blobs.push_back(new CBlob(GetBlob(i))); + } + } + break; + case B_OUTSIDE: + for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) + { + resultavaluacio = (*itavaluacioBlobs < lowLimit) || (*itavaluacioBlobs > highLimit); + if ((resultavaluacio && filterAction == B_INCLUDE) || + (!resultavaluacio && filterAction == B_EXCLUDE)) + { + dst.m_blobs.push_back(new CBlob(GetBlob(i))); + } + } + break; + } + + + // en cas de voler filtrar un CBlobResult i deixar-ho en el mateix CBlobResult + // ( operacio inline ) + if (&dst == this) + { + // esborrem els primers blobs ( que són els originals ) + // ja que els tindrem replicats al final si passen el filtre + blob_vector::iterator itBlobs = m_blobs.begin(); + for (int i = 0; i < numBlobs; i++) + { + delete *itBlobs; + ++itBlobs; + } + m_blobs.erase(m_blobs.begin(), itBlobs); + } + } + + + /** + - FUNCIÓ: GetBlob + - FUNCIONALITAT: Retorna un blob si aquest existeix (index != -1) + - PARÀMETRES: + - indexblob: index del blob a retornar + - RESULTAT: + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /* + - FUNCTION: GetBlob + - FUNCTIONALITY: Gets the n-th blob (without ordering the blobs) + - PARAMETERS: + - indexblob: index in the blob array + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlob CBlobResult::GetBlob(int indexblob) const + { + if (indexblob < 0 || indexblob >= GetNumBlobs()) + RaiseError(EXCEPTION_BLOB_OUT_OF_BOUNDS); + + return *m_blobs[indexblob]; + } + CBlob *CBlobResult::GetBlob(int indexblob) + { + if (indexblob < 0 || indexblob >= GetNumBlobs()) + RaiseError(EXCEPTION_BLOB_OUT_OF_BOUNDS); + + return m_blobs[indexblob]; + } + + /** + - FUNCIÓ: GetNthBlob + - FUNCIONALITAT: Retorna l'enèssim blob segons un determinat criteri + - PARÀMETRES: + - criteri: criteri per ordenar els blobs (objectes derivats de COperadorBlob) + - nBlob: index del blob a retornar + - dst: on es retorna el resultat + - RESULTAT: + - retorna el blob nBlob a dst ordenant els blobs de la classe segons el criteri + en ordre DESCENDENT. Per exemple, per obtenir el blob major: + GetNthBlob( CBlobGetArea(), 0, blobMajor ); + GetNthBlob( CBlobGetArea(), 1, blobMajor ); (segon blob més gran) + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /* + - FUNCTION: GetNthBlob + - FUNCTIONALITY: Gets the n-th blob ordering first the blobs with some criteria + - PARAMETERS: + - criteri: criteria to order the blob array + - nBlob: index of the returned blob in the ordered blob array + - dst: where to store the result + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + void CBlobResult::GetNthBlob(funcio_calculBlob *criteri, int nBlob, CBlob &dst) const + { + // verifiquem que no estem accedint fora el vector de blobs + if (nBlob < 0 || nBlob >= GetNumBlobs()) + { + //RaiseError( EXCEPTION_BLOB_OUT_OF_BOUNDS ); + dst = CBlob(); + return; + } + + double_stl_vector avaluacioBlobs, avaluacioBlobsOrdenat; + double valorEnessim; + + //avaluem els blobs amb la funció pertinent + avaluacioBlobs = GetSTLResult(criteri); + + avaluacioBlobsOrdenat = double_stl_vector(GetNumBlobs()); + + // obtenim els nBlob primers resultats (en ordre descendent) + std::partial_sort_copy(avaluacioBlobs.begin(), + avaluacioBlobs.end(), + avaluacioBlobsOrdenat.begin(), + avaluacioBlobsOrdenat.end(), + std::greater()); + + valorEnessim = avaluacioBlobsOrdenat[nBlob]; + + // busquem el primer blob que té el valor n-ssim + double_stl_vector::const_iterator itAvaluacio = avaluacioBlobs.begin(); + + bool trobatBlob = false; + int indexBlob = 0; + while (itAvaluacio != avaluacioBlobs.end() && !trobatBlob) + { + if (*itAvaluacio == valorEnessim) + { + trobatBlob = true; + dst = CBlob(GetBlob(indexBlob)); + } + ++itAvaluacio; + indexBlob++; + } + } + + /** + - FUNCIÓ: ClearBlobs + - FUNCIONALITAT: Elimina tots els blobs de l'objecte + - PARÀMETRES: + - RESULTAT: + - Allibera tota la memòria dels blobs + - RESTRICCIONS: + - AUTOR: Ricard Borràs Navarra + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /* + - FUNCTION: ClearBlobs + - FUNCTIONALITY: Clears all the blobs from the object and releases all its memory + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + void CBlobResult::ClearBlobs() + { + /*for( int i = 0; i < GetNumBlobs(); i++ ) + { + delete m_blobs[i]; + }*/ + blob_vector::iterator itBlobs = m_blobs.begin(); + while (itBlobs != m_blobs.end()) + { + delete *itBlobs; + ++itBlobs; + } + + m_blobs.clear(); + } + + /** + - FUNCIÓ: RaiseError + - FUNCIONALITAT: Funció per a notificar errors al l'usuari (en debug) i llença + les excepcions + - PARÀMETRES: + - errorCode: codi d'error + - RESULTAT: + - Ensenya un missatge a l'usuari (en debug) i llença una excepció + - RESTRICCIONS: + - AUTOR: Ricard Borràs Navarra + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /* + - FUNCTION: RaiseError + - FUNCTIONALITY: Error handling function + - PARAMETERS: + - errorCode: reason of the error + - RESULT: + - in _DEBUG version, shows a message box with the error. In release is silent. + In both cases throws an exception with the error. + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + void CBlobResult::RaiseError(const int errorCode) const + { + throw errorCode; + } + + + + /************************************************************************** + Auxiliars / Auxiliary functions + **************************************************************************/ + + + /** + - FUNCIÓ: PrintBlobs + - FUNCIONALITAT: Escriu els paràmetres (àrea, perímetre, exterior, mitjana) + de tots els blobs a un fitxer. + - PARÀMETRES: + - nom_fitxer: path complet del fitxer amb el resultat + - RESULTAT: + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /* + - FUNCTION: PrintBlobs + - FUNCTIONALITY: Prints some blob features in an ASCII file + - PARAMETERS: + - nom_fitxer: full path + filename to generate + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + void CBlobResult::PrintBlobs(char *nom_fitxer) const + { + double_stl_vector area, /*perimetre,*/ exterior, mitjana, compacitat, longitud, + externPerimeter, perimetreConvex, perimetre; + int i; + FILE *fitxer_sortida; + + area = GetSTLResult(CBlobGetArea()); + perimetre = GetSTLResult(CBlobGetPerimeter()); + exterior = GetSTLResult(CBlobGetExterior()); + mitjana = GetSTLResult(CBlobGetMean()); + compacitat = GetSTLResult(CBlobGetCompactness()); + longitud = GetSTLResult(CBlobGetLength()); + externPerimeter = GetSTLResult(CBlobGetExternPerimeter()); + perimetreConvex = GetSTLResult(CBlobGetHullPerimeter()); + + fitxer_sortida = fopen(nom_fitxer, "w"); + + for (i = 0; i < GetNumBlobs(); i++) + { + fprintf(fitxer_sortida, "blob %d ->\t a=%7.0f\t p=%8.2f (%8.2f extern)\t pconvex=%8.2f\t ext=%.0f\t m=%7.2f\t c=%3.2f\t l=%8.2f\n", + i, area[i], perimetre[i], externPerimeter[i], perimetreConvex[i], exterior[i], mitjana[i], compacitat[i], longitud[i]); + } + fclose(fitxer_sortida); + + } + +} diff --git a/package_bgs/jmo/BlobResult.h b/package_bgs/MultiLayer/BlobResult.h similarity index 80% rename from package_bgs/jmo/BlobResult.h rename to package_bgs/MultiLayer/BlobResult.h index b232a97..aa3e37b 100644 --- a/package_bgs/jmo/BlobResult.h +++ b/package_bgs/MultiLayer/BlobResult.h @@ -40,29 +40,26 @@ along with BGSLibrary. If not, see . * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -/************************************************************************ - BlobResult.h + /************************************************************************ + BlobResult.h - FUNCIONALITAT: Definició de la classe CBlobResult - AUTOR: Inspecta S.L. - MODIFICACIONS (Modificació, Autor, Data): + FUNCIONALITAT: Definició de la classe CBlobResult + AUTOR: Inspecta S.L. + MODIFICACIONS (Modificació, Autor, Data): - FUNCTIONALITY: Definition of the CBlobResult class - AUTHOR: Inspecta S.L. - MODIFICATIONS (Modification, Author, Date): + FUNCTIONALITY: Definition of the CBlobResult class + AUTHOR: Inspecta S.L. + MODIFICATIONS (Modification, Author, Date): - **************************************************************************/ + **************************************************************************/ #pragma once -#if !defined(_CLASSE_BLOBRESULT_INCLUDED) -#define _CLASSE_BLOBRESULT_INCLUDED - #include "BlobLibraryConfiguration.h" #include -//#include "cxcore.h" + //#include "cxcore.h" #include #include -#include +#include "OpenCvLegacyIncludes.h" #include "blob.h" typedef std::vector double_stl_vector; @@ -71,8 +68,8 @@ typedef std::vector double_stl_vector; Filtres / Filters **************************************************************************/ -//! accions que es poden fer amb els filtres -//! Actions performed by a filter (include or exclude blobs) + //! accions que es poden fer amb els filtres + //! Actions performed by a filter (include or exclude blobs) #define B_INCLUDE 1L #define B_EXCLUDE 2L @@ -92,23 +89,23 @@ typedef std::vector double_stl_vector; Excepcions / Exceptions **************************************************************************/ -//! Excepcions llençades per les funcions: + //! Excepcions llençades per les funcions: #define EXCEPTION_BLOB_OUT_OF_BOUNDS 1000 #define EXCEPCIO_CALCUL_BLOBS 1001 namespace Blob { - //! definició de que es un vector de blobs + //! definició de que es un vector de blobs typedef std::vector blob_vector; /** - Classe que conté un conjunt de blobs i permet extreure'n propietats - o filtrar-los segons determinats criteris. - Class to calculate the blobs of an image and calculate some properties - on them. Also, the class provides functions to filter the blobs using - some criteria. - */ + Classe que conté un conjunt de blobs i permet extreure'n propietats + o filtrar-los segons determinats criteris. + Class to calculate the blobs of an image and calculate some properties + on them. Also, the class provides functions to filter the blobs using + some criteria. + */ class CBlobResult { public: @@ -119,7 +116,7 @@ namespace Blob //! constructor a partir d'una imatge //! Image constructor, it creates an object with the blobs of the image CBlobResult(IplImage *source, IplImage *mask, int threshold, bool findmoments); - //! constructor de còpia + //! constructor de còpia //! Copy constructor CBlobResult(const CBlobResult &source); //! Destructor @@ -139,7 +136,7 @@ namespace Blob #ifdef MATRIXCV_ACTIU //! Calcula un valor sobre tots els blobs de la classe retornant una MatrixCV //! Computes some property on all the blobs of the class - double_vector GetResult( funcio_calculBlob *evaluador ) const; + double_vector GetResult(funcio_calculBlob *evaluador) const; #endif //! Calcula un valor sobre tots els blobs de la classe retornant un std::vector //! Computes some property on all the blobs of the class @@ -149,17 +146,17 @@ namespace Blob //! Computes some property on one blob of the class double GetNumber(int indexblob, funcio_calculBlob *evaluador) const; - //! Retorna aquells blobs que compleixen les condicions del filtre en el destination + //! Retorna aquells blobs que compleixen les condicions del filtre en el destination //! Filters the blobs of the class using some property void Filter(CBlobResult &dst, int filterAction, funcio_calculBlob *evaluador, int condition, double lowLimit, double highLimit = 0); - //! Retorna l'enèssim blob segons un determinat criteri + //! Retorna l'enèssim blob segons un determinat criteri //! Sorts the blobs of the class acording to some criteria and returns the n-th blob void GetNthBlob(funcio_calculBlob *criteri, int nBlob, CBlob &dst) const; - //! Retorna el blob enèssim + //! Retorna el blob enèssim //! Gets the n-th blob of the class ( without sorting ) CBlob GetBlob(int indexblob) const; CBlob *GetBlob(int indexblob); @@ -185,7 +182,7 @@ namespace Blob private: - //! Funció per gestionar els errors + //! Funció per gestionar els errors //! Function to manage the errors void RaiseError(const int errorCode) const; @@ -197,6 +194,3 @@ namespace Blob }; } - -#endif // !defined(_CLASSE_BLOBRESULT_INCLUDED) - diff --git a/package_bgs/jmo/CMultiLayerBGS.cpp b/package_bgs/MultiLayer/CMultiLayerBGS.cpp similarity index 99% rename from package_bgs/jmo/CMultiLayerBGS.cpp rename to package_bgs/MultiLayer/CMultiLayerBGS.cpp index 6daa9b9..4341736 100644 --- a/package_bgs/jmo/CMultiLayerBGS.cpp +++ b/package_bgs/MultiLayer/CMultiLayerBGS.cpp @@ -53,7 +53,7 @@ along with BGSLibrary. If not, see . #include // file I/O #include // math includes #include // I/O streams -#include +#include "OpenCvLegacyIncludes.h" using namespace Blob; @@ -220,16 +220,16 @@ void CMultiLayerBGS::MergeImages(int num, ...) { int img_idx = 0; for (a = 0; a < nRows; a++) - for (b = 0; b < nCols; b++) { - if (img_idx >= num) - break; + for (b = 0; b < nCols; b++) { + if (img_idx >= num) + break; - imgROIRect = cvRect(b * imgSize.width, a * imgSize.height, imgSize.width, imgSize.height); + imgROIRect = cvRect(b * imgSize.width, a * imgSize.height, imgSize.width, imgSize.height); - cvSetImageROI(ppIplImg[num], imgROIRect); - cvCopyImage(ppIplImg[img_idx++], ppIplImg[num]); - cvResetImageROI(ppIplImg[num]); - } + cvSetImageROI(ppIplImg[num], imgROIRect); + cvCopy(ppIplImg[img_idx++], ppIplImg[num]); + cvResetImageROI(ppIplImg[num]); + } delete[] ppIplImg; } @@ -369,7 +369,7 @@ void CMultiLayerBGS::SetBkMaskImage(IplImage *mask_img) { if (m_pBkMaskImg == NULL) { m_pBkMaskImg = cvCreateImage(cvGetSize(mask_img), mask_img->depth, mask_img->nChannels); } - cvCopyImage(mask_img, m_pBkMaskImg); + cvCopy(mask_img, m_pBkMaskImg); } void CMultiLayerBGS::BackgroundSubtractionProcess() { @@ -691,7 +691,7 @@ void CMultiLayerBGS::BackgroundSubtractionProcess() { removed_modes[a] = false; if (LBPs[lbp_idxes[a]].bg_layer_num > curLBP->bg_layer_num && LBPs[lbp_idxes[a]].weight < LBPs[lbp_idxes[a]].max_weight * 0.9f) { /* remove layers */ - //LBPs[lbp_idxes[a]].bg_layer_num = 0; + //LBPs[lbp_idxes[a]].bg_layer_num = 0; removed_modes[a] = true; removed_bg_layers = true; } @@ -835,7 +835,7 @@ void CMultiLayerBGS::GetBackgroundImage(IplImage *bk_img) { ODC.SetImageData(bg_img, org_data); delete[] org_data; - cvCopyImage(m_pBgImg, bk_img); + cvCopy(m_pBgImg, bk_img); } void CMultiLayerBGS::GetForegroundImage(IplImage *fg_img, CvScalar bg_color) { @@ -865,7 +865,7 @@ void CMultiLayerBGS::GetForegroundMaskImage(IplImage *fg_mask_img) { if (m_pROI && (m_pROI->width <= 0 || m_pROI->height <= 0)) return; - //cvCopyImage(m_pFgMaskImg, fg_mask_img); + //cvCopy(m_pFgMaskImg, fg_mask_img); if (m_pROI) { cvSetImageROI(m_pFgMaskImg, *m_pROI); cvSetImageROI(fg_mask_img, *m_pROI); @@ -1391,7 +1391,7 @@ void CMultiLayerBGS::GetCurrentLayeredBackgroundImage(int layered_no, IplImage * } void CMultiLayerBGS::GetColoredBgMultiLayeredImage(IplImage *bg_multi_layer_img, CvScalar *layer_colors) { - cvCopyImage(m_pOrgImg, bg_multi_layer_img); + cvCopy(m_pOrgImg, bg_multi_layer_img); COpencvDataConversion ODC; diff --git a/package_bgs/jmo/CMultiLayerBGS.h b/package_bgs/MultiLayer/CMultiLayerBGS.h similarity index 96% rename from package_bgs/jmo/CMultiLayerBGS.h rename to package_bgs/MultiLayer/CMultiLayerBGS.h index c8698f6..78a0527 100644 --- a/package_bgs/jmo/CMultiLayerBGS.h +++ b/package_bgs/MultiLayer/CMultiLayerBGS.h @@ -43,9 +43,7 @@ along with BGSLibrary. If not, see . // BackgroundSubtraction.h: interface for the CBackgroundSubtraction class. // ////////////////////////////////////////////////////////////////////// - -#if !defined(_MULTI_LAYER_BGS_H_) -#define _MULTI_LAYER_BGS_H_ +#pragma once /* Since the used fast cross bilateral filter codes can not be compiled under Windows, @@ -124,16 +122,16 @@ class CMultiLayerBGS : public CBackgroundSubtractionAPI float weight_updating_learn_rate_per_second, // mode's weight updating learning rate per second float low_init_mode_weight); // the low initial mode weight - //------------------------------------------------------------- - // PROVIDE A POINTER TO THE INPUT IMAGE - // -> INDICATE WHERE THE NEW IMAGE TO PROCESS IS STORED - // - // Here assumes that the input image will contain RGB images. - // The memory of this image is handled by the caller. - // - // The return value indicate whether the actual Background - // Subtraction algorithm handles RGB images (1) or not (0). - // +//------------------------------------------------------------- +// PROVIDE A POINTER TO THE INPUT IMAGE +// -> INDICATE WHERE THE NEW IMAGE TO PROCESS IS STORED +// +// Here assumes that the input image will contain RGB images. +// The memory of this image is handled by the caller. +// +// The return value indicate whether the actual Background +// Subtraction algorithm handles RGB images (1) or not (0). +// int SetRGBInputImage(IplImage * inputImage, CvRect *roi = NULL); //------------------------------------------------------------- @@ -308,6 +306,3 @@ class CMultiLayerBGS : public CBackgroundSubtractionAPI CMultiLayerBGS(); virtual ~CMultiLayerBGS(); }; - -#endif // !defined(_MULTI_LAYER_BGS_H_) - diff --git a/package_bgs/jmo/LocalBinaryPattern.cpp b/package_bgs/MultiLayer/LocalBinaryPattern.cpp similarity index 95% rename from package_bgs/jmo/LocalBinaryPattern.cpp rename to package_bgs/MultiLayer/LocalBinaryPattern.cpp index 6967cf4..060b9ea 100644 --- a/package_bgs/jmo/LocalBinaryPattern.cpp +++ b/package_bgs/MultiLayer/LocalBinaryPattern.cpp @@ -105,13 +105,13 @@ void CLocalBinaryPattern::Initialization(IplImage **first_imgs, int imgs_num, in m_nMaxShift.y = 0; int shift_idx = 0; for (a = 0; a < m_nLBPLevelNum; a++) - for (b = 0; b < m_pNeigPointsNums[a]; b++) { - // compute the offset of neig point - CalNeigPixelOffset(m_pRadiuses[a], m_pNeigPointsNums[a], b, m_pXYShifts[shift_idx].x, m_pXYShifts[shift_idx].y); - m_nMaxShift.x = MAX(m_nMaxShift.x, m_pXYShifts[shift_idx].x); - m_nMaxShift.y = MAX(m_nMaxShift.y, m_pXYShifts[shift_idx].y); - shift_idx++; - } + for (b = 0; b < m_pNeigPointsNums[a]; b++) { + // compute the offset of neig point + CalNeigPixelOffset(m_pRadiuses[a], m_pNeigPointsNums[a], b, m_pXYShifts[shift_idx].x, m_pXYShifts[shift_idx].y); + m_nMaxShift.x = MAX(m_nMaxShift.x, m_pXYShifts[shift_idx].x); + m_nMaxShift.y = MAX(m_nMaxShift.y, m_pXYShifts[shift_idx].y); + shift_idx++; + } m_fRobustWhiteNoise = robust_white_noise; } @@ -155,7 +155,7 @@ void CLocalBinaryPattern::ComputeLBP(PixelLBPStruct *PLBP, CvRect *roi) if (roi) { int x, y; - for (y = 0; y < roi->height; y++) { + for (y = 0; y < roi->height; y++) { _PLBP = PLBP + (y + roi->y)*m_cvImgSize.width + roi->x; for (x = 0; x < roi->width; x++) { cur_pattern = (*_PLBP++).cur_pattern; diff --git a/package_bgs/jmo/LocalBinaryPattern.h b/package_bgs/MultiLayer/LocalBinaryPattern.h similarity index 96% rename from package_bgs/jmo/LocalBinaryPattern.h rename to package_bgs/MultiLayer/LocalBinaryPattern.h index 987a2c2..40d4379 100644 --- a/package_bgs/jmo/LocalBinaryPattern.h +++ b/package_bgs/MultiLayer/LocalBinaryPattern.h @@ -43,11 +43,9 @@ along with BGSLibrary. If not, see . // LocalBinaryPattern.h: interface for the CLocalBinaryPattern class. // ////////////////////////////////////////////////////////////////////// +#pragma once -#if !defined(_LOCAL_BINARY_PATTERN_H_) -#define _LOCAL_BINARY_PATTERN_H_ - -#include +#include "OpenCvLegacyIncludes.h" #include "BGS.h" @@ -98,6 +96,3 @@ class CLocalBinaryPattern IplImage* m_pShiftedImg; }; - -#endif // !defined(_LOCAL_BINARY_PATTERN_H_) - diff --git a/package_bgs/jmo/OpenCvDataConversion.h b/package_bgs/MultiLayer/OpenCvDataConversion.h similarity index 97% rename from package_bgs/jmo/OpenCvDataConversion.h rename to package_bgs/MultiLayer/OpenCvDataConversion.h index a47563e..18371b3 100644 --- a/package_bgs/jmo/OpenCvDataConversion.h +++ b/package_bgs/MultiLayer/OpenCvDataConversion.h @@ -43,11 +43,9 @@ along with BGSLibrary. If not, see . // OpencvDataConversion.h: interface for the COpencvDataConversion class. // ////////////////////////////////////////////////////////////////////// +#pragma once -#if !defined(_OPENCV_DATA_CONVERSION_H_) -#define _OPENCV_DATA_CONVERSION_H_ - -#include +#include "OpenCvLegacyIncludes.h" #include template /* class TI - the type of image data, class TM - the type of matrix data */ @@ -219,6 +217,3 @@ class COpencvDataConversion COpencvDataConversion() {}; virtual ~COpencvDataConversion() {}; }; - -#endif // !defined(_OPENCV_DATA_CONVERSION_H_) - diff --git a/package_bgs/MultiLayer/OpenCvLegacyIncludes.h b/package_bgs/MultiLayer/OpenCvLegacyIncludes.h new file mode 100644 index 0000000..9d30c0e --- /dev/null +++ b/package_bgs/MultiLayer/OpenCvLegacyIncludes.h @@ -0,0 +1,50 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* --- --- --- +* Copyright (C) 2008--2010 Idiap Research Institute (.....@idiap.ch) +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. 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. +* 3. The name of the author may not be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. +*/ +// OpenCvLegacyIncludes.h: necessary includes to compile with OpenCV 3. +// +////////////////////////////////////////////////////////////////////// +#pragma once + +#include "opencv2/core/core_c.h" +#include "opencv2/core/types_c.h" +#include "opencv2/imgproc/imgproc_c.h" diff --git a/package_bgs/MultiLayer/blob.cpp b/package_bgs/MultiLayer/blob.cpp new file mode 100644 index 0000000..5e0fa45 --- /dev/null +++ b/package_bgs/MultiLayer/blob.cpp @@ -0,0 +1,1148 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/* --- --- --- +* Copyright (C) 2008--2010 Idiap Research Institute (.....@idiap.ch) +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. 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. +* 3. The name of the author may not be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. +*/ +/************************************************************************ +Blob.cpp + +- FUNCIONALITAT: Implementació de la classe CBlob +- AUTOR: Inspecta S.L. +MODIFICACIONS (Modificació, Autor, Data): + + +FUNCTIONALITY: Implementation of the CBlob class and some helper classes to perform +some calculations on it +AUTHOR: Inspecta S.L. +MODIFICATIONS (Modification, Author, Date): + +**************************************************************************/ + + +#include +#include "blob.h" + +namespace Blob +{ + + /** + - FUNCIÓ: CBlob + - FUNCIONALITAT: Constructor estàndard + - PARÀMETRES: + - RESULTAT: + - inicialització de totes les variables internes i de l'storage i la sequencia + per a les cantonades del blob + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlob + - FUNCTIONALITY: Standard constructor + - PARAMETERS: + - RESULT: + - memory allocation for the blob edges and initialization of member variables + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlob::CBlob() + { + etiqueta = -1; // Flag indicates null region + exterior = 0; + area = 0.0f; + perimeter = 0.0f; + parent = -1; + minx = LONG_MAX; + maxx = 0; + miny = LONG_MAX; + maxy = 0; + sumx = 0; + sumy = 0; + sumxx = 0; + sumyy = 0; + sumxy = 0; + mean = 0; + stddev = 0; + externPerimeter = 0; + + m_storage = cvCreateMemStorage(0); + edges = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, + sizeof(CvContour), + sizeof(CvPoint), m_storage); + } + + /** + - FUNCIÓ: CBlob + - FUNCIONALITAT: Constructor de còpia + - PARÀMETRES: + - RESULTAT: + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlob + - FUNCTIONALITY: Copy constructor + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlob::CBlob(const CBlob &src) + { + // copiem les propietats del blob origen a l'actual + etiqueta = src.etiqueta; + exterior = src.exterior; + area = src.Area(); + perimeter = src.Perimeter(); + parent = src.parent; + minx = src.minx; + maxx = src.maxx; + miny = src.miny; + maxy = src.maxy; + sumx = src.sumx; + sumy = src.sumy; + sumxx = src.sumxx; + sumyy = src.sumyy; + sumxy = src.sumxy; + mean = src.mean; + stddev = src.stddev; + externPerimeter = src.externPerimeter; + + // copiem els edges del blob origen a l'actual + CvSeqReader reader; + CvSeqWriter writer; + CvPoint edgeactual; + + // creem una sequencia buida per als edges + m_storage = cvCreateMemStorage(0); + edges = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, + sizeof(CvContour), + sizeof(CvPoint), m_storage); + + cvStartReadSeq(src.Edges(), &reader); + cvStartAppendToSeq(edges, &writer); + + for (int i = 0; i < src.Edges()->total; i++) + { + CV_READ_SEQ_ELEM(edgeactual, reader); + CV_WRITE_SEQ_ELEM(edgeactual, writer); + } + + cvEndWriteSeq(&writer); + } + CBlob::CBlob(const CBlob *src) + { + // copiem les propietats del blob origen a l'actual + etiqueta = src->etiqueta; + exterior = src->exterior; + area = src->Area(); + perimeter = src->Perimeter(); + parent = src->parent; + minx = src->minx; + maxx = src->maxx; + miny = src->miny; + maxy = src->maxy; + sumx = src->sumx; + sumy = src->sumy; + sumxx = src->sumxx; + sumyy = src->sumyy; + sumxy = src->sumxy; + mean = src->mean; + stddev = src->stddev; + externPerimeter = src->externPerimeter; + + // copiem els edges del blob origen a l'actual + CvSeqReader reader; + CvSeqWriter writer; + CvPoint edgeactual; + + // creem una sequencia buida per als edges + m_storage = cvCreateMemStorage(0); + edges = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, + sizeof(CvContour), + sizeof(CvPoint), m_storage); + + cvStartReadSeq(src->Edges(), &reader); + cvStartAppendToSeq(edges, &writer); + + for (int i = 0; i < src->Edges()->total; i++) + { + CV_READ_SEQ_ELEM(edgeactual, reader); + CV_WRITE_SEQ_ELEM(edgeactual, writer); + } + + cvEndWriteSeq(&writer); + } + + /** + - FUNCIÓ: ~CBlob + - FUNCIONALITAT: Destructor estàndard + - PARÀMETRES: + - RESULTAT: + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlob + - FUNCTIONALITY: Standard destructor + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlob::~CBlob() + { + // Eliminar vèrtexs del blob + cvClearSeq(edges); + // i la zona de memòria on són + cvReleaseMemStorage(&m_storage); + } + + /** + - FUNCIÓ: operator= + - FUNCIONALITAT: Operador d'assignació + - PARÀMETRES: + - src: blob a assignar a l'actual + - RESULTAT: + - Substitueix el blob actual per el src + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: Assigment operator + - FUNCTIONALITY: Assigns a blob to the current + - PARAMETERS: + - src: blob to assign + - RESULT: + - the current blob is replaced by the src blob + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CBlob& CBlob::operator=(const CBlob &src) + { + // si ja són el mateix, no cal fer res + if (this != &src) + { + // Eliminar vèrtexs del blob + cvClearSeq(edges); + // i la zona de memòria on són + cvReleaseMemStorage(&m_storage); + + // creem una sequencia buida per als edges + m_storage = cvCreateMemStorage(0); + edges = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, + sizeof(CvContour), + sizeof(CvPoint), m_storage); + + // copiem les propietats del blob origen a l'actual + etiqueta = src.etiqueta; + exterior = src.exterior; + area = src.Area(); + perimeter = src.Perimeter(); + parent = src.parent; + minx = src.minx; + maxx = src.maxx; + miny = src.miny; + maxy = src.maxy; + sumx = src.sumx; + sumy = src.sumy; + sumxx = src.sumxx; + sumyy = src.sumyy; + sumxy = src.sumxy; + mean = src.mean; + stddev = src.stddev; + externPerimeter = src.externPerimeter; + + // copiem els edges del blob origen a l'actual + CvSeqReader reader; + CvSeqWriter writer; + CvPoint edgeactual; + + cvStartReadSeq(src.Edges(), &reader); + cvStartAppendToSeq(edges, &writer); + + for (int i = 0; i < src.Edges()->total; i++) + { + CV_READ_SEQ_ELEM(edgeactual, reader); + CV_WRITE_SEQ_ELEM(edgeactual, writer); + } + + cvEndWriteSeq(&writer); + } + return *this; + } + + /** + - FUNCIÓ: FillBlob + - FUNCIONALITAT: Pinta l'interior d'un blob amb el color especificat + - PARÀMETRES: + - imatge: imatge on es vol pintar el el blob + - color: color amb que es vol pintar el blob + - RESULTAT: + - retorna la imatge d'entrada amb el blob pintat + - RESTRICCIONS: + - AUTOR: + - Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: FillBlob + - FUNCTIONALITY: + - Fills the blob with a specified colour + - PARAMETERS: + - imatge: where to paint + - color: colour to paint the blob + - RESULT: + - modifies input image and returns the seed point used to fill the blob + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + void CBlob::FillBlob(IplImage *imatge, CvScalar color, int offsetX /*=0*/, int offsetY /*=0*/) const + { + + //verifiquem que existeixi el blob i que tingui cantonades + if (edges == NULL || edges->total == 0) return; + + CvPoint edgeactual, pt1, pt2; + CvSeqReader reader; + vectorPunts vectorEdges = vectorPunts(edges->total); + vectorPunts::iterator itEdges, itEdgesSeguent; + bool dinsBlob; + int yActual; + + // passem els punts del blob a un vector de punts de les STL + cvStartReadSeq(edges, &reader); + itEdges = vectorEdges.begin(); + while (itEdges != vectorEdges.end()) + { + CV_READ_SEQ_ELEM(edgeactual, reader); + *itEdges = edgeactual; + ++itEdges; + } + // ordenem el vector per les Y's i les X's d'esquerra a dreta + std::sort(vectorEdges.begin(), vectorEdges.end(), comparaCvPoint()); + + // recorrem el vector ordenat i fem linies entre punts consecutius + itEdges = vectorEdges.begin(); + itEdgesSeguent = vectorEdges.begin() + 1; + dinsBlob = true; + while (itEdges != (vectorEdges.end() - 1)) + { + yActual = (*itEdges).y; + + if (((*itEdges).x != (*itEdgesSeguent).x) && + ((*itEdgesSeguent).y == yActual) + ) + { + if (dinsBlob) + { + pt1 = *itEdges; + pt1.x += offsetX; + pt1.y += offsetY; + + pt2 = *itEdgesSeguent; + pt2.x += offsetX; + pt2.y += offsetY; + + cvLine(imatge, pt1, pt2, color); + } + dinsBlob = !dinsBlob; + } + ++itEdges; + ++itEdgesSeguent; + if ((*itEdges).y != yActual) dinsBlob = true; + } + vectorEdges.clear(); + } + + /** + - FUNCIÓ: CopyEdges + - FUNCIONALITAT: Afegeix els vèrtexs del blob al blob destination + - PARÀMETRES: + - destination: blob al que volem afegir els vèrtexs + - RESULTAT: + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CopyEdges + - FUNCTIONALITY: Adds the blob edges to destination + - PARAMETERS: + - destination: where to add the edges + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + void CBlob::CopyEdges(CBlob &destination) const + { + CvSeqReader reader; + CvSeqWriter writer; + CvPoint edgeactual; + + cvStartReadSeq(edges, &reader); + cvStartAppendToSeq(destination.Edges(), &writer); + + for (int i = 0; i < edges->total; i++) + { + CV_READ_SEQ_ELEM(edgeactual, reader); + CV_WRITE_SEQ_ELEM(edgeactual, writer); + } + + cvEndWriteSeq(&writer); + } + + /** + - FUNCIÓ: ClearEdges + - FUNCIONALITAT: Elimina els vèrtexs del blob + - PARÀMETRES: + - RESULTAT: + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: ClearEdges + - FUNCTIONALITY: Delete current blob edges + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + void CBlob::ClearEdges() + { + // Eliminar vèrtexs del blob eliminat + cvClearSeq(edges); + } + + /** + - FUNCIÓ: GetConvexHull + - FUNCIONALITAT: Retorna el poligon convex del blob + - PARÀMETRES: + - dst: sequencia on desarem el resultat (no ha d'estar inicialitzada) + - RESULTAT: + - true si tot ha anat bé + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: GetConvexHull + - FUNCTIONALITY: Calculates the convex hull polygon of the blob + - PARAMETERS: + - dst: where to store the result + - RESULT: + - true if no error ocurred + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + bool CBlob::GetConvexHull(CvSeq **dst) const + { + if (edges != NULL && edges->total > 0) + { + *dst = cvConvexHull2(edges, 0, CV_CLOCKWISE, 0); + return true; + } + return false; + } + + /** + - FUNCIÓ: GetEllipse + - FUNCIONALITAT: Retorna l'ellipse que s'ajusta millor a les cantonades del blob + - PARÀMETRES: + - RESULTAT: + - estructura amb l'ellipse + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 25-05-2005. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: GetEllipse + - FUNCTIONALITY: Calculates the ellipse that best fits the edges of the blob + - PARAMETERS: + - RESULT: + - CvBox2D struct with the calculated ellipse + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + CvBox2D CBlob::GetEllipse() const + { + CvBox2D elipse; + // necessitem 6 punts per calcular l'elipse + if (edges != NULL && edges->total > 6) + { + elipse = cvFitEllipse2(edges); + } + else + { + elipse.center.x = 0.0; + elipse.center.y = 0.0; + elipse.size.width = 0.0; + elipse.size.height = 0.0; + elipse.angle = 0.0; + } + return elipse; + } + + + + /*************************************************************************** + Implementació de les classes per al càlcul de característiques sobre el blob + + Implementation of the helper classes to perform operations on blobs + **************************************************************************/ + + /** + - FUNCIÓ: Moment + - FUNCIONALITAT: Calcula el moment pq del blob + - RESULTAT: + - retorna el moment pq especificat o 0 si el moment no està implementat + - RESTRICCIONS: + - Implementats els moments pq: 00, 01, 10, 20, 02 + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 20-07-2004. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: Moment + - FUNCTIONALITY: Calculates the pq moment of the blob + - PARAMETERS: + - RESULT: + - returns the pq moment or 0 if the moment it is not implemented + - RESTRICTIONS: + - Currently, only implemented the 00, 01, 10, 20, 02 pq moments + - AUTHOR: Ricard Borràs + - CREATION DATE: 20-07-2004. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetMoment::operator()(const CBlob &blob) const + { + //Moment 00 + if ((m_p == 0) && (m_q == 0)) + return blob.Area(); + + //Moment 10 + if ((m_p == 1) && (m_q == 0)) + return blob.SumX(); + + //Moment 01 + if ((m_p == 0) && (m_q == 1)) + return blob.SumY(); + + //Moment 20 + if ((m_p == 2) && (m_q == 0)) + return blob.SumXX(); + + //Moment 02 + if ((m_p == 0) && (m_q == 2)) + return blob.SumYY(); + + return 0; + } + + /** + - FUNCIÓ: HullPerimeter + - FUNCIONALITAT: Calcula la longitud del perimetre convex del blob. + Fa servir la funció d'OpenCV cvConvexHull2 per a + calcular el perimetre convex. + + - PARÀMETRES: + - RESULTAT: + - retorna la longitud del perímetre convex del blob. Si el blob no té coordenades + associades retorna el perímetre normal del blob. + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 20-07-2004. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlobGetHullPerimeter + - FUNCTIONALITY: Calculates the convex hull perimeter of the blob + - PARAMETERS: + - RESULT: + - returns the convex hull perimeter of the blob or the perimeter if the + blob edges could not be retrieved + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetHullPerimeter::operator()(const CBlob &blob) const + { + if (blob.Edges() != NULL && blob.Edges()->total > 0) + { + CvSeq *hull = cvConvexHull2(blob.Edges(), 0, CV_CLOCKWISE, 1); + return fabs(cvArcLength(hull, CV_WHOLE_SEQ, 1)); + } + return blob.Perimeter(); + } + + double CBlobGetHullArea::operator()(const CBlob &blob) const + { + if (blob.Edges() != NULL && blob.Edges()->total > 0) + { + CvSeq *hull = cvConvexHull2(blob.Edges(), 0, CV_CLOCKWISE, 1); + return fabs(cvContourArea(hull)); + } + return blob.Perimeter(); + } + + /** + - FUNCIÓ: MinX_at_MinY + - FUNCIONALITAT: Calcula el valor MinX a MinY. + - PARÀMETRES: + - blob: blob del que volem calcular el valor + - RESULTAT: + - retorna la X minima en la Y minima. + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 20-07-2004. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlobGetMinXatMinY + - FUNCTIONALITY: Calculates the minimum X on the minimum Y + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetMinXatMinY::operator()(const CBlob &blob) const + { + double MinX_at_MinY = LONG_MAX; + + CvSeqReader reader; + CvPoint edgeactual; + + cvStartReadSeq(blob.Edges(), &reader); + + for (int j = 0; j < blob.Edges()->total; j++) + { + CV_READ_SEQ_ELEM(edgeactual, reader); + if ((edgeactual.y == blob.MinY()) && (edgeactual.x < MinX_at_MinY)) + { + MinX_at_MinY = edgeactual.x; + } + } + + return MinX_at_MinY; + } + + /** + - FUNCIÓ: MinY_at_MaxX + - FUNCIONALITAT: Calcula el valor MinX a MaxX. + - PARÀMETRES: + - blob: blob del que volem calcular el valor + - RESULTAT: + - retorna la Y minima en la X maxima. + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 20-07-2004. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlobGetMinXatMinY + - FUNCTIONALITY: Calculates the minimum Y on the maximum X + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetMinYatMaxX::operator()(const CBlob &blob) const + { + double MinY_at_MaxX = LONG_MAX; + + CvSeqReader reader; + CvPoint edgeactual; + + cvStartReadSeq(blob.Edges(), &reader); + + for (int j = 0; j < blob.Edges()->total; j++) + { + CV_READ_SEQ_ELEM(edgeactual, reader); + if ((edgeactual.x == blob.MaxX()) && (edgeactual.y < MinY_at_MaxX)) + { + MinY_at_MaxX = edgeactual.y; + } + } + + return MinY_at_MaxX; + } + + /** + - FUNCIÓ: MaxX_at_MaxY + - FUNCIONALITAT: Calcula el valor MaxX a MaxY. + - PARÀMETRES: + - blob: blob del que volem calcular el valor + - RESULTAT: + - retorna la X maxima en la Y maxima. + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 20-07-2004. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlobGetMaxXatMaxY + - FUNCTIONALITY: Calculates the maximum X on the maximum Y + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetMaxXatMaxY::operator()(const CBlob &blob) const + { + double MaxX_at_MaxY = LONG_MIN; + + CvSeqReader reader; + CvPoint edgeactual; + + cvStartReadSeq(blob.Edges(), &reader); + + for (int j = 0; j < blob.Edges()->total; j++) + { + CV_READ_SEQ_ELEM(edgeactual, reader); + if ((edgeactual.y == blob.MaxY()) && (edgeactual.x > MaxX_at_MaxY)) + { + MaxX_at_MaxY = edgeactual.x; + } + } + + return MaxX_at_MaxY; + } + + /** + - FUNCIÓ: MaxY_at_MinX + - FUNCIONALITAT: Calcula el valor MaxY a MinX. + - PARÀMETRES: + - blob: blob del que volem calcular el valor + - RESULTAT: + - retorna la Y maxima en la X minima. + - RESTRICCIONS: + - AUTOR: Ricard Borràs + - DATA DE CREACIÓ: 20-07-2004. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: CBlobGetMaxYatMinX + - FUNCTIONALITY: Calculates the maximum Y on the minimum X + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetMaxYatMinX::operator()(const CBlob &blob) const + { + double MaxY_at_MinX = LONG_MIN; + + CvSeqReader reader; + CvPoint edgeactual; + + cvStartReadSeq(blob.Edges(), &reader); + + for (int j = 0; j < blob.Edges()->total; j++) + { + CV_READ_SEQ_ELEM(edgeactual, reader); + if ((edgeactual.x == blob.MinY()) && (edgeactual.y > MaxY_at_MinX)) + { + MaxY_at_MinX = edgeactual.y; + } + } + + return MaxY_at_MinX; + } + + /** + Retorna l'elongació del blob (longitud/amplada) + */ + /** + - FUNCTION: CBlobGetElongation + - FUNCTIONALITY: Calculates the elongation of the blob ( length/breadth ) + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - See below to see how the lenght and the breadth are aproximated + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetElongation::operator()(const CBlob &blob) const + { + double ampladaC, longitudC, amplada, longitud; + + ampladaC = (double)(blob.Perimeter() + sqrt(pow(blob.Perimeter(), 2) - 16 * blob.Area())) / 4; + if (ampladaC <= 0.0) return 0; + longitudC = (double)blob.Area() / ampladaC; + + longitud = MAX(longitudC, ampladaC); + amplada = MIN(longitudC, ampladaC); + + return (double)longitud / amplada; + } + + /** + Retorna la compacitat del blob + */ + /** + - FUNCTION: CBlobGetCompactness + - FUNCTIONALITY: Calculates the compactness of the blob + ( maximum for circle shaped blobs, minimum for the rest) + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetCompactness::operator()(const CBlob &blob) const + { + if (blob.Area() != 0.0) + return (double)pow(blob.Perimeter(), 2) / (4 * CV_PI*blob.Area()); + else + return 0.0; + } + + /** + Retorna la rugositat del blob + */ + /** + - FUNCTION: CBlobGetRoughness + - FUNCTIONALITY: Calculates the roughness of the blob + ( ratio between perimeter and convex hull perimeter) + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetRoughness::operator()(const CBlob &blob) const + { + CBlobGetHullPerimeter getHullPerimeter = CBlobGetHullPerimeter(); + + double hullPerimeter = getHullPerimeter(blob); + + if (hullPerimeter != 0.0) + return blob.Perimeter() / hullPerimeter;//HullPerimeter(); + + return 0.0; + } + + /** + Retorna la longitud del blob + */ + /** + - FUNCTION: CBlobGetLength + - FUNCTIONALITY: Calculates the lenght of the blob (the biggest axis of the blob) + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - The lenght is an aproximation to the real lenght + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetLength::operator()(const CBlob &blob) const + { + double ampladaC, longitudC; + double tmp; + + tmp = blob.Perimeter()*blob.Perimeter() - 16 * blob.Area(); + + if (tmp > 0.0) + ampladaC = (double)(blob.Perimeter() + sqrt(tmp)) / 4; + // error intrínsec en els càlculs de l'àrea i el perímetre + else + ampladaC = (double)(blob.Perimeter()) / 4; + + if (ampladaC <= 0.0) return 0; + longitudC = (double)blob.Area() / ampladaC; + + return MAX(longitudC, ampladaC); + } + + /** + Retorna l'amplada del blob + */ + /** + - FUNCTION: CBlobGetBreadth + - FUNCTIONALITY: Calculates the breadth of the blob (the smallest axis of the blob) + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - The breadth is an aproximation to the real breadth + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetBreadth::operator()(const CBlob &blob) const + { + double ampladaC, longitudC; + double tmp; + + tmp = blob.Perimeter()*blob.Perimeter() - 16 * blob.Area(); + + if (tmp > 0.0) + ampladaC = (double)(blob.Perimeter() + sqrt(tmp)) / 4; + // error intrínsec en els càlculs de l'àrea i el perímetre + else + ampladaC = (double)(blob.Perimeter()) / 4; + + if (ampladaC <= 0.0) return 0; + longitudC = (double)blob.Area() / ampladaC; + + return MIN(longitudC, ampladaC); + } + + /** + Calcula la distància entre un punt i el centre del blob + */ + /** + - FUNCTION: CBlobGetDistanceFromPoint + - FUNCTIONALITY: Calculates the euclidean distance between the blob center and + the point specified in the constructor + - PARAMETERS: + - RESULT: + - RESTRICTIONS: + - AUTHOR: Ricard Borràs + - CREATION DATE: 25-05-2005. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetDistanceFromPoint::operator()(const CBlob &blob) const + { + double xmitjana, ymitjana; + CBlobGetXCenter getXCenter; + CBlobGetYCenter getYCenter; + + xmitjana = m_x - getXCenter(blob); + ymitjana = m_y - getYCenter(blob); + + return sqrt((xmitjana*xmitjana) + (ymitjana*ymitjana)); + } + + /** + - FUNCIÓ: BlobGetXYInside + - FUNCIONALITAT: Calcula si un punt cau dins de la capsa rectangular + del blob + - RESULTAT: + - retorna 1 si hi està; 0 si no + - RESTRICCIONS: + - AUTOR: Francesc Pinyol Margalef + - DATA DE CREACIÓ: 16-01-2006. + - MODIFICACIÓ: Data. Autor. Descripció. + */ + /** + - FUNCTION: BlobGetXYInside + - FUNCTIONALITY: Calculates whether a point is inside the + rectangular bounding box of a blob + - PARAMETERS: + - RESULT: + - returns 1 if it is inside; o if not + - RESTRICTIONS: + - AUTHOR: Francesc Pinyol Margalef + - CREATION DATE: 16-01-2006. + - MODIFICATION: Date. Author. Description. + */ + double CBlobGetXYInside::operator()(const CBlob &blob) const + { + if (blob.Edges() == NULL || blob.Edges()->total == 0) return 0.0; + + // passem els punts del blob a un vector de punts de les STL + CvSeqReader reader; + CBlob::vectorPunts vectorEdges; + CBlob::vectorPunts::iterator itEdges, itEdgesSeguent; + CvPoint edgeactual; + bool dinsBlob; + + // agafem tots els punts amb la mateixa y que l'actual + cvStartReadSeq(blob.Edges(), &reader); + + for (int i = 0; i < blob.Edges()->total; i++) + { + CV_READ_SEQ_ELEM(edgeactual, reader); + if (edgeactual.y == m_p.y) + vectorEdges.push_back(edgeactual); + } + + if (vectorEdges.empty()) return 0.0; + + // ordenem el vector per les Y's i les X's d'esquerra a dreta + std::sort(vectorEdges.begin(), vectorEdges.end(), CBlob::comparaCvPoint()); + + // recorrem el punts del blob de la mateixa fila que el punt d'entrada + // i mirem si la X del punt d'entrada està entre dos coordenades "plenes" + // del blob + itEdges = vectorEdges.begin(); + itEdgesSeguent = vectorEdges.begin() + 1; + dinsBlob = true; + + while (itEdges != (vectorEdges.end() - 1)) + { + if ((*itEdges).x <= m_p.x && (*itEdgesSeguent).x >= m_p.x && dinsBlob) + { + vectorEdges.clear(); + return 1.0; + } + + ++itEdges; + ++itEdgesSeguent; + dinsBlob = !dinsBlob; + } + + vectorEdges.clear(); + return 0.0; + } + +#ifdef BLOB_OBJECT_FACTORY + + /** + - FUNCIÓ: RegistraTotsOperadors + - FUNCIONALITAT: Registrar tots els operadors definits a blob.h + - PARÀMETRES: + - fabricaOperadorsBlob: fàbrica on es registraran els operadors + - RESULTAT: + - Modifica l'objecte fabricaOperadorsBlob + - RESTRICCIONS: + - Només es registraran els operadors de blob.h. Si se'n volen afegir, cal afegir-los amb + el mètode Register de la fàbrica. + - AUTOR: rborras + - DATA DE CREACIÓ: 2006/05/18 + - MODIFICACIÓ: Data. Autor. Descripció. + */ + void RegistraTotsOperadors(t_OperadorBlobFactory &fabricaOperadorsBlob) + { + // blob shape + fabricaOperadorsBlob.Register(CBlobGetArea().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetBreadth().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetCompactness().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetElongation().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetExterior().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetLength().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetPerimeter().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetRoughness().GetNom(), Type2Type()); + + // extern pixels + fabricaOperadorsBlob.Register(CBlobGetExternPerimeterRatio().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetExternHullPerimeterRatio().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetExternPerimeter().GetNom(), Type2Type()); + + + // hull + fabricaOperadorsBlob.Register(CBlobGetHullPerimeter().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetHullArea().GetNom(), Type2Type()); + + + // elipse info + fabricaOperadorsBlob.Register(CBlobGetMajorAxisLength().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetMinorAxisLength().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetAxisRatio().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetOrientation().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetOrientationCos().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetAreaElipseRatio().GetNom(), Type2Type()); + + // min an max + fabricaOperadorsBlob.Register(CBlobGetMaxX().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetMaxY().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetMinX().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetMinY().GetNom(), Type2Type()); + + fabricaOperadorsBlob.Register(CBlobGetMaxXatMaxY().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetMaxYatMinX().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetMinXatMinY().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetMinYatMaxX().GetNom(), Type2Type()); + + // grey level stats + fabricaOperadorsBlob.Register(CBlobGetMean().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetStdDev().GetNom(), Type2Type()); + + // coordinate info + fabricaOperadorsBlob.Register(CBlobGetXYInside().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetDiffY().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetDiffX().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetXCenter().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetYCenter().GetNom(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetDistanceFromPoint().GetNom(), Type2Type()); + + // moments + fabricaOperadorsBlob.Register(CBlobGetMoment().GetNom(), Type2Type()); + + } + +#endif + +} + diff --git a/package_bgs/jmo/blob.h b/package_bgs/MultiLayer/blob.h similarity index 87% rename from package_bgs/jmo/blob.h rename to package_bgs/MultiLayer/blob.h index 67bdf11..b7d5fe1 100644 --- a/package_bgs/jmo/blob.h +++ b/package_bgs/MultiLayer/blob.h @@ -43,9 +43,9 @@ along with BGSLibrary. If not, see . /************************************************************************ Blob.h -FUNCIONALITAT: Definició de la classe CBlob +FUNCIONALITAT: Definició de la classe CBlob AUTOR: Inspecta S.L. -MODIFICACIONS (Modificació, Autor, Data): +MODIFICACIONS (Modificació, Autor, Data): FUNCTIONALITY: Definition of the CBlob class and some helper classes to perform some calculations on it @@ -53,51 +53,46 @@ AUTHOR: Inspecta S.L. MODIFICATIONS (Modification, Author, Date): **************************************************************************/ - -//! Disable warnings referred to 255 character truncation for the std:map -//#pragma warning( disable : 4786 ) - -#ifndef CBLOB_INSPECTA_INCLUDED -#define CBLOB_INSPECTA_INCLUDED +#pragma once //#include "cxcore.h" #include "BlobLibraryConfiguration.h" #include #include #include -#include -//! Factor de conversió de graus a radians +#include "OpenCvLegacyIncludes.h" +//! Factor de conversió de graus a radians #define DEGREE2RAD (CV_PI / 180.0) namespace Blob { /** - Classe que representa un blob, entés com un conjunt de pixels del - mateix color contigus en una imatge binaritzada. + Classe que representa un blob, entés com un conjunt de pixels del + mateix color contigus en una imatge binaritzada. - Class to represent a blob, a group of connected pixels in a binary image - */ + Class to represent a blob, a group of connected pixels in a binary image + */ class CBlob { public: - //! Constructor estàndard + //! Constructor estàndard //! Standard constructor CBlob(); - //! Constructor de còpia + //! Constructor de còpia //! Copy constructor CBlob(const CBlob &src); CBlob(const CBlob *src); - //! Destructor estàndard + //! Destructor estàndard //! Standard Destructor ~CBlob(); - //! Operador d'assignació + //! Operador d'assignació //! Assigment operator CBlob& operator=(const CBlob &src); - //! Indica si el blob està buit ( no té cap info associada ) + //! Indica si el blob està buit ( no té cap info associada ) //! Shows if the blob has associated information bool IsEmpty() const { @@ -107,13 +102,13 @@ namespace Blob //! Neteja les cantonades del blob //! Clears the edges of the blob void ClearEdges(); - //! Copia les cantonades del blob a un altre (les afegeix al destí) + //! Copia les cantonades del blob a un altre (les afegeix al destí) //! Adds the blob edges to another blob void CopyEdges(CBlob &destination) const; //! Retorna el poligon convex del blob //! Calculates the convex hull of the blob bool GetConvexHull(CvSeq **dst) const; - //! Calcula l'elipse que s'adapta als vèrtexs del blob + //! Calcula l'elipse que s'adapta als vèrtexs del blob //! Fits an ellipse to the blob edges CvBox2D GetEllipse() const; @@ -124,8 +119,8 @@ namespace Blob //! Funcions GET sobre els valors dels blobs //! Get functions - inline int Label() const { return etiqueta; } - inline int Parent() const { return parent; } + inline int Label() const { return etiqueta; } + inline int Parent() const { return parent; } inline double Area() const { return area; } inline double Perimeter() const { return perimeter; } inline double ExternPerimeter() const { return externPerimeter; } @@ -176,14 +171,14 @@ namespace Blob //! mitjana //! mean of the grey scale values of the blob pixels double mean; - //! desviació standard + //! desviació standard //! standard deviation of the grey scale values of the blob pixels double stddev; - //! àrea de memòria on es desaran els punts de contorn del blob + //! àrea de memòria on es desaran els punts de contorn del blob //! storage which contains the edges of the blob CvMemStorage *m_storage; - //! Sequència de punts del contorn del blob + //! Sequència de punts del contorn del blob //! Sequence with the edges of the blob CvSeq *edges; @@ -194,7 +189,7 @@ namespace Blob //! Helper class to compare two CvPoints (for sorting in FillBlob) struct comparaCvPoint : public std::binary_function { - //! Definim que un punt és menor com més amunt a la dreta estigui + //! Definim que un punt és menor com més amunt a la dreta estigui bool operator()(CvPoint a, CvPoint b) { if (a.y == b.y) @@ -208,22 +203,22 @@ namespace Blob /************************************************************************** - Definició de les classes per a fer operacions sobre els blobs + Definició de les classes per a fer operacions sobre els blobs - Helper classes to perform operations on blobs - **************************************************************************/ + Helper classes to perform operations on blobs + **************************************************************************/ - //! Classe d'on derivarem totes les operacions sobre els blobs - //! Interface to derive all blob operations + //! Classe d'on derivarem totes les operacions sobre els blobs + //! Interface to derive all blob operations class COperadorBlob { public: - virtual ~COperadorBlob(){}; + virtual ~COperadorBlob() {}; - //! Aplica l'operació al blob + //! Aplica l'operació al blob virtual double operator()(const CBlob &blob) const = 0; - //! Obté el nom de l'operador + //! Obté el nom de l'operador virtual const char *GetNom() const = 0; operator COperadorBlob*() const @@ -236,8 +231,8 @@ namespace Blob #ifdef BLOB_OBJECT_FACTORY /** - Funció per comparar dos identificadors dins de la fàbrica de COperadorBlobs - */ + Funció per comparar dos identificadors dins de la fàbrica de COperadorBlobs + */ struct functorComparacioIdOperador { bool operator()(const char* s1, const char* s2) const @@ -249,12 +244,12 @@ namespace Blob //! Definition of Object factory type for COperadorBlob objects typedef ObjectFactory t_OperadorBlobFactory; - //! Funció global per a registrar tots els operadors definits a blob.h - void RegistraTotsOperadors( t_OperadorBlobFactory &fabricaOperadorsBlob ); + //! Funció global per a registrar tots els operadors definits a blob.h + void RegistraTotsOperadors(t_OperadorBlobFactory &fabricaOperadorsBlob); #endif - //! Classe per calcular l'àrea d'un blob + //! Classe per calcular l'àrea d'un blob //! Class to get the area of a blob class CBlobGetArea : public COperadorBlob { @@ -284,7 +279,7 @@ namespace Blob } }; - //! Classe que diu si un blob és extern o no + //! Classe que diu si un blob és extern o no //! Class to get the extern flag of a blob class CBlobGetExterior : public COperadorBlob { @@ -314,7 +309,7 @@ namespace Blob } }; - //! Classe per calcular la desviació estàndard dels nivells de gris d'un blob + //! Classe per calcular la desviació estàndard dels nivells de gris d'un blob //! Class to get the standard deviation of the grey level values of a blob class CBlobGetStdDev : public COperadorBlob { @@ -365,7 +360,7 @@ namespace Blob } }; - //! Classe per calcular la diferència en X del blob + //! Classe per calcular la diferència en X del blob class CBlobGetDiffX : public COperadorBlob { public: @@ -379,7 +374,7 @@ namespace Blob } }; - //! Classe per calcular la diferència en X del blob + //! Classe per calcular la diferència en X del blob class CBlobGetDiffY : public COperadorBlob { public: @@ -398,7 +393,7 @@ namespace Blob class CBlobGetMoment : public COperadorBlob { public: - //! Constructor estàndard + //! Constructor estàndard //! Standard constructor (gets the 00 moment) CBlobGetMoment() { @@ -434,7 +429,7 @@ namespace Blob } }; - //! Classe per calcular l'àrea del poligon convex d'un blob + //! Classe per calcular l'àrea del poligon convex d'un blob //! Class to calculate the convex hull area of a blob class CBlobGetHullArea : public COperadorBlob { @@ -494,7 +489,7 @@ namespace Blob } }; - //! Classe per a calcular la x mínima + //! Classe per a calcular la x mínima //! Class to get the minimum x class CBlobGetMinX : public COperadorBlob { @@ -509,7 +504,7 @@ namespace Blob } }; - //! Classe per a calcular la x màxima + //! Classe per a calcular la x màxima //! Class to get the maximum x class CBlobGetMaxX : public COperadorBlob { @@ -524,7 +519,7 @@ namespace Blob } }; - //! Classe per a calcular la y mínima + //! Classe per a calcular la y mínima //! Class to get the minimum y class CBlobGetMinY : public COperadorBlob { @@ -539,7 +534,7 @@ namespace Blob } }; - //! Classe per a calcular la y màxima + //! Classe per a calcular la y màxima //! Class to get the maximum y class CBlobGetMaxY : public COperadorBlob { @@ -579,7 +574,7 @@ namespace Blob } }; - //! Classe per calcular la distància entre el centre del blob i un punt donat + //! Classe per calcular la distància entre el centre del blob i un punt donat //! Class to calculate the euclidean distance between the center of a blob and a given point class CBlobGetDistanceFromPoint : public COperadorBlob { @@ -603,7 +598,7 @@ namespace Blob } private: - // coordenades del punt on volem calcular la distància + // coordenades del punt on volem calcular la distància double m_x, m_y; }; @@ -623,8 +618,8 @@ namespace Blob }; //! Classe per calcular el ratio entre el perimetre i nombre pixels externs - //! valors propers a 0 indiquen que la majoria del blob és intern - //! valors propers a 1 indiquen que la majoria del blob és extern + //! valors propers a 0 indiquen que la majoria del blob és intern + //! valors propers a 1 indiquen que la majoria del blob és extern //! Class to calculate the ratio between the perimeter and the number of extern pixels class CBlobGetExternPerimeterRatio : public COperadorBlob { @@ -643,8 +638,8 @@ namespace Blob }; //! Classe per calcular el ratio entre el perimetre convex i nombre pixels externs - //! valors propers a 0 indiquen que la majoria del blob és intern - //! valors propers a 1 indiquen que la majoria del blob és extern + //! valors propers a 0 indiquen que la majoria del blob és intern + //! valors propers a 1 indiquen que la majoria del blob és extern //! Class to calculate the ratio between the perimeter and the number of extern pixels class CBlobGetExternHullPerimeterRatio : public COperadorBlob { @@ -713,7 +708,7 @@ namespace Blob }; //! Classe per calcular el ratio entre l'area de la elipse i la de la taca - //! Class + //! Class class CBlobGetAreaElipseRatio : public COperadorBlob { public: @@ -755,7 +750,7 @@ namespace Blob } }; - //! Classe per calcular l'orientació de l'ellipse del blob en radians + //! Classe per calcular l'orientació de l'ellipse del blob en radians //! Class to calculate the orientation of the ellipse that fits the blob edges in radians class CBlobGetOrientation : public COperadorBlob { @@ -776,7 +771,7 @@ namespace Blob } }; - //! Classe per calcular el cosinus de l'orientació de l'ellipse del blob + //! Classe per calcular el cosinus de l'orientació de l'ellipse del blob //! Class to calculate the cosinus of the orientation of the ellipse that fits the blob edges class CBlobGetOrientationCos : public COperadorBlob { @@ -793,7 +788,7 @@ namespace Blob }; - //! Classe per calcular el ratio entre l'eix major i menor de la el·lipse + //! Classe per calcular el ratio entre l'eix major i menor de la el·lipse //! Class to calculate the ratio between both axes of the ellipse class CBlobGetAxisRatio : public COperadorBlob { @@ -816,7 +811,7 @@ namespace Blob class CBlobGetXYInside : public COperadorBlob { public: - //! Constructor estàndard + //! Constructor estàndard //! Standard constructor CBlobGetXYInside() { @@ -841,6 +836,3 @@ namespace Blob }; } - -#endif //CBLOB_INSPECTA_INCLUDED - diff --git a/package_bgs/PAWCS.cpp b/package_bgs/PAWCS.cpp new file mode 100644 index 0000000..908b3ae --- /dev/null +++ b/package_bgs/PAWCS.cpp @@ -0,0 +1,93 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "PAWCS.h" + +using namespace bgslibrary::algorithms; + +PAWCS::PAWCS() : pPAWCS(nullptr), +fRelLBSPThreshold(BGSPAWCS_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD), +nDescDistThresholdOffset(BGSPAWCS_DEFAULT_DESC_DIST_THRESHOLD_OFFSET), +nMinColorDistThreshold(BGSPAWCS_DEFAULT_MIN_COLOR_DIST_THRESHOLD), +nMaxNbWords(BGSPAWCS_DEFAULT_MAX_NB_WORDS), +nSamplesForMovingAvgs(BGSPAWCS_DEFAULT_N_SAMPLES_FOR_MV_AVGS) +{ + std::cout << "PAWCS()" << std::endl; + setup("./config/PAWCS.xml"); +} +PAWCS::~PAWCS() +{ + if (pPAWCS) + delete pPAWCS; + std::cout << "~PAWCS()" << std::endl; +} + +void PAWCS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + if (firstTime) + { + pPAWCS = new BackgroundSubtractorPAWCS( + fRelLBSPThreshold, nDescDistThresholdOffset, nMinColorDistThreshold, + nMaxNbWords, nSamplesForMovingAvgs); + + pPAWCS->initialize(img_input, cv::Mat(img_input.size(), CV_8UC1, cv::Scalar_(255))); + firstTime = false; + } + + pPAWCS->apply(img_input, img_foreground); + pPAWCS->getBackgroundImage(img_background); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + { + imshow("PAWCS FG", img_foreground); + imshow("PAWCS BG", img_background); + } +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); +} + +void PAWCS::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteReal(fs, "fRelLBSPThreshold", fRelLBSPThreshold); + cvWriteInt(fs, "nDescDistThresholdOffset", nDescDistThresholdOffset); + cvWriteInt(fs, "nMinColorDistThreshold", nMinColorDistThreshold); + cvWriteInt(fs, "nMaxNbWords", nMaxNbWords); + cvWriteInt(fs, "nSamplesForMovingAvgs", nSamplesForMovingAvgs); + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void PAWCS::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + fRelLBSPThreshold = cvReadRealByName(fs, nullptr, "fRelLBSPThreshold", BGSPAWCS_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD); + nDescDistThresholdOffset = cvReadIntByName(fs, nullptr, "nDescDistThresholdOffset", BGSPAWCS_DEFAULT_DESC_DIST_THRESHOLD_OFFSET); + nMinColorDistThreshold = cvReadIntByName(fs, nullptr, "nMinColorDistThreshold", BGSPAWCS_DEFAULT_MIN_COLOR_DIST_THRESHOLD); + nMaxNbWords = cvReadIntByName(fs, nullptr, "nMaxNbWords", BGSPAWCS_DEFAULT_MAX_NB_WORDS); + nSamplesForMovingAvgs = cvReadIntByName(fs, nullptr, "nSamplesForMovingAvgs", BGSPAWCS_DEFAULT_N_SAMPLES_FOR_MV_AVGS); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + + cvReleaseFileStorage(&fs); +} diff --git a/package_bgs/PAWCS.h b/package_bgs/PAWCS.h new file mode 100644 index 0000000..173bcf6 --- /dev/null +++ b/package_bgs/PAWCS.h @@ -0,0 +1,48 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "LBSP/BackgroundSubtractorPAWCS.h" + +namespace bgslibrary +{ + namespace algorithms + { + class PAWCS : public IBGS + { + private: + BackgroundSubtractorPAWCS* pPAWCS; + + float fRelLBSPThreshold; + size_t nDescDistThresholdOffset; + size_t nMinColorDistThreshold; + size_t nMaxNbWords; + size_t nSamplesForMovingAvgs; + + public: + PAWCS(); + ~PAWCS(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/PBAS/PBAS.cpp b/package_bgs/PBAS/PBAS.cpp new file mode 100644 index 0000000..bc3ad40 --- /dev/null +++ b/package_bgs/PBAS/PBAS.cpp @@ -0,0 +1,585 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "PBAS.h" + +PBAS::PBAS(void) : N(20), R_lower(18), Raute_min(2), T_lower(2), T_upper(200), R_scale(5), R_incdec(0.05), T_dec(0.05), T_inc(1) +{ + std::cout << "PBAS()" << std::endl; + + //feature vector + alpha = 7.0; + beta = 1.0; + formerMeanNorm = 0; + width = 0; + + //result image + foregroundValue = 255; + backgroundValue = 0; + + //length of random array + countOfRandomNumb = 1000; + + //the T(x_i) value needs initiation + T_init = R_lower; + + //check if something is moving in the picture + isMove = false; + + //for init, count number of runs + runs = 0; + newInitialization(); +} + +void PBAS::newInitialization() +{ + if (!randomN.empty()) + randomN.clear(); + + if (!randomX.empty()) + randomX.clear(); + + if (!randomY.empty()) + randomY.clear(); + + if (!randomMinDist.empty()) + randomMinDist.clear(); + + if (!randomT.empty()) + randomT.clear(); + + if (!randomTN.empty()) + randomTN.clear(); + + for (int l = 0; l < countOfRandomNumb; l++) + { + randomN.push_back((int)randomGenerator.uniform((int)0, (int)N)); + randomX.push_back((int)randomGenerator.uniform(-1, +2)); + randomY.push_back((int)randomGenerator.uniform(-1, +2)); + randomMinDist.push_back((int)randomGenerator.uniform((int)0, (int)N)); + randomT.push_back((int)randomGenerator.uniform((int)0, (int)T_upper)); + randomTN.push_back((int)randomGenerator.uniform((int)0, (int)T_upper)); + } +} + +PBAS::~PBAS(void) +{ + std::cout << "~PBAS()" << std::endl; + + randomN.clear(); + randomX.clear(); + randomY.clear(); + randomMinDist.clear(); + randomT.clear(); + randomTN.clear(); + + for (int k = 0; k < backgroundModel.size(); ++k) + { + if (chans == 1) + { + backgroundModel.at(k).at(0).release(); + backgroundModel.at(k).at(1).release(); + } + else + { + backgroundModel.at(k).at(0).release(); + backgroundModel.at(k).at(1).release(); + backgroundModel.at(k).at(2).release(); + + backgroundModel.at(k).at(3).release(); + backgroundModel.at(k).at(4).release(); + backgroundModel.at(k).at(5).release(); + } + } + + backgroundModel.clear(); + meanMinDist.release(); + + actualR.release(); + actualT.release(); + + sobelX.release(); + sobelY.release(); +} + +bool PBAS::process(cv::Mat* input, cv::Mat* output) +{ + if (width != input->cols) + { + width = input->cols; + chans = input->channels(); + height = input->rows; + + if (input->rows < 1 || input->cols < 1) + { + std::cout << "Error: Occurrence of to small (or empty?) image size in PBAS. STOPPING " << std::endl; + return false; + } + } + + //iniate the background model + init(input); + + resultMap = new cv::Mat(input->rows, input->cols, CV_8UC1); + + //calculate features + calculateFeatures(¤tFeatures, input); + + //set sumMagnitude to zero at beginning and then sum up in the loop + sumMagnitude = 0; + long glCounterFore = 0; + isMove = false; + + //Here starts the whole processing of each pixel of the image + // for each pixel + for (int j = 0; j < resultMap->rows; ++j) + { + resultMap_Pt = resultMap->ptr(j); + currentFeaturesM_Pt.clear(); + currentFeaturesC_Pt.clear(); + std::vector fT; + std::vector uT; + B_Mag_Pts.clear(); + B_Col_Pts.clear(); + + for (int z = 0; z < chans; ++z) + { + currentFeaturesM_Pt.push_back(currentFeatures.at(z).ptr(j)); + currentFeaturesC_Pt.push_back(currentFeatures.at(z + chans).ptr(j)); + + B_Mag_Pts.push_back(fT); + + B_Col_Pts.push_back(uT); + } + + meanMinDist_Pt = meanMinDist.ptr(j); + actualR_Pt = actualR.ptr(j); + actualT_Pt = actualT.ptr(j); + + for (int k = 0; k < runs; ++k) + { + for (int z = 0; z < chans; ++z) + { + B_Mag_Pts.at(z).push_back(backgroundModel.at(k).at(z).ptr(j)); + B_Col_Pts.at(z).push_back(backgroundModel.at(k).at(z + chans).ptr(j)); + } + } + + for (int i = 0; i < resultMap->cols; ++i) + { + //Compare each pixel to in the worst runtime-case each background model + int count = 0; + int index = 0; + + double norm = 0.0; + double dist = 0.0; + double minDist = 1000.0; + int entry = randomGenerator.uniform(3, countOfRandomNumb - 4); + + do + { + if (chans == 3) + { + norm = sqrt( + (((double)B_Mag_Pts.at(0).at(index)[i] - ((double)*currentFeaturesM_Pt.at(0)))*((double)B_Mag_Pts.at(0).at(index)[i] - ((double)*currentFeaturesM_Pt.at(0)))) + + (((double)B_Mag_Pts.at(1).at(index)[i] - ((double)*currentFeaturesM_Pt.at(1)))*((double)B_Mag_Pts.at(1).at(index)[i] - ((double)*currentFeaturesM_Pt.at(1)))) + + (((double)B_Mag_Pts.at(2).at(index)[i] - ((double)*currentFeaturesM_Pt.at(2)))*((double)B_Mag_Pts.at(2).at(index)[i] - ((double)*currentFeaturesM_Pt.at(2)))) + ); + + dist = sqrt( + (((double)B_Col_Pts.at(0).at(index)[i] - ((double)*currentFeaturesC_Pt.at(0)))*((double)B_Col_Pts.at(0).at(index)[i] - ((double)*currentFeaturesC_Pt.at(0)))) + + (((double)B_Col_Pts.at(1).at(index)[i] - ((double)*currentFeaturesC_Pt.at(1)))*((double)B_Col_Pts.at(1).at(index)[i] - ((double)*currentFeaturesC_Pt.at(1)))) + + (((double)B_Col_Pts.at(2).at(index)[i] - ((double)*currentFeaturesC_Pt.at(2)))*((double)B_Col_Pts.at(2).at(index)[i] - ((double)*currentFeaturesC_Pt.at(2)))) + ); + } + else + { + norm = abs((((double)B_Mag_Pts.at(0).at(index)[i] - + ((double)*currentFeaturesM_Pt.at(0)))*((double)B_Mag_Pts.at(0).at(index)[i] - ((double)*currentFeaturesM_Pt.at(0))))); + + dist = abs((((double)B_Col_Pts.at(0).at(index)[i] - + ((double)*currentFeaturesC_Pt.at(0)))*((double)B_Col_Pts.at(0).at(index)[i] - ((double)*currentFeaturesC_Pt.at(0)))) + ); + } + dist = ((double)alpha*(norm / formerMeanMag) + beta*dist); + + if ((dist < *actualR_Pt)) + { + ++count; + if (minDist > dist) + minDist = dist; + } + else + { + sumMagnitude += (double)(norm); + ++glCounterFore; + } + ++index; + } while ((count < Raute_min) && (index < runs)); + + + //############################################# + //update backgroundmodel + // is BACKGROUND + if (count >= Raute_min) + { + *resultMap_Pt = 0; + double ratio = std::ceil((double)T_upper / (double)(*actualT_Pt)); + //in the first run every distance is zero, because there is no background model + //in the secont run, we have already one image as background model, hence a + // reasonable minDist could be found -> because of the partly 1/run changing in the running average, we set in the first try meanMinDist to the actual minDist value + if (runs < N && runs > 2) + { + *meanMinDist_Pt = ((((float)(runs - 1)) * (*meanMinDist_Pt)) + (float)minDist) / ((float)runs); + } + else if (runs < N && runs == 2) + { + *meanMinDist_Pt = (float)minDist; + } + + //1. update model + if (runs == N) + { + //Update current pixel + //check if random numer is smaller than ratio + if (randomT.at(entry) < ratio) + { + // replace randomly chosen sample + int rand = randomN.at(entry + 1); //randomGenerator.uniform((int)0,(int)N-1); + for (int z = 0; z < chans; ++z) + { + B_Mag_Pts.at(z).at(rand)[i] = (float)*currentFeaturesM_Pt.at(z); + B_Col_Pts.at(z).at(rand)[i] = (uchar)*currentFeaturesC_Pt.at(z); + + } + + *meanMinDist_Pt = ((((float)(N - 1)) * (*meanMinDist_Pt)) + (float)minDist) / ((float)N); + } + + //Update neighboring pixel model + if (randomTN.at(entry) < ratio) + { + //choose neighboring pixel randomly + int xNeigh = randomX.at(entry) + i; + int yNeigh = randomY.at(entry) + j; + checkValid(&xNeigh, &yNeigh); + + // replace randomly chosen sample + int rand = randomN.at(entry - 1); + for (int z = 0; z < chans; ++z) + { + (backgroundModel.at(rand)).at(z).at(yNeigh, xNeigh) = currentFeatures.at(z).at(yNeigh, xNeigh); + (backgroundModel.at(rand)).at(z + chans).at(yNeigh, xNeigh) = currentFeatures.at(z + chans).at(yNeigh, xNeigh); + } + } + } + } + else + { + // store pixel as foreground + *resultMap_Pt = 255; + + //there is some movement + isMove = true; + } + + //#######################//#######################//#######################//####################### + //control loops + //#######################//#######################//#######################//####################### + //update R + decisionThresholdRegulator(actualR_Pt, meanMinDist_Pt); + + //update T + learningRateRegulator(actualT_Pt, meanMinDist_Pt, resultMap_Pt); + + //#######################//#######################//#######################//####################### + //#######################//#######################//#######################//####################### + + //jump to next pixel + ++resultMap_Pt; + for (int z = 0; z < chans; ++z) + { + ++currentFeaturesM_Pt.at(z); + ++currentFeaturesC_Pt.at(z); + } + + ++meanMinDist_Pt; + ++actualR_Pt; + ++actualT_Pt; + } + } + + resultMap->copyTo(*output); + + //if there is no foreground -> no magnitudes fount + //-> initiate some low value to prevent diving through zero + double meanMag = sumMagnitude / (double)(glCounterFore + 1); //height*width); + + if (meanMag > 20) + formerMeanMag = meanMag; + else + formerMeanMag = 20; + + delete resultMap; + + for (int z = 0; z < chans; ++z) + { + currentFeatures.at(z + chans).release(); + currentFeatures.at(z).release(); + } + + return true; +} + +void PBAS::decisionThresholdRegulator(float* pt, float* meanDist) +{ + //update R + double tempR = *pt; + double newThresh = (*meanDist)*R_scale; + + if (tempR < newThresh) + { + tempR += tempR * R_incdec; + } + else + { + tempR -= tempR * R_incdec; + } + + if (tempR >= R_lower) + *pt = (float)tempR; + else + *pt = (float)R_lower; +} + +void PBAS::learningRateRegulator(float* pt, float* meanDist, uchar* isFore) +{ + //time update + double tempT = *pt; + + if ((int)*isFore < 128) + { + tempT -= T_inc / (*meanDist + 1.0); + } + else + { + tempT += T_dec / (*meanDist + 1.0); + } + + if (tempT > T_lower && tempT < T_upper) + *pt = (float)tempT; +} + +void PBAS::checkValid(int *x, int *y) +{ + if (*x < 0) + { + *x = 0; + } + else if (*x >= width) + { + *x = width - 1; + } + + if (*y < 0) + { + *y = 0; + } + else if (*y >= height) + { + *y = height - 1; + } +} + +void PBAS::init(cv::Mat* input) +{ + if (runs < N) + { + std::vector init; + calculateFeatures(&init, input); + backgroundModel.push_back(init); + + if (chans == 1) + { + init.at(0).release(); + init.at(1).release(); + } + else + { + init.at(0).release(); + init.at(1).release(); + init.at(2).release(); + init.at(3).release(); + init.at(4).release(); + init.at(5).release(); + } + + init.clear(); + + if (runs == 0) + { + meanMinDist.create(input->size(), CV_32FC1); + meanMinDist.zeros(input->rows, input->cols, CV_32FC1); + + actualR.create(input->rows, input->cols, CV_32FC1); + actualT.create(input->rows, input->cols, CV_32FC1); + + float* ptRs, *ptTs; //, *ptM; + for (int rows = 0; rows < actualR.rows; ++rows) + { + ptRs = actualR.ptr(rows); + ptTs = actualT.ptr(rows); + + for (int cols = 0; cols < actualR.cols; ++cols) + { + ptRs[cols] = (float)R_lower; + ptTs[cols] = (float)T_init; + } + } + } + + ++runs; + } +} + +void PBAS::calculateFeatures(std::vector* feature, cv::Mat* inputImage) +{ + if (!feature->empty()) + feature->clear(); + + cv::Mat mag[3], dir; + + if (inputImage->channels() == 3) + { + std::vector rgbChannels(3); + cv::split(*inputImage, rgbChannels); + + for (int l = 0; l < 3; ++l) + { + cv::Sobel(rgbChannels.at(l), sobelX, CV_32F, 1, 0, 3, 1, 0.0); + cv::Sobel(rgbChannels.at(l), sobelY, CV_32F, 0, 1, 3, 1, 0.0); + + // Compute the L2 norm and direction of the gradient + cv::cartToPolar(sobelX, sobelY, mag[l], dir, true); + feature->push_back(mag[l]); + sobelX.release(); + sobelY.release(); + } + + feature->push_back(rgbChannels.at(0)); + feature->push_back(rgbChannels.at(1)); + feature->push_back(rgbChannels.at(2)); + rgbChannels.at(0).release(); + rgbChannels.at(1).release(); + rgbChannels.at(2).release(); + } + else + { + cv::Sobel(*inputImage, sobelX, CV_32F, 1, 0, 3, 1, 0.0); + cv::Sobel(*inputImage, sobelY, CV_32F, 0, 1, 3, 1, 0.0); + + // Compute the L2 norm and direction of the gradient + cv::cartToPolar(sobelX, sobelY, mag[0], dir, true); + feature->push_back(mag[0]); + + cv::Mat temp; + inputImage->copyTo(temp); + feature->push_back(temp); + temp.release(); + } + + mag[0].release(); + mag[1].release(); + mag[2].release(); + dir.release(); +} + +void PBAS::setN(int temp) +{ + N = temp; + newInitialization(); +} + +void PBAS::setRaute_min(int temp) +{ + Raute_min = temp; +} + +void PBAS::setR_lower(double temp) +{ + R_lower = temp; +} + +void PBAS::setR_incdec(double temp) +{ + R_incdec = temp; +} + +void PBAS::setR_scale(double temp) +{ + R_scale = temp; +} + +void PBAS::setT_init(double temp) +{ + T_init = temp; +} + +void PBAS::setT_lower(double temp) +{ + T_lower = temp; +} + +void PBAS::setT_upper(double temp) +{ + T_upper = temp; + newInitialization(); +} + +void PBAS::setT_dec(double temp) +{ + T_dec = temp; +} + +void PBAS::setT_inc(double temp) +{ + T_inc = temp; +} + +void PBAS::setAlpha(double temp) +{ + alpha = temp; +} + +void PBAS::setBeta(double temp) +{ + beta = temp; +} + +bool PBAS::isMovement() +{ + return isMove; +} + +//cv::Mat* PBAS::getR1_xi() +//{ +// return &actualR; +//} +// +//cv::Mat* PBAS::getT_xi() +//{ +// return &actualT; +//} diff --git a/package_bgs/PBAS/PBAS.h b/package_bgs/PBAS/PBAS.h new file mode 100644 index 0000000..9014a28 --- /dev/null +++ b/package_bgs/PBAS/PBAS.h @@ -0,0 +1,207 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +//Implementation of the PBAS from: +// +//M. Hofmann, P. Tiefenbacher, G. Rigoll +//"Background Segmentation with Feedback: The Pixel-Based Adaptive Segmenter", +//in proc of IEEE Workshop on Change Detection, 2012 +// +//Note: some changes, to improve the speed and memory requirements, were achieved in comparison to the +//described PBAS algorithm in the paper above. +// +//Example usage: +// //Somewhere during initalization: +// #include "PBAS.h" +// #include +// PBAS pbas; +// +// //you might want to change some parameters of the PBAS here... +// .... +// +// //repeat for each frame +// //make gaussian blur for reducing image noise +//cv::Mat bluredImage; +//cv::Mat pbastResult; +//cv::GaussianBlur(singleFrame, bluredImage, cv::Size(5,5), 1.5); +// +// //process image and receive segmentation in pbasResult +//pbas.process(&bluredImage, &pbasResult); +// +// //make medianBlur on the result to reduce "salt and pepper noise" +// //of the per pixel wise segmentation +//cv::medianBlur(pbasResult, pbasResult, 5); +// +// +// +//Author: P.Tiefenbacher, https://sites.google.com/site/pbassegmenter/ +//Technische Universität München, Germany +//Date: 22-May-2012, Version:0.1 +/////////// +#pragma once + +#include +#include +//#include + +class PBAS +{ +public: + PBAS(void); + ~PBAS(void); + bool process(cv::Mat* input, cv::Mat* output); + + void setN(int); + void setRaute_min(int); + void setR_lower(double); + void setR_incdec(double); + void setR_scale(double); + void setT_init(double); + void setT_lower(double); + void setT_upper(double); + void setT_dec(double); + void setT_inc(double); + void setAlpha(double); + void setBeta(double); + + bool isMovement(); + + +private: + void calculateFeatures(std::vector* feature, cv::Mat* inputImage); + void checkValid(int *x, int *y); + void decisionThresholdRegulator(float* pt, float* meanDistArr); + void learningRateRegulator(float* pt, float* meanDist, uchar* isFore); + void init(cv::Mat*); + void newInitialization(); + + cv::Mat meanMinDist; + float* meanMinDist_Pt; + + + + int width, height; + int chans; + + //balance of feature pixel value to conture value + double alpha, beta; + //################################################################################## + + double formerMeanNorm; + + //define value of foreground/background pixels + int foregroundValue, backgroundValue; + + //################################################################################## + //random number parameters + + //random number generator + cv::RNG randomGenerator; + + //length of random array initialization + long countOfRandomNumb; + + //pre - initialize the randomNumbers for better performance + std::vector randomN, randomMinDist, randomX, randomY, randomT, randomTN; + + //################################################################################### + + //check if something is moving + bool isMove; + + //for init, count number of runs + int runs; + + + cv::Mat* resultMap; + std::vector currentFeatures; + + std::vector currentFeaturesM_Pt; + std::vector currentFeaturesC_Pt; + uchar* resultMap_Pt; + + std::vector>B_Mag_Pts; + std::vector>B_Col_Pts; + + double sumMagnitude; + double formerMeanMag; + float formerDistanceBack; + + //#################################################################################### + //N - Number: Defining the size of the background-history-model + // number of samples per pixel + //size of background history B(x_i) + int N; + // background model + std::vector> backgroundModel; + //#################################################################################### + //#################################################################################### + //R-Threshhold - Variables + //minimal Threshold for foreground and initial value of R(x_i) + // radius of the sphere -> lower border boundary + double R_lower; + + //factor which defines new threshold of R(x_i) together with meanMinDist(x_i) + // scale for the sphere threshhold to define pixel-based Thresholds + double R_scale; + + //decreasing / increasing factor of R(x_i) + // increasing/decreasing factor for the r-Threshold based on the result of rTreshScale * meanMinDistBackground + double R_incdec; + + cv::Mat actualR; + float* actualR_Pt; //new pixel-based r-threshhold -> pointer to arrays + //##################################################################################### + //#################################################################################### + //counter for minimal distance to background + // Defining the number of background-model-images, which have a lowerDIstance to the current Image than defined by the R-Thresholds, that are necessary + // to decide that this pixel is background + int Raute_min; + //##################################################################################### + //#################################################################################### + //initial value of inverse update factor T(x_i) + // Initialize the background-model update rate + double T_init; + + //increasing Factor of the update rate 1/T(x_i) + // scale that defines the increasing of the update rate of the background model, if the current pixel is background + //--> more frequently updates if pixel is background because, there shouln't be any change + double T_inc; + + //upper boundary of T(x_i) + // defining an upper value, that nrSubsampling can achieve, thus it doesn't reach to an infinite value, where actually no update is possible + // at all + double T_upper; + + //lower boundary of T(x_i) + // defining a minimum value for nrSubsampling --> base value 2.0 + double T_lower; + + //decreasing factor of the update rate 1/T(x_i) + // opposite scale to increasingRateScale, for decreasing the update rate of the background model, if the current pixel is foreground + //--> Thesis: Our Foreground is a real moving object -> thus the background-model is good, so don't update it + double T_dec; + + //holds update rate of current pixel + cv::Mat actualT; + float* actualT_Pt; + + //##################################################################################### + + + cv::Mat sobelX, sobelY; +}; + diff --git a/package_bgs/PixelBasedAdaptiveSegmenter.cpp b/package_bgs/PixelBasedAdaptiveSegmenter.cpp new file mode 100644 index 0000000..8a3de97 --- /dev/null +++ b/package_bgs/PixelBasedAdaptiveSegmenter.cpp @@ -0,0 +1,126 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "PixelBasedAdaptiveSegmenter.h" + +using namespace bgslibrary::algorithms; + +PixelBasedAdaptiveSegmenter::PixelBasedAdaptiveSegmenter() : + enableInputBlur(true), enableOutputBlur(true), + alpha(7.0), beta(1.0), N(20), Raute_min(2), R_incdec(0.05), R_lower(18), + R_scale(5), T_dec(0.05), T_inc(1), T_init(18), T_lower(2), T_upper(200) +{ + std::cout << "PixelBasedAdaptiveSegmenter()" << std::endl; + setup("./config/PixelBasedAdaptiveSegmenter.xml"); +} + +PixelBasedAdaptiveSegmenter::~PixelBasedAdaptiveSegmenter() +{ + std::cout << "~PixelBasedAdaptiveSegmenter()" << std::endl; +} + +void PixelBasedAdaptiveSegmenter::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + if (firstTime) + { + pbas.setAlpha(alpha); + pbas.setBeta(beta); + pbas.setN(N); + pbas.setRaute_min(Raute_min); + pbas.setR_incdec(R_incdec); + pbas.setR_lower(R_lower); + pbas.setR_scale(R_scale); + pbas.setT_dec(T_dec); + pbas.setT_inc(T_inc); + pbas.setT_init(T_init); + pbas.setT_lower(T_lower); + pbas.setT_upper(T_upper); + } + + cv::Mat img_input_new; + if (enableInputBlur) + cv::GaussianBlur(img_input, img_input_new, cv::Size(5, 5), 1.5); + else + img_input.copyTo(img_input_new); + + pbas.process(&img_input_new, &img_foreground); + img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + + if (enableOutputBlur) + cv::medianBlur(img_foreground, img_foreground, 5); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("PBAS", img_foreground); +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); + + firstTime = false; +} + +void PixelBasedAdaptiveSegmenter::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteInt(fs, "enableInputBlur", enableInputBlur); + cvWriteInt(fs, "enableOutputBlur", enableOutputBlur); + + cvWriteReal(fs, "alpha", alpha); + cvWriteReal(fs, "beta", beta); + cvWriteInt(fs, "N", N); + cvWriteInt(fs, "Raute_min", Raute_min); + cvWriteReal(fs, "R_incdec", R_incdec); + cvWriteInt(fs, "R_lower", R_lower); + cvWriteInt(fs, "R_scale", R_scale); + cvWriteReal(fs, "T_dec", T_dec); + cvWriteInt(fs, "T_inc", T_inc); + cvWriteInt(fs, "T_init", T_init); + cvWriteInt(fs, "T_lower", T_lower); + cvWriteInt(fs, "T_upper", T_upper); + + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void PixelBasedAdaptiveSegmenter::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + enableInputBlur = cvReadIntByName(fs, nullptr, "enableInputBlur", true); + enableOutputBlur = cvReadIntByName(fs, nullptr, "enableOutputBlur", true); + + alpha = cvReadRealByName(fs, nullptr, "alpha", 7.0); + beta = cvReadRealByName(fs, nullptr, "beta", 1.0); + N = cvReadIntByName(fs, nullptr, "N", 20); + Raute_min = cvReadIntByName(fs, nullptr, "Raute_min", 2); + R_incdec = cvReadRealByName(fs, nullptr, "R_incdec", 0.05); + R_lower = cvReadIntByName(fs, nullptr, "R_lower", 18); + R_scale = cvReadIntByName(fs, nullptr, "R_scale", 5); + T_dec = cvReadRealByName(fs, nullptr, "T_dec", 0.05); + T_inc = cvReadIntByName(fs, nullptr, "T_inc", 1); + T_init = cvReadIntByName(fs, nullptr, "T_init", 18); + T_lower = cvReadIntByName(fs, nullptr, "T_lower", 2); + T_upper = cvReadIntByName(fs, nullptr, "T_upper", 200); + + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + + cvReleaseFileStorage(&fs); +} diff --git a/package_bgs/PixelBasedAdaptiveSegmenter.h b/package_bgs/PixelBasedAdaptiveSegmenter.h new file mode 100644 index 0000000..36dd0ad --- /dev/null +++ b/package_bgs/PixelBasedAdaptiveSegmenter.h @@ -0,0 +1,58 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "PBAS/PBAS.h" + +namespace bgslibrary +{ + namespace algorithms + { + class PixelBasedAdaptiveSegmenter : public IBGS + { + private: + PBAS pbas; + + bool enableInputBlur; + bool enableOutputBlur; + + float alpha; + float beta; + int N; + int Raute_min; + float R_incdec; + int R_lower; + int R_scale; + float T_dec; + int T_inc; + int T_init; + int T_lower; + int T_upper; + + public: + PixelBasedAdaptiveSegmenter(); + ~PixelBasedAdaptiveSegmenter(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/SigmaDelta.cpp b/package_bgs/SigmaDelta.cpp new file mode 100644 index 0000000..d305226 --- /dev/null +++ b/package_bgs/SigmaDelta.cpp @@ -0,0 +1,101 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "SigmaDelta.h" + +using namespace bgslibrary::algorithms; + +SigmaDelta::SigmaDelta() : + ampFactor(1), minVar(15), maxVar(255), algorithm(sdLaMa091New()) +{ + applyParams(); + std::cout << "SigmaDelta()" << std::endl; + setup("./config/SigmaDelta.xml"); +} + +SigmaDelta::~SigmaDelta() +{ + sdLaMa091Free(algorithm); + std::cout << "~SigmaDelta()" << std::endl; +} + +void SigmaDelta::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + if (firstTime) + { + sdLaMa091AllocInit_8u_C3R(algorithm, img_input.data, img_input.cols, img_input.rows, img_input.step); + img_foreground = cv::Mat(img_input.size(), CV_8UC1); + img_background = cv::Mat(img_input.size(), CV_8UC3); + firstTime = false; + } + else + { + cv::Mat img_output_tmp(img_input.rows, img_input.cols, CV_8UC3); + sdLaMa091Update_8u_C3R(algorithm, img_input.data, img_output_tmp.data); + + unsigned char* tmpBuffer = (unsigned char*)img_output_tmp.data; + unsigned char* outBuffer = (unsigned char*)img_foreground.data; + + for (size_t i = 0; i < img_foreground.total(); ++i) { + *outBuffer = *tmpBuffer; + ++outBuffer; + tmpBuffer += img_output_tmp.channels(); + } + } + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("Sigma-Delta", img_foreground); +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); +} + +void SigmaDelta::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteInt(fs, "ampFactor", ampFactor); + cvWriteInt(fs, "minVar", minVar); + cvWriteInt(fs, "maxVar", maxVar); + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void SigmaDelta::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + ampFactor = cvReadIntByName(fs, nullptr, "ampFactor", 1); + minVar = cvReadIntByName(fs, nullptr, "minVar", 15); + maxVar = cvReadIntByName(fs, nullptr, "maxVar", 255); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + + applyParams(); + + cvReleaseFileStorage(&fs); +} + +void SigmaDelta::applyParams() +{ + sdLaMa091SetAmplificationFactor(algorithm, ampFactor); + sdLaMa091SetMinimalVariance(algorithm, minVar); + sdLaMa091SetMaximalVariance(algorithm, maxVar); +} diff --git a/package_bgs/SigmaDelta.h b/package_bgs/SigmaDelta.h new file mode 100644 index 0000000..c7b1a2c --- /dev/null +++ b/package_bgs/SigmaDelta.h @@ -0,0 +1,49 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" + +//extern "C" { +#include "SigmaDelta/sdLaMa091.h" +//} + +namespace bgslibrary +{ + namespace algorithms + { + class SigmaDelta : public IBGS + { + private: + unsigned int ampFactor; + unsigned int minVar; + unsigned int maxVar; + sdLaMa091_t* algorithm; + + public: + SigmaDelta(); + ~SigmaDelta(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + void applyParams(); + }; + } +} diff --git a/package_bgs/bl/sdLaMa091.cpp b/package_bgs/SigmaDelta/sdLaMa091.cpp similarity index 97% rename from package_bgs/bl/sdLaMa091.cpp rename to package_bgs/SigmaDelta/sdLaMa091.cpp index 7071438..286556b 100644 --- a/package_bgs/bl/sdLaMa091.cpp +++ b/package_bgs/SigmaDelta/sdLaMa091.cpp @@ -84,7 +84,7 @@ static inline uint8_t max(uint8_t a, uint8_t b) { } sdLaMa091_t* sdLaMa091New(void) { - sdLaMa091_t* sdLaMa091 = (sdLaMa091_t*) malloc(sizeof(*sdLaMa091)); + sdLaMa091_t* sdLaMa091 = (sdLaMa091_t*)malloc(sizeof(*sdLaMa091)); #ifdef DEFENSIVE_ALLOC if (sdLaMa091 == NULL) { @@ -151,7 +151,7 @@ int32_t sdLaMa091AllocInit_8u_C1R(sdLaMa091_t* sdLaMa091, sdLaMa091->numBytes = stride * height; sdLaMa091->unusedBytes = stride - sdLaMa091->width; - sdLaMa091->Mt = (uint8_t*) malloc(sdLaMa091->numBytes); + sdLaMa091->Mt = (uint8_t*)malloc(sdLaMa091->numBytes); #ifdef DEFENSIVE_ALLOC if (sdLaMa091->Mt == NULL) { outputError("Cannot allocate sdLaMa091->Mt table"); @@ -160,7 +160,7 @@ int32_t sdLaMa091AllocInit_8u_C1R(sdLaMa091_t* sdLaMa091, #endif memcpy(sdLaMa091->Mt, image_data, sdLaMa091->numBytes); - sdLaMa091->Ot = (uint8_t*) malloc(sdLaMa091->numBytes); + sdLaMa091->Ot = (uint8_t*)malloc(sdLaMa091->numBytes); #ifdef DEFENSIVE_ALLOC if (sdLaMa091->Ot == NULL) { outputError("Cannot allocate sdLaMa091->Ot table"); @@ -170,16 +170,16 @@ int32_t sdLaMa091AllocInit_8u_C1R(sdLaMa091_t* sdLaMa091, uint8_t* workOt = sdLaMa091->Ot; for (uint32_t i = 0; i < sdLaMa091->numBytes; i += sdLaMa091->stride) { - + for (uint32_t j = 0; j < sdLaMa091->width; ++j, ++workOt) *workOt = 0; - + if (sdLaMa091->unusedBytes > 0) workOt += sdLaMa091->unusedBytes; } - sdLaMa091->Vt = (uint8_t*) malloc(sdLaMa091->numBytes); + sdLaMa091->Vt = (uint8_t*)malloc(sdLaMa091->numBytes); #ifdef DEFENSIVE_ALLOC if (sdLaMa091->Vt == NULL) { outputError("Cannot allocate sdLaMa091->Vt table"); @@ -188,13 +188,13 @@ int32_t sdLaMa091AllocInit_8u_C1R(sdLaMa091_t* sdLaMa091, #endif uint8_t* workVt = sdLaMa091->Vt; - + for (uint32_t i = 0; i < sdLaMa091->numBytes; i += sdLaMa091->stride) { - + for (uint32_t j = 0; j < sdLaMa091->width; ++j, ++workVt) *workVt = sdLaMa091->Vmin; - + if (sdLaMa091->unusedBytes > 0) workVt += sdLaMa091->unusedBytes; } @@ -375,13 +375,13 @@ int32_t sdLaMa091Update_8u_C1R(sdLaMa091_t* sdLaMa091, } #endif - + const uint8_t* workImage = image_data; uint8_t* workMt = sdLaMa091->Mt; - + for (uint32_t i = 0; i < sdLaMa091->numBytes; i += sdLaMa091->stride) { - + for (uint32_t j = 0; j < sdLaMa091->width; ++j, ++workImage, ++workMt) { if (*workMt < *workImage) ++(*workMt); @@ -389,7 +389,7 @@ int32_t sdLaMa091Update_8u_C1R(sdLaMa091_t* sdLaMa091, --(*workMt); } - + if (sdLaMa091->unusedBytes > 0) { workImage += sdLaMa091->unusedBytes; workMt += sdLaMa091->unusedBytes; @@ -400,14 +400,14 @@ int32_t sdLaMa091Update_8u_C1R(sdLaMa091_t* sdLaMa091, workMt = sdLaMa091->Mt; uint8_t* workOt = sdLaMa091->Ot; - + for (uint32_t i = 0; i < sdLaMa091->numBytes; i += sdLaMa091->stride) { - + for (uint32_t j = 0; j < sdLaMa091->width; ++j, ++workImage, ++workMt, ++workOt) *workOt = absVal(*workMt - *workImage); - + if (sdLaMa091->unusedBytes > 0) { workImage += sdLaMa091->unusedBytes; workMt += sdLaMa091->unusedBytes; @@ -415,13 +415,13 @@ int32_t sdLaMa091Update_8u_C1R(sdLaMa091_t* sdLaMa091, } } - + workOt = sdLaMa091->Ot; uint8_t* workVt = sdLaMa091->Vt; - + for (uint32_t i = 0; i < sdLaMa091->numBytes; i += sdLaMa091->stride) { - + for (uint32_t j = 0; j < sdLaMa091->width; ++j, ++workOt, ++workVt) { uint32_t ampOt = sdLaMa091->N * *workOt; @@ -433,30 +433,30 @@ int32_t sdLaMa091Update_8u_C1R(sdLaMa091_t* sdLaMa091, *workVt = max(min(*workVt, sdLaMa091->Vmax), sdLaMa091->Vmin); } - + if (sdLaMa091->unusedBytes > 0) { workOt += sdLaMa091->unusedBytes; workVt += sdLaMa091->unusedBytes; } } - + workOt = sdLaMa091->Ot; workVt = sdLaMa091->Vt; - + for (uint32_t i = 0; i < sdLaMa091->numBytes; i += sdLaMa091->stride) { - + for (uint32_t j = 0; j < sdLaMa091->width; ++j, ++segmentation_map, ++workOt, ++workVt) { - + if (*workOt < *workVt) *segmentation_map = BACKGROUND; else *segmentation_map = FOREGROUND; } - + if (sdLaMa091->unusedBytes > 0) { segmentation_map += sdLaMa091->unusedBytes; workOt += sdLaMa091->unusedBytes; @@ -525,13 +525,13 @@ int32_t sdLaMa091Update_8u_C3R(sdLaMa091_t* sdLaMa091, } #endif - + const uint8_t* workImage = image_data; uint8_t* workMt = sdLaMa091->Mt; - + for (uint32_t i = 0; i < sdLaMa091->numBytes; i += sdLaMa091->stride) { - + for (uint32_t j = 0; j < sdLaMa091->rgbWidth; ++j, ++workImage, ++workMt) { if (*workMt < *workImage) ++(*workMt); @@ -539,26 +539,26 @@ int32_t sdLaMa091Update_8u_C3R(sdLaMa091_t* sdLaMa091, --(*workMt); } - + if (sdLaMa091->rgbUnusedBytes > 0) { workImage += sdLaMa091->rgbUnusedBytes; workMt += sdLaMa091->rgbUnusedBytes; } } - + workImage = image_data; workMt = sdLaMa091->Mt; uint8_t* workOt = sdLaMa091->Ot; - + for (uint32_t i = 0; i < sdLaMa091->numBytes; i += sdLaMa091->stride) { - + for (uint32_t j = 0; j < sdLaMa091->rgbWidth; ++j, ++workImage, ++workMt, ++workOt) *workOt = absVal(*workMt - *workImage); - + if (sdLaMa091->rgbUnusedBytes > 0) { workImage += sdLaMa091->rgbUnusedBytes; workMt += sdLaMa091->rgbUnusedBytes; @@ -569,9 +569,9 @@ int32_t sdLaMa091Update_8u_C3R(sdLaMa091_t* sdLaMa091, workOt = sdLaMa091->Ot; uint8_t* workVt = sdLaMa091->Vt; - + for (uint32_t i = 0; i < sdLaMa091->numBytes; i += sdLaMa091->stride) { - + for (uint32_t j = 0; j < sdLaMa091->rgbWidth; ++j, ++workOt, ++workVt) { uint32_t ampOt = sdLaMa091->N * *workOt; @@ -583,7 +583,7 @@ int32_t sdLaMa091Update_8u_C3R(sdLaMa091_t* sdLaMa091, *workVt = max(min(*workVt, sdLaMa091->Vmax), sdLaMa091->Vmin); } - + if (sdLaMa091->rgbUnusedBytes > 0) { workOt += sdLaMa091->rgbUnusedBytes; workVt += sdLaMa091->rgbUnusedBytes; @@ -593,19 +593,19 @@ int32_t sdLaMa091Update_8u_C3R(sdLaMa091_t* sdLaMa091, workOt = sdLaMa091->Ot; workVt = sdLaMa091->Vt; - + for (uint32_t i = 0; i < sdLaMa091->numBytes; i += sdLaMa091->stride) { - + uint32_t numColor = 0; - + bool isForeground = false; - + for (uint32_t j = 0; j < sdLaMa091->rgbWidth; ++j, ++workOt, ++workVt) { if (*workOt >= *workVt) isForeground = true; - + if (numColor == BLUE) { if (isForeground) { *segmentation_map = FOREGROUND; @@ -626,7 +626,7 @@ int32_t sdLaMa091Update_8u_C3R(sdLaMa091_t* sdLaMa091, numColor = (numColor + 1) % CHANNELS; } - + if (sdLaMa091->rgbUnusedBytes > 0) { segmentation_map += sdLaMa091->rgbUnusedBytes; workOt += sdLaMa091->rgbUnusedBytes; diff --git a/package_bgs/bl/sdLaMa091.h b/package_bgs/SigmaDelta/sdLaMa091.h similarity index 96% rename from package_bgs/bl/sdLaMa091.h rename to package_bgs/SigmaDelta/sdLaMa091.h index bec93fb..cf9e777 100644 --- a/package_bgs/bl/sdLaMa091.h +++ b/package_bgs/SigmaDelta/sdLaMa091.h @@ -14,11 +14,9 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#ifndef SD_LA_MA_091_H_ -#define SD_LA_MA_091_H_ +#pragma once #include -#include "stdbool.h" #include #include #include @@ -68,5 +66,3 @@ int32_t sdLaMa091Update_8u_C3R(sdLaMa091_t* sdLaMa091, uint8_t* segmentation_map); int32_t sdLaMa091Free(sdLaMa091_t* sdLaMa091); - -#endif diff --git a/package_bgs/StaticFrameDifferenceBGS.cpp b/package_bgs/StaticFrameDifference.cpp similarity index 53% rename from package_bgs/StaticFrameDifferenceBGS.cpp rename to package_bgs/StaticFrameDifference.cpp index 3463c45..2faa40c 100644 --- a/package_bgs/StaticFrameDifferenceBGS.cpp +++ b/package_bgs/StaticFrameDifference.cpp @@ -14,41 +14,41 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "StaticFrameDifferenceBGS.h" +#include "StaticFrameDifference.h" -StaticFrameDifferenceBGS::StaticFrameDifferenceBGS() : firstTime(true), enableThreshold(true), threshold(15), showOutput(true) +using namespace bgslibrary::algorithms; + +StaticFrameDifference::StaticFrameDifference() : + enableThreshold(true), threshold(15) { - std::cout << "StaticFrameDifferenceBGS()" << std::endl; + std::cout << "StaticFrameDifference()" << std::endl; + setup("./config/StaticFrameDifference.xml"); } -StaticFrameDifferenceBGS::~StaticFrameDifferenceBGS() +StaticFrameDifference::~StaticFrameDifference() { - std::cout << "~StaticFrameDifferenceBGS()" << std::endl; + std::cout << "~StaticFrameDifference()" << std::endl; } -void StaticFrameDifferenceBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void StaticFrameDifference::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; + init(img_input, img_output, img_bgmodel); - if(img_background.empty()) + if (img_background.empty()) img_input.copyTo(img_background); - - loadConfig(); - - if(firstTime) - saveConfig(); cv::absdiff(img_input, img_background, img_foreground); - if(img_foreground.channels() == 3) + if (img_foreground.channels() == 3) cv::cvtColor(img_foreground, img_foreground, CV_BGR2GRAY); - if(enableThreshold) + if (enableThreshold) cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY); - if(showOutput) +#ifndef MEX_COMPILE_FLAG + if (showOutput) cv::imshow("Static Frame Difference", img_foreground); +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); @@ -56,9 +56,9 @@ void StaticFrameDifferenceBGS::process(const cv::Mat &img_input, cv::Mat &img_ou firstTime = false; } -void StaticFrameDifferenceBGS::saveConfig() +void StaticFrameDifference::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/StaticFrameDifferenceBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "enableThreshold", enableThreshold); cvWriteInt(fs, "threshold", threshold); @@ -67,13 +67,13 @@ void StaticFrameDifferenceBGS::saveConfig() cvReleaseFileStorage(&fs); } -void StaticFrameDifferenceBGS::loadConfig() +void StaticFrameDifference::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/StaticFrameDifferenceBGS.xml", 0, CV_STORAGE_READ); - - enableThreshold = cvReadIntByName(fs, 0, "enableThreshold", true); - threshold = cvReadIntByName(fs, 0, "threshold", 15); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + enableThreshold = cvReadIntByName(fs, nullptr, "enableThreshold", true); + threshold = cvReadIntByName(fs, nullptr, "threshold", 15); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); -} \ No newline at end of file +} diff --git a/package_bgs/StaticFrameDifference.h b/package_bgs/StaticFrameDifference.h new file mode 100644 index 0000000..8c8474c --- /dev/null +++ b/package_bgs/StaticFrameDifference.h @@ -0,0 +1,42 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" + +namespace bgslibrary +{ + namespace algorithms + { + class StaticFrameDifference : public IBGS + { + private: + bool enableThreshold; + int threshold; + + public: + StaticFrameDifference(); + ~StaticFrameDifference(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/SuBSENSE.cpp b/package_bgs/SuBSENSE.cpp new file mode 100644 index 0000000..c8a0e5d --- /dev/null +++ b/package_bgs/SuBSENSE.cpp @@ -0,0 +1,96 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "SuBSENSE.h" + +using namespace bgslibrary::algorithms; + +SuBSENSE::SuBSENSE() : + pSubsense(0), + fRelLBSPThreshold(BGSSUBSENSE_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD), + nDescDistThresholdOffset(BGSSUBSENSE_DEFAULT_DESC_DIST_THRESHOLD_OFFSET), + nMinColorDistThreshold(BGSSUBSENSE_DEFAULT_MIN_COLOR_DIST_THRESHOLD), + nBGSamples(BGSSUBSENSE_DEFAULT_NB_BG_SAMPLES), + nRequiredBGSamples(BGSSUBSENSE_DEFAULT_REQUIRED_NB_BG_SAMPLES), + nSamplesForMovingAvgs(BGSSUBSENSE_DEFAULT_N_SAMPLES_FOR_MV_AVGS) +{ + std::cout << "SuBSENSE()" << std::endl; +} + +SuBSENSE::~SuBSENSE() { + if (pSubsense) + delete pSubsense; + std::cout << "~SuBSENSE()" << std::endl; +} + +void SuBSENSE::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + if (firstTime) + { + pSubsense = new BackgroundSubtractorSuBSENSE( + fRelLBSPThreshold, nDescDistThresholdOffset, nMinColorDistThreshold, + nBGSamples, nRequiredBGSamples, nSamplesForMovingAvgs); + + pSubsense->initialize(img_input, cv::Mat(img_input.size(), CV_8UC1, cv::Scalar_(255))); + firstTime = false; + } + + pSubsense->apply(img_input, img_foreground); + pSubsense->getBackgroundImage(img_background); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + { + imshow("SuBSENSE FG", img_foreground); + imshow("SuBSENSE BG", img_background); + } +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); +} + +void SuBSENSE::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteReal(fs, "fRelLBSPThreshold", fRelLBSPThreshold); + cvWriteInt(fs, "nDescDistThresholdOffset", nDescDistThresholdOffset); + cvWriteInt(fs, "nMinColorDistThreshold", nMinColorDistThreshold); + cvWriteInt(fs, "nBGSamples", nBGSamples); + cvWriteInt(fs, "nRequiredBGSamples", nRequiredBGSamples); + cvWriteInt(fs, "nSamplesForMovingAvgs", nSamplesForMovingAvgs); + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void SuBSENSE::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + fRelLBSPThreshold = cvReadRealByName(fs, nullptr, "fRelLBSPThreshold", BGSSUBSENSE_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD); + nDescDistThresholdOffset = cvReadIntByName(fs, nullptr, "nDescDistThresholdOffset", BGSSUBSENSE_DEFAULT_DESC_DIST_THRESHOLD_OFFSET); + nMinColorDistThreshold = cvReadIntByName(fs, nullptr, "nMinColorDistThreshold", BGSSUBSENSE_DEFAULT_MIN_COLOR_DIST_THRESHOLD); + nBGSamples = cvReadIntByName(fs, nullptr, "nBGSamples", BGSSUBSENSE_DEFAULT_NB_BG_SAMPLES); + nRequiredBGSamples = cvReadIntByName(fs, nullptr, "nRequiredBGSamples", BGSSUBSENSE_DEFAULT_REQUIRED_NB_BG_SAMPLES); + nSamplesForMovingAvgs = cvReadIntByName(fs, nullptr, "nSamplesForMovingAvgs", BGSSUBSENSE_DEFAULT_N_SAMPLES_FOR_MV_AVGS); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + + cvReleaseFileStorage(&fs); +} diff --git a/package_bgs/SuBSENSE.h b/package_bgs/SuBSENSE.h new file mode 100644 index 0000000..9ac9aa6 --- /dev/null +++ b/package_bgs/SuBSENSE.h @@ -0,0 +1,49 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "LBSP/BackgroundSubtractorSuBSENSE.h" + +namespace bgslibrary +{ + namespace algorithms + { + class SuBSENSE : public IBGS + { + private: + BackgroundSubtractorSuBSENSE* pSubsense; + + float fRelLBSPThreshold; + size_t nDescDistThresholdOffset; + size_t nMinColorDistThreshold; + size_t nBGSamples; + size_t nRequiredBGSamples; + size_t nSamplesForMovingAvgs; + + public: + SuBSENSE(); + ~SuBSENSE(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/T2F/FuzzyUtils.cpp b/package_bgs/T2F/FuzzyUtils.cpp new file mode 100644 index 0000000..a151136 --- /dev/null +++ b/package_bgs/T2F/FuzzyUtils.cpp @@ -0,0 +1,512 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "FuzzyUtils.h" + +FuzzyUtils::FuzzyUtils(void) {} + +FuzzyUtils::~FuzzyUtils(void) {} + +void FuzzyUtils::LBP(IplImage* InputImage, IplImage* LBPimage) +{ + PixelUtils p; + + float* neighberPixel = (float*)malloc(9 * sizeof(float)); + float* BinaryValue = (float*)malloc(9 * sizeof(float)); + float* CarreExp = (float*)malloc(9 * sizeof(float)); + float* valLBP = (float*)malloc(1 * sizeof(float)); + + *valLBP = 0; + + int x = 0, y = 0; + + // on implemente les 8 valeurs puissance de 2 qui correspondent aux 8 elem. d'image voisins au elem. d'image central + *(CarreExp + 0) = 1.0; + *(CarreExp + 1) = 2.0; + *(CarreExp + 2) = 4.0; + *(CarreExp + 3) = 8.0; + *(CarreExp + 4) = 0.0; + *(CarreExp + 5) = 16.0; + *(CarreExp + 6) = 32.0; + *(CarreExp + 7) = 64.0; + *(CarreExp + 8) = 128.0; + + //le calcule de LBP + //pour les 4 coins + /* 1.*/ + if (x == 0 && y == 0) + { + p.getNeighberhoodGrayPixel(InputImage, x, y, neighberPixel); + getBinValue(neighberPixel, BinaryValue, 4, 0); + *valLBP = *valLBP + ((*(BinaryValue + 1))*(*(CarreExp + 1)) + (*(BinaryValue + 2))*(*(CarreExp + 2)) + (*(BinaryValue + 3))*(*(CarreExp + 3))) / 255.0; + p.PutGrayPixel(LBPimage, x, y, *valLBP); + } + + /* 2.*/ + if (x == 0 && y == InputImage->width) + { + *valLBP = 0; + p.getNeighberhoodGrayPixel(InputImage, x, y, neighberPixel); + getBinValue(neighberPixel, BinaryValue, 4, 1); + *valLBP = *valLBP + ((*(BinaryValue))*(*(CarreExp)) + (*(BinaryValue + 2))*(*(CarreExp + 2)) + (*(BinaryValue + 3))*(*(CarreExp + 3))) / 255.0; + p.PutGrayPixel(LBPimage, x, y, *valLBP); + } + + /* 3.*/ + if (x == InputImage->height && y == 0) + { + *valLBP = 0; + p.getNeighberhoodGrayPixel(InputImage, x, y, neighberPixel); + getBinValue(neighberPixel, BinaryValue, 4, 2); + *valLBP = *valLBP + ((*(BinaryValue))*(*(CarreExp)) + (*(BinaryValue + 1))*(*(CarreExp + 1)) + (*(BinaryValue + 3))*(*(CarreExp + 3))) / 255.0; + p.PutGrayPixel(LBPimage, x, y, *valLBP); + } + + /* 4.*/ + if (x == InputImage->height && y == InputImage->width) + { + *valLBP = 0; + p.getNeighberhoodGrayPixel(InputImage, x, y, neighberPixel); + getBinValue(neighberPixel, BinaryValue, 4, 3); + *valLBP = *valLBP + ((*(BinaryValue))*(*(CarreExp)) + (*(BinaryValue + 1))*(*(CarreExp + 1)) + (*(BinaryValue + 2))*(*(CarreExp + 2))) / 255.0; + p.PutGrayPixel(LBPimage, x, y, *valLBP); + } + + //le calcul de LBP pour la première ligne : L(0) + if (x == 0 && (y != 0 && y != InputImage->width)) + { + for (int y = 1; y < InputImage->width - 1; y++) + { + p.getNeighberhoodGrayPixel(InputImage, x, y, neighberPixel); + getBinValue(neighberPixel, BinaryValue, 6, 4); + *valLBP = 0; + *valLBP = *valLBP + ((*(BinaryValue))*(*(CarreExp)) + (*(BinaryValue + 1))*(*(CarreExp + 1)) + (*(BinaryValue + 2))*(*(CarreExp + 2)) + (*(BinaryValue + 3))*(*(CarreExp + 3)) + (*(BinaryValue + 5))*(*(CarreExp + 5))) / 255.0; + p.PutGrayPixel(LBPimage, x, y, *valLBP); + } + } + + //le calcul de LBP pour la dernière colonne : C(w) + if ((x != 0 && x != InputImage->height) && y == InputImage->width) + { + for (int x = 1; x < InputImage->height - 1; x++) + { + p.getNeighberhoodGrayPixel(InputImage, x, y, neighberPixel); + getBinValue(neighberPixel, BinaryValue, 6, 4); + *valLBP = 0; + *valLBP = *valLBP + ((*(BinaryValue))*(*(CarreExp)) + (*(BinaryValue + 1))*(*(CarreExp + 1)) + (*(BinaryValue + 2))*(*(CarreExp + 2)) + (*(BinaryValue + 3))*(*(CarreExp + 3)) + (*(BinaryValue + 5))*(*(CarreExp + 5))) / 255.0; + p.PutGrayPixel(LBPimage, x, y, *valLBP); + } + } + + //le calcul de LBP pour la dernière ligne : L(h) + if (x == InputImage->height && (y != 0 && y != InputImage->width)) + { + for (int y = 1; y < InputImage->width - 1; y++) + { + p.getNeighberhoodGrayPixel(InputImage, x, y, neighberPixel); + getBinValue(neighberPixel, BinaryValue, 6, 1); + *valLBP = 0; + *valLBP = *valLBP + ((*(BinaryValue))*(*(CarreExp)) + (*(BinaryValue + 2))*(*(CarreExp + 2)) + (*(BinaryValue + 3))*(*(CarreExp + 3)) + (*(BinaryValue + 4))*(*(CarreExp + 4)) + (*(BinaryValue + 5))*(*(CarreExp + 5))) / 255.0; + p.PutGrayPixel(LBPimage, x, y, *valLBP); + } + } + + //le calcul de LBP pour la première colonne : C(0) + if ((x != 0 && x != InputImage->height) && y == 0) + { + for (int x = 1; x < InputImage->height - 1; x++) + { + p.getNeighberhoodGrayPixel(InputImage, x, y, neighberPixel); + getBinValue(neighberPixel, BinaryValue, 6, 2); + *valLBP = 0; + *valLBP = *valLBP + ((*(BinaryValue))*(*(CarreExp + 5)) + (*(BinaryValue + 1))*(*(CarreExp + 6)) + (*(BinaryValue + 3))*(*(CarreExp + 3)) + (*(BinaryValue + 4))*(*(CarreExp)) + (*(BinaryValue + 5))*(*(CarreExp + 1))) / 255.0; + p.PutGrayPixel(LBPimage, x, y, *valLBP); + } + } + + //pour le reste des elements d'image + for (int y = 1; y < InputImage->height - 1; y++) + { + for (int x = 1; x < InputImage->width - 1; x++) + { + p.getNeighberhoodGrayPixel(InputImage, x, y, neighberPixel); + getBinValue(neighberPixel, BinaryValue, 9, 4); + //le calcul de la valeur du LBP pour chaque elem. d'im. + *valLBP = 0; + for (int l = 0; l < 9; l++) + *valLBP = *valLBP + ((*(BinaryValue + l)) * (*(CarreExp + l))) / 255.0; + //printf("\nvalLBP(%d,%d)=%f",x,y,*valLBP); + p.PutGrayPixel(LBPimage, x, y, *valLBP); + } + } + + free(neighberPixel); + free(BinaryValue); + free(CarreExp); + free(valLBP); +} + +void FuzzyUtils::getBinValue(float* neighberGrayPixel, float* BinaryValue, int m, int n) +{ + // la comparaison entre la valeur d'elem d'image central et les valeurs des elem. d'im. voisins + // m = le numero des elements (4, 6 ou 9); + // n = la position de l'element central; + + int h = 0; + for (int k = 0; k < m; k++) + { + if (*(neighberGrayPixel + k) >= *(neighberGrayPixel + n)) + { + *(BinaryValue + h) = 1; + h++; + } + else + { + *(BinaryValue + h) = 0; + h++; + } + } +} + +void FuzzyUtils::SimilarityDegreesImage(IplImage* CurrentImage, IplImage* BGImage, IplImage* DeltaImage, int n, int color_space) +{ + PixelUtils p; + int i, j; + + if (n == 1) + { + float* CurrentGrayPixel = (float*)malloc(1 * (sizeof(float))); + float* BGGrayPixel = (float*)malloc(1 * (sizeof(float))); + float* DeltaGrayPixel = (float*)malloc(1 * (sizeof(float))); + + for (i = 0; i < CurrentImage->width; i++) + { + for (j = 0; j < CurrentImage->height; j++) + { + p.GetGrayPixel(CurrentImage, i, j, CurrentGrayPixel); + p.GetGrayPixel(BGImage, i, j, BGGrayPixel); + RatioPixels(CurrentGrayPixel, BGGrayPixel, DeltaGrayPixel, 1); + p.PutGrayPixel(DeltaImage, i, j, *DeltaGrayPixel); + } + } + + free(CurrentGrayPixel); + free(BGGrayPixel); + free(DeltaGrayPixel); + } + + if (n != 1) + { + IplImage* ConvertedCurrentImage = cvCreateImage(cvSize(CurrentImage->width, CurrentImage->height), IPL_DEPTH_32F, 3); + IplImage* ConvertedBGImage = cvCreateImage(cvSize(CurrentImage->width, CurrentImage->height), IPL_DEPTH_32F, 3); + + float* ConvertedCurrentPixel = (float*)malloc(3 * (sizeof(float))); + float* ConvertedBGPixel = (float*)malloc(3 * (sizeof(float))); + float* DeltaConvertedPixel = (float*)malloc(3 * (sizeof(float))); + + p.ColorConversion(CurrentImage, ConvertedCurrentImage, color_space); + p.ColorConversion(BGImage, ConvertedBGImage, color_space); + + for (i = 0; i < CurrentImage->width; i++) + { + for (j = 0; j < CurrentImage->height; j++) + { + p.GetPixel(ConvertedCurrentImage, i, j, ConvertedCurrentPixel); + p.GetPixel(ConvertedBGImage, i, j, ConvertedBGPixel); + RatioPixels(ConvertedCurrentPixel, ConvertedBGPixel, DeltaConvertedPixel, 3); + p.PutPixel(DeltaImage, i, j, DeltaConvertedPixel); + } + } + + free(ConvertedCurrentPixel); + free(ConvertedBGPixel); + free(DeltaConvertedPixel); + + cvReleaseImage(&ConvertedCurrentImage); + cvReleaseImage(&ConvertedBGImage); + } +} + +void FuzzyUtils::RatioPixels(float* CurrentPixel, float* BGPixel, float* DeltaPixel, int n) +{ + if (n == 1) + { + if (*CurrentPixel < *BGPixel) + *DeltaPixel = *CurrentPixel / *BGPixel; + + if (*CurrentPixel > *BGPixel) + *DeltaPixel = *BGPixel / *CurrentPixel; + + if (*CurrentPixel == *BGPixel) + *DeltaPixel = 1.0; + } + + if (n == 3) + for (int i = 0; i < 3; i++) + { + if (*(CurrentPixel + i) < *(BGPixel + i)) + *(DeltaPixel + i) = *(CurrentPixel + i) / *(BGPixel + i); + + if (*(CurrentPixel + i) > *(BGPixel + i)) + *(DeltaPixel + i) = *(BGPixel + i) / *(CurrentPixel + i); + + if (*(CurrentPixel + i) == *(BGPixel + i)) + *(DeltaPixel + i) = 1.0; + } +} + +void FuzzyUtils::getFuzzyIntegralSugeno(IplImage* H, IplImage* Delta, int n, float *MeasureG, IplImage* OutputImage) +{ + // MeasureG : est un vecteur contenant 3 mesure g (g1,g2,g3) tel que : g1+g2+g3=1 + // n : =2 cad aggreger les 2 images "H" et "Delta" + // =1 cad aggreger uniquement les valeurs des composantes couleurs de l'image "Delta" + + PixelUtils p; + + float* HTexturePixel = (float*)malloc(1 * sizeof(float)); + float* DeltaOhtaPixel = (float*)malloc(3 * (sizeof(float))); + int *Indice = (int*)malloc(3 * (sizeof(int))); + float *HI = (float*)malloc(3 * (sizeof(float))); + float *Integral = (float*)malloc(3 * (sizeof(float))); + float* X = (float*)malloc(1 * sizeof(float)); + float* XiXj = (float*)malloc(1 * sizeof(float)); + float IntegralFlou; + + *Indice = 0; + *(Indice + 1) = 1; + *(Indice + 2) = 2; + *X = 1.0; + + for (int i = 0; i < H->width; i++) + { + for (int j = 0; j < H->height; j++) + { + p.GetGrayPixel(H, i, j, HTexturePixel); + p.GetPixel(Delta, i, j, DeltaOhtaPixel); + + *(HI + 0) = *(HTexturePixel + 0); + *(HI + 1) = *(DeltaOhtaPixel + 0); + *(HI + 2) = *(DeltaOhtaPixel + 1); + + Trier(HI, 3, Indice); + + *XiXj = *(MeasureG + (*(Indice + 1))) + (*(MeasureG + (*(Indice + 2)))); + + *(Integral + 0) = min((HI + (*(Indice + 0))), X); + *(Integral + 1) = min((HI + (*(Indice + 1))), XiXj); + *(Integral + 2) = min((HI + (*(Indice + 2))), ((MeasureG + (*(Indice + 2))))); + + IntegralFlou = max(Integral, 3); + p.PutGrayPixel(OutputImage, i, j, IntegralFlou); + } + } + + free(HTexturePixel); + free(DeltaOhtaPixel); + free(Indice); + free(HI); + free(X); + free(XiXj); + free(Integral); +} + +void FuzzyUtils::getFuzzyIntegralChoquet(IplImage* H, IplImage* Delta, int n, float *MeasureG, IplImage* OutputImage) +{ + // MeasureG : est un vecteur contenant 3 mesure g (g1,g2,g3) tel que : g1+g2+g3=1 + // n : =2 cad aggreger les 2 images "H" et "Delta" + // =1 cad aggreger uniquement les valeurs des composantes couleurs de l'image "Delta" + + PixelUtils p; + + float* HTexturePixel = (float*)malloc(1 * sizeof(float)); + float* DeltaOhtaPixel = (float*)malloc(3 * (sizeof(float))); + int *Indice = (int*)malloc(3 * (sizeof(int))); + float *HI = (float*)malloc(3 * (sizeof(float))); + float *Integral = (float*)malloc(3 * (sizeof(float))); + float* X = (float*)malloc(1 * sizeof(float)); + float* XiXj = (float*)malloc(1 * sizeof(float)); + float IntegralFlou; + + *Indice = 0; + *(Indice + 1) = 1; + *(Indice + 2) = 2; + *X = 1.0; + + for (int i = 0; i < Delta->width; i++) + { + for (int j = 0; j < Delta->height; j++) + { + if (n == 2) + { + p.GetGrayPixel(H, i, j, HTexturePixel); + p.GetPixel(Delta, i, j, DeltaOhtaPixel); + + *(HI + 0) = *(HTexturePixel + 0); + *(HI + 1) = *(DeltaOhtaPixel + 0); + *(HI + 2) = *(DeltaOhtaPixel + 1); + } + + if (n == 1) + { + //remplir HI par les valeurs des 3 composantes couleurs uniquement + p.GetPixel(Delta, i, j, DeltaOhtaPixel); + + *(HI + 0) = *(DeltaOhtaPixel + 0); + //*(HI+0) = *(DeltaOhtaPixel+2); + *(HI + 1) = *(DeltaOhtaPixel + 1); + *(HI + 2) = *(DeltaOhtaPixel + 2); + } + + Trier(HI, 3, Indice); + *XiXj = *(MeasureG + (*(Indice + 1))) + (*(MeasureG + (*(Indice + 2)))); + + *(Integral + 0) = *(HI + (*(Indice + 0)))* (*X - *XiXj); + *(Integral + 1) = *(HI + (*(Indice + 1)))* (*XiXj - *(MeasureG + (*(Indice + 2)))); + *(Integral + 2) = *(HI + (*(Indice + 2)))* (*(MeasureG + (*(Indice + 2)))); + + IntegralFlou = *(Integral + 0) + *(Integral + 1) + *(Integral + 2); + p.PutGrayPixel(OutputImage, i, j, IntegralFlou); + } + } + + free(HTexturePixel); + free(DeltaOhtaPixel); + free(Indice); + free(HI); + free(X); + free(XiXj); + free(Integral); +} + +void FuzzyUtils::FuzzyMeasureG(float g1, float g2, float g3, float *G) +{ + *(G + 0) = g1; + *(G + 1) = g2; + *(G + 2) = g3; +} + +void FuzzyUtils::Trier(float* g, int n, int* index) +{ + // Cette fonction trie un vecteur g par ordre croissant et + // sort egalement l'indice des elements selon le trie dans le vecteur "index" supposé initialisé par des valeurs de 1 a n + + float t; + int r, a, b; + + for (a = 1; a <= n; a++) + { + for (b = n - 1; b >= a; b--) + if (*(g + b - 1) < (*(g + b))) + { + // ordre croissant des élements + t = *(g + b - 1); + *(g + b - 1) = *(g + b); + *(g + b) = t; + + // ordre des indices des élements du vecteur g + r = *(index + b - 1); + *(index + b - 1) = *(index + b); + *(index + b) = r; + } + } +} + +float FuzzyUtils::min(float *a, float *b) +{ + float min = 0; + + if (*a >= (*b)) + min = *b; + else + min = *a; + + return min; +} + +float FuzzyUtils::max(float* g, int n) +{ + float max = 0; + + for (int i = 0; i < n; i++) + { + if (*(g + i) >= max) + max = *(g + i); + } + + return max; +} + +void FuzzyUtils::gDeDeux(float* a, float* b, float* lambda) +{ + float* c = (float*)malloc(1 * sizeof(float)); + *c = *a + (*b) + (*lambda) * (*a) * (*b); +} + +void FuzzyUtils::getLambda(float* g) +{ + float a, b; + float* lambda = (float*)malloc(1 * sizeof(float)); + + a = (*(g + 0) * (*(g + 1)) + (*(g + 1)) * (*(g + 2)) + (*(g + 0)) * (*(g + 2))); + *lambda = -(*(g + 0) * (*(g + 1)) + (*(g + 1)) * (*(g + 2)) + (*(g + 0)) * (*(g + 2))) / (*(g + 0) * (*(g + 1)) * (*(g + 2))); + b = (*(g + 0) * (*(g + 1)) * (*(g + 2))); + + //printf("\na:%f",a); + //printf("\nb:%f",b); + //printf("\nlambda:%f", *lambda); + + free(lambda); +} + +void FuzzyUtils::AdaptativeSelectiveBackgroundModelUpdate(IplImage* CurrentImage, IplImage* BGImage, IplImage* OutputImage, IplImage* Integral, float seuil, float alpha) +{ + PixelUtils p; + + float beta = 0.0; + float* CurentImagePixel = (float*)malloc(3 * sizeof(float)); + float* BGImagePixel = (float*)malloc(3 * sizeof(float)); + float* OutputImagePixel = (float*)malloc(3 * sizeof(float)); + float* IntegralImagePixel = (float*)malloc(1 * sizeof(float)); + float *Maximum = (float*)malloc(1 * sizeof(float)); + float *Minimum = (float*)malloc(1 * sizeof(float)); + + p.ForegroundMaximum(Integral, Maximum, 1); + p.ForegroundMinimum(Integral, Minimum, 1); + + for (int i = 0; i < CurrentImage->width; i++) + { + for (int j = 0; j < CurrentImage->height; j++) + { + p.GetPixel(CurrentImage, i, j, CurentImagePixel); + p.GetPixel(BGImage, i, j, BGImagePixel); + p.GetGrayPixel(Integral, i, j, IntegralImagePixel); + + beta = 1 - ((*IntegralImagePixel) - ((*Minimum / (*Minimum - *Maximum)) * (*IntegralImagePixel) - (*Minimum * (*Maximum) / (*Minimum - *Maximum)))); + + for (int k = 0; k < 3; k++) + *(OutputImagePixel + k) = beta * (*(BGImagePixel + k)) + (1 - beta) * (alpha * (*(CurentImagePixel + k)) + (1 - alpha) * (*(BGImagePixel + k))); + + p.PutPixel(OutputImage, i, j, OutputImagePixel); + } + } + + free(CurentImagePixel); + free(BGImagePixel); + free(OutputImagePixel); + free(IntegralImagePixel); + free(Maximum); + free(Minimum); +} diff --git a/package_bgs/tb/FuzzyUtils.h b/package_bgs/T2F/FuzzyUtils.h similarity index 86% rename from package_bgs/tb/FuzzyUtils.h rename to package_bgs/T2F/FuzzyUtils.h index 43fc9ad..9a53a83 100644 --- a/package_bgs/tb/FuzzyUtils.h +++ b/package_bgs/T2F/FuzzyUtils.h @@ -15,19 +15,8 @@ You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ #pragma once -/* -Code provided by Thierry BOUWMANS - -Maitre de Conférences -Laboratoire MIA -Université de La Rochelle -17000 La Rochelle -France -tbouwman@univ-lr.fr -http://sites.google.com/site/thierrybouwmans/ -*/ -#include "PixelUtils.h" +#include "../../package_analysis/PixelUtils.h" class FuzzyUtils { @@ -44,7 +33,7 @@ class FuzzyUtils void getFuzzyIntegralSugeno(IplImage* H, IplImage* Delta, int n, float *MeasureG, IplImage* OutputImage); void getFuzzyIntegralChoquet(IplImage* H, IplImage* Delta, int n, float *MeasureG, IplImage* OutputImage); void FuzzyMeasureG(float g1, float g2, float g3, float *G); - void Trier(float* g, int n, int* index); + void Trier(float* g, int n, int* index); float min(float *a, float *b); float max(float *g, int n); void gDeDeux(float* a, float* b, float* lambda); diff --git a/package_bgs/tb/MRF.cpp b/package_bgs/T2F/MRF.cpp similarity index 58% rename from package_bgs/tb/MRF.cpp rename to package_bgs/T2F/MRF.cpp index cfde8af..291a846 100644 --- a/package_bgs/tb/MRF.cpp +++ b/package_bgs/T2F/MRF.cpp @@ -58,68 +58,68 @@ MRF_TC::MRF_TC() MRF_TC::~MRF_TC() { - delete []classes; - delete []old_labeling; - delete []in_image_data; - delete []local_evidence; + delete[]classes; + delete[]old_labeling; + delete[]in_image_data; + delete[]local_evidence; } double MRF_TC::TimeEnergy2(int i, int j, int label) { double energy = 0.0; - - if(old_labeling[i][j] == (label*255)) + + if (old_labeling[i][j] == (label * 255)) energy -= beta_time; else energy += beta_time; - if(i != height-1) // south + if (i != height - 1) // south { - if(label*255 == old_labeling[i+1][j]) + if (label * 255 == old_labeling[i + 1][j]) energy -= beta_time; else energy += beta_time; - if((j != width-1) && (label*255 == old_labeling[i+1][j+1])) + if ((j != width - 1) && (label * 255 == old_labeling[i + 1][j + 1])) energy -= beta_time; else energy += beta_time; - - if((j != 0) && (label*255 == old_labeling[i+1][j-1])) + + if ((j != 0) && (label * 255 == old_labeling[i + 1][j - 1])) energy -= beta_time; else energy += beta_time; } - if(j != width-1) // east + if (j != width - 1) // east { - if(label*255 == old_labeling[i][j+1]) + if (label * 255 == old_labeling[i][j + 1]) energy -= beta_time; else energy += beta_time; } - if(i != 0) // nord + if (i != 0) // nord { - if(label*255 == old_labeling[i-1][j]) + if (label * 255 == old_labeling[i - 1][j]) energy -= beta_time; else energy += beta_time; - if((j != width-1) && (label*255 == old_labeling[i-1][j+1])) + if ((j != width - 1) && (label * 255 == old_labeling[i - 1][j + 1])) energy -= beta_time; else energy += beta_time; - - if((j != 0) && (label*255 == old_labeling[i-1][j-1])) + + if ((j != 0) && (label * 255 == old_labeling[i - 1][j - 1])) energy -= beta_time; else energy += beta_time; } - if(j != 0) // west + if (j != 0) // west { - if(label*255 == old_labeling[i][j-1]) + if (label * 255 == old_labeling[i][j - 1]) energy -= beta_time; else energy += beta_time; @@ -132,53 +132,53 @@ double MRF_TC::Doubleton2(int i, int j, int label) { double energy = 0.0; - if(i != height-1) // south + if (i != height - 1) // south { - if(label == classes[i+1][j]) + if (label == classes[i + 1][j]) energy -= beta; else energy += beta; - if((j != width-1) && (label == classes[i+1][j+1])) + if ((j != width - 1) && (label == classes[i + 1][j + 1])) energy -= beta; else energy += beta; - - if((j != 0) && (label == classes[i+1][j-1])) + + if ((j != 0) && (label == classes[i + 1][j - 1])) energy -= beta; else energy += beta; } - if(j != width-1) // east + if (j != width - 1) // east { - if(label == classes[i][j+1]) + if (label == classes[i][j + 1]) energy -= beta; else energy += beta; } - if(i != 0) // nord + if (i != 0) // nord { - if(label == classes[i-1][j]) + if (label == classes[i - 1][j]) energy -= beta; else energy += beta; - if((j != width-1) && (label == classes[i-1][j+1])) + if ((j != width - 1) && (label == classes[i - 1][j + 1])) energy -= beta; else energy += beta; - if((j != 0) && (label == classes[i-1][j-1])) + if ((j != 0) && (label == classes[i - 1][j - 1])) energy -= beta; else energy += beta; } - if(j != 0) // west + if (j != 0) // west { - if(label == classes[i][j-1]) + if (label == classes[i][j - 1]) energy -= beta; else energy += beta; @@ -196,17 +196,17 @@ void MRF_TC::OnIterationOver2(void) void MRF_TC::Build_Classes_OldLabeling_InImage_LocalEnergy() { int i; - classes = new int* [height]; + classes = new int*[height]; old_labeling = new int *[height]; - in_image_data = new int* [height]; - local_evidence = new float* [height]; + in_image_data = new int*[height]; + local_evidence = new float*[height]; - for(i = 0; i < height; ++i) + for (i = 0; i < height; ++i) { classes[i] = new int[width]; old_labeling[i] = new int[width]; in_image_data[i] = new int[width]; - local_evidence[i] = new float[width*2]; + local_evidence[i] = new float[width * 2]; } } @@ -215,19 +215,19 @@ void MRF_TC::InitEvidence2(GMM *gmm, HMM *hmm, IplImage *labeling) int i, j; background = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3); - cvCopy(background2,background.Ptr()); + cvCopy(background2, background.Ptr()); unsigned char *in_data = (unsigned char *)(in_image->imageData); unsigned char *labeling_data = (unsigned char *)(labeling->imageData); - for(i = 0; i < height; ++i) + for (i = 0; i < height; ++i) { - for(j = 0; j < width; ++j) + for (j = 0; j < width; ++j) { - in_image_data[i][j] = in_data[(i*in_image->widthStep)+j]; - old_labeling[i][j] = labeling_data[i*width+j]; - - if(in_image_data[i][j] == 255) + in_image_data[i][j] = in_data[(i*in_image->widthStep) + j]; + old_labeling[i][j] = labeling_data[i*width + j]; + + if (in_image_data[i][j] == 255) classes[i][j] = 1; else classes[i][j] = 0; @@ -241,29 +241,29 @@ void MRF_TC::InitEvidence2(GMM *gmm, HMM *hmm, IplImage *labeling) float pixel; int modes = 3; - float mu; + float mu; - for(i = 0; i < height; ++i) + for (i = 0; i < height; ++i) { - for(j = 0; j < width; ++j) + for (j = 0; j < width; ++j) { - variance = gmm[(i*width+j) * modes+0].variance; - muR = gmm[(i*width+j) * modes+0].muR; - muG = gmm[(i*width+j) * modes+0].muG; - muB = gmm[(i*width+j) * modes+0].muB; + variance = gmm[(i*width + j) * modes + 0].variance; + muR = gmm[(i*width + j) * modes + 0].muR; + muG = gmm[(i*width + j) * modes + 0].muG; + muB = gmm[(i*width + j) * modes + 0].muB; - mu = (muR + muG + muB)/3; + mu = (muR + muG + muB) / 3; - pixel = (background(i,j,0) + background(i,j,1) + background(i,j,2))/3; + pixel = (background(i, j, 0) + background(i, j, 1) + background(i, j, 2)) / 3; - if(variance == 0) variance = 1; + if (variance == 0) variance = 1; - local_evidence[i][j*2+0] = pow((pixel-mu),2)/2/variance; + local_evidence[i][j * 2 + 0] = pow((pixel - mu), 2) / 2 / variance; - if(pixel >= mu) - local_evidence[i][j*2+1] = pow((pixel - mu - 2.5*sqrt(variance)),2)/2/variance; + if (pixel >= mu) + local_evidence[i][j * 2 + 1] = pow((pixel - mu - 2.5*sqrt(variance)), 2) / 2 / variance; else - local_evidence[i][j*2+1] = pow((pixel - mu + 2.5*sqrt(variance)),2)/2/variance; + local_evidence[i][j * 2 + 1] = pow((pixel - mu + 2.5*sqrt(variance)), 2) / 2 / variance; } } } @@ -274,12 +274,12 @@ void MRF_TC::CreateOutput2() int i, j; unsigned char *out_data; - out_data = (unsigned char *) out_image->imageData; + out_data = (unsigned char *)out_image->imageData; // create output image for (i = 0; i < height; ++i) - for(j = 0; j < width; ++j) - out_data[(i*width) + j] = (unsigned char)((classes[i][j])*255); + for (j = 0; j < width; ++j) + out_data[(i*width) + j] = (unsigned char)((classes[i][j]) * 255); } //calculate the whole energy @@ -288,12 +288,12 @@ double MRF_TC::CalculateEnergy2() double sum = 0.0; int i, j, k; // !FAIL! - for(i = 0; i < height; ++i) + for (i = 0; i < height; ++i) { - for(j = 0; j < width; ++j) + for (j = 0; j < width; ++j) { k = classes[i][j]; - sum = sum + local_evidence[i][j*2+k] + Doubleton2(i,j,k) + TimeEnergy2(i, j, k);//min the value + sum = sum + local_evidence[i][j * 2 + k] + Doubleton2(i, j, k) + TimeEnergy2(i, j, k);//min the value } } //sum = 0.1; @@ -303,7 +303,7 @@ double MRF_TC::CalculateEnergy2() // local energy double MRF_TC::LocalEnergy2(int i, int j, int label) { - return local_evidence[i][j*2+label] + Doubleton2(i,j,label) + TimeEnergy2(i,j,label); + return local_evidence[i][j * 2 + label] + Doubleton2(i, j, label) + TimeEnergy2(i, j, label); } void MRF_TC::ICM2() @@ -317,22 +317,22 @@ void MRF_TC::ICM2() do { - for(i = 0; i < height; ++i) - for(j = 0; j < width; ++j) + for (i = 0; i < height; ++i) + for (j = 0; j < width; ++j) { - localenergy0 = LocalEnergy2(i,j,0); - localenergy1 = LocalEnergy2(i,j,1); - - if(localenergy0 < localenergy1) + localenergy0 = LocalEnergy2(i, j, 0); + localenergy1 = LocalEnergy2(i, j, 1); + + if (localenergy0 < localenergy1) classes[i][j] = 0; else classes[i][j] = 1; } - //E = CalculateEnergy2(); - //summa_deltaE = fabs(E_old-E); - //E_old = E; - ++K; - OnIterationOver2(); - }while(K < 2); + //E = CalculateEnergy2(); + //summa_deltaE = fabs(E_old-E); + //E_old = E; + ++K; + OnIterationOver2(); + } while (K < 2); } diff --git a/package_bgs/tb/MRF.h b/package_bgs/T2F/MRF.h similarity index 95% rename from package_bgs/tb/MRF.h rename to package_bgs/T2F/MRF.h index c745899..1276a30 100644 --- a/package_bgs/tb/MRF.h +++ b/package_bgs/T2F/MRF.h @@ -14,8 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#ifndef MRF_H -#define MRF_H +#pragma once #include "T2FMRF.h" @@ -53,7 +52,7 @@ namespace Algorithms ////////////////////////////////////////////////////////////////////////// // alpha value for MMD - double alpha; + double alpha; ////////////////////////////////////////////////////////////////////////// //current global energy @@ -64,7 +63,7 @@ namespace Algorithms int K; ////////////////////////////////////////////////////////////////////////// - //labeling image + //labeling image int **classes; //input image int **in_image_data; @@ -75,7 +74,7 @@ namespace Algorithms /************************************************************************/ /* the Markov Random Field with time constraints for T2FGMM */ /************************************************************************/ - class MRF_TC: public MRF + class MRF_TC : public MRF { private: double beta_time; @@ -103,5 +102,3 @@ namespace Algorithms }; } } - -#endif diff --git a/package_bgs/tb/T2FGMM.cpp b/package_bgs/T2F/T2FGMM.cpp similarity index 69% rename from package_bgs/tb/T2FGMM.cpp rename to package_bgs/T2F/T2FGMM.cpp index a49407b..6ff9997 100644 --- a/package_bgs/tb/T2FGMM.cpp +++ b/package_bgs/T2F/T2FGMM.cpp @@ -17,8 +17,8 @@ along with BGSLibrary. If not, see . /**************************************************************************** * * T2FGMM.cpp -* -* Purpose: Implementation of the T2 Fuzzy Gaussian Mixture Models (T2GMMs) +* +* Purpose: Implementation of the T2 Fuzzy Gaussian Mixture Models (T2GMMs) * "Modeling of Dynamic Backgrounds by Type-2 Fuzzy Gaussians Mixture Models" * Author: Fida El Baf, Thierry Bouwmans, September 2008. @@ -33,9 +33,9 @@ int compareT2FGMM(const void* _gmm1, const void* _gmm2) GMM gmm1 = *(GMM*)_gmm1; GMM gmm2 = *(GMM*)_gmm2; - if(gmm1.significants < gmm2.significants) + if (gmm1.significants < gmm2.significants) return 1; - else if(gmm1.significants == gmm2.significants) + else if (gmm1.significants == gmm2.significants) return 0; else return -1; @@ -53,10 +53,10 @@ T2FGMM::~T2FGMM() void T2FGMM::Initalize(const BgsParams& param) { - m_params = (T2FGMMParams&) param; + m_params = (T2FGMMParams&)param; // Tbf - the threshold - m_bg_threshold = 0.75f; // 1-cf from the paper + m_bg_threshold = 0.75f; // 1-cf from the paper // Tgenerate - the threshold m_variance = 36.0f; // sigma for the new mode @@ -71,11 +71,11 @@ void T2FGMM::Initalize(const BgsParams& param) // Factor control for the T2FGMM-UM [0,3] //km = (float) 1.5; - km = (float) m_params.KM(); + km = (float)m_params.KM(); // Factor control for the T2FGMM-UV [0.3,1] //kv = (float) 0.6; - kv = (float) m_params.KV(); + kv = (float)m_params.KV(); } RgbImage* T2FGMM::Background() @@ -87,7 +87,7 @@ void T2FGMM::InitModel(const RgbImage& data) { m_modes_per_pixel.Clear(); - for(unsigned int i = 0; i < m_params.Size()*m_params.MaxModes(); ++i) + for (unsigned int i = 0; i < m_params.Size()*m_params.MaxModes(); ++i) { m_modes[i].weight = 0; m_modes[i].variance = 0; @@ -98,13 +98,13 @@ void T2FGMM::InitModel(const RgbImage& data) } } -void T2FGMM::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) +void T2FGMM::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) { // it doesn't make sense to have conditional updates in the GMM framework } -void T2FGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& numModes, - unsigned char& low_threshold, unsigned char& high_threshold) +void T2FGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& numModes, + unsigned char& low_threshold, unsigned char& high_threshold) { // calculate distances to the modes (+ sort???) // here we need to go in descending order!!! @@ -119,19 +119,19 @@ void T2FGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& // calculate number of Gaussians to include in the background model int backgroundGaussians = 0; double sum = 0.0; - for(int i = 0; i < numModes; ++i) + for (int i = 0; i < numModes; ++i) { - if(sum < m_bg_threshold) + if (sum < m_bg_threshold) { backgroundGaussians++; - sum += m_modes[posPixel+i].weight; + sum += m_modes[posPixel + i].weight; } else break; } // update all distributions and check for match with current pixel - for(int iModes = 0; iModes < numModes; iModes++) + for (int iModes = 0; iModes < numModes; iModes++) { pos = posPixel + iModes; float weight = m_modes[pos].weight; @@ -159,45 +159,45 @@ void T2FGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& float HB; // T2FGMM-UM - if(m_params.Type() == TYPE_T2FGMM_UM) + if (m_params.Type() == TYPE_T2FGMM_UM) { - if((pixel(0)muR+km*var)) - HR=2*km*dR/var; + if ((pixel(0) < muR - km*var) || (pixel(0) > muR + km*var)) + HR = 2 * km*dR / var; else - HR=dR*dR/(2*var*var)+km*dR/var+km*km/2; + HR = dR*dR / (2 * var*var) + km*dR / var + km*km / 2; - if((pixel(1)muG+km*var)) - HG=2*km*dG/var; + if ((pixel(1) < muG - km*var) || (pixel(1) > muG + km*var)) + HG = 2 * km*dG / var; else - HG=dG*dG/(2*var*var)+km*dG/var+km*km/2; + HG = dG*dG / (2 * var*var) + km*dG / var + km*km / 2; - if((pixel(2)muB+km*var)) - HB=2*km*dB/var; + if ((pixel(2) < muB - km*var) || (pixel(2) > muB + km*var)) + HB = 2 * km*dB / var; else - HB=dB*dB/(2*var*var)+km*dB/var+km*km/2; + HB = dB*dB / (2 * var*var) + km*dB / var + km*km / 2; } // T2FGMM-UV - if(m_params.Type() == TYPE_T2FGMM_UV) + if (m_params.Type() == TYPE_T2FGMM_UV) { - HR = (1/(kv*kv)-kv*kv) * (pixel(0)-muR) * (pixel(0)-muR)/(2*var); - HG = (1/(kv*kv)-kv*kv) * (pixel(1)-muG) * (pixel(1)-muG)/(2*var); - HB = (1/(kv*kv)-kv*kv) * (pixel(2)-muB) * (pixel(2)-muB)/(2*var); + HR = (1 / (kv*kv) - kv*kv) * (pixel(0) - muR) * (pixel(0) - muR) / (2 * var); + HG = (1 / (kv*kv) - kv*kv) * (pixel(1) - muG) * (pixel(1) - muG) / (2 * var); + HB = (1 / (kv*kv) - kv*kv) * (pixel(2) - muB) * (pixel(2) - muB) / (2 * var); } - + // calculate the squared distance float dist = (HR*HR + HG*HG + HB*HB); - if(dist < m_params.HighThreshold()*var && iModes < backgroundGaussians) + if (dist < m_params.HighThreshold()*var && iModes < backgroundGaussians) bBackgroundHigh = true; // a match occurs when the pixel is within sqrt(fTg) standard deviations of the distribution - if(dist < m_params.LowThreshold()*var) + if (dist < m_params.LowThreshold()*var) { bFitsPDF = true; // check if this Gaussian is part of the background model - if(iModes < backgroundGaussians) + if (iModes < backgroundGaussians) bBackgroundLow = true; //update distribution @@ -209,16 +209,16 @@ void T2FGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& m_modes[pos].muB = muB - k*(dB); //limit the variance - float sigmanew = var + k*(dist-var); - m_modes[pos].variance = sigmanew < 4 ? 4 : sigmanew > 5*m_variance ? 5*m_variance : sigmanew; + float sigmanew = var + k*(dist - var); + m_modes[pos].variance = sigmanew < 4 ? 4 : sigmanew > 5 * m_variance ? 5 * m_variance : sigmanew; m_modes[pos].significants = m_modes[pos].weight / sqrt(m_modes[pos].variance); } else { weight = fOneMinAlpha*weight; - if(weight < 0.0) + if (weight < 0.0) { - weight=0.0; + weight = 0.0; numModes--; } @@ -229,9 +229,9 @@ void T2FGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& else { weight = fOneMinAlpha*weight; - if(weight < 0.0) + if (weight < 0.0) { - weight=0.0; + weight = 0.0; numModes--; } m_modes[pos].weight = weight; @@ -243,25 +243,25 @@ void T2FGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& // renormalize weights so they add to one double invTotalWeight = 1.0 / totalWeight; - for(int iLocal = 0; iLocal < numModes; iLocal++) + for (int iLocal = 0; iLocal < numModes; iLocal++) { m_modes[posPixel + iLocal].weight *= (float)invTotalWeight; - m_modes[posPixel + iLocal].significants = m_modes[posPixel + iLocal].weight + m_modes[posPixel + iLocal].significants = m_modes[posPixel + iLocal].weight / sqrt(m_modes[posPixel + iLocal].variance); } - // Sort significance values so they are in desending order. - qsort(&m_modes[posPixel], numModes, sizeof(GMM), compareT2FGMM); + // Sort significance values so they are in desending order. + qsort(&m_modes[posPixel], numModes, sizeof(GMM), compareT2FGMM); // make new mode if needed and exit - if(!bFitsPDF) + if (!bFitsPDF) { - if(numModes < m_params.MaxModes()) + if (numModes < m_params.MaxModes()) numModes++; //else - // the weakest mode will be replaced - - pos = posPixel + numModes-1; + // the weakest mode will be replaced + + pos = posPixel + numModes - 1; m_modes[pos].muR = pixel.ch[0]; m_modes[pos].muG = pixel.ch[1]; @@ -277,26 +277,26 @@ void T2FGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& //renormalize weights int iLocal; float sum = 0.0; - for(iLocal = 0; iLocal < numModes; iLocal++) - sum += m_modes[posPixel+ iLocal].weight; - - double invSum = 1.0/sum; - for(iLocal = 0; iLocal < numModes; iLocal++) + for (iLocal = 0; iLocal < numModes; iLocal++) + sum += m_modes[posPixel + iLocal].weight; + + double invSum = 1.0 / sum; + for (iLocal = 0; iLocal < numModes; iLocal++) { m_modes[posPixel + iLocal].weight *= (float)invSum; m_modes[posPixel + iLocal].significants = m_modes[posPixel + iLocal].weight / sqrt(m_modes[posPixel + iLocal].variance); } } - // Sort significance values so they are in desending order. + // Sort significance values so they are in desending order. qsort(&(m_modes[posPixel]), numModes, sizeof(GMM), compareT2FGMM); - if(bBackgroundLow) + if (bBackgroundLow) low_threshold = BACKGROUND; else low_threshold = FOREGROUND; - - if(bBackgroundHigh) + + if (bBackgroundHigh) high_threshold = BACKGROUND; else high_threshold = FOREGROUND; @@ -316,21 +316,21 @@ void T2FGMM::Subtract(int frame_num, const RgbImage& data, BwImage& low_threshol long posPixel; // update each pixel of the image - for(unsigned int r = 0; r < m_params.Height(); ++r) + for (unsigned int r = 0; r < m_params.Height(); ++r) { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { // update model + background subtract posPixel = (r*m_params.Width() + c) * m_params.MaxModes(); - SubtractPixel(posPixel, data(r,c), m_modes_per_pixel(r,c), low_threshold, high_threshold); + SubtractPixel(posPixel, data(r, c), m_modes_per_pixel(r, c), low_threshold, high_threshold); - low_threshold_mask(r,c) = low_threshold; - high_threshold_mask(r,c) = high_threshold; + low_threshold_mask(r, c) = low_threshold; + high_threshold_mask(r, c) = high_threshold; - m_background(r,c,0) = (unsigned char) m_modes[posPixel].muR; - m_background(r,c,1) = (unsigned char) m_modes[posPixel].muG; - m_background(r,c,2) = (unsigned char) m_modes[posPixel].muB; + m_background(r, c, 0) = (unsigned char)m_modes[posPixel].muR; + m_background(r, c, 1) = (unsigned char)m_modes[posPixel].muG; + m_background(r, c, 2) = (unsigned char)m_modes[posPixel].muB; } } } diff --git a/package_bgs/tb/T2FGMM.h b/package_bgs/T2F/T2FGMM.h similarity index 92% rename from package_bgs/tb/T2FGMM.h rename to package_bgs/T2F/T2FGMM.h index 7d966db..203c350 100644 --- a/package_bgs/tb/T2FGMM.h +++ b/package_bgs/T2F/T2FGMM.h @@ -18,18 +18,16 @@ along with BGSLibrary. If not, see . * * T2FGMM.h * -* Purpose: Implementation of the T2 Fuzzy Gaussian Mixture Models (T2GMMs) +* Purpose: Implementation of the T2 Fuzzy Gaussian Mixture Models (T2GMMs) * "Modeling of Dynamic Backgrounds by Type-2 Fuzzy Gaussians Mixture Models" * Author: Fida El Baf, Thierry Bouwmans, September 2008 * * This code is based on code by Z. Zivkovic's written for his enhanced GMM -* background subtraction algorithm: +* background subtraction algorithm: * * Zivkovic's code can be obtained at: www.zoranz.net ******************************************************************************/ - -#ifndef T2F_GMM_ -#define T2F_GMM_ +#pragma once #include "../dp/Bgs.h" #include "../dp/GrimsonGMM.h" @@ -55,9 +53,9 @@ namespace Algorithms float &KV() { return m_kv; } private: - // Threshold on the squared dist. to decide when a sample is close to an existing - // components. If it is not close to any a new component will be generated. - // Smaller threshold values lead to more generated components and higher threshold values + // Threshold on the squared dist. to decide when a sample is close to an existing + // components. If it is not close to any a new component will be generated. + // Smaller threshold values lead to more generated components and higher threshold values // lead to a small number of components but they can grow too large. // // It is usual easiest to think of these thresholds as being the number of variances away @@ -66,7 +64,7 @@ namespace Algorithms float m_high_threshold; // alpha - speed of update - if the time interval you want to average over is T - // set alpha=1/T. + // set alpha=1/T. float m_alpha; // Maximum number of modes (Gaussian components) that will be used per pixel @@ -92,12 +90,12 @@ namespace Algorithms void Initalize(const BgsParams& param); void InitModel(const RgbImage& data); - void Subtract(int frame_num, const RgbImage& data, BwImage& low_threshold_mask, BwImage& high_threshold_mask); + void Subtract(int frame_num, const RgbImage& data, BwImage& low_threshold_mask, BwImage& high_threshold_mask); void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); RgbImage* Background(); - private: + private: void SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& numModes, unsigned char& lowThreshold, unsigned char& highThreshold); // User adjustable parameters @@ -109,8 +107,8 @@ namespace Algorithms // it is considered foreground float m_bg_threshold; //1-cf from the paper - // Initial variance for the newly generated components. - // It will will influence the speed of adaptation. A good guess should be made. + // Initial variance for the newly generated components. + // It will will influence the speed of adaptation. A good guess should be made. // A simple way is to estimate the typical standard deviation from the images. float m_variance; @@ -131,5 +129,3 @@ namespace Algorithms }; } } - -#endif diff --git a/package_bgs/tb/T2FMRF.cpp b/package_bgs/T2F/T2FMRF.cpp similarity index 70% rename from package_bgs/tb/T2FMRF.cpp rename to package_bgs/T2F/T2FMRF.cpp index 372f564..e722d02 100644 --- a/package_bgs/tb/T2FMRF.cpp +++ b/package_bgs/T2F/T2FMRF.cpp @@ -18,12 +18,12 @@ along with BGSLibrary. If not, see . * * T2FMRF.cpp * -* Purpose: Implementation of the T2 Fuzzy Gaussian Mixture Models (T2GMMs) +* Purpose: Implementation of the T2 Fuzzy Gaussian Mixture Models (T2GMMs) * "Modeling of Dynamic Backgrounds by Type-2 Fuzzy Gaussians Mixture Models" * Author: Fida El Baf, Thierry Bouwmans, September 2008 * * This code is based on code by Z. Zivkovic's written for his enhanced GMM -* background subtraction algorithm: +* background subtraction algorithm: * * Zivkovic's code can be obtained at: www.zoranz.net ******************************************************************************/ @@ -37,9 +37,9 @@ int compareT2FMRF(const void* _gmm1, const void* _gmm2) GMM gmm1 = *(GMM*)_gmm1; GMM gmm2 = *(GMM*)_gmm2; - if(gmm1.significants < gmm2.significants) + if (gmm1.significants < gmm2.significants) return 1; - else if(gmm1.significants == gmm2.significants) + else if (gmm1.significants == gmm2.significants) return 0; else return -1; @@ -66,10 +66,10 @@ T2FMRF::~T2FMRF() void T2FMRF::Initalize(const BgsParams& param) { - m_params = (T2FMRFParams&) param; + m_params = (T2FMRFParams&)param; // Tbf - the threshold - m_bg_threshold = 0.75f; // 1-cf from the paper + m_bg_threshold = 0.75f; // 1-cf from the paper // Tgenerate - the threshold m_variance = 36.0f; // sigma for the new mode @@ -87,11 +87,11 @@ void T2FMRF::Initalize(const BgsParams& param) // Factor control for the T2FGMM-UM [0,3] // km = (float) 2; //1.5; - km = (float) m_params.KM(); + km = (float)m_params.KM(); // Factor control for the T2FGMM-UV [0.3,1] // kv = (float) 0.9; //0.6; - kv = (float) m_params.KV(); + kv = (float)m_params.KV(); } RgbImage* T2FMRF::Background() @@ -103,7 +103,7 @@ void T2FMRF::InitModel(const RgbImage& data) { m_modes_per_pixel.Clear(); - for(unsigned int i = 0; i < m_params.Size()*m_params.MaxModes(); ++i) + for (unsigned int i = 0; i < m_params.Size()*m_params.MaxModes(); ++i) { m_modes[i].weight = 0; m_modes[i].variance = 0; @@ -124,13 +124,13 @@ void T2FMRF::InitModel(const RgbImage& data) } } -void T2FMRF::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) +void T2FMRF::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) { // it doesn't make sense to have conditional updates in the GMM framework } -void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, unsigned char& numModes, - unsigned char& low_threshold, unsigned char& high_threshold) +void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, unsigned char& numModes, + unsigned char& low_threshold, unsigned char& high_threshold) { // calculate distances to the modes (+ sort???) // here we need to go in descending order!!! @@ -144,7 +144,7 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, float Ab2f = m_state[posPixel].Ab2f; float Af2b = m_state[posPixel].Af2b; float Af2f = m_state[posPixel].Af2f; - float T = m_state[posPixel].T; + //float T = m_state[posPixel].T; float fOneMinAlpha = 1 - m_params.Alpha(); float totalWeight = 0.0f; @@ -152,12 +152,12 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, // calculate number of Gaussians to include in the background model int backgroundGaussians = 0; double sum = 0.0; - for(int i = 0; i < numModes; ++i) + for (int i = 0; i < numModes; ++i) { - if(sum < m_bg_threshold) + if (sum < m_bg_threshold) { backgroundGaussians++; - sum += m_modes[posGMode+i].weight; + sum += m_modes[posGMode + i].weight; } else break; @@ -192,57 +192,57 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, float HB; // T2FMRF-UM - if(m_params.Type() == TYPE_T2FMRF_UM) + if (m_params.Type() == TYPE_T2FMRF_UM) { - if((pixel(0) < muR-km*var) || (pixel(0) > muR+km*var)) - HR = 2*km*dR/var; + if ((pixel(0) < muR - km*var) || (pixel(0) > muR + km*var)) + HR = 2 * km*dR / var; else - HR = dR*dR/(2*var*var)+km*dR/var+km*km/2; + HR = dR*dR / (2 * var*var) + km*dR / var + km*km / 2; - if((pixel(1) < muG-km*var) || (pixel(1) > muG+km*var)) - HG = 2*km*dG/var; + if ((pixel(1) < muG - km*var) || (pixel(1) > muG + km*var)) + HG = 2 * km*dG / var; else - HG = dG*dG/(2*var*var)+km*dG/var+km*km/2; + HG = dG*dG / (2 * var*var) + km*dG / var + km*km / 2; - if((pixel(2) < muB-km*var) || (pixel(2) > muB+km*var)) - HB = 2*km*dB/var; + if ((pixel(2) < muB - km*var) || (pixel(2) > muB + km*var)) + HB = 2 * km*dB / var; else - HB = dB*dB/(2*var*var)+km*dB/var+km*km/2; + HB = dB*dB / (2 * var*var) + km*dB / var + km*km / 2; } // T2FGMM-UV - if(m_params.Type() == TYPE_T2FMRF_UV) + if (m_params.Type() == TYPE_T2FMRF_UV) { - HR = (1/(kv*kv)-kv*kv) * (pixel(0)-muR) * (pixel(0)-muR)/(2*var); - HG = (1/(kv*kv)-kv*kv) * (pixel(1)-muG) * (pixel(1)-muG)/(2*var); - HB = (1/(kv*kv)-kv*kv) * (pixel(2)-muB) * (pixel(2)-muB)/(2*var); + HR = (1 / (kv*kv) - kv*kv) * (pixel(0) - muR) * (pixel(0) - muR) / (2 * var); + HG = (1 / (kv*kv) - kv*kv) * (pixel(1) - muG) * (pixel(1) - muG) / (2 * var); + HB = (1 / (kv*kv) - kv*kv) * (pixel(2) - muB) * (pixel(2) - muB) / (2 * var); } float ro; if (CurrentState == background) { - if (Ab2b!=0) ro = (Ab2f/Ab2b); + if (Ab2b != 0) ro = (Ab2f / Ab2b); else ro = 10; } else { - if(Af2b!=0) ro = (Af2f/Af2b); + if (Af2b != 0) ro = (Af2f / Af2b); else ro = 10; } - + // calculate the squared distance float dist = (HR*HR + HG*HG + HB*HB); - if(dist < m_params.HighThreshold()*var && iModes < backgroundGaussians) + if (dist < m_params.HighThreshold()*var && iModes < backgroundGaussians) bBackgroundHigh = true; // a match occurs when the pixel is within sqrt(fTg) standard deviations of the distribution - if(dist < m_params.LowThreshold()*var) + if (dist < m_params.LowThreshold()*var) { bFitsPDF = true; // check if this Gaussian is part of the background model - if(iModes < backgroundGaussians) + if (iModes < backgroundGaussians) bBackgroundLow = true; //update distribution @@ -254,8 +254,8 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, m_modes[pos].muB = muB - k*(dB); //limit the variance - float sigmanew = var + k*(dist-var); - m_modes[pos].variance = sigmanew < 4 ? 4 : sigmanew > 5*m_variance ? 5*m_variance : sigmanew; + float sigmanew = var + k*(dist - var); + m_modes[pos].variance = sigmanew < 4 ? 4 : sigmanew > 5 * m_variance ? 5 * m_variance : sigmanew; m_modes[pos].significants = m_modes[pos].weight / sqrt(m_modes[pos].variance); } else @@ -263,7 +263,7 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, weight = fOneMinAlpha*weight; if (weight < 0.0) { - weight=0.0; + weight = 0.0; numModes--; } @@ -276,7 +276,7 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, weight = fOneMinAlpha*weight; if (weight < 0.0) { - weight=0.0; + weight = 0.0; numModes--; } m_modes[pos].weight = weight; @@ -294,18 +294,18 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, m_modes[posGMode + iLocal].significants = m_modes[posGMode + iLocal].weight / sqrt(m_modes[posGMode + iLocal].variance); } - // Sort significance values so they are in desending order. - qsort(&m_modes[posGMode], numModes, sizeof(GMM), compareT2FMRF); + // Sort significance values so they are in desending order. + qsort(&m_modes[posGMode], numModes, sizeof(GMM), compareT2FMRF); // make new mode if needed and exit - if(!bFitsPDF) + if (!bFitsPDF) { - if(numModes < m_params.MaxModes()) + if (numModes < m_params.MaxModes()) numModes++; //else - // the weakest mode will be replaced - - pos = posGMode + numModes-1; + // the weakest mode will be replaced + + pos = posGMode + numModes - 1; m_modes[pos].muR = pixel.ch[0]; m_modes[pos].muG = pixel.ch[1]; @@ -313,7 +313,7 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, m_modes[pos].variance = m_variance; m_modes[pos].significants = 0; // will be set below - if(numModes == 1) + if (numModes == 1) m_modes[pos].weight = 1; else m_modes[pos].weight = m_params.Alpha(); @@ -321,33 +321,33 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, //renormalize weights int iLocal; float sum = 0.0; - for(iLocal = 0; iLocal < numModes; iLocal++) - sum += m_modes[posGMode+ iLocal].weight; - - double invSum = 1.0/sum; - for(iLocal = 0; iLocal < numModes; iLocal++) + for (iLocal = 0; iLocal < numModes; iLocal++) + sum += m_modes[posGMode + iLocal].weight; + + double invSum = 1.0 / sum; + for (iLocal = 0; iLocal < numModes; iLocal++) { m_modes[posGMode + iLocal].weight *= (float)invSum; m_modes[posGMode + iLocal].significants = m_modes[posPixel + iLocal].weight / sqrt(m_modes[posGMode + iLocal].variance); } } - // Sort significance values so they are in desending order. + // Sort significance values so they are in desending order. qsort(&(m_modes[posGMode]), numModes, sizeof(GMM), compareT2FMRF); - if(bBackgroundLow) + if (bBackgroundLow) { low_threshold = BACKGROUND; m_state[posPixel].State = background; - if(CurrentState == background) + if (CurrentState == background) { float b2b = fOneMinAlpha*Ab2b + m_params.Alpha(); float b2f = fOneMinAlpha*Ab2f; float b = b2b + b2f; - m_state[posPixel].Ab2b = b2b/b; - m_state[posPixel].Ab2f = b2f/b; + m_state[posPixel].Ab2b = b2b / b; + m_state[posPixel].Ab2f = b2f / b; m_state[posPixel].T = m_state[posPixel].Ab2b; } else @@ -356,8 +356,8 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, float f2f = fOneMinAlpha*Af2f; float f = f2b + f2f; - m_state[posPixel].Af2b = f2b/f; - m_state[posPixel].Af2f = f2f/f; + m_state[posPixel].Af2b = f2b / f; + m_state[posPixel].Af2f = f2f / f; m_state[posPixel].T = m_state[posPixel].Af2b; } } @@ -366,14 +366,14 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, low_threshold = FOREGROUND; m_state[posPixel].State = foreground; - if(CurrentState == background) + if (CurrentState == background) { float b2b = fOneMinAlpha*Ab2b; float b2f = fOneMinAlpha*Ab2f + m_params.Alpha(); float b = b2b + b2f; - m_state[posPixel].Ab2b = b2b/b; - m_state[posPixel].Ab2f = b2f/b; + m_state[posPixel].Ab2b = b2b / b; + m_state[posPixel].Ab2f = b2f / b; m_state[posPixel].T = m_state[posPixel].Ab2b; } else @@ -382,13 +382,13 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, float f2f = fOneMinAlpha*Af2f + m_params.Alpha(); float f = f2b + f2f; - m_state[posPixel].Af2b = f2b/f; - m_state[posPixel].Af2f = f2f/f; + m_state[posPixel].Af2b = f2b / f; + m_state[posPixel].Af2f = f2f / f; m_state[posPixel].T = m_state[posPixel].Af2b; } } - if(bBackgroundHigh) + if (bBackgroundHigh) high_threshold = BACKGROUND; else high_threshold = FOREGROUND; @@ -402,30 +402,30 @@ void T2FMRF::SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, // (the memory should already be reserved) // values: 255-foreground, 125-shadow, 0-background /////////////////////////////////////////////////////////////////////////////// -void T2FMRF::Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask) +void T2FMRF::Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask) { unsigned char low_threshold, high_threshold; long posPixel; long posGMode; // update each pixel of the image - for(unsigned int r = 0; r < m_params.Height(); ++r) + for (unsigned int r = 0; r < m_params.Height(); ++r) { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { // update model + background subtract posPixel = r*m_params.Width() + c; posGMode = (r*m_params.Width() + c) * m_params.MaxModes(); - SubtractPixel(posPixel, posGMode, data(r,c), m_modes_per_pixel(r,c), low_threshold, high_threshold); + SubtractPixel(posPixel, posGMode, data(r, c), m_modes_per_pixel(r, c), low_threshold, high_threshold); - low_threshold_mask(r,c) = low_threshold; - high_threshold_mask(r,c) = high_threshold; + low_threshold_mask(r, c) = low_threshold; + high_threshold_mask(r, c) = high_threshold; - m_background(r,c,0) = (unsigned char) m_modes[posGMode].muR; - m_background(r,c,1) = (unsigned char) m_modes[posGMode].muG; - m_background(r,c,2) = (unsigned char) m_modes[posGMode].muB; + m_background(r, c, 0) = (unsigned char)m_modes[posGMode].muR; + m_background(r, c, 1) = (unsigned char)m_modes[posGMode].muG; + m_background(r, c, 2) = (unsigned char)m_modes[posGMode].muB; } } } diff --git a/package_bgs/tb/T2FMRF.h b/package_bgs/T2F/T2FMRF.h similarity index 91% rename from package_bgs/tb/T2FMRF.h rename to package_bgs/T2F/T2FMRF.h index 00e464c..1015426 100644 --- a/package_bgs/tb/T2FMRF.h +++ b/package_bgs/T2F/T2FMRF.h @@ -18,18 +18,16 @@ along with BGSLibrary. If not, see . * * T2FMRF.h * -* Purpose: Implementation of the T2 Fuzzy Gaussian Mixture Models (T2GMMs) +* Purpose: Implementation of the T2 Fuzzy Gaussian Mixture Models (T2GMMs) * "Modeling of Dynamic Backgrounds by Type-2 Fuzzy Gaussians Mixture Models" * Author: Fida El Baf, Thierry Bouwmans, September 2008 * * This code is based on code by Z. Zivkovic's written for his enhanced GMM -* background subtraction algorithm: +* background subtraction algorithm: * * Zivkovic's code can be obtained at: www.zoranz.net ******************************************************************************/ - -#ifndef T2F_MRF_ -#define T2F_MRF_ +#pragma once #include "../dp/Bgs.h" #include "../dp/GrimsonGMM.h" @@ -41,9 +39,9 @@ namespace Algorithms const int TYPE_T2FMRF_UM = 0; const int TYPE_T2FMRF_UV = 1; - enum HiddenState {background, foreground}; + enum HiddenState { background, foreground }; - typedef struct HMMState + typedef struct HMMState { float T; //Hidden State @@ -80,9 +78,9 @@ namespace Algorithms float &KV() { return m_kv; } private: - // Threshold on the squared dist. to decide when a sample is close to an existing - // components. If it is not close to any a new component will be generated. - // Smaller threshold values lead to more generated components and higher threshold values + // Threshold on the squared dist. to decide when a sample is close to an existing + // components. If it is not close to any a new component will be generated. + // Smaller threshold values lead to more generated components and higher threshold values // lead to a small number of components but they can grow too large. // // It is usual easiest to think of these thresholds as being the number of variances away @@ -91,7 +89,7 @@ namespace Algorithms float m_high_threshold; // alpha - speed of update - if the time interval you want to average over is T - // set alpha=1/T. + // set alpha=1/T. float m_alpha; // Maximum number of modes (Gaussian components) that will be used per pixel @@ -116,7 +114,7 @@ namespace Algorithms void Initalize(const BgsParams& param); void InitModel(const RgbImage& data); - void Subtract(int frame_num, const RgbImage& data, BwImage& low_threshold_mask, BwImage& high_threshold_mask); + void Subtract(int frame_num, const RgbImage& data, BwImage& low_threshold_mask, BwImage& high_threshold_mask); void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); RgbImage* Background(); @@ -124,7 +122,7 @@ namespace Algorithms GMM *gmm(void); HMM *hmm(void); - private: + private: void SubtractPixel(long posPixel, long posGMode, const RgbPixel& pixel, unsigned char& numModes, unsigned char& lowThreshold, unsigned char& highThreshold); // User adjustable parameters @@ -136,8 +134,8 @@ namespace Algorithms // it is considered foreground float m_bg_threshold; //1-cf from the paper - // Initial variance for the newly generated components. - // It will will influence the speed of adaptation. A good guess should be made. + // Initial variance for the newly generated components. + // It will will influence the speed of adaptation. A good guess should be made. // A simple way is to estimate the typical standard deviation from the images. float m_variance; @@ -160,5 +158,3 @@ namespace Algorithms }; } } - -#endif diff --git a/package_bgs/tb/T2FGMM_UM.cpp b/package_bgs/T2FGMM_UM.cpp similarity index 64% rename from package_bgs/tb/T2FGMM_UM.cpp rename to package_bgs/T2FGMM_UM.cpp index 31a5338..ad1b40e 100644 --- a/package_bgs/tb/T2FGMM_UM.cpp +++ b/package_bgs/T2FGMM_UM.cpp @@ -16,9 +16,13 @@ along with BGSLibrary. If not, see . */ #include "T2FGMM_UM.h" -T2FGMM_UM::T2FGMM_UM() : firstTime(true), frameNumber(0), threshold(9.0), alpha(0.01), km(1.5f), kv(0.6f), gaussians(3), showOutput(true) +using namespace bgslibrary::algorithms; + +T2FGMM_UM::T2FGMM_UM() : + frameNumber(0), threshold(9.0), alpha(0.01), km(1.5f), kv(0.6f), gaussians(3) { std::cout << "T2FGMM_UM()" << std::endl; + setup("./config/T2FGMM_UM.xml"); } T2FGMM_UM::~T2FGMM_UM() @@ -28,23 +32,16 @@ T2FGMM_UM::~T2FGMM_UM() void T2FGMM_UM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); - + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) frame_data.ReleaseMemory(false); frame_data = frame; - if(firstTime) + if (firstTime) { - int width = img_input.size().width; + int width = img_input.size().width; int height = img_input.size().height; lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); @@ -55,7 +52,7 @@ void T2FGMM_UM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & params.SetFrameSize(width, height); params.LowThreshold() = threshold; - params.HighThreshold() = 2*params.LowThreshold(); + params.HighThreshold() = 2 * params.LowThreshold(); params.Alpha() = alpha; params.MaxModes() = gaussians; params.Type() = TYPE_T2FGMM_UM; @@ -69,13 +66,18 @@ void T2FGMM_UM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & bgs.Subtract(frameNumber, frame_data, lowThresholdMask, highThresholdMask); lowThresholdMask.Clear(); bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - if(showOutput) - cv::imshow("T2FGMM-UM", foreground); - - foreground.copyTo(img_output); + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + //img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("T2FGMM-UM", img_foreground); +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); delete frame; firstTime = false; @@ -84,7 +86,7 @@ void T2FGMM_UM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & void T2FGMM_UM::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/T2FGMM_UM.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteReal(fs, "threshold", threshold); cvWriteReal(fs, "alpha", alpha); @@ -98,14 +100,14 @@ void T2FGMM_UM::saveConfig() void T2FGMM_UM::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/T2FGMM_UM.xml", 0, CV_STORAGE_READ); - - threshold = cvReadRealByName(fs, 0, "threshold", 9.0); - alpha = cvReadRealByName(fs, 0, "alpha", 0.01); - km = cvReadRealByName(fs, 0, "km", 1.5); - kv = cvReadRealByName(fs, 0, "kv", 0.6); - gaussians = cvReadIntByName(fs, 0, "gaussians", 3); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadRealByName(fs, nullptr, "threshold", 9.0); + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.01); + km = cvReadRealByName(fs, nullptr, "km", 1.5); + kv = cvReadRealByName(fs, nullptr, "kv", 0.6); + gaussians = cvReadIntByName(fs, nullptr, "gaussians", 3); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/tb/T2FGMM_UM.h b/package_bgs/T2FGMM_UM.h similarity index 52% rename from package_bgs/tb/T2FGMM_UM.h rename to package_bgs/T2FGMM_UM.h index ae6e29f..c58aeb7 100644 --- a/package_bgs/tb/T2FGMM_UM.h +++ b/package_bgs/T2FGMM_UM.h @@ -16,43 +16,42 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "../IBGS.h" -#include "T2FGMM.h" +#include "IBGS.h" +#include "T2F/T2FGMM.h" using namespace Algorithms::BackgroundSubtraction; -class T2FGMM_UM : public IBGS +namespace bgslibrary { -private: - bool firstTime; - long frameNumber; - IplImage* frame; - RgbImage frame_data; - - T2FGMMParams params; - T2FGMM bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - double threshold; - double alpha; - float km; - float kv; - int gaussians; - bool showOutput; - -public: - T2FGMM_UM(); - ~T2FGMM_UM(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class T2FGMM_UM : public IBGS + { + private: + long frameNumber; + IplImage* frame; + RgbImage frame_data; + + T2FGMMParams params; + T2FGMM bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + + double threshold; + double alpha; + float km; + float kv; + int gaussians; + + public: + T2FGMM_UM(); + ~T2FGMM_UM(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/tb/T2FGMM_UV.cpp b/package_bgs/T2FGMM_UV.cpp similarity index 64% rename from package_bgs/tb/T2FGMM_UV.cpp rename to package_bgs/T2FGMM_UV.cpp index c6f99f1..4e9708f 100644 --- a/package_bgs/tb/T2FGMM_UV.cpp +++ b/package_bgs/T2FGMM_UV.cpp @@ -16,9 +16,13 @@ along with BGSLibrary. If not, see . */ #include "T2FGMM_UV.h" -T2FGMM_UV::T2FGMM_UV() : firstTime(true), frameNumber(0), threshold(9.0), alpha(0.01), km(1.5f), kv(0.6f), gaussians(3), showOutput(true) +using namespace bgslibrary::algorithms; + +T2FGMM_UV::T2FGMM_UV() : + frameNumber(0), threshold(9.0), alpha(0.01), km(1.5f), kv(0.6f), gaussians(3) { std::cout << "T2FGMM_UV()" << std::endl; + setup("./config/T2FGMM_UV.xml"); } T2FGMM_UV::~T2FGMM_UV() @@ -28,23 +32,16 @@ T2FGMM_UV::~T2FGMM_UV() void T2FGMM_UV::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); - + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) frame_data.ReleaseMemory(false); frame_data = frame; - if(firstTime) + if (firstTime) { - int width = img_input.size().width; + int width = img_input.size().width; int height = img_input.size().height; lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); @@ -55,7 +52,7 @@ void T2FGMM_UV::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & params.SetFrameSize(width, height); params.LowThreshold() = threshold; - params.HighThreshold() = 2*params.LowThreshold(); + params.HighThreshold() = 2 * params.LowThreshold(); params.Alpha() = alpha; params.MaxModes() = gaussians; params.Type() = TYPE_T2FGMM_UV; @@ -69,13 +66,18 @@ void T2FGMM_UV::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & bgs.Subtract(frameNumber, frame_data, lowThresholdMask, highThresholdMask); lowThresholdMask.Clear(); bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - if(showOutput) - cv::imshow("T2FGMM-UV", foreground); - - foreground.copyTo(img_output); + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + //img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("T2FGMM-UV", img_foreground); +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); delete frame; firstTime = false; @@ -84,7 +86,7 @@ void T2FGMM_UV::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & void T2FGMM_UV::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/T2FGMM_UV.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteReal(fs, "threshold", threshold); cvWriteReal(fs, "alpha", alpha); @@ -98,14 +100,14 @@ void T2FGMM_UV::saveConfig() void T2FGMM_UV::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/T2FGMM_UV.xml", 0, CV_STORAGE_READ); - - threshold = cvReadRealByName(fs, 0, "threshold", 9.0); - alpha = cvReadRealByName(fs, 0, "alpha", 0.01); - km = cvReadRealByName(fs, 0, "km", 1.5); - kv = cvReadRealByName(fs, 0, "kv", 0.6); - gaussians = cvReadIntByName(fs, 0, "gaussians", 3); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadRealByName(fs, nullptr, "threshold", 9.0); + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.01); + km = cvReadRealByName(fs, nullptr, "km", 1.5); + kv = cvReadRealByName(fs, nullptr, "kv", 0.6); + gaussians = cvReadIntByName(fs, nullptr, "gaussians", 3); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/tb/T2FGMM_UV.h b/package_bgs/T2FGMM_UV.h similarity index 52% rename from package_bgs/tb/T2FGMM_UV.h rename to package_bgs/T2FGMM_UV.h index 79edcc2..6102f3a 100644 --- a/package_bgs/tb/T2FGMM_UV.h +++ b/package_bgs/T2FGMM_UV.h @@ -16,43 +16,42 @@ along with BGSLibrary. If not, see . */ #pragma once -#include -#include - - -#include "../IBGS.h" -#include "T2FGMM.h" +#include "IBGS.h" +#include "T2F/T2FGMM.h" using namespace Algorithms::BackgroundSubtraction; -class T2FGMM_UV : public IBGS +namespace bgslibrary { -private: - bool firstTime; - long frameNumber; - IplImage* frame; - RgbImage frame_data; - - T2FGMMParams params; - T2FGMM bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - double threshold; - double alpha; - float km; - float kv; - int gaussians; - bool showOutput; - -public: - T2FGMM_UV(); - ~T2FGMM_UV(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - + namespace algorithms + { + class T2FGMM_UV : public IBGS + { + private: + long frameNumber; + IplImage* frame; + RgbImage frame_data; + + T2FGMMParams params; + T2FGMM bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + + double threshold; + double alpha; + float km; + float kv; + int gaussians; + + public: + T2FGMM_UV(); + ~T2FGMM_UV(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/tb/T2FMRF_UM.cpp b/package_bgs/T2FMRF_UM.cpp similarity index 59% rename from package_bgs/tb/T2FMRF_UM.cpp rename to package_bgs/T2FMRF_UM.cpp index 5b41d8a..e0e6dae 100644 --- a/package_bgs/tb/T2FMRF_UM.cpp +++ b/package_bgs/T2FMRF_UM.cpp @@ -16,10 +16,13 @@ along with BGSLibrary. If not, see . */ #include "T2FMRF_UM.h" -T2FMRF_UM::T2FMRF_UM() : firstTime(true), frameNumber(0), threshold(9.0), alpha(0.01), -km(2.f), kv(0.9f), gaussians(3), showOutput(true) +using namespace bgslibrary::algorithms; + +T2FMRF_UM::T2FMRF_UM() : + frameNumber(0), threshold(9.0), alpha(0.01), km(2.f), kv(0.9f), gaussians(3) { std::cout << "T2FMRF_UM()" << std::endl; + setup("./config/DPMean.xml"); } T2FMRF_UM::~T2FMRF_UM() @@ -29,23 +32,16 @@ T2FMRF_UM::~T2FMRF_UM() void T2FMRF_UM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); - + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) frame_data.ReleaseMemory(false); frame_data = frame; - if(firstTime) + if (firstTime) { - int width = img_input.size().width; + int width = img_input.size().width; int height = img_input.size().height; lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); @@ -56,7 +52,7 @@ void T2FMRF_UM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & params.SetFrameSize(width, height); params.LowThreshold() = threshold; - params.HighThreshold() = 2*params.LowThreshold(); + params.HighThreshold() = 2 * params.LowThreshold(); params.Alpha() = alpha; params.MaxModes() = gaussians; params.Type() = TYPE_T2FMRF_UM; @@ -70,7 +66,7 @@ void T2FMRF_UM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & old = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); mrf.height = height; - mrf.width = width; + mrf.width = width; mrf.Build_Classes_OldLabeling_InImage_LocalEnergy(); firstTime = false; @@ -80,32 +76,37 @@ void T2FMRF_UM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & cvCopy(lowThresholdMask.Ptr(), old); /************************************************************************/ - /* the code for MRF, it can be noted when using other methods */ - /************************************************************************/ - //the optimization process is done when the foreground detection is stable, - if(frameNumber >= 10) - { - gmm = bgs.gmm(); - hmm = bgs.hmm(); - mrf.background2 = frame_data.Ptr(); - mrf.in_image = lowThresholdMask.Ptr(); - mrf.out_image = lowThresholdMask.Ptr(); - mrf.InitEvidence2(gmm,hmm,old_labeling); - mrf.ICM2(); - cvCopy(mrf.out_image, lowThresholdMask.Ptr()); - } + /* the code for MRF, it can be noted when using other methods */ + /************************************************************************/ + //the optimization process is done when the foreground detection is stable, + if (frameNumber >= 10) + { + gmm = bgs.gmm(); + hmm = bgs.hmm(); + mrf.background2 = frame_data.Ptr(); + mrf.in_image = lowThresholdMask.Ptr(); + mrf.out_image = lowThresholdMask.Ptr(); + mrf.InitEvidence2(gmm, hmm, old_labeling); + mrf.ICM2(); + cvCopy(mrf.out_image, lowThresholdMask.Ptr()); + } cvCopy(old, old_labeling); lowThresholdMask.Clear(); bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - if(showOutput) - cv::imshow("T2FMRF-UM", foreground); - - foreground.copyTo(img_output); + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + //img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("T2FMRF-UM", img_foreground); +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); delete frame; frameNumber++; @@ -113,7 +114,7 @@ void T2FMRF_UM::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & void T2FMRF_UM::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/T2FMRF_UM.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteReal(fs, "threshold", threshold); cvWriteReal(fs, "alpha", alpha); @@ -127,14 +128,14 @@ void T2FMRF_UM::saveConfig() void T2FMRF_UM::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/T2FMRF_UM.xml", 0, CV_STORAGE_READ); - - threshold = cvReadRealByName(fs, 0, "threshold", 9.0); - alpha = cvReadRealByName(fs, 0, "alpha", 0.01); - km = cvReadRealByName(fs, 0, "km", 2); - kv = cvReadRealByName(fs, 0, "kv", 0.9); - gaussians = cvReadIntByName(fs, 0, "gaussians", 3); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadRealByName(fs, nullptr, "threshold", 9.0); + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.01); + km = cvReadRealByName(fs, nullptr, "km", 2); + kv = cvReadRealByName(fs, nullptr, "kv", 0.9); + gaussians = cvReadIntByName(fs, nullptr, "gaussians", 3); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/T2FMRF_UM.h b/package_bgs/T2FMRF_UM.h new file mode 100644 index 0000000..01f6014 --- /dev/null +++ b/package_bgs/T2FMRF_UM.h @@ -0,0 +1,64 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "T2F/MRF.h" + +using namespace Algorithms::BackgroundSubtraction; + +namespace bgslibrary +{ + namespace algorithms + { + class T2FMRF_UM : public IBGS + { + private: + long frameNumber; + IplImage *frame; + RgbImage frame_data; + + IplImage *old_labeling; + IplImage *old; + + T2FMRFParams params; + T2FMRF bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + + double threshold; + double alpha; + float km; + float kv; + int gaussians; + + MRF_TC mrf; + GMM *gmm; + HMM *hmm; + + public: + T2FMRF_UM(); + ~T2FMRF_UM(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/tb/T2FMRF_UV.cpp b/package_bgs/T2FMRF_UV.cpp similarity index 59% rename from package_bgs/tb/T2FMRF_UV.cpp rename to package_bgs/T2FMRF_UV.cpp index 03f2595..7f43c02 100644 --- a/package_bgs/tb/T2FMRF_UV.cpp +++ b/package_bgs/T2FMRF_UV.cpp @@ -16,10 +16,13 @@ along with BGSLibrary. If not, see . */ #include "T2FMRF_UV.h" -T2FMRF_UV::T2FMRF_UV() : firstTime(true), frameNumber(0), threshold(9.0), alpha(0.01), -km(2.f), kv(0.9f), gaussians(3), showOutput(true) +using namespace bgslibrary::algorithms; + +T2FMRF_UV::T2FMRF_UV() : + frameNumber(0), threshold(9.0), alpha(0.01), km(2.f), kv(0.9f), gaussians(3) { std::cout << "T2FMRF_UV()" << std::endl; + setup("./config/T2FMRF_UV.xml"); } T2FMRF_UV::~T2FMRF_UV() @@ -29,23 +32,16 @@ T2FMRF_UV::~T2FMRF_UV() void T2FMRF_UV::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); - + init(img_input, img_output, img_bgmodel); frame = new IplImage(img_input); - - if(firstTime) + + if (firstTime) frame_data.ReleaseMemory(false); frame_data = frame; - if(firstTime) + if (firstTime) { - int width = img_input.size().width; + int width = img_input.size().width; int height = img_input.size().height; lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); @@ -56,7 +52,7 @@ void T2FMRF_UV::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & params.SetFrameSize(width, height); params.LowThreshold() = threshold; - params.HighThreshold() = 2*params.LowThreshold(); + params.HighThreshold() = 2 * params.LowThreshold(); params.Alpha() = alpha; params.MaxModes() = gaussians; params.Type() = TYPE_T2FMRF_UV; @@ -70,7 +66,7 @@ void T2FMRF_UV::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & old = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); mrf.height = height; - mrf.width = width; + mrf.width = width; mrf.Build_Classes_OldLabeling_InImage_LocalEnergy(); firstTime = false; @@ -80,32 +76,37 @@ void T2FMRF_UV::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & cvCopy(lowThresholdMask.Ptr(), old); /************************************************************************/ - /* the code for MRF, it can be noted when using other methods */ - /************************************************************************/ - //the optimization process is done when the foreground detection is stable, - if(frameNumber >= 10) - { - gmm = bgs.gmm(); - hmm = bgs.hmm(); - mrf.background2 = frame_data.Ptr(); - mrf.in_image = lowThresholdMask.Ptr(); - mrf.out_image = lowThresholdMask.Ptr(); - mrf.InitEvidence2(gmm,hmm,old_labeling); - mrf.ICM2(); - cvCopy(mrf.out_image, lowThresholdMask.Ptr()); - } + /* the code for MRF, it can be noted when using other methods */ + /************************************************************************/ + //the optimization process is done when the foreground detection is stable, + if (frameNumber >= 10) + { + gmm = bgs.gmm(); + hmm = bgs.hmm(); + mrf.background2 = frame_data.Ptr(); + mrf.in_image = lowThresholdMask.Ptr(); + mrf.out_image = lowThresholdMask.Ptr(); + mrf.InitEvidence2(gmm, hmm, old_labeling); + mrf.ICM2(); + cvCopy(mrf.out_image, lowThresholdMask.Ptr()); + } cvCopy(old, old_labeling); lowThresholdMask.Clear(); bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - if(showOutput) - cv::imshow("T2FMRF-UV", foreground); - - foreground.copyTo(img_output); + img_foreground = cv::cvarrToMat(highThresholdMask.Ptr()); + img_background = cv::cvarrToMat(bgs.Background()->Ptr()); + //img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + cv::imshow("T2FMRF-UV", img_foreground); +#endif + + img_foreground.copyTo(img_output); + img_background.copyTo(img_bgmodel); delete frame; frameNumber++; @@ -113,7 +114,7 @@ void T2FMRF_UV::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat & void T2FMRF_UV::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/T2FMRF_UV.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteReal(fs, "threshold", threshold); cvWriteReal(fs, "alpha", alpha); @@ -127,14 +128,14 @@ void T2FMRF_UV::saveConfig() void T2FMRF_UV::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/T2FMRF_UV.xml", 0, CV_STORAGE_READ); - - threshold = cvReadRealByName(fs, 0, "threshold", 9.0); - alpha = cvReadRealByName(fs, 0, "alpha", 0.01); - km = cvReadRealByName(fs, 0, "km", 2); - kv = cvReadRealByName(fs, 0, "kv", 0.9); - gaussians = cvReadIntByName(fs, 0, "gaussians", 3); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + threshold = cvReadRealByName(fs, nullptr, "threshold", 9.0); + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.01); + km = cvReadRealByName(fs, nullptr, "km", 2); + kv = cvReadRealByName(fs, nullptr, "kv", 0.9); + gaussians = cvReadIntByName(fs, nullptr, "gaussians", 3); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/T2FMRF_UV.h b/package_bgs/T2FMRF_UV.h new file mode 100644 index 0000000..1c69171 --- /dev/null +++ b/package_bgs/T2FMRF_UV.h @@ -0,0 +1,64 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "T2F/MRF.h" + +using namespace Algorithms::BackgroundSubtraction; + +namespace bgslibrary +{ + namespace algorithms + { + class T2FMRF_UV : public IBGS + { + private: + long frameNumber; + IplImage *frame; + RgbImage frame_data; + + IplImage *old_labeling; + IplImage *old; + + T2FMRFParams params; + T2FMRF bgs; + BwImage lowThresholdMask; + BwImage highThresholdMask; + + double threshold; + double alpha; + float km; + float kv; + int gaussians; + + MRF_TC mrf; + GMM *gmm; + HMM *hmm; + + public: + T2FMRF_UV(); + ~T2FMRF_UV(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/TwoPoints.cpp b/package_bgs/TwoPoints.cpp new file mode 100644 index 0000000..aee4684 --- /dev/null +++ b/package_bgs/TwoPoints.cpp @@ -0,0 +1,112 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ + +#include "TwoPoints.h" + +using namespace bgslibrary::algorithms; + +TwoPoints::TwoPoints() : + matchingThreshold(DEFAULT_MATCH_THRESH), + updateFactor(DEFAULT_UPDATE_FACTOR), model(nullptr) +{ + std::cout << "TwoPoints()" << std::endl; + //model = static_cast(libtwopointsModel_New()); + model = libtwopointsModel_New(); + setup("./config/TwoPoints.xml"); +} + +TwoPoints::~TwoPoints() +{ + std::cout << "~TwoPoints()" << std::endl; + libtwopointsModel_Free(model); +} + +void TwoPoints::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + if (img_input.empty()) + return; + + cv::Mat updatingMask; + cv::Mat img_input_grayscale; + + // Convert input image to a grayscale image + cvtColor(img_input, img_input_grayscale, CV_BGR2GRAY); + + if (firstTime) + { + // Create a buffer for the output image. + //img_output = Mat(img_input.rows, img_input.cols, CV_8UC1); + + // Initialization of the ViBe model. + libtwopointsModel_AllocInit_8u_C1R(model, img_input_grayscale.data, img_input.cols, img_input.rows); + + // Sets default model values. + // libvibeModel_Sequential_SetMatchingThreshold(model, matchingThreshold); + // libvibeModel_Sequential_SetUpdateFactor(model, updateFactor); + } + + libtwopointsModel_Segmentation_8u_C1R(model, img_input_grayscale.data, img_output.data); + + updatingMask = cv::Mat(img_input.rows, img_input.cols, CV_8UC1); + // Work on the output and define the updating mask + for (int i = 0; i < img_input.cols * img_input.rows; i++) + { + if (img_output.data[i] == 0) // Foreground pixel + { + updatingMask.data[i] = 0; + img_output.data[i] = 255; + } + else // Background + { + updatingMask.data[i] = 255; + img_output.data[i] = 0; + } + } + + libtwopointsModel_Update_8u_C1R(model, img_input_grayscale.data, updatingMask.data); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + imshow("Two points", img_output); +#endif + + firstTime = false; +} + +void TwoPoints::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteInt(fs, "matchingThreshold", matchingThreshold); + cvWriteInt(fs, "updateFactor", updateFactor); + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void TwoPoints::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + matchingThreshold = cvReadRealByName(fs, nullptr, "matchingThreshold", DEFAULT_MATCH_THRESH); + updateFactor = cvReadRealByName(fs, nullptr, "updateFactor", DEFAULT_UPDATE_FACTOR); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", false); + + cvReleaseFileStorage(&fs); +} diff --git a/package_bgs/TwoPoints.h b/package_bgs/TwoPoints.h new file mode 100644 index 0000000..b42c231 --- /dev/null +++ b/package_bgs/TwoPoints.h @@ -0,0 +1,46 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "TwoPoints/two_points.h" + +namespace bgslibrary +{ + namespace algorithms + { + class TwoPoints : public IBGS + { + private: + static const int DEFAULT_MATCH_THRESH = 20; + static const int DEFAULT_UPDATE_FACTOR = 16; + int matchingThreshold; + int updateFactor; + twopointsModel_t* model; + + public: + TwoPoints(); + ~TwoPoints(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/TwoPoints/two_points.cpp b/package_bgs/TwoPoints/two_points.cpp new file mode 100644 index 0000000..d8ee86b --- /dev/null +++ b/package_bgs/TwoPoints/two_points.cpp @@ -0,0 +1,394 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include + +#include "two_points.h" + +static unsigned int abs_uint(const int i) +{ + return (i >= 0) ? i : -i; +} + +struct twopointsModel +{ + /* Parameters. */ + uint32_t width; + uint32_t height; + uint32_t numberOfSamples; + uint32_t matchingThreshold; + uint32_t matchingNumber; + uint32_t updateFactor; + + /* Storage for the history. */ + uint8_t *historyImage1; + uint8_t *historyImage2; + + /* Buffers with random values. */ + uint32_t *jump; + int *neighbor; +}; + +// ----------------------------------------------------------------------------- +// Creates the data structure +// ----------------------------------------------------------------------------- +twopointsModel_t *libtwopointsModel_New() +{ + /* Model structure alloc. */ + twopointsModel_t *model = NULL; + model = (twopointsModel_t*)calloc(1, sizeof(*model)); + assert(model != NULL); + + /* Default parameters values. */ + model->matchingThreshold = 20; + model->updateFactor = 16; + + /* Storage for the history. */ + model->historyImage1 = NULL; + model->historyImage2 = NULL; + + /* Buffers with random values. */ + model->jump = NULL; + model->neighbor = NULL; + + return(model); +} + +// ---------------------------------------------------------------------------- +// Frees the structure +// ---------------------------------------------------------------------------- +int32_t libtwopointsModel_Free(twopointsModel_t *model) +{ + if (model == NULL) + return(-1); + + if (model->historyImage1 != NULL) { + free(model->historyImage1); + model->historyImage1 = NULL; + } + if (model->historyImage2 != NULL) { + free(model->historyImage2); + model->historyImage2 = NULL; + } + if (model->jump != NULL) { + free(model->jump); + model->jump = NULL; + } + if (model->neighbor != NULL) { + free(model->neighbor); + model->neighbor = NULL; + } + free(model); + + return(0); +} + +// ----------------------------------------------------------------------------- +// Allocates and initializes a C1R model structure +// ----------------------------------------------------------------------------- +int32_t libtwopointsModel_AllocInit_8u_C1R( + twopointsModel_t *model, + const uint8_t *image_data, + const uint32_t width, + const uint32_t height +) +{ + // Some basic checks. */ + assert((image_data != NULL) && (model != NULL)); + assert((width > 0) && (height > 0)); + + /* Finish model alloc - parameters values cannot be changed anymore. */ + model->width = width; + model->height = height; + + /* Creates the historyImage structure. */ + model->historyImage1 = NULL; + model->historyImage1 = (uint8_t*)malloc(width * height * sizeof(uint8_t)); + model->historyImage2 = NULL; + model->historyImage2 = (uint8_t*)malloc(width * height * sizeof(uint8_t)); + + assert(model->historyImage1 != NULL); + assert(model->historyImage2 != NULL); + + for (int index = width * height - 1; index >= 0; --index) { + uint8_t value = image_data[index]; + + int value_plus_noise = value - 10; + if (value_plus_noise < 0) { value_plus_noise = 0; } + if (value_plus_noise > 255) { value_plus_noise = 255; } + model->historyImage1[index] = value_plus_noise; + + value_plus_noise = value + 10; + if (value_plus_noise < 0) { value_plus_noise = 0; } + if (value_plus_noise > 255) { value_plus_noise = 255; } + model->historyImage2[index] = value_plus_noise; + + // Swaps the two values if needed + if (model->historyImage1[index] > model->historyImage2[index]) { + uint8_t val = model->historyImage1[index]; + model->historyImage1[index] = model->historyImage2[index]; + model->historyImage2[index] = val; + } + } + + /* Fills the buffers with random values. */ + int size = (width > height) ? 2 * width + 1 : 2 * height + 1; + + model->jump = (uint32_t*)malloc(size * sizeof(*(model->jump))); + assert(model->jump != NULL); + + model->neighbor = (int*)malloc(size * sizeof(*(model->neighbor))); + assert(model->neighbor != NULL); + + + for (int i = 0; i < size; ++i) { + model->jump[i] = (rand() % (2 * model->updateFactor)) + 1; // Values between 1 and 2 * updateFactor. + model->neighbor[i] = ((rand() % 3) - 1) + ((rand() % 3) - 1) * width; // Values between { -width - 1, ... , width + 1 }. + } + + return(0); +} + +// ----------------------------------------------------------------------------- +// Segmentation of a C1R model +// ----------------------------------------------------------------------------- +int32_t libtwopointsModel_Segmentation_8u_C1R( + twopointsModel_t *model, + const uint8_t *image_data, + uint8_t *segmentation_map +) +{ + assert((image_data != NULL) && (model != NULL) && (segmentation_map != NULL)); + assert((model->width > 0) && (model->height > 0)); + assert((model->jump != NULL) && (model->neighbor != NULL)); + + /* Some variables. */ + uint32_t width = model->width; + uint32_t height = model->height; + uint32_t matchingThreshold = model->matchingThreshold; + + uint8_t *historyImage1 = model->historyImage1; + uint8_t *historyImage2 = model->historyImage2; + + /* Segmentation. */ + memset(segmentation_map, 0, width * height); + + uint8_t *first = historyImage1; + for (int index = width * height - 1; index >= 0; --index) { + // We adapt the threshold + matchingThreshold = model->matchingThreshold; + if (matchingThreshold < abs_uint(historyImage2[index] - historyImage1[index])) { + matchingThreshold = abs_uint(historyImage2[index] - historyImage1[index]); + } + if (abs_uint(image_data[index] - first[index]) <= matchingThreshold) + segmentation_map[index]++; + } + + first = historyImage2; + for (int index = width * height - 1; index >= 0; --index) { + // We adapt the threshold + matchingThreshold = model->matchingThreshold; + if (matchingThreshold < abs_uint(historyImage2[index] - historyImage1[index])) { + matchingThreshold = abs_uint(historyImage2[index] - historyImage1[index]); + } + if (abs_uint(image_data[index] - first[index]) <= matchingThreshold) + segmentation_map[index]++; + } + + return(0); +} + +// ---------------------------------------------------------------------------- +// Update a C1R model +// ---------------------------------------------------------------------------- +int doUpdate(const uint8_t value) +{ + if (value == 0) return 0; + else return 1; +} + + +int32_t libtwopointsModel_Update_8u_C1R( + twopointsModel_t *model, + const uint8_t *image_data, + uint8_t *updating_mask +) +{ + assert((image_data != NULL) && (model != NULL) && (updating_mask != NULL)); + assert((model->width > 0) && (model->height > 0)); + assert((model->jump != NULL) && (model->neighbor != NULL)); + + // Some variables. + uint32_t width = model->width; + uint32_t height = model->height; + + uint8_t *historyImage1 = model->historyImage1; + uint8_t *historyImage2 = model->historyImage2; + + // Updating. + uint32_t *jump = model->jump; + int *neighbor = model->neighbor; + + // All the frame, except the border. + uint32_t shift, indX, indY; + unsigned int x, y; + + for (y = 1; y < height - 1; ++y) { + shift = rand() % width; + indX = jump[shift]; // index_jump should never be zero (> 1). + + while (indX < width - 1) { + int index = indX + y * width; + + if (doUpdate(updating_mask[index])) { + uint8_t value = image_data[index]; + // In-place substitution. + // if (2*value < (historyImage1[index]+historyImage2[index]) ) { + if (rand() % 2 == 0) { + historyImage1[index] = value; + } + else { + historyImage2[index] = value; + } + + // Propagation + int index_neighbor = index + neighbor[shift]; + if (2 * value < (historyImage1[index_neighbor] + historyImage2[index_neighbor])) { + // if (rand()%2 == 0 ) { + historyImage1[index_neighbor] = value; + } + else { + historyImage2[index_neighbor] = value; + } + } + + ++shift; + indX += jump[shift]; + } + } + + // First row. + y = 0; + shift = rand() % width; + indX = jump[shift]; // index_jump should never be zero (> 1). + + while (indX <= width - 1) { + int index = indX + y * width; + + if (doUpdate(updating_mask[index])) { + uint8_t value = image_data[index]; + // In-place substitution. + // if (2*value < (historyImage1[index]+historyImage2[index]) ) { + if (rand() % 2 == 0) { + historyImage1[index] = value; + } + else { + historyImage2[index] = value; + } + } + + ++shift; + indX += jump[shift]; + } + + // Last row. + y = height - 1; + shift = rand() % width; + indX = jump[shift]; // index_jump should never be zero (> 1). + + while (indX <= width - 1) { + int index = indX + y * width; + + if (doUpdate(updating_mask[index])) { + uint8_t value = image_data[index]; + // In-place substitution. + // if (2*value < (historyImage1[index]+historyImage2[index]) ) { + if (rand() % 2 == 0) { + historyImage1[index] = value; + } + else { + historyImage2[index] = value; + } + } + + ++shift; + indX += jump[shift]; + } + + // First column. + x = 0; + shift = rand() % height; + indY = jump[shift]; // index_jump should never be zero (> 1). + + while (indY <= height - 1) { + int index = x + indY * width; + + if (doUpdate(updating_mask[index])) { + uint8_t value = image_data[index]; + // In-place substitution. + // if (2*value < (historyImage1[index]+historyImage2[index]) ) { + if (rand() % 2 == 0) { + historyImage1[index] = value; + } + else { + historyImage2[index] = value; + } + } + + ++shift; + indY += jump[shift]; + } + + // Last column. + x = width - 1; + shift = rand() % height; + indY = jump[shift]; // index_jump should never be zero (> 1). + + while (indY <= height - 1) { + int index = x + indY * width; + + if (doUpdate(updating_mask[index])) { + uint8_t value = image_data[index]; + // In-place substitution. + // if (2*value < (historyImage1[index]+historyImage2[index]) ) { + if (rand() % 2 == 0) { + historyImage1[index] = value; + } + else { + historyImage2[index] = value; + } + } + + ++shift; + indY += jump[shift]; + } + + // The first pixel! + if (rand() % model->updateFactor == 0) { + if (doUpdate(updating_mask[0])) { + uint8_t value = image_data[0]; + // In-place substitution. + if (rand() % 2 == 0) { + historyImage1[0] = value; + } + else { + historyImage2[0] = value; + } + } + } + + return(0); +} diff --git a/package_bgs/TwoPoints/two_points.h b/package_bgs/TwoPoints/two_points.h new file mode 100644 index 0000000..a64152a --- /dev/null +++ b/package_bgs/TwoPoints/two_points.h @@ -0,0 +1,50 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include +#include +#include +#include + +#define COLOR_BACKGROUND 0 /*!< Default label for background pixels */ +#define COLOR_FOREGROUND 255 /*!< Default label for foreground pixels. Note that some authors chose any value different from 0 instead */ + +typedef struct twopointsModel twopointsModel_t; + +twopointsModel_t *libtwopointsModel_New(); + +int32_t libtwopointsModel_Free(twopointsModel_t *model); + +int32_t libtwopointsModel_AllocInit_8u_C1R( + twopointsModel_t *model, + const uint8_t *image_data, + const uint32_t width, + const uint32_t height +); + +int32_t libtwopointsModel_Segmentation_8u_C1R( + twopointsModel_t *model, + const uint8_t *image_data, + uint8_t *segmentation_map +); + +int32_t libtwopointsModel_Update_8u_C1R( + twopointsModel_t *model, + const uint8_t *image_data, + uint8_t *updating_mask +); diff --git a/package_bgs/ViBe.cpp b/package_bgs/ViBe.cpp new file mode 100644 index 0000000..0d3125f --- /dev/null +++ b/package_bgs/ViBe.cpp @@ -0,0 +1,98 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ + +#include "ViBe.h" + +using namespace bgslibrary::algorithms; + +ViBe::ViBe() : + //numberOfSamples(DEFAULT_NUM_SAMPLES), + matchingThreshold(DEFAULT_MATCH_THRESH), + matchingNumber(DEFAULT_MATCH_NUM), + updateFactor(DEFAULT_UPDATE_FACTOR), + model(nullptr) +{ + std::cout << "ViBe()" << std::endl; + model = libvibeModel_Sequential_New(); + setup("./config/ViBe.xml"); +} + +ViBe::~ViBe() +{ + std::cout << "~ViBe()" << std::endl; + libvibeModel_Sequential_Free(model); +} + +void ViBe::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + if (img_input.empty()) + return; + + if (firstTime) + { + /* Create a buffer for the output image. */ + //img_output = cv::Mat(img_input.rows, img_input.cols, CV_8UC1); + + /* Initialization of the ViBe model. */ + libvibeModel_Sequential_AllocInit_8u_C3R(model, img_input.data, img_input.cols, img_input.rows); + + /* Sets default model values. */ + //libvibeModel_Sequential_SetNumberOfSamples(model, numberOfSamples); + libvibeModel_Sequential_SetMatchingThreshold(model, matchingThreshold); + libvibeModel_Sequential_SetMatchingNumber(model, matchingNumber); + libvibeModel_Sequential_SetUpdateFactor(model, updateFactor); + } + + libvibeModel_Sequential_Segmentation_8u_C3R(model, img_input.data, img_output.data); + //libvibeModel_Sequential_Update_8u_C3R(model, model_img_input.data, img_output.data); + libvibeModel_Sequential_Update_8u_C3R(model, img_input.data, img_output.data); + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + imshow("ViBe", img_output); +#endif + + firstTime = false; +} + +void ViBe::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + //cvWriteInt(fs, "numberOfSamples", numberOfSamples); + cvWriteInt(fs, "matchingThreshold", matchingThreshold); + cvWriteInt(fs, "matchingNumber", matchingNumber); + cvWriteInt(fs, "updateFactor", updateFactor); + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void ViBe::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + //numberOfSamples = cvReadIntByName(fs, nullptr, "numberOfSamples", DEFAULT_NUM_SAMPLES); + matchingThreshold = cvReadRealByName(fs, nullptr, "matchingThreshold", DEFAULT_MATCH_THRESH); + matchingNumber = cvReadRealByName(fs, nullptr, "matchingNumber", DEFAULT_MATCH_NUM); + updateFactor = cvReadRealByName(fs, nullptr, "updateFactor", DEFAULT_UPDATE_FACTOR); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", false); + + cvReleaseFileStorage(&fs); +} diff --git a/package_bgs/ViBe.h b/package_bgs/ViBe.h new file mode 100644 index 0000000..c8014cb --- /dev/null +++ b/package_bgs/ViBe.h @@ -0,0 +1,52 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "ViBe/vibe-background-sequential.h" + +namespace bgslibrary +{ + namespace algorithms + { + class ViBe : public IBGS + { + private: + static const int DEFAULT_NUM_SAMPLES = 20; + static const int DEFAULT_MATCH_THRESH = 20; + static const int DEFAULT_MATCH_NUM = 2; + static const int DEFAULT_UPDATE_FACTOR = 16; + + private: + //int numberOfSamples; + int matchingThreshold; + int matchingNumber; + int updateFactor; + vibeModel_Sequential_t* model; + + public: + ViBe(); + ~ViBe(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/ViBe/LICENSE b/package_bgs/ViBe/LICENSE new file mode 100644 index 0000000..7f22a77 --- /dev/null +++ b/package_bgs/ViBe/LICENSE @@ -0,0 +1,44 @@ + Authors: + -------- + Olivier Barnich and + Marc Van Droogenbroeck + + Licence: + -------- + ViBe is covered by a patent (see http://www.ulg.ac.be/telecom/research/vibe). + We do not provide the source code but provide an object file and an interface. + + Permission to use ViBe without payment of fee is granted + for nonprofit educational and research purposes only. + + This work may not be copied or reproduced in whole or in part for any + purpose. + + Copying, reproduction, or republishing for any purpose shall require + a license. Please contact the author in such cases. + All the code is provided without any guarantee. + + + How to refer to us: + ------------------- + + If you want to refer to this work, please cite the following publications: + + Barnich and M. Van Droogenbroeck. ViBe: A universal background subtraction algorithm for video sequences. In IEEE Transactions on Image Processing, 20(6):1709-1724, June 2011. + +@article{Barnich2011ViBe, + title = {{ViBe}: A universal background subtraction algorithm for video sequences}, + author = {O. Barnich and M. {Van Droogenbroeck}}, + journal = {IEEE Transactions on Image Processing}, + volume = {20}, + number = {6}, + pages = {1709-1724}, + month = {June}, + year = {2011}, + keywords = {ViBe, Background, Background subtraction, Segmentation, Motion, Motion detection}, + pdf = {http://orbi.ulg.ac.be/bitstream/2268/81248/1/Barnich2011ViBe.pdf}, + doi = {10.1109/TIP.2010.2101613}, + url = {http://hdl.handle.net/2268/81248} +} + + diff --git a/package_bgs/ViBe/vibe-background-sequential.cpp b/package_bgs/ViBe/vibe-background-sequential.cpp new file mode 100644 index 0000000..c4b9178 --- /dev/null +++ b/package_bgs/ViBe/vibe-background-sequential.cpp @@ -0,0 +1,929 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/** +@file vibe-background-sequential.c +@brief Implementation of vibe-background-sequential.h +@author Marc Van Droogenbroeck +@date May 2014 +*/ +#include +#include + +#include "vibe-background-sequential.h" + +#define NUMBER_OF_HISTORY_IMAGES 2 + +uint32_t distance_Han2014Improved(uint8_t pixel, uint8_t bg) +{ + uint8_t min, max; + + // Computes R = 0.13 min{ max[bg,26], 230} + max = 26; + if (bg > max) { max = bg; } + + min = 230; + if (min > max) { min = max; } + + return (uint32_t)(0.13*min); +} + +static int abs_uint(const int i) +{ + return (i >= 0) ? i : -i; +} + +static int32_t distance_is_close_8u_C3R(uint8_t r1, uint8_t g1, uint8_t b1, uint8_t r2, uint8_t g2, uint8_t b2, uint32_t threshold) +{ + return (abs_uint(r1 - r2) + abs_uint(g1 - g2) + abs_uint(b1 - b2) <= 4.5 * threshold); +} + +struct vibeModel_Sequential +{ + /* Parameters. */ + uint32_t width; + uint32_t height; + uint32_t numberOfSamples; + uint32_t matchingThreshold; + uint32_t matchingNumber; + uint32_t updateFactor; + + /* Storage for the history. */ + uint8_t *historyImage; + uint8_t *historyBuffer; + uint32_t lastHistoryImageSwapped; + + /* Buffers with random values. */ + uint32_t *jump; + int *neighbor; + uint32_t *position; +}; + +// ----------------------------------------------------------------------------- +// Print parameters +// ----------------------------------------------------------------------------- +uint32_t libvibeModel_Sequential_PrintParameters(const vibeModel_Sequential_t *model) +{ + printf( + "Using ViBe background subtraction algorithm\n" + " - Number of samples per pixel: %03d\n" + " - Number of matches needed: %03d\n" + " - Matching threshold: %03d\n" + " - Model update subsampling factor: %03d\n", + libvibeModel_Sequential_GetNumberOfSamples(model), + libvibeModel_Sequential_GetMatchingNumber(model), + libvibeModel_Sequential_GetMatchingThreshold(model), + libvibeModel_Sequential_GetUpdateFactor(model) + ); + + return(0); +} + +// ----------------------------------------------------------------------------- +// Creates the data structure +// ----------------------------------------------------------------------------- +vibeModel_Sequential_t *libvibeModel_Sequential_New() +{ + /* Model structure alloc. */ + vibeModel_Sequential_t *model = NULL; + model = (vibeModel_Sequential_t*)calloc(1, sizeof(*model)); + assert(model != NULL); + + /* Default parameters values. */ + model->numberOfSamples = 20; + model->matchingThreshold = 20; + model->matchingNumber = 2; + model->updateFactor = 16; + + /* Storage for the history. */ + model->historyImage = NULL; + model->historyBuffer = NULL; + model->lastHistoryImageSwapped = 0; + + /* Buffers with random values. */ + model->jump = NULL; + model->neighbor = NULL; + model->position = NULL; + + return(model); +} + +// ----------------------------------------------------------------------------- +// Some "Get-ers" +// ----------------------------------------------------------------------------- +uint32_t libvibeModel_Sequential_GetNumberOfSamples(const vibeModel_Sequential_t *model) +{ + assert(model != NULL); return(model->numberOfSamples); +} + +uint32_t libvibeModel_Sequential_GetMatchingNumber(const vibeModel_Sequential_t *model) +{ + assert(model != NULL); return(model->matchingNumber); +} + +uint32_t libvibeModel_Sequential_GetMatchingThreshold(const vibeModel_Sequential_t *model) +{ + assert(model != NULL); return(model->matchingThreshold); +} + +uint32_t libvibeModel_Sequential_GetUpdateFactor(const vibeModel_Sequential_t *model) +{ + assert(model != NULL); return(model->updateFactor); +} + +// ----------------------------------------------------------------------------- +// Some "Set-ers" +// ----------------------------------------------------------------------------- +int32_t libvibeModel_Sequential_SetMatchingThreshold( + vibeModel_Sequential_t *model, + const uint32_t matchingThreshold +) { + assert(model != NULL); + assert(matchingThreshold > 0); + + model->matchingThreshold = matchingThreshold; + + return(0); +} + +// ----------------------------------------------------------------------------- +int32_t libvibeModel_Sequential_SetMatchingNumber( + vibeModel_Sequential_t *model, + const uint32_t matchingNumber +) { + assert(model != NULL); + assert(matchingNumber > 0); + + model->matchingNumber = matchingNumber; + + return(0); +} + +// ----------------------------------------------------------------------------- +int32_t libvibeModel_Sequential_SetUpdateFactor( + vibeModel_Sequential_t *model, + const uint32_t updateFactor +) { + assert(model != NULL); + assert(updateFactor > 0); + + model->updateFactor = updateFactor; + + /* We also need to change the values of the jump buffer ! */ + assert(model->jump != NULL); + + /* Shifts. */ + int size = (model->width > model->height) ? 2 * model->width + 1 : 2 * model->height + 1; + + for (int i = 0; i < size; ++i) + model->jump[i] = (updateFactor == 1) ? 1 : (rand() % (2 * model->updateFactor)) + 1; // 1 or values between 1 and 2 * updateFactor. + + return(0); +} + +// ---------------------------------------------------------------------------- +// Frees the structure +// ---------------------------------------------------------------------------- +int32_t libvibeModel_Sequential_Free(vibeModel_Sequential_t *model) +{ + if (model == NULL) + return(-1); + + if (model->historyBuffer == NULL) { + free(model); + return(0); + } + + free(model->historyImage); + free(model->historyBuffer); + free(model->jump); + free(model->neighbor); + free(model->position); + free(model); + + return(0); +} + +// ----------------------------------------------------------------------------- +// Allocates and initializes a C1R model structure +// ----------------------------------------------------------------------------- +int32_t libvibeModel_Sequential_AllocInit_8u_C1R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + const uint32_t width, + const uint32_t height +) { + // Some basic checks. */ + assert((image_data != NULL) && (model != NULL)); + assert((width > 0) && (height > 0)); + + /* Finish model alloc - parameters values cannot be changed anymore. */ + model->width = width; + model->height = height; + + /* Creates the historyImage structure. */ + model->historyImage = NULL; + model->historyImage = (uint8_t*)malloc(NUMBER_OF_HISTORY_IMAGES * width * height * sizeof(*(model->historyImage))); + + assert(model->historyImage != NULL); + + for (int i = 0; i < NUMBER_OF_HISTORY_IMAGES; ++i) { + for (int index = width * height - 1; index >= 0; --index) + model->historyImage[i * width * height + index] = image_data[index]; + } + + /* Now creates and fills the history buffer. */ + model->historyBuffer = (uint8_t*)malloc(width * height * (model->numberOfSamples - NUMBER_OF_HISTORY_IMAGES) * sizeof(uint8_t)); + assert(model->historyBuffer != NULL); + + for (int index = width * height - 1; index >= 0; --index) { + uint8_t value = image_data[index]; + + for (int x = 0; x < model->numberOfSamples - NUMBER_OF_HISTORY_IMAGES; ++x) { + int value_plus_noise = value + rand() % 20 - 10; + + if (value_plus_noise < 0) { value_plus_noise = 0; } + if (value_plus_noise > 255) { value_plus_noise = 255; } + + model->historyBuffer[index * (model->numberOfSamples - NUMBER_OF_HISTORY_IMAGES) + x] = value_plus_noise; + } + } + + /* Fills the buffers with random values. */ + int size = (width > height) ? 2 * width + 1 : 2 * height + 1; + + model->jump = (uint32_t*)malloc(size * sizeof(*(model->jump))); + assert(model->jump != NULL); + + model->neighbor = (int*)malloc(size * sizeof(*(model->neighbor))); + assert(model->neighbor != NULL); + + model->position = (uint32_t*)malloc(size * sizeof(*(model->position))); + assert(model->position != NULL); + + for (int i = 0; i < size; ++i) { + model->jump[i] = (rand() % (2 * model->updateFactor)) + 1; // Values between 1 and 2 * updateFactor. + model->neighbor[i] = ((rand() % 3) - 1) + ((rand() % 3) - 1) * width; // Values between { -width - 1, ... , width + 1 }. + model->position[i] = rand() % (model->numberOfSamples); // Values between 0 and numberOfSamples - 1. + } + + return(0); +} + +// ----------------------------------------------------------------------------- +// Segmentation of a C1R model +// ----------------------------------------------------------------------------- +int32_t libvibeModel_Sequential_Segmentation_8u_C1R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + uint8_t *segmentation_map +) { + /* Basic checks. */ + assert((image_data != NULL) && (model != NULL) && (segmentation_map != NULL)); + assert((model->width > 0) && (model->height > 0)); + assert(model->historyBuffer != NULL); + assert((model->jump != NULL) && (model->neighbor != NULL) && (model->position != NULL)); + + /* Some variables. */ + uint32_t width = model->width; + uint32_t height = model->height; + uint32_t matchingNumber = model->matchingNumber; + uint32_t matchingThreshold = model->matchingThreshold; + + uint8_t *historyImage = model->historyImage; + uint8_t *historyBuffer = model->historyBuffer; + + /* Segmentation. */ + memset(segmentation_map, matchingNumber - 1, width * height); + + /* First history Image structure. */ + for (int index = width * height - 1; index >= 0; --index) { + //if (abs_uint(image_data[index] - historyImage[index]) > matchingThreshold) + if (abs_uint(image_data[index] - historyImage[index]) > distance_Han2014Improved(image_data[index], historyImage[index])) + segmentation_map[index] = matchingNumber; + } + + /* Next historyImages. */ + for (int i = 1; i < NUMBER_OF_HISTORY_IMAGES; ++i) { + uint8_t *pels = historyImage + i * width * height; + + for (int index = width * height - 1; index >= 0; --index) { + // if (abs_uint(image_data[index] - pels[index]) <= matchingThreshold) + if (abs_uint(image_data[index] - pels[index]) <= distance_Han2014Improved(image_data[index], pels[index])) + --segmentation_map[index]; + } + } + + /* For swapping. */ + model->lastHistoryImageSwapped = (model->lastHistoryImageSwapped + 1) % NUMBER_OF_HISTORY_IMAGES; + uint8_t *swappingImageBuffer = historyImage + (model->lastHistoryImageSwapped) * width * height; + + /* Now, we move in the buffer and leave the historyImages. */ + int numberOfTests = (model->numberOfSamples - NUMBER_OF_HISTORY_IMAGES); + + for (int index = width * height - 1; index >= 0; --index) { + if (segmentation_map[index] > 0) { + /* We need to check the full border and swap values with the first or second historyImage. + * We still need to find a match before we can stop our search. + */ + uint32_t indexHistoryBuffer = index * numberOfTests; + uint8_t currentValue = image_data[index]; + + for (int i = numberOfTests; i > 0; --i, ++indexHistoryBuffer) { + // if (abs_uint(currentValue - historyBuffer[indexHistoryBuffer]) <= matchingThreshold) { + if (abs_uint(currentValue - historyBuffer[indexHistoryBuffer]) <= distance_Han2014Improved(currentValue, historyBuffer[indexHistoryBuffer])) { + --segmentation_map[index]; + + /* Swaping: Putting found value in history image buffer. */ + uint8_t temp = swappingImageBuffer[index]; + swappingImageBuffer[index] = historyBuffer[indexHistoryBuffer]; + historyBuffer[indexHistoryBuffer] = temp; + + /* Exit inner loop. */ + if (segmentation_map[index] <= 0) break; + } + } // for + } // if + } // for + + /* Produces the output. Note that this step is application-dependent. */ + for (uint8_t *mask = segmentation_map; mask < segmentation_map + (width * height); ++mask) + if (*mask > 0) *mask = COLOR_FOREGROUND; + + return(0); +} + +// ---------------------------------------------------------------------------- +// Update a C1R model +// ---------------------------------------------------------------------------- +int32_t libvibeModel_Sequential_Update_8u_C1R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + uint8_t *updating_mask +) { + /* Basic checks . */ + assert((image_data != NULL) && (model != NULL) && (updating_mask != NULL)); + assert((model->width > 0) && (model->height > 0)); + assert(model->historyBuffer != NULL); + assert((model->jump != NULL) && (model->neighbor != NULL) && (model->position != NULL)); + + /* Some variables. */ + uint32_t width = model->width; + uint32_t height = model->height; + + uint8_t *historyImage = model->historyImage; + uint8_t *historyBuffer = model->historyBuffer; + + /* Some utility variable. */ + int numberOfTests = (model->numberOfSamples - NUMBER_OF_HISTORY_IMAGES); + + /* Updating. */ + uint32_t *jump = model->jump; + int *neighbor = model->neighbor; + uint32_t *position = model->position; + + /* All the frame, except the border. */ + uint32_t shift, indX, indY; + int x, y; + + for (y = 1; y < height - 1; ++y) { + shift = rand() % width; + indX = jump[shift]; // index_jump should never be zero (> 1). + + while (indX < width - 1) { + int index = indX + y * width; + + if (updating_mask[index] == COLOR_BACKGROUND) { + /* In-place substitution. */ + uint8_t value = image_data[index]; + int index_neighbor = index + neighbor[shift]; + + if (position[shift] < NUMBER_OF_HISTORY_IMAGES) { + historyImage[index + position[shift] * width * height] = value; + historyImage[index_neighbor + position[shift] * width * height] = value; + } + else { + int pos = position[shift] - NUMBER_OF_HISTORY_IMAGES; + historyBuffer[index * numberOfTests + pos] = value; + historyBuffer[index_neighbor * numberOfTests + pos] = value; + } + } + + ++shift; + indX += jump[shift]; + } + } + + /* First row. */ + y = 0; + shift = rand() % width; + indX = jump[shift]; // index_jump should never be zero (> 1). + + while (indX <= width - 1) { + int index = indX + y * width; + + if (updating_mask[index] == COLOR_BACKGROUND) { + if (position[shift] < NUMBER_OF_HISTORY_IMAGES) + historyImage[index + position[shift] * width * height] = image_data[index]; + else { + int pos = position[shift] - NUMBER_OF_HISTORY_IMAGES; + historyBuffer[index * numberOfTests + pos] = image_data[index]; + } + } + + ++shift; + indX += jump[shift]; + } + + /* Last row. */ + y = height - 1; + shift = rand() % width; + indX = jump[shift]; // index_jump should never be zero (> 1). + + while (indX <= width - 1) { + int index = indX + y * width; + + if (updating_mask[index] == COLOR_BACKGROUND) { + if (position[shift] < NUMBER_OF_HISTORY_IMAGES) + historyImage[index + position[shift] * width * height] = image_data[index]; + else { + int pos = position[shift] - NUMBER_OF_HISTORY_IMAGES; + historyBuffer[index * numberOfTests + pos] = image_data[index]; + } + } + + ++shift; + indX += jump[shift]; + } + + /* First column. */ + x = 0; + shift = rand() % height; + indY = jump[shift]; // index_jump should never be zero (> 1). + + while (indY <= height - 1) { + int index = x + indY * width; + + if (updating_mask[index] == COLOR_BACKGROUND) { + if (position[shift] < NUMBER_OF_HISTORY_IMAGES) + historyImage[index + position[shift] * width * height] = image_data[index]; + else { + int pos = position[shift] - NUMBER_OF_HISTORY_IMAGES; + historyBuffer[index * numberOfTests + pos] = image_data[index]; + } + } + + ++shift; + indY += jump[shift]; + } + + /* Last column. */ + x = width - 1; + shift = rand() % height; + indY = jump[shift]; // index_jump should never be zero (> 1). + + while (indY <= height - 1) { + int index = x + indY * width; + + if (updating_mask[index] == COLOR_BACKGROUND) { + if (position[shift] < NUMBER_OF_HISTORY_IMAGES) + historyImage[index + position[shift] * width * height] = image_data[index]; + else { + int pos = position[shift] - NUMBER_OF_HISTORY_IMAGES; + historyBuffer[index * numberOfTests + pos] = image_data[index]; + } + } + + ++shift; + indY += jump[shift]; + } + + /* The first pixel! */ + if (rand() % model->updateFactor == 0) { + if (updating_mask[0] == 0) { + int position = rand() % model->numberOfSamples; + + if (position < NUMBER_OF_HISTORY_IMAGES) + historyImage[position * width * height] = image_data[0]; + else { + int pos = position - NUMBER_OF_HISTORY_IMAGES; + historyBuffer[pos] = image_data[0]; + } + } + } + + return(0); +} + +// ---------------------------------------------------------------------------- +// -------------------------- The same for C3R models ------------------------- +// ---------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// Allocates and initializes a C3R model structure +// ----------------------------------------------------------------------------- +int32_t libvibeModel_Sequential_AllocInit_8u_C3R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + const uint32_t width, + const uint32_t height +) { + /* Some basic checks. */ + assert((image_data != NULL) && (model != NULL)); + assert((width > 0) && (height > 0)); + + /* Finish model alloc - parameters values cannot be changed anymore. */ + model->width = width; + model->height = height; + + /* Creates the historyImage structure. */ + model->historyImage = NULL; + model->historyImage = (uint8_t*)malloc(NUMBER_OF_HISTORY_IMAGES * (3 * width) * height * sizeof(uint8_t)); + assert(model->historyImage != NULL); + + for (int i = 0; i < NUMBER_OF_HISTORY_IMAGES; ++i) { + for (int index = (3 * width) * height - 1; index >= 0; --index) + model->historyImage[i * (3 * width) * height + index] = image_data[index]; + } + + assert(model->historyImage != NULL); + + /* Now creates and fills the history buffer. */ + model->historyBuffer = (uint8_t *)malloc((3 * width) * height * (model->numberOfSamples - NUMBER_OF_HISTORY_IMAGES) * sizeof(uint8_t)); + assert(model->historyBuffer != NULL); + + for (int index = (3 * width) * height - 1; index >= 0; --index) { + uint8_t value = image_data[index]; + + for (int x = 0; x < model->numberOfSamples - NUMBER_OF_HISTORY_IMAGES; ++x) { + int value_plus_noise = value + rand() % 20 - 10; + + if (value_plus_noise < 0) { value_plus_noise = 0; } + if (value_plus_noise > 255) { value_plus_noise = 255; } + + model->historyBuffer[index * (model->numberOfSamples - NUMBER_OF_HISTORY_IMAGES) + x] = value_plus_noise; + } + } + + /* Fills the buffers with random values. */ + int size = (width > height) ? 2 * width + 1 : 2 * height + 1; + + model->jump = (uint32_t*)malloc(size * sizeof(*(model->jump))); + assert(model->jump != NULL); + + model->neighbor = (int*)malloc(size * sizeof(*(model->neighbor))); + assert(model->neighbor != NULL); + + model->position = (uint32_t*)malloc(size * sizeof(*(model->position))); + assert(model->position != NULL); + + for (int i = 0; i < size; ++i) { + model->jump[i] = (rand() % (2 * model->updateFactor)) + 1; // Values between 1 and 2 * updateFactor. + model->neighbor[i] = ((rand() % 3) - 1) + ((rand() % 3) - 1) * width; // Values between { width - 1, ... , width + 1 }. + model->position[i] = rand() % (model->numberOfSamples); // Values between 0 and numberOfSamples - 1. + } + + return(0); +} + +// ----------------------------------------------------------------------------- +// Segmentation of a C3R model +// ----------------------------------------------------------------------------- +int32_t libvibeModel_Sequential_Segmentation_8u_C3R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + uint8_t *segmentation_map +) { + /* Basic checks. */ + assert((image_data != NULL) && (model != NULL) && (segmentation_map != NULL)); + assert((model->width > 0) && (model->height > 0)); + assert(model->historyBuffer != NULL); + assert((model->jump != NULL) && (model->neighbor != NULL) && (model->position != NULL)); + + /* Some variables. */ + uint32_t width = model->width; + uint32_t height = model->height; + uint32_t matchingNumber = model->matchingNumber; + uint32_t matchingThreshold = model->matchingThreshold; + + uint8_t *historyImage = model->historyImage; + uint8_t *historyBuffer = model->historyBuffer; + + /* Segmentation. */ + memset(segmentation_map, matchingNumber - 1, width * height); + + /* First history Image structure. */ + uint8_t *first = historyImage; + + for (int index = width * height - 1; index >= 0; --index) { + if ( + !distance_is_close_8u_C3R( + image_data[3 * index], image_data[3 * index + 1], image_data[3 * index + 2], + first[3 * index], first[3 * index + 1], first[3 * index + 2], matchingThreshold + ) + ) + segmentation_map[index] = matchingNumber; + } + + /* Next historyImages. */ + for (int i = 1; i < NUMBER_OF_HISTORY_IMAGES; ++i) { + uint8_t *pels = historyImage + i * (3 * width) * height; + + for (int index = width * height - 1; index >= 0; --index) { + if ( + distance_is_close_8u_C3R( + image_data[3 * index], image_data[3 * index + 1], image_data[3 * index + 2], + pels[3 * index], pels[3 * index + 1], pels[3 * index + 2], matchingThreshold + ) + ) + --segmentation_map[index]; + } + } + + // For swapping + model->lastHistoryImageSwapped = (model->lastHistoryImageSwapped + 1) % NUMBER_OF_HISTORY_IMAGES; + uint8_t *swappingImageBuffer = historyImage + (model->lastHistoryImageSwapped) * (3 * width) * height; + + // Now, we move in the buffer and leave the historyImages + int numberOfTests = (model->numberOfSamples - NUMBER_OF_HISTORY_IMAGES); + + for (int index = width * height - 1; index >= 0; --index) { + if (segmentation_map[index] > 0) { + /* We need to check the full border and swap values with the first or second historyImage. + * We still need to find a match before we can stop our search. + */ + uint32_t indexHistoryBuffer = (3 * index) * numberOfTests; + + for (int i = numberOfTests; i > 0; --i, indexHistoryBuffer += 3) { + if ( + distance_is_close_8u_C3R( + image_data[(3 * index)], image_data[(3 * index) + 1], image_data[(3 * index) + 2], + historyBuffer[indexHistoryBuffer], historyBuffer[indexHistoryBuffer + 1], historyBuffer[indexHistoryBuffer + 2], + matchingThreshold + ) + ) + --segmentation_map[index]; + + /* Swaping: Putting found value in history image buffer. */ + uint8_t temp_r = swappingImageBuffer[(3 * index)]; + uint8_t temp_g = swappingImageBuffer[(3 * index) + 1]; + uint8_t temp_b = swappingImageBuffer[(3 * index) + 2]; + + swappingImageBuffer[(3 * index)] = historyBuffer[indexHistoryBuffer]; + swappingImageBuffer[(3 * index) + 1] = historyBuffer[indexHistoryBuffer + 1]; + swappingImageBuffer[(3 * index) + 2] = historyBuffer[indexHistoryBuffer + 2]; + + historyBuffer[indexHistoryBuffer] = temp_r; + historyBuffer[indexHistoryBuffer + 1] = temp_g; + historyBuffer[indexHistoryBuffer + 2] = temp_b; + + /* Exit inner loop. */ + if (segmentation_map[index] <= 0) break; + } // for + } // if + } // for + + /* Produces the output. Note that this step is application-dependent. */ + for (uint8_t *mask = segmentation_map; mask < segmentation_map + (width * height); ++mask) + if (*mask > 0) *mask = COLOR_FOREGROUND; + + return(0); +} + +// ---------------------------------------------------------------------------- +// Update a C3R model +// ---------------------------------------------------------------------------- +int32_t libvibeModel_Sequential_Update_8u_C3R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + uint8_t *updating_mask +) { + /* Basic checks. */ + assert((image_data != NULL) && (model != NULL) && (updating_mask != NULL)); + assert((model->width > 0) && (model->height > 0)); + assert(model->historyBuffer != NULL); + assert((model->jump != NULL) && (model->neighbor != NULL) && (model->position != NULL)); + + /* Some variables. */ + uint32_t width = model->width; + uint32_t height = model->height; + + uint8_t *historyImage = model->historyImage; + uint8_t *historyBuffer = model->historyBuffer; + + /* Some utility variable. */ + int numberOfTests = (model->numberOfSamples - NUMBER_OF_HISTORY_IMAGES); + + /* Updating. */ + uint32_t *jump = model->jump; + int *neighbor = model->neighbor; + uint32_t *position = model->position; + + /* All the frame, except the border. */ + uint32_t shift, indX, indY; + int x, y; + + for (y = 1; y < height - 1; ++y) { + shift = rand() % width; + indX = jump[shift]; // index_jump should never be zero (> 1). + + while (indX < width - 1) { + int index = indX + y * width; + + if (updating_mask[index] == COLOR_BACKGROUND) { + /* In-place substitution. */ + uint8_t r = image_data[3 * index]; + uint8_t g = image_data[3 * index + 1]; + uint8_t b = image_data[3 * index + 2]; + + int index_neighbor = 3 * (index + neighbor[shift]); + + if (position[shift] < NUMBER_OF_HISTORY_IMAGES) { + historyImage[3 * index + position[shift] * (3 * width) * height] = r; + historyImage[3 * index + position[shift] * (3 * width) * height + 1] = g; + historyImage[3 * index + position[shift] * (3 * width) * height + 2] = b; + + historyImage[index_neighbor + position[shift] * (3 * width) * height] = r; + historyImage[index_neighbor + position[shift] * (3 * width) * height + 1] = g; + historyImage[index_neighbor + position[shift] * (3 * width) * height + 2] = r; + } + else { + int pos = position[shift] - NUMBER_OF_HISTORY_IMAGES; + + historyBuffer[(3 * index) * numberOfTests + 3 * pos] = r; + historyBuffer[(3 * index) * numberOfTests + 3 * pos + 1] = g; + historyBuffer[(3 * index) * numberOfTests + 3 * pos + 2] = b; + + historyBuffer[index_neighbor * numberOfTests + 3 * pos] = r; + historyBuffer[index_neighbor * numberOfTests + 3 * pos + 1] = g; + historyBuffer[index_neighbor * numberOfTests + 3 * pos + 2] = b; + } + } + + ++shift; + indX += jump[shift]; + } + } + + /* First row. */ + y = 0; + shift = rand() % width; + indX = jump[shift]; // index_jump should never be zero (> 1). + + while (indX <= width - 1) { + int index = indX + y * width; + + uint8_t r = image_data[3 * index]; + uint8_t g = image_data[3 * index + 1]; + uint8_t b = image_data[3 * index + 2]; + + if (updating_mask[index] == COLOR_BACKGROUND) { + if (position[shift] < NUMBER_OF_HISTORY_IMAGES) { + historyImage[3 * index + position[shift] * (3 * width) * height] = r; + historyImage[3 * index + position[shift] * (3 * width) * height + 1] = g; + historyImage[3 * index + position[shift] * (3 * width) * height + 2] = b; + } + else { + int pos = position[shift] - NUMBER_OF_HISTORY_IMAGES; + + historyBuffer[(3 * index) * numberOfTests + 3 * pos] = r; + historyBuffer[(3 * index) * numberOfTests + 3 * pos + 1] = g; + historyBuffer[(3 * index) * numberOfTests + 3 * pos + 2] = b; + } + } + + ++shift; + indX += jump[shift]; + } + + /* Last row. */ + y = height - 1; + shift = rand() % width; + indX = jump[shift]; // index_jump should never be zero (> 1). + + while (indX <= width - 1) { + int index = indX + y * width; + + uint8_t r = image_data[3 * index]; + uint8_t g = image_data[3 * index + 1]; + uint8_t b = image_data[3 * index + 2]; + + if (updating_mask[index] == COLOR_BACKGROUND) { + if (position[shift] < NUMBER_OF_HISTORY_IMAGES) { + historyImage[3 * index + position[shift] * (3 * width) * height] = r; + historyImage[3 * index + position[shift] * (3 * width) * height + 1] = g; + historyImage[3 * index + position[shift] * (3 * width) * height + 2] = b; + } + else { + int pos = position[shift] - NUMBER_OF_HISTORY_IMAGES; + + historyBuffer[(3 * index) * numberOfTests + 3 * pos] = r; + historyBuffer[(3 * index) * numberOfTests + 3 * pos + 1] = g; + historyBuffer[(3 * index) * numberOfTests + 3 * pos + 2] = b; + } + } + + ++shift; + indX += jump[shift]; + } + + /* First column. */ + x = 0; + shift = rand() % height; + indY = jump[shift]; // index_jump should never be zero (> 1). + + while (indY <= height - 1) { + int index = x + indY * width; + + uint8_t r = image_data[3 * index]; + uint8_t g = image_data[3 * index + 1]; + uint8_t b = image_data[3 * index + 2]; + + if (updating_mask[index] == COLOR_BACKGROUND) { + if (position[shift] < NUMBER_OF_HISTORY_IMAGES) { + historyImage[3 * index + position[shift] * (3 * width) * height] = r; + historyImage[3 * index + position[shift] * (3 * width) * height + 1] = g; + historyImage[3 * index + position[shift] * (3 * width) * height + 2] = b; + } + else { + int pos = position[shift] - NUMBER_OF_HISTORY_IMAGES; + historyBuffer[(3 * index) * numberOfTests + 3 * pos] = r; + historyBuffer[(3 * index) * numberOfTests + 3 * pos + 1] = g; + historyBuffer[(3 * index) * numberOfTests + 3 * pos + 2] = b; + } + } + + ++shift; + indY += jump[shift]; + } + + /* Last column. */ + x = width - 1; + shift = rand() % height; + indY = jump[shift]; // index_jump should never be zero (> 1). + + while (indY <= height - 1) { + int index = x + indY * width; + + uint8_t r = image_data[3 * index]; + uint8_t g = image_data[3 * index + 1]; + uint8_t b = image_data[3 * index + 2]; + + if (updating_mask[index] == COLOR_BACKGROUND) { + if (position[shift] < NUMBER_OF_HISTORY_IMAGES) { + historyImage[3 * index + position[shift] * (3 * width) * height] = r; + historyImage[3 * index + position[shift] * (3 * width) * height + 1] = g; + historyImage[3 * index + position[shift] * (3 * width) * height + 2] = b; + } + else { + int pos = position[shift] - NUMBER_OF_HISTORY_IMAGES; + + historyBuffer[(3 * index) * numberOfTests + 3 * pos] = r; + historyBuffer[(3 * index) * numberOfTests + 3 * pos + 1] = g; + historyBuffer[(3 * index) * numberOfTests + 3 * pos + 2] = b; + } + } + + ++shift; + indY += jump[shift]; + } + + /* The first pixel! */ + if (rand() % model->updateFactor == 0) { + if (updating_mask[0] == 0) { + int position = rand() % model->numberOfSamples; + + uint8_t r = image_data[0]; + uint8_t g = image_data[1]; + uint8_t b = image_data[2]; + + if (position < NUMBER_OF_HISTORY_IMAGES) { + historyImage[position * (3 * width) * height] = r; + historyImage[position * (3 * width) * height + 1] = g; + historyImage[position * (3 * width) * height + 2] = b; + } + else { + int pos = position - NUMBER_OF_HISTORY_IMAGES; + + historyBuffer[3 * pos] = r; + historyBuffer[3 * pos + 1] = g; + historyBuffer[3 * pos + 2] = b; + } + } + } + + return(0); +} diff --git a/package_bgs/ViBe/vibe-background-sequential.h b/package_bgs/ViBe/vibe-background-sequential.h new file mode 100644 index 0000000..068c7d1 --- /dev/null +++ b/package_bgs/ViBe/vibe-background-sequential.h @@ -0,0 +1,293 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +/** + @file vibe-background-sequential.h + @brief Interface for the ViBe library + @author Marc Van Droogenbroeck + @date July 2014 + @details + + Full documentation is available online at: + http://www.ulg.ac.be/telecom/research/vibe/doc + + All technical details are available in the following paper: +O. Barnich and M. Van Droogenbroeck. ViBe: A universal background subtraction algorithm for video sequences. IEEE Transactions on Image Processing, 20(6):1709-1724, June 2011. + +\verbatim +BiBTeX information + + @article{Barnich2011ViBe, + title = {{ViBe}: A universal background subtraction algorithm for video sequences}, + author = {O. Barnich and M. {Van Droogenbroeck}}, + journal = {IEEE Transactions on Image Processing}, + volume = {20}, + number = {6}, + pages = {1709-1724}, + month = {June}, + year = {2011}, + keywords = {ViBe, Background, Background subtraction, Segmentation, Motion, Motion detection}, + pdf = {http://orbi.ulg.ac.be/bitstream/2268/145853/1/Barnich2011ViBe.pdf}, + doi = {10.1109/TIP.2010.2101613}, + url = {http://hdl.handle.net/2268/145853} + } +\endverbatim +See +\cite Barnich2011ViBe +*/ +#pragma once + +#include +#include +#include +#include + +#define COLOR_BACKGROUND 0 /*!< Default label for background pixels */ +#define COLOR_FOREGROUND 255 /*!< Default label for foreground pixels. Note that some authors chose any value different from 0 instead */ + +/** + * \typedef struct vibeModel_Sequential_t + * \brief Data structure for the background subtraction model. + * + * This data structure contains the background model as well as some paramaters value. + * The code is designed to hide all the implementation details to the user to ease its use. + */ +typedef struct vibeModel_Sequential vibeModel_Sequential_t; + +/** + * Allocation of a new data structure where the background model will be stored. + * Please note that this function only creates the structure to host the data. + * This data structures will only be filled with a call to \ref libvibeModel_Sequential_AllocInit_8u_C1R. + * + * \result A pointer to a newly allocated \ref vibeModel_Sequential_t + * structure, or NULL in the case of an error. + */ +vibeModel_Sequential_t *libvibeModel_Sequential_New(); + +/** + * ViBe uses several parameters. + * You can print and change some of them if you want. However, default + * value should meet your needs for most videos. + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @return + */ +uint32_t libvibeModel_Sequential_PrintParameters(const vibeModel_Sequential_t *model); + +/** + * Setter. + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @param numberOfSamples + * @return + */ +int32_t libvibeModel_Sequential_SetNumberOfSamples( + vibeModel_Sequential_t *model, + const uint32_t numberOfSamples +); + +/** + * Setter. + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @return + */ +uint32_t libvibeModel_Sequential_GetNumberOfSamples(const vibeModel_Sequential_t *model); + +/** + * Setter. + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @param matchingThreshold + * @return + */ +int32_t libvibeModel_Sequential_SetMatchingThreshold( + vibeModel_Sequential_t *model, + const uint32_t matchingThreshold +); + +/** + * Setter. + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @return + */ +uint32_t libvibeModel_Sequential_GetMatchingThreshold(const vibeModel_Sequential_t *model); + +/** + * Setter. + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @param matchingNumber + * @return + */ +int32_t libvibeModel_Sequential_SetMatchingNumber( + vibeModel_Sequential_t *model, + const uint32_t matchingNumber +); + +/** + * Setter. + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @param updateFactor New value for the update factor. Please note that the update factor is to be understood as a probability of updating. More specifically, an update factor of 16 means that 1 out of every 16 background pixels is updated. Likewise, an update factor of 1 means that every background pixel is updated. + * @return + */ +int32_t libvibeModel_Sequential_SetUpdateFactor( + vibeModel_Sequential_t *model, + const uint32_t updateFactor +); + +/** + * Getter. + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @return + */ +uint32_t libvibeModel_Sequential_GetMatchingNumber(const vibeModel_Sequential_t *model); + + +/** + * Getter. + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @return + */ +uint32_t libvibeModel_Sequential_GetUpdateFactor(const vibeModel_Sequential_t *model); + +/** + * \brief Frees all the memory used by the model and deallocates the structure. + * + * This function frees all the memory allocated by \ref libvibeModel_SequentialNew and + * \ref libvibeModel_Sequential_AllocInit_8u_C1R or \ref libvibeModel_Sequential_AllocInit_8u_C3R. + * @param model The data structure with ViBe's background subtraction model and parameters. + * @return + */ +int32_t libvibeModel_Sequential_Free(vibeModel_Sequential_t *model); + +/** + * The two following functions allocate the required memory according to the + * model parameters and the dimensions of the input images. + * You must use the "C1R" function for grayscale images and the "C3R" for color + * images. + * These 2 functions also initialize the background model using the content + * of *image_data which is the pixel buffer of the first image of your stream. + */ + // ------------------------- Single channel images ---------------------------- + /** + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @param image_data + * @param width + * @param height + * @return + */ +int32_t libvibeModel_Sequential_AllocInit_8u_C1R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + const uint32_t width, + const uint32_t height +); + +/* These 2 functions perform 2 operations: + * - they classify the pixels *image_data using the provided model and store + * the results in *segmentation_map. + * - they update *model according to these results and the content of + * *image_data. + * You must use the "C1R" function for grayscale images and the "C3R" for color + * images. + */ + /** + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @param image_data + * @param segmentation_map + * @return + */ +int32_t libvibeModel_Sequential_Segmentation_8u_C1R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + uint8_t *segmentation_map +); + +/** + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @param image_data + * @param updating_mask + * @return + */ +int32_t libvibeModel_Sequential_Update_8u_C1R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + uint8_t *updating_mask +); + +// ------------------------- Three channel images ----------------------------- +/** + * The pixel values of color images are arranged in the following order + * RGBRGBRGB... (or HSVHSVHSVHSVHSVHSV...) + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @param image_data + * @param width + * @param height + * @return + */ +int32_t libvibeModel_Sequential_AllocInit_8u_C3R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + const uint32_t width, + const uint32_t height +); + +/* These 2 functions perform 2 operations: + * - they classify the pixels *image_data using the provided model and store + * the results in *segmentation_map. + * - they update *model according to these results and the content of + * *image_data. + * You must use the "C1R" function for grayscale images and the "C3R" for color + * images. + */ + /** + * The pixel values of color images are arranged in the following order + * RGBRGBRGB... (or HSVHSVHSVHSVHSVHSV...) + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @param image_data + * @param segmentation_map + * @return + */ +int32_t libvibeModel_Sequential_Segmentation_8u_C3R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + uint8_t *segmentation_map +); + +/** + * The pixel values of color images are arranged in the following order + * RGBRGBRGB... (or HSVHSVHSVHSVHSVHSV...) + * + * @param model The data structure with ViBe's background subtraction model and parameters. + * @param image_data + * @param updating_mask + * @return + */ +int32_t libvibeModel_Sequential_Update_8u_C3R( + vibeModel_Sequential_t *model, + const uint8_t *image_data, + uint8_t *updating_mask +); diff --git a/package_bgs/av/VuMeter.cpp b/package_bgs/VuMeter.cpp similarity index 52% rename from package_bgs/av/VuMeter.cpp rename to package_bgs/VuMeter.cpp index 29a1477..99baee9 100644 --- a/package_bgs/av/VuMeter.cpp +++ b/package_bgs/VuMeter.cpp @@ -16,9 +16,13 @@ along with BGSLibrary. If not, see . */ #include "VuMeter.h" -VuMeter::VuMeter() : firstTime(true), showOutput(true), enableFilter(true), binSize(8), alpha(0.995), threshold(0.03) +using namespace bgslibrary::algorithms; + +VuMeter::VuMeter() : + enableFilter(true), binSize(8), alpha(0.995), threshold(0.03) { std::cout << "VuMeter()" << std::endl; + setup("./config/VuMeter.xml"); } VuMeter::~VuMeter() @@ -32,69 +36,62 @@ VuMeter::~VuMeter() void VuMeter::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - else - frame = new IplImage(img_input); - - loadConfig(); + init(img_input, img_output, img_bgmodel); + frame = new IplImage(img_input); - if(firstTime) + if (firstTime) { bgs.SetAlpha(alpha); bgs.SetBinSize(binSize); bgs.SetThreshold(threshold); - gray = cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U,1); - cvCvtColor(frame,gray,CV_RGB2GRAY); + gray = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 1); + cvCvtColor(frame, gray, CV_RGB2GRAY); - background = cvCreateImage(cvGetSize(gray),IPL_DEPTH_8U,1); + background = cvCreateImage(cvGetSize(gray), IPL_DEPTH_8U, 1); cvCopy(gray, background); - mask = cvCreateImage(cvGetSize(gray),IPL_DEPTH_8U,1); + mask = cvCreateImage(cvGetSize(gray), IPL_DEPTH_8U, 1); cvZero(mask); - - saveConfig(); } else - cvCvtColor(frame,gray,CV_RGB2GRAY); - - bgs.UpdateBackground(gray,background,mask); - cv::Mat img_foreground(mask); - cv::Mat img_bkg(background); + cvCvtColor(frame, gray, CV_RGB2GRAY); + + bgs.UpdateBackground(gray, background, mask); + img_foreground = cv::cvarrToMat(mask); + img_background = cv::cvarrToMat(background); - if(enableFilter) + if (enableFilter) { - cv::erode(img_foreground,img_foreground,cv::Mat()); + cv::erode(img_foreground, img_foreground, cv::Mat()); cv::medianBlur(img_foreground, img_foreground, 5); } - if(showOutput) +#ifndef MEX_COMPILE_FLAG + if (showOutput) { - if(!img_foreground.empty()) - cv::imshow("VuMeter", img_foreground); - - if(!img_bkg.empty()) - cv::imshow("VuMeter Bkg Model", img_bkg); + cv::imshow("VuMeter", img_foreground); + cv::imshow("VuMeter Bkg Model", img_background); } +#endif img_foreground.copyTo(img_output); - img_bkg.copyTo(img_bgmodel); - + img_background.copyTo(img_bgmodel); + delete frame; firstTime = false; } void VuMeter::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/VuMeter.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "enableFilter", enableFilter); - + cvWriteInt(fs, "binSize", binSize); cvWriteReal(fs, "alpha", alpha); cvWriteReal(fs, "threshold", threshold); - + cvWriteInt(fs, "showOutput", showOutput); cvReleaseFileStorage(&fs); @@ -102,15 +99,15 @@ void VuMeter::saveConfig() void VuMeter::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/VuMeter.xml", 0, CV_STORAGE_READ); - - enableFilter = cvReadIntByName(fs, 0, "enableFilter", true); - - binSize = cvReadIntByName(fs, 0, "binSize", 8); - alpha = cvReadRealByName(fs, 0, "alpha", 0.995); - threshold = cvReadRealByName(fs, 0, "threshold", 0.03); - - showOutput = cvReadIntByName(fs, 0, "showOutput", true); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + enableFilter = cvReadIntByName(fs, nullptr, "enableFilter", true); + + binSize = cvReadIntByName(fs, nullptr, "binSize", 8); + alpha = cvReadRealByName(fs, nullptr, "alpha", 0.995); + threshold = cvReadRealByName(fs, nullptr, "threshold", 0.03); + + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); } diff --git a/package_bgs/VuMeter.h b/package_bgs/VuMeter.h new file mode 100644 index 0000000..fefd3ec --- /dev/null +++ b/package_bgs/VuMeter.h @@ -0,0 +1,52 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" +#include "VuMeter/TBackgroundVuMeter.h" + +namespace bgslibrary +{ + namespace algorithms + { + class VuMeter : public IBGS + { + private: + TBackgroundVuMeter bgs; + + IplImage *frame; + IplImage *gray; + IplImage *background; + IplImage *mask; + + bool enableFilter; + int binSize; + double alpha; + double threshold; + + public: + VuMeter(); + ~VuMeter(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/av/TBackground.cpp b/package_bgs/VuMeter/TBackground.cpp similarity index 77% rename from package_bgs/av/TBackground.cpp rename to package_bgs/VuMeter/TBackground.cpp index d05bbd2..4e93729 100644 --- a/package_bgs/av/TBackground.cpp +++ b/package_bgs/VuMeter/TBackground.cpp @@ -72,19 +72,19 @@ bool TBackground::isInitOk(IplImage * pSource, IplImage *pBackground, IplImage * { bool bResult = true; - if(pSource == NULL || pSource->nChannels != 1 || pSource->depth != IPL_DEPTH_8U) + if (pSource == NULL || pSource->nChannels != 1 || pSource->depth != IPL_DEPTH_8U) bResult = false; - if(bResult) + if (bResult) { int nbl, nbc; nbl = pSource->height; nbc = pSource->width; - - if(pBackground == NULL || pBackground->width != nbc || pBackground->height != nbl || pBackground->imageSize != pSource->imageSize) + + if (pBackground == NULL || pBackground->width != nbc || pBackground->height != nbl || pBackground->imageSize != pSource->imageSize) bResult = false; - - if(pMotionMask == NULL || pMotionMask->width != nbc || pMotionMask->height != nbl || pMotionMask->imageSize != pSource->imageSize) + + if (pMotionMask == NULL || pMotionMask->width != nbc || pMotionMask->height != nbl || pMotionMask->imageSize != pSource->imageSize) bResult = false; } @@ -100,7 +100,7 @@ IplImage *TBackground::CreateTestImg() { IplImage *pImage = cvCreateImage(cvSize(256, 256), IPL_DEPTH_8U, 3); - if(pImage != NULL) + if (pImage != NULL) cvSetZero(pImage); return pImage; @@ -112,33 +112,33 @@ int TBackground::UpdateTest(IplImage *pSource, IplImage *pBackground, IplImage * CvScalar Color; unsigned char *ptr; - if(pTest == NULL || !isInitOk(pSource, pBackground, pSource)) + if (pTest == NULL || !isInitOk(pSource, pBackground, pSource)) nErr = 1; - if(!nErr) + if (!nErr) { - if(pTest->width != 256 || pTest->height != 256 || pTest->nChannels != 3) + if (pTest->width != 256 || pTest->height != 256 || pTest->nChannels != 3) nErr = 1; - if(nX < 0 || nX > pSource->width || nY < 0 || nY > pSource->height) + if (nX < 0 || nX > pSource->width || nY < 0 || nY > pSource->height) nErr = 1; - switch(nInd) + switch (nInd) { - case 0 : Color = cvScalar(128, 0, 0); break; - case 1 : Color = cvScalar(0, 128, 0); break; - case 2 : Color = cvScalar(0, 0, 128); break; - default : nErr = 1; + case 0: Color = cvScalar(128, 0, 0); break; + case 1: Color = cvScalar(0, 128, 0); break; + case 2: Color = cvScalar(0, 0, 128); break; + default: nErr = 1; } } - if(!nErr) + if (!nErr) { int l, c; // recupere l'indice de la colonne ptr = (unsigned char *)(pTest->imageData); c = *ptr; - + // efface la colonne cvLine(pTest, cvPoint(c, 0), cvPoint(c, 255), cvScalar(0)); *ptr += 1; diff --git a/package_bgs/av/TBackground.h b/package_bgs/VuMeter/TBackground.h similarity index 99% rename from package_bgs/av/TBackground.h rename to package_bgs/VuMeter/TBackground.h index 463063f..dbaaa6c 100644 --- a/package_bgs/av/TBackground.h +++ b/package_bgs/VuMeter/TBackground.h @@ -26,7 +26,6 @@ along with BGSLibrary. If not, see . #include #include - class TBackground { public: diff --git a/package_bgs/av/TBackgroundVuMeter.cpp b/package_bgs/VuMeter/TBackgroundVuMeter.cpp similarity index 74% rename from package_bgs/av/TBackgroundVuMeter.cpp rename to package_bgs/VuMeter/TBackgroundVuMeter.cpp index 7fade7a..c4fd7a7 100644 --- a/package_bgs/av/TBackgroundVuMeter.cpp +++ b/package_bgs/VuMeter/TBackgroundVuMeter.cpp @@ -46,11 +46,11 @@ void TBackgroundVuMeter::Clear(void) { TBackground::Clear(); - if(m_pHist != NULL) + if (m_pHist != NULL) { - for(int i = 0; i < m_nBinCount; ++i) + for (int i = 0; i < m_nBinCount; ++i) { - if(m_pHist[i] != NULL) + if (m_pHist[i] != NULL) cvReleaseImage(&m_pHist[i]); } @@ -68,14 +68,14 @@ void TBackgroundVuMeter::Reset(void) TBackground::Reset(); - if(m_pHist != NULL) + if (m_pHist != NULL) { // fVal = (m_nBinCount != 0) ? (float)(1.0 / (double)m_nBinCount) : (float)0.0; fVal = 0.0; - for(int i = 0; i < m_nBinCount; ++i) + for (int i = 0; i < m_nBinCount; ++i) { - if(m_pHist[i] != NULL) + if (m_pHist[i] != NULL) { cvSetZero(m_pHist[i]); cvAddS(m_pHist[i], cvScalar(fVal), m_pHist[i]); @@ -98,18 +98,18 @@ std::string TBackgroundVuMeter::GetParameterName(int nInd) nNb = TBackground::GetParameterCount(); - if(nInd >= nNb) + if (nInd >= nNb) { nInd -= nNb; - switch(nInd) + switch (nInd) { - case 0 : csResult = "Bin size"; break; - case 1 : csResult = "Alpha"; break; - case 2 : csResult = "Threshold"; break; + case 0: csResult = "Bin size"; break; + case 1: csResult = "Alpha"; break; + case 2: csResult = "Threshold"; break; } } - else + else csResult = TBackground::GetParameterName(nInd); return csResult; @@ -122,22 +122,22 @@ std::string TBackgroundVuMeter::GetParameterValue(int nInd) nNb = TBackground::GetParameterCount(); - if(nInd >= nNb) + if (nInd >= nNb) { nInd -= nNb; char buff[100]; - - switch(nInd) + + switch (nInd) { - case 0 : sprintf(buff, "%d", m_nBinSize); break; - case 1 : sprintf(buff, "%.3f", m_fAlpha); break; - case 2 : sprintf(buff, "%.2f", m_fThreshold); break; + case 0: sprintf(buff, "%d", m_nBinSize); break; + case 1: sprintf(buff, "%.3f", m_fAlpha); break; + case 2: sprintf(buff, "%.2f", m_fThreshold); break; } csResult = buff; } - else + else csResult = TBackground::GetParameterValue(nInd); return csResult; @@ -151,19 +151,19 @@ int TBackgroundVuMeter::SetParameterValue(int nInd, std::string csNew) nNb = TBackground::GetParameterCount(); - if(nInd >= nNb) + if (nInd >= nNb) { nInd -= nNb; - switch(nInd) + switch (nInd) { - case 0 : SetBinSize(atoi(csNew.c_str())); break; - case 1 : SetAlpha(atof(csNew.c_str())); break; - case 2 : SetThreshold(atof(csNew.c_str())); break; - default : nErr = 1; + case 0: SetBinSize(atoi(csNew.c_str())); break; + case 1: SetAlpha(atof(csNew.c_str())); break; + case 2: SetThreshold(atof(csNew.c_str())); break; + default: nErr = 1; } } - else + else nErr = TBackground::SetParameterValue(nInd, csNew); return nErr; @@ -178,42 +178,42 @@ int TBackgroundVuMeter::Init(IplImage * pSource) nErr = TBackground::Init(pSource); - if(pSource == NULL) + if (pSource == NULL) nErr = 1; // calcul le nb de bin - if(!nErr) + if (!nErr) { nbl = pSource->height; nbc = pSource->width; m_nBinCount = (m_nBinSize != 0) ? 256 / m_nBinSize : 0; - if(m_nBinCount <= 0 || m_nBinCount > 256) + if (m_nBinCount <= 0 || m_nBinCount > 256) nErr = 1; } // creation du tableau de pointeur - if(!nErr) + if (!nErr) { m_pHist = new IplImage *[m_nBinCount]; - if(m_pHist == NULL) + if (m_pHist == NULL) nErr = 1; } // creation des images - if(!nErr) + if (!nErr) { - for(int i = 0; i < m_nBinCount; ++i) + for (int i = 0; i < m_nBinCount; ++i) { m_pHist[i] = cvCreateImage(cvSize(nbc, nbl), IPL_DEPTH_32F, 1); - if(m_pHist[i] == NULL) + if (m_pHist[i] == NULL) nErr = 1; } } - if(!nErr) + if (!nErr) Reset(); else Clear(); @@ -228,28 +228,28 @@ bool TBackgroundVuMeter::isInitOk(IplImage * pSource, IplImage *pBackground, Ipl bResult = TBackground::isInitOk(pSource, pBackground, pMotionMask); - if(pSource == NULL) + if (pSource == NULL) bResult = false; - if(m_nBinSize == 0) + if (m_nBinSize == 0) bResult = false; - if(bResult) + if (bResult) { i = (m_nBinSize != 0) ? 256 / m_nBinSize : 0; - if(i != m_nBinCount || m_pHist == NULL) + if (i != m_nBinCount || m_pHist == NULL) bResult = false; } - if(bResult) + if (bResult) { int nbl = pSource->height; int nbc = pSource->width; - for(i = 0; i < m_nBinCount; ++i) + for (i = 0; i < m_nBinCount; ++i) { - if(m_pHist[i] == NULL || m_pHist[i]->width != nbc || m_pHist[i]->height != nbl) + if (m_pHist[i] == NULL || m_pHist[i]->width != nbc || m_pHist[i]->height != nbl) bResult = false; } } @@ -263,10 +263,10 @@ int TBackgroundVuMeter::UpdateBackground(IplImage *pSource, IplImage *pBackgroun unsigned char *ptrs, *ptrb, *ptrm; float *ptr1, *ptr2; - if(!isInitOk(pSource, pBackground, pMotionMask)) + if (!isInitOk(pSource, pBackground, pMotionMask)) nErr = Init(pSource); - - if(!nErr) + + if (!nErr) { m_nCount++; int nbc = pSource->width; @@ -274,23 +274,23 @@ int TBackgroundVuMeter::UpdateBackground(IplImage *pSource, IplImage *pBackgroun unsigned char v = m_nBinSize; // multiplie tout par alpha - for(int i = 0; i < m_nBinCount; ++i) + for (int i = 0; i < m_nBinCount; ++i) cvConvertScale(m_pHist[i], m_pHist[i], m_fAlpha, 0.0); - for(int l = 0; l < nbl; ++l) + for (int l = 0; l < nbl; ++l) { ptrs = (unsigned char *)(pSource->imageData + pSource->widthStep * l); ptrm = (unsigned char *)(pMotionMask->imageData + pMotionMask->widthStep * l); ptrb = (unsigned char *)(pBackground->imageData + pBackground->widthStep * l); - for(int c = 0; c < nbc; ++c, ptrs++, ptrb++, ptrm++) + for (int c = 0; c < nbc; ++c, ptrs++, ptrb++, ptrm++) { - // recherche le bin à augmenter + // recherche le bin à augmenter int i = *ptrs / v; - - if(i < 0 || i >= m_nBinCount) + + if (i < 0 || i >= m_nBinCount) i = 0; - + ptr1 = (float *)(m_pHist[i]->imageData + m_pHist[i]->widthStep * l); ptr1 += c; @@ -299,19 +299,19 @@ int TBackgroundVuMeter::UpdateBackground(IplImage *pSource, IplImage *pBackgroun // recherche le bin du fond actuel i = *ptrb / v; - - if(i < 0 || i >= m_nBinCount) + + if (i < 0 || i >= m_nBinCount) i = 0; - + ptr2 = (float *)(m_pHist[i]->imageData + m_pHist[i]->widthStep * l); ptr2 += c; - if(*ptr2 < *ptr1) + if (*ptr2 < *ptr1) *ptrb = *ptrs; } } - if(m_nCount < 5) + if (m_nCount < 5) cvSetZero(pMotionMask); } @@ -322,10 +322,10 @@ IplImage *TBackgroundVuMeter::CreateTestImg() { IplImage *pImage = NULL; - if(m_nBinCount > 0) + if (m_nBinCount > 0) pImage = cvCreateImage(cvSize(m_nBinCount, 100), IPL_DEPTH_8U, 3); - if(pImage != NULL) + if (pImage != NULL) cvSetZero(pImage); return pImage; @@ -336,31 +336,31 @@ int TBackgroundVuMeter::UpdateTest(IplImage *pSource, IplImage *pBackground, Ipl int nErr = 0; float *ptrf; - if(pTest == NULL || !isInitOk(pSource, pBackground, pSource)) + if (pTest == NULL || !isInitOk(pSource, pBackground, pSource)) nErr = 1; - if(!nErr) + if (!nErr) { int nbl = pTest->height; int nbc = pTest->width; - if(nbl != 100 || nbc != m_nBinCount) + if (nbl != 100 || nbc != m_nBinCount) nErr = 1; - if(nX < 0 || nX >= pSource->width || nY < 0 || nY >= pSource->height) + if (nX < 0 || nX >= pSource->width || nY < 0 || nY >= pSource->height) nErr = 1; } - if(!nErr) + if (!nErr) { cvSetZero(pTest); - for(int i = 0; i < m_nBinCount; ++i) + for (int i = 0; i < m_nBinCount; ++i) { ptrf = (float *)(m_pHist[i]->imageData + m_pHist[i]->widthStep * nY); ptrf += nX; - if(*ptrf >= 0 || *ptrf <= 1.0) { + if (*ptrf >= 0 || *ptrf <= 1.0) { cvLine(pTest, cvPoint(i, 100), cvPoint(i, (int)(100.0 * (1.0 - *ptrf))), cvScalar(0, 255, 0)); } } diff --git a/package_bgs/av/TBackgroundVuMeter.h b/package_bgs/VuMeter/TBackgroundVuMeter.h similarity index 95% rename from package_bgs/av/TBackgroundVuMeter.h rename to package_bgs/VuMeter/TBackgroundVuMeter.h index 1a19324..36fe0d0 100644 --- a/package_bgs/av/TBackgroundVuMeter.h +++ b/package_bgs/VuMeter/TBackgroundVuMeter.h @@ -44,13 +44,13 @@ class TBackgroundVuMeter : public TBackground virtual std::string GetParameterValue(int nInd); virtual int SetParameterValue(int nInd, std::string csNew); - inline void SetBinSize(int nNew) { m_nBinSize = (nNew > 0 && nNew < 255) ? nNew : 8; } + inline void SetBinSize(int nNew) { m_nBinSize = (nNew > 0 && nNew < 255) ? nNew : 8; } inline double GetBinSize() { return m_nBinSize; } - inline void SetAlpha(double fNew) { m_fAlpha = (fNew > 0.0 && fNew < 1.0) ? fNew : 0.995; } + inline void SetAlpha(double fNew) { m_fAlpha = (fNew > 0.0 && fNew < 1.0) ? fNew : 0.995; } inline double GetAlpha() { return m_fAlpha; } - inline void SetThreshold(double fNew) { m_fThreshold = (fNew > 0.0 && fNew < 1.0) ? fNew : 0.03; } + inline void SetThreshold(double fNew) { m_fThreshold = (fNew > 0.0 && fNew < 1.0) ? fNew : 0.03; } inline double GetThreshold() { return m_fThreshold; } protected: diff --git a/package_bgs/WeightedMovingMeanBGS.cpp b/package_bgs/WeightedMovingMean.cpp similarity index 51% rename from package_bgs/WeightedMovingMeanBGS.cpp rename to package_bgs/WeightedMovingMean.cpp index 841c467..3a427fc 100644 --- a/package_bgs/WeightedMovingMeanBGS.cpp +++ b/package_bgs/WeightedMovingMean.cpp @@ -14,77 +14,72 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "WeightedMovingMeanBGS.h" +#include "WeightedMovingMean.h" -WeightedMovingMeanBGS::WeightedMovingMeanBGS() : firstTime(true), enableWeight(true), enableThreshold(true), threshold(15), showOutput(true), showBackground(false) +using namespace bgslibrary::algorithms; + +WeightedMovingMean::WeightedMovingMean() : + enableWeight(true), enableThreshold(true), threshold(15) { - std::cout << "WeightedMovingMeanBGS()" << std::endl; + std::cout << "WeightedMovingMean()" << std::endl; + setup("./config/WeightedMovingMean.xml"); } -WeightedMovingMeanBGS::~WeightedMovingMeanBGS() +WeightedMovingMean::~WeightedMovingMean() { - std::cout << "~WeightedMovingMeanBGS()" << std::endl; + std::cout << "~WeightedMovingMean()" << std::endl; } -void WeightedMovingMeanBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void WeightedMovingMean::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); + init(img_input, img_output, img_bgmodel); - if(img_input_prev_1.empty()) - { + if (img_input_prev_1.empty()) { img_input.copyTo(img_input_prev_1); return; } - if(img_input_prev_2.empty()) - { + if (img_input_prev_2.empty()) { img_input_prev_1.copyTo(img_input_prev_2); img_input.copyTo(img_input_prev_1); return; } - + cv::Mat img_input_f(img_input.size(), CV_32F); - img_input.convertTo(img_input_f, CV_32F, 1./255.); + img_input.convertTo(img_input_f, CV_32F, 1. / 255.); cv::Mat img_input_prev_1_f(img_input.size(), CV_32F); - img_input_prev_1.convertTo(img_input_prev_1_f, CV_32F, 1./255.); + img_input_prev_1.convertTo(img_input_prev_1_f, CV_32F, 1. / 255.); cv::Mat img_input_prev_2_f(img_input.size(), CV_32F); - img_input_prev_2.convertTo(img_input_prev_2_f, CV_32F, 1./255.); + img_input_prev_2.convertTo(img_input_prev_2_f, CV_32F, 1. / 255.); cv::Mat img_background_f(img_input.size(), CV_32F); - - if(enableWeight) + + if (enableWeight) img_background_f = ((img_input_f * 0.5) + (img_input_prev_1_f * 0.3) + (img_input_prev_2_f * 0.2)); else - img_background_f = ((img_input_f) + (img_input_prev_1_f) + (img_input_prev_2_f)) / 3.0; - - cv::Mat img_background(img_background_f.size(), CV_8U); + img_background_f = ((img_input_f)+(img_input_prev_1_f)+(img_input_prev_2_f)) / 3.0; double minVal, maxVal; minVal = 0.; maxVal = 1.; - img_background_f.convertTo(img_background, CV_8U, 255.0/(maxVal - minVal), -minVal); - - if(showBackground) - cv::imshow("W Moving Mean BG Model", img_background); + img_background_f.convertTo(img_background, CV_8U, 255.0 / (maxVal - minVal), -minVal); - cv::Mat img_foreground; cv::absdiff(img_input, img_background, img_foreground); - if(img_foreground.channels() == 3) + if (img_foreground.channels() == 3) cv::cvtColor(img_foreground, img_foreground, CV_BGR2GRAY); - if(enableThreshold) + if (enableThreshold) cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY); - if(showOutput) +#ifndef MEX_COMPILE_FLAG + if (showOutput) + { cv::imshow("W Moving Mean FG Mask", img_foreground); + cv::imshow("W Moving Mean BG Model", img_background); + } +#endif img_foreground.copyTo(img_output); img_background.copyTo(img_bgmodel); @@ -95,28 +90,26 @@ void WeightedMovingMeanBGS::process(const cv::Mat &img_input, cv::Mat &img_outpu firstTime = false; } -void WeightedMovingMeanBGS::saveConfig() +void WeightedMovingMean::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/WeightedMovingMeanBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "enableWeight", enableWeight); cvWriteInt(fs, "enableThreshold", enableThreshold); cvWriteInt(fs, "threshold", threshold); cvWriteInt(fs, "showOutput", showOutput); - cvWriteInt(fs, "showBackground", showBackground); cvReleaseFileStorage(&fs); } -void WeightedMovingMeanBGS::loadConfig() +void WeightedMovingMean::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/WeightedMovingMeanBGS.xml", 0, CV_STORAGE_READ); - - enableWeight = cvReadIntByName(fs, 0, "enableWeight", true); - enableThreshold = cvReadIntByName(fs, 0, "enableThreshold", true); - threshold = cvReadIntByName(fs, 0, "threshold", 15); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - showBackground = cvReadIntByName(fs, 0, "showBackground", false); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + enableWeight = cvReadIntByName(fs, nullptr, "enableWeight", true); + enableThreshold = cvReadIntByName(fs, nullptr, "enableThreshold", true); + threshold = cvReadIntByName(fs, nullptr, "threshold", 15); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); cvReleaseFileStorage(&fs); -} \ No newline at end of file +} diff --git a/package_bgs/WeightedMovingMean.h b/package_bgs/WeightedMovingMean.h new file mode 100644 index 0000000..4a1a3c5 --- /dev/null +++ b/package_bgs/WeightedMovingMean.h @@ -0,0 +1,45 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" + +namespace bgslibrary +{ + namespace algorithms + { + class WeightedMovingMean : public IBGS + { + private: + cv::Mat img_input_prev_1; + cv::Mat img_input_prev_2; + bool enableWeight; + bool enableThreshold; + int threshold; + + public: + WeightedMovingMean(); + ~WeightedMovingMean(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/WeightedMovingVarianceBGS.cpp b/package_bgs/WeightedMovingVariance.cpp similarity index 62% rename from package_bgs/WeightedMovingVarianceBGS.cpp rename to package_bgs/WeightedMovingVariance.cpp index 0317a3c..2855a92 100644 --- a/package_bgs/WeightedMovingVarianceBGS.cpp +++ b/package_bgs/WeightedMovingVariance.cpp @@ -14,68 +14,61 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -#include "WeightedMovingVarianceBGS.h" +#include "WeightedMovingVariance.h" -WeightedMovingVarianceBGS::WeightedMovingVarianceBGS() : firstTime(true), enableWeight(true), - enableThreshold(true), threshold(15), showOutput(true) +using namespace bgslibrary::algorithms; + +WeightedMovingVariance::WeightedMovingVariance() : + enableWeight(true), enableThreshold(true), threshold(15) { - std::cout << "WeightedMovingVarianceBGS()" << std::endl; + std::cout << "WeightedMovingVariance()" << std::endl; + setup("./config/WeightedMovingVariance.xml"); } -WeightedMovingVarianceBGS::~WeightedMovingVarianceBGS() +WeightedMovingVariance::~WeightedMovingVariance() { - std::cout << "~WeightedMovingVarianceBGS()" << std::endl; + std::cout << "~WeightedMovingVariance()" << std::endl; } -void WeightedMovingVarianceBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +void WeightedMovingVariance::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) { - if(img_input.empty()) - return; - - loadConfig(); + init(img_input, img_output, img_bgmodel); - if(firstTime) - saveConfig(); - - if(img_input_prev_1.empty()) - { + if (img_input_prev_1.empty()) { img_input.copyTo(img_input_prev_1); return; } - if(img_input_prev_2.empty()) - { + if (img_input_prev_2.empty()) { img_input_prev_1.copyTo(img_input_prev_2); img_input.copyTo(img_input_prev_1); return; } cv::Mat img_input_f(img_input.size(), CV_32F); - img_input.convertTo(img_input_f, CV_32F, 1./255.); + img_input.convertTo(img_input_f, CV_32F, 1. / 255.); cv::Mat img_input_prev_1_f(img_input.size(), CV_32F); - img_input_prev_1.convertTo(img_input_prev_1_f, CV_32F, 1./255.); + img_input_prev_1.convertTo(img_input_prev_1_f, CV_32F, 1. / 255.); cv::Mat img_input_prev_2_f(img_input.size(), CV_32F); - img_input_prev_2.convertTo(img_input_prev_2_f, CV_32F, 1./255.); - - cv::Mat img_foreground; + img_input_prev_2.convertTo(img_input_prev_2_f, CV_32F, 1. / 255.); // Weighted mean cv::Mat img_mean_f(img_input.size(), CV_32F); - - if(enableWeight) + + if (enableWeight) img_mean_f = ((img_input_f * 0.5) + (img_input_prev_1_f * 0.3) + (img_input_prev_2_f * 0.2)); else img_mean_f = ((img_input_f * 0.3) + (img_input_prev_1_f * 0.3) + (img_input_prev_2_f * 0.3)); - + // Weighted variance cv::Mat img_1_f(img_input.size(), CV_32F); cv::Mat img_2_f(img_input.size(), CV_32F); cv::Mat img_3_f(img_input.size(), CV_32F); cv::Mat img_4_f(img_input.size(), CV_32F); - if(enableWeight) + if (enableWeight) { img_1_f = computeWeightedVariance(img_input_f, img_mean_f, 0.5); img_2_f = computeWeightedVariance(img_input_prev_1_f, img_mean_f, 0.3); @@ -89,73 +82,71 @@ void WeightedMovingVarianceBGS::process(const cv::Mat &img_input, cv::Mat &img_o img_3_f = computeWeightedVariance(img_input_prev_2_f, img_mean_f, 0.3); img_4_f = (img_1_f + img_2_f + img_3_f); } - + // Standard deviation cv::Mat img_sqrt_f(img_input.size(), CV_32F); cv::sqrt(img_4_f, img_sqrt_f); cv::Mat img_sqrt(img_input.size(), CV_8U); double minVal, maxVal; minVal = 0.; maxVal = 1.; - img_sqrt_f.convertTo(img_sqrt, CV_8U, 255.0/(maxVal - minVal), -minVal); + img_sqrt_f.convertTo(img_sqrt, CV_8U, 255.0 / (maxVal - minVal), -minVal); img_sqrt.copyTo(img_foreground); - if(img_foreground.channels() == 3) + if (img_foreground.channels() == 3) cv::cvtColor(img_foreground, img_foreground, CV_BGR2GRAY); - if(enableThreshold) + if (enableThreshold) cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY); - if(showOutput) +#ifndef MEX_COMPILE_FLAG + if (showOutput) cv::imshow("W Moving Variance", img_foreground); +#endif img_foreground.copyTo(img_output); + img_background = cv::Mat::zeros(img_input.size(), img_input.type()); + img_background.copyTo(img_bgmodel); + img_input_prev_1.copyTo(img_input_prev_2); img_input.copyTo(img_input_prev_1); - - firstTime = false; -} -//unused -cv::Mat WeightedMovingVarianceBGS::computeWeightedMean(const std::vector &v_img_input_f, const std::vector &weights) -{ - cv::Mat img; - return img; + firstTime = false; } -cv::Mat WeightedMovingVarianceBGS::computeWeightedVariance(const cv::Mat &img_input_f, const cv::Mat &img_mean_f, const double weight) +cv::Mat WeightedMovingVariance::computeWeightedVariance(const cv::Mat &img_input_f, const cv::Mat &img_mean_f, const double weight) { //ERROR in return (weight * ((cv::abs(img_input_f - img_mean_f))^2.)); - + cv::Mat img_f_absdiff(img_input_f.size(), CV_32F); cv::absdiff(img_input_f, img_mean_f, img_f_absdiff); cv::Mat img_f_pow(img_input_f.size(), CV_32F); cv::pow(img_f_absdiff, 2.0, img_f_pow); cv::Mat img_f = weight * img_f_pow; - + return img_f; } -void WeightedMovingVarianceBGS::saveConfig() +void WeightedMovingVariance::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/WeightedMovingVarianceBGS.xml", 0, CV_STORAGE_WRITE); + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); cvWriteInt(fs, "enableWeight", enableWeight); cvWriteInt(fs, "enableThreshold", enableThreshold); cvWriteInt(fs, "threshold", threshold); cvWriteInt(fs, "showOutput", showOutput); - + cvReleaseFileStorage(&fs); } -void WeightedMovingVarianceBGS::loadConfig() +void WeightedMovingVariance::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/WeightedMovingVarianceBGS.xml", 0, CV_STORAGE_READ); - - enableWeight = cvReadIntByName(fs, 0, "enableWeight", true); - enableThreshold = cvReadIntByName(fs, 0, "enableThreshold", true); - threshold = cvReadIntByName(fs, 0, "threshold", 15); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + enableWeight = cvReadIntByName(fs, nullptr, "enableWeight", true); + enableThreshold = cvReadIntByName(fs, nullptr, "enableThreshold", true); + threshold = cvReadIntByName(fs, nullptr, "threshold", 15); + showOutput = cvReadIntByName(fs, nullptr, "showOutput", true); + cvReleaseFileStorage(&fs); } diff --git a/package_bgs/WeightedMovingVariance.h b/package_bgs/WeightedMovingVariance.h new file mode 100644 index 0000000..79198b4 --- /dev/null +++ b/package_bgs/WeightedMovingVariance.h @@ -0,0 +1,46 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "IBGS.h" + +namespace bgslibrary +{ + namespace algorithms + { + class WeightedMovingVariance : public IBGS + { + private: + cv::Mat img_input_prev_1; + cv::Mat img_input_prev_2; + bool enableWeight; + bool enableThreshold; + int threshold; + + public: + WeightedMovingVariance(); + ~WeightedMovingVariance(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + cv::Mat computeWeightedVariance(const cv::Mat &img_input_f, const cv::Mat &img_mean_f, const double weight); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/WeightedMovingVarianceBGS.h b/package_bgs/WeightedMovingVarianceBGS.h deleted file mode 100644 index 88c2ec8..0000000 --- a/package_bgs/WeightedMovingVarianceBGS.h +++ /dev/null @@ -1,48 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#include -#include - - -#include "IBGS.h" - -class WeightedMovingVarianceBGS : public IBGS -{ -private: - bool firstTime; - cv::Mat img_input_prev_1; - cv::Mat img_input_prev_2; - bool enableWeight; - bool enableThreshold; - int threshold; - bool showOutput; - -public: - WeightedMovingVarianceBGS(); - ~WeightedMovingVarianceBGS(); - - cv::Mat computeWeightedMean(const std::vector &v_img_input_f, const std::vector &weights); - cv::Mat computeWeightedVariance(const cv::Mat &img_input_f, const cv::Mat &img_mean_f, const double weight); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; diff --git a/package_bgs/_template_/Amber.cpp b/package_bgs/_template_/Amber.cpp new file mode 100644 index 0000000..2e29ac0 --- /dev/null +++ b/package_bgs/_template_/Amber.cpp @@ -0,0 +1,112 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ + +#include "Amber.h" + +using namespace bgslibrary::algorithms; + +Amber::Amber() : model(nullptr) +{ + std::cout << "Amber()" << std::endl; + /* Initialization of the Amber model */ + model = libamberModelNew(); + setup("./config/Amber.xml"); +} + +Amber::~Amber() +{ + std::cout << "~Amber()" << std::endl; + libamberModelFree(model); +} + +void Amber::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + init(img_input, img_output, img_bgmodel); + + if (img_input.empty()) + return; + + // Start the initialization + unsigned int width = img_input.cols; + unsigned int height = img_input.rows; + + if (img_input.channels() != 3) + { + std::cout << "Only works for 3 channels images. Sorry for that" << std::endl; + return; + } + + unsigned int stride = width * 3; + unsigned char* image = static_cast(img_input.data); + + if (firstTime) + { + /* Create a buffer for the output image */ + //img_output = Mat(img_input.rows, img_input.cols, CV_8UC1); + + /* Sets default model values */ + // libamberModelSetNumberOfSamples(model, nbSamples); + // libamberModelSetMatchingThreshold(model, matchingThreshold); + // libamberModelSetMatchingNumber(model, matchingNumber); + // libamberModelSetUpdateFactor(model, init_subsamplingFactor); + // libamberModelPrintParameters(model); + + /* Initiliazes the Amber model */ + libamberModelAllocInit_8u_C3R(model, image, width, height); + } + + /* Create temporary buffers */ + unsigned char* output_segmentationMap = static_cast(calloc(width * height, sizeof(unsigned char))); + libamberGetSegmentation_8u_C3R(model, image, output_segmentationMap); + + unsigned char* oBuffer = static_cast(img_output.data); + unsigned char* tmpSegmentationMap = output_segmentationMap; + + for (int i = 0; i < width * height; i++) + { + *oBuffer = *tmpSegmentationMap; + + ++oBuffer; + ++tmpSegmentationMap; + } + +#ifndef MEX_COMPILE_FLAG + if (showOutput) + imshow("Amber", img_output); +#endif + + firstTime = false; + free(output_segmentationMap); +} + +void Amber::saveConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_WRITE); + + cvWriteInt(fs, "showOutput", showOutput); + + cvReleaseFileStorage(&fs); +} + +void Amber::loadConfig() +{ + CvFileStorage* fs = cvOpenFileStorage(config_xml.c_str(), nullptr, CV_STORAGE_READ); + + showOutput = cvReadIntByName(fs, nullptr, "showOutput", false); + + cvReleaseFileStorage(&fs); +} diff --git a/package_bgs/_template_/Amber.h b/package_bgs/_template_/Amber.h new file mode 100644 index 0000000..37f0f8d --- /dev/null +++ b/package_bgs/_template_/Amber.h @@ -0,0 +1,45 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include +#include + +#include "../IBGS.h" +#include "amber/amber.h" + +namespace bgslibrary +{ + namespace algorithms + { + class Amber : public IBGS + { + private: + amberModel* model; + + public: + Amber(); + ~Amber(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig(); + void loadConfig(); + }; + } +} diff --git a/package_bgs/_template_/MyBGS.cpp b/package_bgs/_template_/MyBGS.cpp new file mode 100644 index 0000000..10d5588 --- /dev/null +++ b/package_bgs/_template_/MyBGS.cpp @@ -0,0 +1,44 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#include "MyBGS.h" + +using namespace bgslibrary::algorithms; + +MyBGS::MyBGS() {} +MyBGS::~MyBGS() {} + +void MyBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) +{ + if (img_input.empty()) + return; + + if (img_previous.empty()) + img_input.copyTo(img_previous); + + cv::Mat img_foreground; + cv::absdiff(img_previous, img_input, img_foreground); + + if (img_foreground.channels() == 3) + cv::cvtColor(img_foreground, img_foreground, CV_BGR2GRAY); + + cv::threshold(img_foreground, img_foreground, 15, 255, cv::THRESH_BINARY); + + img_foreground.copyTo(img_output); + img_previous.copyTo(img_bgmodel); + + img_input.copyTo(img_previous); +} diff --git a/package_bgs/ck/LbpMrf.h b/package_bgs/_template_/MyBGS.h similarity index 65% rename from package_bgs/ck/LbpMrf.h rename to package_bgs/_template_/MyBGS.h index 6fd7267..dea2a2f 100644 --- a/package_bgs/ck/LbpMrf.h +++ b/package_bgs/_template_/MyBGS.h @@ -16,29 +16,28 @@ along with BGSLibrary. If not, see . */ #pragma once -#include #include #include "../IBGS.h" -class MotionDetection; - -class LbpMrf : public IBGS +namespace bgslibrary { -private: - bool firstTime; - MotionDetection* Detector; - cv::Mat img_foreground; - cv::Mat img_segmentation; - bool showOutput; - -public: - LbpMrf(); - ~LbpMrf(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; + namespace algorithms + { + class MyBGS : public IBGS + { + private: + cv::Mat img_previous; + + public: + MyBGS(); + ~MyBGS(); + + void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); + + private: + void saveConfig() {} + void loadConfig() {} + }; + } +} diff --git a/package_bgs/_template_/amber/amber.c b/package_bgs/_template_/amber/amber.c new file mode 100644 index 0000000..00bdfb1 --- /dev/null +++ b/package_bgs/_template_/amber/amber.c @@ -0,0 +1,80 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ + +#include "amber.h" + +#define COLOR_BACKGROUND 0 +#define COLOR_FOREGROUND 255 + +amberModel* libamberModelNew() +{ + /* model structure alloc */ + amberModel* model = NULL; + model = (amberModel*)calloc(1, sizeof(amberModel)); + assert(model != NULL); + + /* default parameters values */ + model->height = 0; + + return(model); +} + +// -------------------------------------------------- +int32_t libamberModelAllocInit_8u_C3R(amberModel* model, + const uint8_t *image_data, + const uint32_t width, + const uint32_t height) +{ + + /* basic checks */ + assert((image_data != NULL) && (model != NULL)); + assert((model->width > 0) && (model->height > 0)); + + /* finish model alloc - parameters values cannot be changed anymore */ + model->width = width; + model->height = height; + + return(0); +} + +// -------------------------------------------------- +int32_t libamberGetSegmentation_8u_C3R(amberModel* model, + const uint8_t *image_data, + uint8_t *segmentation_map) +{ + /* basic checks */ + assert((image_data != NULL) && (model != NULL) && (segmentation_map != NULL)); + assert((model->width > 0) && (model->height > 0)); + + return(0); +} + +// -------------------------------------------------- +int32_t libamberModelFree(amberModel* model) +{ + if (model == NULL) + return(-1); + + free(model); + + return(0); +} + +/* For compilation with g++ */ +#ifdef __cplusplus +} +#endif diff --git a/package_bgs/_template_/amber/amber.h b/package_bgs/_template_/amber/amber.h new file mode 100644 index 0000000..0206179 --- /dev/null +++ b/package_bgs/_template_/amber/amber.h @@ -0,0 +1,64 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include +#include +#include + +#define COLOR_BACKGROUND 0 +#define COLOR_FOREGROUND 255 + +/* For compilation with g++ */ +#ifdef __cplusplus +extern "C" +{ +#endif + /* end of addition for compilation with g++ */ + +#ifndef _STDINT_H +// This is used to make it compatible for cross-compilation + typedef unsigned char uint8_t; + typedef int int32_t; + typedef unsigned int uint32_t; +#endif // _STDINT_H + + typedef struct { + uint32_t width; + uint32_t height; + } amberModel; + + /** Allocation of a new data structure where the background model + will be stored */ + amberModel* libamberModelNew(); + + int32_t libamberModelAllocInit_8u_C3R(amberModel* model, + const uint8_t *image_data, + const uint32_t width, + const uint32_t height); + + int32_t libamberGetSegmentation_8u_C3R(amberModel* model, + const uint8_t *image_data, + uint8_t *segmentation_map); + + + int32_t libamberModelFree(amberModel* model); + + /* For compilation with g++ */ +#ifdef __cplusplus +} +#endif diff --git a/package_bgs/ae/KDE.h b/package_bgs/ae/KDE.h deleted file mode 100644 index adcc659..0000000 --- a/package_bgs/ae/KDE.h +++ /dev/null @@ -1,58 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#include -#include - - -#include "NPBGSubtractor.h" -#include "../IBGS.h" - -class KDE : public IBGS -{ -private: - NPBGSubtractor *p; - int rows; - int cols; - int color_channels; - int SequenceLength; - int TimeWindowSize; - int SDEstimationFlag; - int lUseColorRatiosFlag; - double th; - double alpha; - int framesToLearn; - int frameNumber; - bool firstTime; - bool showOutput; - - cv::Mat img_foreground; - unsigned char *FGImage; - unsigned char *FilteredFGImage; - unsigned char **DisplayBuffers; - -public: - KDE(); - ~KDE(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; diff --git a/package_bgs/ae/NPBGSubtractor.cpp b/package_bgs/ae/NPBGSubtractor.cpp deleted file mode 100644 index 3f0620f..0000000 --- a/package_bgs/ae/NPBGSubtractor.cpp +++ /dev/null @@ -1,1160 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -/* -* -* Copyright 2001 by Ahmed Elgammal All rights reserved. -* -* Permission to use, copy, or modify this software and its documentation -* for educational and research purposes only and without fee is hereby -* granted, provided that this copyright notice and the original authors's -* name appear on all copies and supporting documentation. If individual -* files are separated from this distribution directory structure, this -* copyright notice must be included. For any other uses of this software, -* in original or modified form, including but not limited to distribution -* in whole or in part, specific prior permission must be obtained from -* Author or UMIACS. These programs shall not be used, rewritten, or -* adapted as the basis of a commercial software or hardware product -* without first obtaining appropriate licenses from Author. -* Other than these cases, no part of this software may be used or -* distributed without written permission of the author. -* -* Neither the author nor UMIACS make any representations about the -* suitability of this software for any purpose. It is provided -* "as is" without express or implied warranty. -* -* Ahmed Elgammal -* -* University of Maryland at College Park -* UMIACS -* A.V. Williams Bldg. -* CollegePark, MD 20742 -* E-mail: elgammal@umiacs.umd.edu -* -**/ - -// NPBGSubtractor.cpp: implementation of the NPBGSubtractor class. -// -////////////////////////////////////////////////////////////////////// - -#include "NPBGSubtractor.h" -#include -#include -#include - -//#ifdef _DEBUG -//#undef THIS_FILE -//static char THIS_FILE[]=__FILE__; -//#define new DEBUG_NEW -//#endif - -void BGR2SnGnRn(unsigned char * in_image, - unsigned char * out_image, - unsigned int rows, - unsigned int cols) -{ - unsigned int i; - unsigned int r2,r3; - unsigned int r,g,b; - double s; - - for(i = 0; i < rows*cols*3; i += 3) - { - b=in_image[i]; - g=in_image[i+1]; - r=in_image[i+2]; - - // calculate color ratios - s = (double) 255 / (double) (b+g+r+30); - - r2 =(unsigned int) ((g+10) * s ); - r3 =(unsigned int) ((r+10) * s ); - - out_image[i] = (unsigned char) (((unsigned int) b+g+r) / 3); - out_image[i+1] = (unsigned char) (r2 > 255 ? 255 : r2) ; - out_image[i+2] = (unsigned char) (r3 > 255 ? 255 : r3) ; - } -} - -void UpdateDiffHist(unsigned char * image1, unsigned char * image2, DynamicMedianHistogram * pHist) -{ - unsigned int j; - int bin,diff; - - unsigned int imagesize = pHist->imagesize; - unsigned char histbins = pHist->histbins; - unsigned char *pAbsDiffHist = pHist->Hist; - - int histbins_1 = histbins-1; - - for(j = 0; j < imagesize; j++) - { - diff = (int) image1[j] - (int) image2[j]; - diff = abs(diff); - // update histogram - bin = (diff < histbins ? diff : histbins_1); - pAbsDiffHist[j*histbins+bin]++; - } -} - -void FindHistMedians(DynamicMedianHistogram * pAbsDiffHist) -{ - unsigned char * Hist = pAbsDiffHist->Hist; - unsigned char * MedianBins = pAbsDiffHist->MedianBins; - unsigned char * AccSum = pAbsDiffHist->AccSum; - unsigned char histsum = pAbsDiffHist->histsum; - unsigned char histbins = pAbsDiffHist->histbins; - unsigned int imagesize = pAbsDiffHist->imagesize; - - int sum; - int bin; - unsigned int histindex; - unsigned char medianCount=histsum/2; - unsigned int j; - - // find medians - for(j = 0; j < imagesize; j++) - { - // find the median - bin=0; - sum=0; - - histindex=j*histbins; - - while(sum < medianCount) - { - sum+=Hist[histindex+bin]; - bin++; - } - - bin--; - - MedianBins[j]=bin; - AccSum[j]=sum; - } -} - -DynamicMedianHistogram BuildAbsDiffHist(unsigned char * pSequence, - unsigned int rows, - unsigned int cols, - unsigned int color_channels, - unsigned int SequenceLength, - unsigned int histbins) -{ - - unsigned int imagesize=rows*cols*color_channels; - unsigned int i; - - DynamicMedianHistogram Hist; - - unsigned char *pAbsDiffHist = new unsigned char[rows*cols*color_channels*histbins]; - unsigned char *pMedianBins = new unsigned char[rows*cols*color_channels]; - unsigned char *pMedianFreq = new unsigned char[rows*cols*color_channels]; - unsigned char *pAccSum = new unsigned char[rows*cols*color_channels]; - - memset(pAbsDiffHist,0,rows*cols*color_channels*histbins); - - Hist.Hist = pAbsDiffHist; - Hist.MedianBins = pMedianBins; - Hist.MedianFreq = pMedianFreq; - Hist.AccSum = pAccSum; - Hist.histbins = histbins; - Hist.imagesize = rows*cols*color_channels; - Hist.histsum = SequenceLength-1; - - unsigned char *image1, *image2; - for(i = 1; i < SequenceLength; i++) - { - // find diff between frame i,i-1; - image1 = pSequence+(i-1)*imagesize; - image2 = pSequence+(i)*imagesize; - - UpdateDiffHist(image1,image2,&Hist); - } - - FindHistMedians(&Hist); - - return Hist; -} - -void EstimateSDsFromAbsDiffHist(DynamicMedianHistogram * pAbsDiffHist, - unsigned char * pSDs, - unsigned int imagesize, - double MinSD, - double MaxSD, - unsigned int kernelbins) -{ - double v; - double kernelbinfactor=(kernelbins-1)/(MaxSD-MinSD); - int medianCount; - int sum; - int bin; - unsigned int histindex; - unsigned int j; - unsigned int x1,x2; - - unsigned char *Hist =pAbsDiffHist->Hist; - unsigned char *MedianBins =pAbsDiffHist->MedianBins; - unsigned char *AccSum =pAbsDiffHist->AccSum; - unsigned char histsum =pAbsDiffHist->histsum; - unsigned char histbins =pAbsDiffHist->histbins; - - medianCount=(histsum)/2 ; - - for(j = 0; j < imagesize; j++) - { - histindex=j*histbins; - - bin=MedianBins[j]; - sum=AccSum[j]; - - x1=sum-Hist[histindex+bin]; - x2=sum; - - // interpolate to get the median - // x1 < 50 % < x2 - - v =1.04 * ((double) bin-(double) (x2-medianCount)/ (double) (x2-x1)); - v=( v <= MinSD ? MinSD : v); - - // convert sd to kernel table bin - - bin=(int) (v>=MaxSD ? kernelbins-1 : floor((v-MinSD)*kernelbinfactor+.5)); - - assert(bin>=0 && bin < kernelbins ); - - pSDs[j]=bin; - } -} - -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// - -NPBGSubtractor::NPBGSubtractor(){} - -NPBGSubtractor::~NPBGSubtractor() -{ - delete AbsDiffHist.Hist; - delete AbsDiffHist.MedianBins; - delete AbsDiffHist.MedianFreq; - delete AbsDiffHist.AccSum; - delete KernelTable; - delete BGModel->SDbinsImage; - delete BGModel; - delete Pimage1; - delete Pimage2; - delete tempFrame; - delete imageindex->List; - delete imageindex; -} - -int NPBGSubtractor::Intialize(unsigned int prows, - unsigned int pcols, - unsigned int pcolor_channels, - unsigned int SequenceLength, - unsigned int pTimeWindowSize, - unsigned char pSDEstimationFlag, - unsigned char pUseColorRatiosFlag) -{ - - rows=prows; - cols=pcols; - color_channels=pcolor_channels; - imagesize=rows*cols*color_channels; - SdEstimateFlag = pSDEstimationFlag; - UseColorRatiosFlag=pUseColorRatiosFlag; - //SampleSize = SequenceLength; - - AdaptBGFlag = FALSE; - // - SubsetFlag = TRUE; - - UpdateSDRate = 0; - - BGModel = new NPBGmodel(rows,cols,color_channels,SequenceLength,pTimeWindowSize,500); - - Pimage1= new double[rows*cols]; - Pimage2= new double[rows*cols]; - - tempFrame= new unsigned char[rows*cols*3]; - - imageindex = new ImageIndex; - imageindex->List= new unsigned int [rows*cols]; - - // error checking - if (BGModel==NULL) - return 0; - - return 1; -} - -void NPBGSubtractor::AddFrame(unsigned char *ImageBuffer) -{ - if(UseColorRatiosFlag && color_channels==3) - BGR2SnGnRn(ImageBuffer,ImageBuffer,rows,cols); - - BGModel->AddFrame(ImageBuffer); -} - -void NPBGSubtractor::Estimation() -{ - int SampleSize=BGModel->SampleSize; - - memset(BGModel->TemporalMask,0,rows*cols*BGModel->TemporalBufferLength); - - //BGModel->AccMask= new unsigned int [rows*cols]; - memset(BGModel->AccMask,0,rows*cols*sizeof(unsigned int)); - - unsigned char *pSDs = new unsigned char[rows*cols*color_channels]; - - //DynamicMedianHistogram AbsDiffHist; - - int Abshistbins = 20; - - TimeIndex=0; - - // estimate standard deviations - - if(SdEstimateFlag) - { - AbsDiffHist = BuildAbsDiffHist(BGModel->Sequence,rows,cols,color_channels,SampleSize,Abshistbins); - EstimateSDsFromAbsDiffHist(&AbsDiffHist,pSDs,imagesize,SEGMAMIN,SEGMAMAX,SEGMABINS); - } - else - { - unsigned int bin; - bin = (unsigned int) floor(((DEFAULTSEGMA-SEGMAMIN)*SEGMABINS)/(SEGMAMAX-SEGMAMIN)); - memset(pSDs,bin,rows*cols*color_channels*sizeof(unsigned char)); - } - - BGModel->SDbinsImage=pSDs; - - // Generate the Kernel - KernelTable = new KernelLUTable(KERNELHALFWIDTH,SEGMAMIN,SEGMAMAX,SEGMABINS); -} - -/*********************************************************************/ - -void BuildImageIndex(unsigned char * Image, - ImageIndex * imageIndex, - unsigned int rows, - unsigned int cols) -{ - unsigned int i,j; - unsigned int r,c; - unsigned int * image_list; - - j=cols+1; - i=0; - image_list=imageIndex->List; - - for(r = 1; r < rows-1; r++) - { - for(c = 1; c < cols-1; c++) - { - if(Image[j]) - image_list[i++]=j; - - j++; - } - j+=2; - } - - imageIndex->cnt = i; -} - -/*********************************************************************/ - -void HystExpandOperatorIndexed(unsigned char * inImage, - ImageIndex * inIndex, - double * Pimage, - double hyst_th, - unsigned char * outImage, - ImageIndex * outIndex, - unsigned int urows, - unsigned int ucols) -{ - unsigned int * in_list; - unsigned int in_cnt; - unsigned int * out_list; - - int rows,cols; - - int Nbr[9]; - unsigned int i,j; - unsigned int k; - unsigned int idx; - - rows=(int) urows; - cols=(int) ucols; - - in_cnt=inIndex->cnt; - in_list=inIndex->List; - - Nbr[0]=-cols-1; - Nbr[1]=-cols; - Nbr[2]=-cols+1; - Nbr[3]=-1; - Nbr[4]=0; - Nbr[5]=1; - Nbr[6]=cols-1; - Nbr[7]=cols; - Nbr[8]=cols+1; - - memset(outImage,0,rows*cols); - - out_list=outIndex->List; - k=0; - - for(i = 0; i < in_cnt; i++) - { - for(j = 0; j < 9; j++) - { - idx = in_list[i] + Nbr[j]; - - if(Pimage[idx] < hyst_th) - outImage[idx] = 255; - } - } - - // build index for out image - BuildImageIndex(outImage,outIndex,urows,ucols); -} - -/*********************************************************************/ - -void HystShrinkOperatorIndexed(unsigned char * inImage, - ImageIndex * inIndex, - double * Pimage, - double hyst_th, - unsigned char * outImage, - ImageIndex * outIndex, - unsigned int urows, - unsigned int ucols) -{ - unsigned int * in_list; - unsigned int in_cnt; - unsigned int * out_list; - - int rows,cols; - - int Nbr[9]; - unsigned int i,j; - unsigned int k,idx; - - rows=(int) urows; - cols=(int) ucols; - - in_cnt=inIndex->cnt; - in_list=inIndex->List; - - Nbr[0]=-cols-1; - Nbr[1]=-cols; - Nbr[2]=-cols+1; - Nbr[3]=-1; - Nbr[4]=0; - Nbr[5]=1; - Nbr[6]=cols-1; - Nbr[7]=cols; - Nbr[8]=cols+1; - - memset(outImage,0,rows*cols); - - out_list=outIndex->List; - k=0; - - for(i = 0; i < in_cnt; i++) - { - idx = in_list[i]; - j = 0; - - while(j < 9 && inImage[idx+Nbr[j]]) - j++; - - if(j >= 9 || Pimage[idx] <= hyst_th) - outImage[idx]=255; - } - - BuildImageIndex(outImage,outIndex,rows,cols); -} - -/*********************************************************************/ - -void ExpandOperatorIndexed(unsigned char * inImage, - ImageIndex * inIndex, - unsigned char * outImage, - ImageIndex * outIndex, - unsigned int urows, - unsigned int ucols) -{ - unsigned int * in_list; - unsigned int in_cnt; - unsigned int * out_list; - - int rows,cols; - - int Nbr[9]; - unsigned int i,j; - unsigned int k; - unsigned int idx; - - rows=(int) urows; - cols=(int) ucols; - - in_cnt=inIndex->cnt; - in_list=inIndex->List; - - Nbr[0]=-cols-1; - Nbr[1]=-cols; - Nbr[2]=-cols+1; - Nbr[3]=-1; - Nbr[4]=0; - Nbr[5]=1; - Nbr[6]=cols-1; - Nbr[7]=cols; - Nbr[8]=cols+1; - - - memset(outImage,0,rows*cols); - - - out_list=outIndex->List; - k=0; - for (i=0; icnt; - in_list=inIndex->List; - - Nbr[0]=-cols-1; - Nbr[1]=-cols; - Nbr[2]=-cols+1; - Nbr[3]=-1; - Nbr[4]=0; - Nbr[5]=1; - Nbr[6]=cols-1; - Nbr[7]=cols; - Nbr[8]=cols+1; - - - memset(outImage,0,rows*cols); - - out_list=outIndex->List; - k=0; - for (i=0; i=9) { - outImage[idx]=255; - } - } - - BuildImageIndex(outImage,outIndex,rows,cols); -} - -/*********************************************************************/ - -void NoiseFilter_o(unsigned char * Image, - unsigned char * ResultIm, - int rows, - int cols, - unsigned char th) -{ - /* assuming input is 1 for on, 0 for off */ - - - int r,c; - unsigned char *p,*n,*nw,*ne,*e,*w,*s,*sw,*se; - unsigned int v; - unsigned int TH; - - unsigned char * ResultPtr; - - TH=255*th; - - memset(ResultIm,0,rows*cols); - - p=Image+cols+1; - ResultPtr=ResultIm+cols+1; - - for(r=1;r=TH) - *ResultPtr=255; - else - *ResultPtr=0; - } - p++; - ResultPtr++; - } - p+=2; - ResultPtr+=2; - } -} - -/*********************************************************************/ - -void NPBGSubtractor::SequenceBGUpdate_Pairs(unsigned char * image, - unsigned char * Mask) -{ - unsigned int i,ic; - unsigned char * pSequence =BGModel->Sequence; - unsigned char * PixelQTop =BGModel->PixelQTop; - unsigned int Top =BGModel->Top; - unsigned int rate; - - int TemporalBufferTop =(int) BGModel->TemporalBufferTop; - unsigned char * pTemporalBuffer = BGModel->TemporalBuffer; - unsigned char * pTemporalMask = BGModel->TemporalMask; - int TemporalBufferLength = BGModel->TemporalBufferLength; - - unsigned int * AccMask = BGModel->AccMask; - unsigned int ResetMaskTh = BGModel->ResetMaskTh; - - unsigned char *pAbsDiffHist = AbsDiffHist.Hist; - unsigned char histbins = AbsDiffHist.histbins; - int histbins_1=histbins-1; - - int TimeWindowSize = BGModel->TimeWindowSize; - int SampleSize = BGModel->SampleSize; - - int TemporalBufferNext; - - unsigned int imagebuffersize=rows*cols*color_channels; - unsigned int imagespatialsize=rows*cols; - - unsigned char mask; - - unsigned int histindex; - unsigned char diff; - unsigned char bin; - - static int TBCount=0; - - unsigned char * pTBbase1, * pTBbase2; - unsigned char * pModelbase1, * pModelbase2; - - rate=TimeWindowSize/SampleSize; - rate=(rate > 2) ? rate : 2; - - - TemporalBufferNext=(TemporalBufferTop+1) - % TemporalBufferLength; - - // pointers to Masks : Top and Next - unsigned char * pTMaskTop=pTemporalMask+TemporalBufferTop*imagespatialsize; - unsigned char * pTMaskNext=pTemporalMask+TemporalBufferNext*imagespatialsize; - - // pointers to TB frames: Top and Next - unsigned char * pTBTop=pTemporalBuffer+TemporalBufferTop*imagebuffersize; - unsigned char * pTBNext=pTemporalBuffer+TemporalBufferNext*imagebuffersize; - - if ( ((TimeIndex) % rate == 0) && TBCount >= TemporalBufferLength ) - { - for(i=0,ic=0;i ResetMaskTh) - Mask[i]=0; - } - - // add new mask - memcpy(pTMaskTop,Mask,imagespatialsize); - - // advance Temporal buffer pointer - TemporalBufferTop=(TemporalBufferTop+1) % TemporalBufferLength; - - BGModel->TemporalBufferTop=TemporalBufferTop; - - TBCount++; - - // estimate SDs - - if (SdEstimateFlag && UpdateSDRate && ((TimeIndex) % UpdateSDRate == 0)) - { - double MaxSD = KernelTable->maxsegma; - double MinSD = KernelTable->minsegma; - int KernelBins = KernelTable->segmabins; - - unsigned char * pSDs= BGModel->SDbinsImage; - - FindHistMedians(&(AbsDiffHist)); - EstimateSDsFromAbsDiffHist(&(AbsDiffHist),pSDs,imagebuffersize,MinSD,MaxSD,KernelBins); - } - - TimeIndex++; -} - -/*********************************************************************/ - -void DisplayPropabilityImageWithThresholding(double * Pimage, - unsigned char * DisplayImage, - double Threshold, - unsigned int rows, - unsigned int cols) -{ - double p; - - for(unsigned int i=0;i Threshold) ? 0 : 255; - } -} - -/*********************************************************************/ - -void NPBGSubtractor::NPBGSubtraction_Subset_Kernel( - unsigned char * image, - unsigned char * FGImage, - unsigned char * FilteredFGImage) -{ - unsigned int i,j; - unsigned char *pSequence =BGModel->Sequence; - - unsigned int SampleSize = BGModel->SampleSize; - - double *kerneltable = KernelTable->kerneltable; - int KernelHalfWidth = KernelTable->tablehalfwidth; - double *KernelSum = KernelTable->kernelsums; - double KernelMaxSigma = KernelTable->maxsegma; - double KernelMinSigma = KernelTable->minsegma; - int KernelBins = KernelTable->segmabins; - unsigned char * SDbins= BGModel->SDbinsImage; - - unsigned char * SaturationImage=FilteredFGImage; - - // default sigmas .. to be removed. - double sigma1; - double sigma2; - double sigma3; - - sigma1=2.25; - sigma2=2.25; - sigma3=2.25; - - double p; - double th; - - double alpha; - - alpha= AlphaValue; - - /* intialize FG image */ - - memset(FGImage,0,rows*cols); - - //Threshold=1; - th = Threshold * SampleSize; - - double sum=0,kernel1,kernel2,kernel3; - int k,g; - - - if (color_channels==1) - { - // gray scale - - int kernelbase; - - for (i=0;i betau_over_alpha) - { - x1=(int) (g-betau); - x2=(int) (g+betau); - } - else - { - x1=(int) (g*brightness_lowerbound+0.5); - x2=(int) (g*brightness_upperbound+0.5); - } - - if(x1 gmax) - bin = KernelBins -1 ; - else - bin= (int) ((g-gmin) * gfactor + 0.5); - - kernelbase1=bin*kerneltablewidth; - - k= (g- image[i]) +KernelHalfWidth; - kernel1=kerneltable[kernelbase1+k]; - - g=pSequence[base+1]; - k= (g- image[i+1]) +KernelHalfWidth; - kernel2=kerneltable[kernelbase2+k]; - - g=pSequence[base+2]; - k= (g- image[i+2]) +KernelHalfWidth; - kernel3=kerneltable[kernelbase3+k]; - - sum+=kernel1*kernel2*kernel3; - j++; - } - - p=sum / j; - Pimage1[ig]=p; - } - } - else // RGB color - { - unsigned int ig; - int base; - - int kernelbase1; - int kernelbase2; - int kernelbase3; - unsigned int kerneltablewidth=2*KernelHalfWidth+1; - - for (i=0,ig=0;i. -*/ -#pragma once - -#include -#include - - -#include "TBackgroundVuMeter.h" -#include "../IBGS.h" - -class VuMeter : public IBGS -{ -private: - TBackgroundVuMeter bgs; - - IplImage *frame; - IplImage *gray; - IplImage *background; - IplImage *mask; - - bool firstTime; - bool showOutput; - bool enableFilter; - - int binSize; - double alpha; - double threshold; - -public: - VuMeter(); - ~VuMeter(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; diff --git a/package_bgs/bgslibrary.h b/package_bgs/bgslibrary.h new file mode 100644 index 0000000..3895ba8 --- /dev/null +++ b/package_bgs/bgslibrary.h @@ -0,0 +1,65 @@ +/* +This file is part of BGSLibrary. + +BGSLibrary is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BGSLibrary is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with BGSLibrary. If not, see . +*/ +#pragma once + +#include "FrameDifference.h" +#include "StaticFrameDifference.h" +#include "WeightedMovingMean.h" +#include "WeightedMovingVariance.h" +#include "MixtureOfGaussianV1.h" // Only for OpenCV >= 2 +#include "MixtureOfGaussianV2.h" +#include "AdaptiveBackgroundLearning.h" +#include "AdaptiveSelectiveBackgroundLearning.h" +#include "KNN.h" // Only for OpenCV >= 3 +#include "GMG.h" // Only for OpenCV >= 2.4.3 +#include "DPAdaptiveMedian.h" +#include "DPGrimsonGMM.h" +#include "DPZivkovicAGMM.h" +#include "DPMean.h" +#include "DPWrenGA.h" +#include "DPPratiMediod.h" +#include "DPEigenbackground.h" +#include "DPTexture.h" +#include "T2FGMM_UM.h" +#include "T2FGMM_UV.h" +#include "T2FMRF_UM.h" +#include "T2FMRF_UV.h" +#include "FuzzySugenoIntegral.h" +#include "FuzzyChoquetIntegral.h" +#include "LBSimpleGaussian.h" +#include "LBFuzzyGaussian.h" +#include "LBMixtureOfGaussians.h" +#include "LBAdaptiveSOM.h" +#include "LBFuzzyAdaptiveSOM.h" +#include "LBP_MRF.h" +#include "MultiLayer.h" +#include "PixelBasedAdaptiveSegmenter.h" +#include "VuMeter.h" +#include "KDE.h" +#include "IndependentMultimodal.h" +#include "MultiCue.h" +#include "SigmaDelta.h" +#include "SuBSENSE.h" +#include "LOBSTER.h" +#include "PAWCS.h" +#include "TwoPoints.h" +#include "ViBe.h" + +//#include "_template_/MyBGS.h" +//#include "_template_/Amber.h" + +using namespace bgslibrary::algorithms; diff --git a/package_bgs/bl/SigmaDeltaBGS.cpp b/package_bgs/bl/SigmaDeltaBGS.cpp deleted file mode 100644 index a9c7914..0000000 --- a/package_bgs/bl/SigmaDeltaBGS.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "SigmaDeltaBGS.h" - -SigmaDeltaBGS::SigmaDeltaBGS() : -firstTime(true), -ampFactor(1), -minVar(15), -maxVar(255), -algorithm(sdLaMa091New()), -showOutput(true) { - - applyParams(); - std::cout << "SigmaDeltaBGS()" << std::endl; -} - -SigmaDeltaBGS::~SigmaDeltaBGS() { - sdLaMa091Free(algorithm); - std::cout << "~SigmaDeltaBGS()" << std::endl; -} - -void SigmaDeltaBGS::process( - const cv::Mat &img_input, - cv::Mat &img_output, - cv::Mat &img_bgmodel - ) { - if (img_input.empty()) - return; - - loadConfig(); - - if (firstTime) { - saveConfig(); - sdLaMa091AllocInit_8u_C3R(algorithm, img_input.data, img_input.cols, img_input.rows, img_input.step); - - firstTime = false; - return; - } - - img_output = cv::Mat(img_input.rows, img_input.cols, CV_8UC1); - cv::Mat img_output_tmp(img_input.rows, img_input.cols, CV_8UC3); - - sdLaMa091Update_8u_C3R(algorithm, img_input.data, img_output_tmp.data); - - unsigned char* tmpBuffer = (unsigned char*)img_output_tmp.data; - unsigned char* outBuffer = (unsigned char*)img_output.data; - - for (size_t i = 0; i < img_output.total(); ++i) { - *outBuffer = *tmpBuffer; - - ++outBuffer; - tmpBuffer += img_output_tmp.channels(); - } - - if (showOutput) - cv::imshow("Sigma-Delta", img_output); -} - -void SigmaDeltaBGS::saveConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/SigmaDeltaBGS.xml", 0, CV_STORAGE_WRITE); - - cvWriteInt(fs, "ampFactor", ampFactor); - cvWriteInt(fs, "minVar", minVar); - cvWriteInt(fs, "maxVar", maxVar); - cvWriteInt(fs, "showOutput", showOutput); - - cvReleaseFileStorage(&fs); -} - -void SigmaDeltaBGS::loadConfig() { - CvFileStorage* fs = cvOpenFileStorage("./config/SigmaDeltaBGS.xml", 0, CV_STORAGE_READ); - - ampFactor = cvReadIntByName(fs, 0, "ampFactor", 1); - minVar = cvReadIntByName(fs, 0, "minVar", 15); - maxVar = cvReadIntByName(fs, 0, "maxVar", 255); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - - applyParams(); - - cvReleaseFileStorage(&fs); -} - -void SigmaDeltaBGS::applyParams() { - sdLaMa091SetAmplificationFactor(algorithm, ampFactor); - sdLaMa091SetMinimalVariance(algorithm, minVar); - sdLaMa091SetMaximalVariance(algorithm, maxVar); -} diff --git a/package_bgs/bl/SigmaDeltaBGS.h b/package_bgs/bl/SigmaDeltaBGS.h deleted file mode 100644 index a4be271..0000000 --- a/package_bgs/bl/SigmaDeltaBGS.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include -#include - - -#include "../IBGS.h" - -//extern "C" { -#include "sdLaMa091.h" -//} - -class SigmaDeltaBGS : public IBGS { -private: - - bool firstTime; - unsigned int ampFactor; - unsigned int minVar; - unsigned int maxVar; - sdLaMa091_t* algorithm; - bool showOutput; - -public: - - SigmaDeltaBGS(); - - ~SigmaDeltaBGS(); - - void process( - const cv::Mat &img_input, - cv::Mat &img_output, - cv::Mat &img_bgmodel - ); - -private: - - void saveConfig(); - - void loadConfig(); - - void applyParams(); -}; diff --git a/package_bgs/bl/stdbool.h b/package_bgs/bl/stdbool.h deleted file mode 100644 index abeb3b8..0000000 --- a/package_bgs/bl/stdbool.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -//#ifndef false -// #define false 0 -//#endif - -//#ifndef true -// #define true 1 -//#endif - -//#ifndef bool -// #define bool int -//#endif - -// #ifdef _WIN32 -// #endif diff --git a/package_bgs/ck/MEDefs.cpp b/package_bgs/ck/MEDefs.cpp deleted file mode 100644 index 9347353..0000000 --- a/package_bgs/ck/MEDefs.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of the AiBO+ project - * - * Copyright (C) 2005-2013 Csaba Kertész (csaba.kertesz@gmail.com) - * - * AiBO+ is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * AiBO+ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. - * - */ - -#include "MEDefs.hpp" - -#include - -float MERound(float number) -{ - double FracPart = 0.0; - double IntPart = 0.0; - float Ret = 0.0; - - FracPart = modf((double)number, &IntPart); - if (number >= 0) - { - Ret = (float)(FracPart >= 0.5 ? IntPart+1 : IntPart); - } else { - Ret = (float)(FracPart <= -0.5 ? IntPart-1 : IntPart); - } - return Ret; -} diff --git a/package_bgs/ck/README.TXT b/package_bgs/ck/README.TXT deleted file mode 100644 index d76c6a3..0000000 --- a/package_bgs/ck/README.TXT +++ /dev/null @@ -1,135 +0,0 @@ -################################################################### -# # -# MAXFLOW - software for computing mincut/maxflow in a graph # -# Version 2.2 # -# http://www.cs.cornell.edu/People/vnk/software.html # -# # -# Yuri Boykov (yuri@csd.uwo.ca) # -# Vladimir Kolmogorov (vnk@cs.cornell.edu) # -# 2001 # -# # -################################################################### - -1. Introduction. - -This software library implements the maxflow algorithm -described in - - An Experimental Comparison of Min-Cut/Max-Flow Algorithms - for Energy Minimization in Vision. - Yuri Boykov and Vladimir Kolmogorov. - In IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI), - September 2004 - -This algorithm was developed by Yuri Boykov and Vladimir Kolmogorov -at Siemens Corporate Research. To make it available for public use, -it was later reimplemented by Vladimir Kolmogorov based on open publications. - -If you use this software for research purposes, you should cite -the aforementioned paper in any resulting publication. - -Tested under windows, Visual C++ 6.0 compiler and unix (SunOS 5.8 -and RedHat Linux 7.0, GNU c++ compiler). - -################################################################## - -2. License. - - Copyright 2001 Vladimir Kolmogorov (vnk@cs.cornell.edu), Yuri Boykov (yuri@csd.uwo.ca). - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -################################################################## - -3. Graph representation. - -There are two versions of the algorithm using different -graph representations (adjacency list and forward star). -The former one uses more than twice as much memory as the -latter one but is 10-20% faster. - -Memory allocation (assuming that all capacities are 'short' - 2 bytes): - - | Nodes | Arcs ------------------------------------------- -Adjacency list | *24 bytes | *14 bytes -Forward star | *28 bytes | 6 bytes - -(* means that often it should be rounded up to be a multiple of 4 -- some compilers (e.g. Visual C++) seem to round up elements -of arrays unless the are structures containing only char[].) - -Note that arcs are always added in pairs - in forward and reverse directions. -Arcs between nodes and terminals (the source and the sink) are -not stored as arcs, but rather as a part of nodes. - -The assumption for the forward star representation is that -the maximum number of arcs per node (except the source -and the sink) is much less than ARC_BLOCK_SIZE (1024 by default). - -Both versions have the same interface. - -################################################################## - -4. Example usage. - -This section shows how to use the library to compute -a minimum cut on the following graph: - - SOURCE - / \ - 1/ \2 - / 3 \ - node0 -----> node1 - | <----- | - | 4 | - \ / - 5\ /6 - \ / - SINK - -/////////////////////////////////////////////////// - -#include -#include "graph.h" - -void main() -{ - Graph::node_id nodes[2]; - Graph *g = new Graph(); - - nodes[0] = g -> add_node(); - nodes[1] = g -> add_node(); - g -> set_tweights(nodes[0], 1, 5); - g -> set_tweights(nodes[1], 2, 6); - g -> add_edge(nodes[0], nodes[1], 3, 4); - - Graph::flowtype flow = g -> maxflow(); - - printf("Flow = %d\n", flow); - printf("Minimum cut:\n"); - if (g->what_segment(nodes[0]) == Graph::SOURCE) - printf("node0 is in the SOURCE set\n"); - else - printf("node0 is in the SINK set\n"); - if (g->what_segment(nodes[1]) == Graph::SOURCE) - printf("node1 is in the SOURCE set\n"); - else - printf("node1 is in the SINK set\n"); - - delete g; -} - -/////////////////////////////////////////////////// diff --git a/package_bgs/db/IndependentMultimodalBGS.cpp b/package_bgs/db/IndependentMultimodalBGS.cpp deleted file mode 100644 index 5312075..0000000 --- a/package_bgs/db/IndependentMultimodalBGS.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "IndependentMultimodalBGS.h" - -IndependentMultimodalBGS::IndependentMultimodalBGS() : fps(10), firstTime(true), showOutput(true){ - pIMBS = new BackgroundSubtractorIMBS(fps); -} -IndependentMultimodalBGS::~IndependentMultimodalBGS(){ - delete pIMBS; -} - -void IndependentMultimodalBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) -{ - if(img_input.empty()) - return; - - loadConfig(); - - if (firstTime) - saveConfig(); - - //get the fgmask and update the background model - pIMBS->apply(img_input, img_foreground); - - //get background image - pIMBS->getBackgroundImage(img_background); - - img_foreground.copyTo(img_output); - img_background.copyTo(img_bgmodel); - - if (showOutput) - { - cv::imshow("IMBS FG", img_foreground); - cv::imshow("IMBS BG", img_background); - } - - firstTime = false; -} - -void IndependentMultimodalBGS::saveConfig() -{ - CvFileStorage* fs = cvOpenFileStorage("./config/IndependentMultimodalBGS.xml", 0, CV_STORAGE_WRITE); - - cvWriteInt(fs, "showOutput", showOutput); - - cvReleaseFileStorage(&fs); -} - -void IndependentMultimodalBGS::loadConfig() -{ - CvFileStorage* fs = cvOpenFileStorage("./config/IndependentMultimodalBGS.xml", 0, CV_STORAGE_READ); - - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - - cvReleaseFileStorage(&fs); -} diff --git a/package_bgs/db/IndependentMultimodalBGS.h b/package_bgs/db/IndependentMultimodalBGS.h deleted file mode 100644 index 64b83bd..0000000 --- a/package_bgs/db/IndependentMultimodalBGS.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - - -#include "imbs.hpp" - -#include "../IBGS.h" - -class IndependentMultimodalBGS : public IBGS -{ -private: - BackgroundSubtractorIMBS* pIMBS; - int fps; - bool firstTime; - cv::Mat img_foreground; - cv::Mat img_background; - bool showOutput; - -public: - IndependentMultimodalBGS(); - ~IndependentMultimodalBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; \ No newline at end of file diff --git a/package_bgs/dp/AdaptiveMedianBGS.cpp b/package_bgs/dp/AdaptiveMedianBGS.cpp index 16b3988..da1add9 100644 --- a/package_bgs/dp/AdaptiveMedianBGS.cpp +++ b/package_bgs/dp/AdaptiveMedianBGS.cpp @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * AdaptiveMedianBGS.cpp * -* Purpose: Implementation of the simple adaptive median background +* Purpose: Implementation of the simple adaptive median background * subtraction algorithm described in: * "Segmentation and tracking of piglets in images" * by McFarlane and Schofield @@ -37,77 +37,77 @@ using namespace Algorithms::BackgroundSubtraction; void AdaptiveMedianBGS::Initalize(const BgsParams& param) { - m_params = (AdaptiveMedianParams&)param; + m_params = (AdaptiveMedianParams&)param; - m_median = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); - cvSet(m_median.Ptr(), CV_RGB(BACKGROUND,BACKGROUND,BACKGROUND)); + m_median = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); + cvSet(m_median.Ptr(), CV_RGB(BACKGROUND, BACKGROUND, BACKGROUND)); } RgbImage* AdaptiveMedianBGS::Background() { - return &m_median; + return &m_median; } void AdaptiveMedianBGS::InitModel(const RgbImage& data) { - // initialize the background model - for (unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - m_median(r,c) = data(r,c); - } - } + // initialize the background model + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + m_median(r, c) = data(r, c); + } + } } -void AdaptiveMedianBGS::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) +void AdaptiveMedianBGS::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) { - if(frame_num % m_params.SamplingRate() == 1) - { - // update background model - for (unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - // perform conditional updating only if we are passed the learning phase - if(update_mask(r,c) == BACKGROUND || frame_num < m_params.LearningFrames()) - { - for(int ch = 0; ch < NUM_CHANNELS; ++ch) - { - if(data(r,c,ch) > m_median(r,c,ch)) - { - m_median(r,c,ch)++; - } - else if(data(r,c,ch) < m_median(r,c,ch)) - { - m_median(r,c,ch)--; - } - } - } - } - } - } + if (frame_num % m_params.SamplingRate() == 1) + { + // update background model + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + // perform conditional updating only if we are passed the learning phase + if (update_mask(r, c) == BACKGROUND || frame_num < m_params.LearningFrames()) + { + for (int ch = 0; ch < NUM_CHANNELS; ++ch) + { + if (data(r, c, ch) > m_median(r, c, ch)) + { + m_median(r, c, ch)++; + } + else if (data(r, c, ch) < m_median(r, c, ch)) + { + m_median(r, c, ch)--; + } + } + } + } + } + } } -void AdaptiveMedianBGS::SubtractPixel(int r, int c, const RgbPixel& pixel, - unsigned char& low_threshold, unsigned char& high_threshold) +void AdaptiveMedianBGS::SubtractPixel(int r, int c, const RgbPixel& pixel, + unsigned char& low_threshold, unsigned char& high_threshold) { - // perform background subtraction - low_threshold = high_threshold = FOREGROUND; - - int diffR = abs(pixel(0) - m_median(r,c,0)); - int diffG = abs(pixel(1) - m_median(r,c,1)); - int diffB = abs(pixel(2) - m_median(r,c,2)); - - if(diffR <= m_params.LowThreshold() && diffG <= m_params.LowThreshold() && diffB <= m_params.LowThreshold()) - { - low_threshold = BACKGROUND; - } - - if(diffR <= m_params.HighThreshold() && diffG <= m_params.HighThreshold() && diffB <= m_params.HighThreshold()) - { - high_threshold = BACKGROUND; - } + // perform background subtraction + low_threshold = high_threshold = FOREGROUND; + + int diffR = abs(pixel(0) - m_median(r, c, 0)); + int diffG = abs(pixel(1) - m_median(r, c, 1)); + int diffB = abs(pixel(2) - m_median(r, c, 2)); + + if (diffR <= m_params.LowThreshold() && diffG <= m_params.LowThreshold() && diffB <= m_params.LowThreshold()) + { + low_threshold = BACKGROUND; + } + + if (diffR <= m_params.HighThreshold() && diffG <= m_params.HighThreshold() && diffB <= m_params.HighThreshold()) + { + high_threshold = BACKGROUND; + } } /////////////////////////////////////////////////////////////////////////////// @@ -118,23 +118,23 @@ void AdaptiveMedianBGS::SubtractPixel(int r, int c, const RgbPixel& pixel, // (the memory should already be reserved) // values: 255-foreground, 0-background /////////////////////////////////////////////////////////////////////////////// -void AdaptiveMedianBGS::Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask) +void AdaptiveMedianBGS::Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask) { - unsigned char low_threshold, high_threshold; - - // update each pixel of the image - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - // perform background subtraction - SubtractPixel(r, c, data(r,c), low_threshold, high_threshold); - - // setup silhouette mask - low_threshold_mask(r,c) = low_threshold; - high_threshold_mask(r,c) = high_threshold; - } - } + unsigned char low_threshold, high_threshold; + + // update each pixel of the image + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + // perform background subtraction + SubtractPixel(r, c, data(r, c), low_threshold, high_threshold); + + // setup silhouette mask + low_threshold_mask(r, c) = low_threshold; + high_threshold_mask(r, c) = high_threshold; + } + } } diff --git a/package_bgs/dp/AdaptiveMedianBGS.h b/package_bgs/dp/AdaptiveMedianBGS.h index d199b81..79a04b3 100644 --- a/package_bgs/dp/AdaptiveMedianBGS.h +++ b/package_bgs/dp/AdaptiveMedianBGS.h @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * AdaptiveMedianBGS.hpp * -* Purpose: Implementation of the simple adaptive median background +* Purpose: Implementation of the simple adaptive median background * subtraction algorithm described in: * "Segmentation and tracking of piglets in images" * by McFarlane and Schofield @@ -26,64 +26,65 @@ along with BGSLibrary. If not, see . * Author: Donovan Parks, September 2007 Example: - Algorithms::BackgroundSubtraction::AdaptiveMedianParams params; - params.SetFrameSize(width, height); - params.LowThreshold() = 40; - params.HighThreshold() = 2*params.LowThreshold(); - params.SamplingRate() = 7; - params.LearningFrames() = 30; - - Algorithms::BackgroundSubtraction::AdaptiveMedianBGS bgs; - bgs.Initalize(params); + Algorithms::BackgroundSubtraction::AdaptiveMedianParams params; + params.SetFrameSize(width, height); + params.LowThreshold() = 40; + params.HighThreshold() = 2*params.LowThreshold(); + params.SamplingRate() = 7; + params.LearningFrames() = 30; + + Algorithms::BackgroundSubtraction::AdaptiveMedianBGS bgs; + bgs.Initalize(params); ******************************************************************************/ +#pragma once #include "Bgs.h" namespace Algorithms { - namespace BackgroundSubtraction - { - // --- Parameters used by the Adaptive Median BGS algorithm --- - class AdaptiveMedianParams : public BgsParams - { - public: - unsigned char &LowThreshold() { return m_low_threshold; } - unsigned char &HighThreshold() { return m_high_threshold; } + namespace BackgroundSubtraction + { + // --- Parameters used by the Adaptive Median BGS algorithm --- + class AdaptiveMedianParams : public BgsParams + { + public: + unsigned char &LowThreshold() { return m_low_threshold; } + unsigned char &HighThreshold() { return m_high_threshold; } - int &SamplingRate() { return m_samplingRate; } - int &LearningFrames() { return m_learning_frames; } + int &SamplingRate() { return m_samplingRate; } + int &LearningFrames() { return m_learning_frames; } - private: - unsigned char m_low_threshold; - unsigned char m_high_threshold; + private: + unsigned char m_low_threshold; + unsigned char m_high_threshold; - int m_samplingRate; - int m_learning_frames; - }; + int m_samplingRate; + int m_learning_frames; + }; - // --- Adaptive Median BGS algorithm --- - class AdaptiveMedianBGS : public Bgs - { - public: - virtual ~AdaptiveMedianBGS() {} + // --- Adaptive Median BGS algorithm --- + class AdaptiveMedianBGS : public Bgs + { + public: + virtual ~AdaptiveMedianBGS() {} - void Initalize(const BgsParams& param); + void Initalize(const BgsParams& param); - void InitModel(const RgbImage& data); - void Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask); - void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); + void InitModel(const RgbImage& data); + void Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask); + void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); - RgbImage* Background(); + RgbImage* Background(); - private: - void SubtractPixel(int r, int c, const RgbPixel& pixel, - unsigned char& low_threshold, unsigned char& high_threshold); + private: + void SubtractPixel(int r, int c, const RgbPixel& pixel, + unsigned char& low_threshold, unsigned char& high_threshold); - AdaptiveMedianParams m_params; + AdaptiveMedianParams m_params; - RgbImage m_median; - }; - } + RgbImage m_median; + }; + } } diff --git a/package_bgs/dp/Bgs.h b/package_bgs/dp/Bgs.h index 5f91246..26fff70 100644 --- a/package_bgs/dp/Bgs.h +++ b/package_bgs/dp/Bgs.h @@ -23,45 +23,41 @@ along with BGSLibrary. If not, see . * Author: Donovan Parks, October 2007 * ******************************************************************************/ - -#ifndef BGS_H_ -#define BGS_H_ +#pragma once #include "Image.h" #include "BgsParams.h" namespace Algorithms { - namespace BackgroundSubtraction - { - class Bgs - { - public: - static const int BACKGROUND = 0; - static const int FOREGROUND = 255; +namespace BackgroundSubtraction +{ +class Bgs +{ +public: + static const int BACKGROUND = 0; + static const int FOREGROUND = 255; - virtual ~Bgs() {} + virtual ~Bgs() {} - // Initialize any data required by the BGS algorithm. Should be called once before calling - // any of the following functions. - virtual void Initalize(const BgsParams& param) = 0; + // Initialize any data required by the BGS algorithm. Should be called once before calling + // any of the following functions. + virtual void Initalize(const BgsParams& param) = 0; - // Initialize the background model. Typically, the background model is initialized using the first - // frame of the incoming video stream, but alternatives are possible. - virtual void InitModel(const RgbImage& data) = 0; + // Initialize the background model. Typically, the background model is initialized using the first + // frame of the incoming video stream, but alternatives are possible. + virtual void InitModel(const RgbImage& data) = 0; - // Subtract the current frame from the background model and produce a binary foreground mask using - // both a low and high threshold value. - virtual void Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask) = 0; + // Subtract the current frame from the background model and produce a binary foreground mask using + // both a low and high threshold value. + virtual void Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask) = 0; - // Update the background model. Only pixels set to background in update_mask are updated. - virtual void Update(int frame_num, const RgbImage& data, const BwImage& update_mask) = 0; + // Update the background model. Only pixels set to background in update_mask are updated. + virtual void Update(int frame_num, const RgbImage& data, const BwImage& update_mask) = 0; - // Return the current background model. - virtual RgbImage *Background() = 0; - }; - } + // Return the current background model. + virtual RgbImage *Background() = 0; +}; +} } - -#endif diff --git a/package_bgs/dp/BgsParams.h b/package_bgs/dp/BgsParams.h index a63b1ac..d2e7d29 100644 --- a/package_bgs/dp/BgsParams.h +++ b/package_bgs/dp/BgsParams.h @@ -24,36 +24,32 @@ along with BGSLibrary. If not, see . * Author: Donovan Parks, May 2008 * ******************************************************************************/ - -#ifndef BGS_PARAMS_H_ -#define BGS_PARAMS_H_ +#pragma once namespace Algorithms { - namespace BackgroundSubtraction - { - class BgsParams - { - public: - virtual ~BgsParams() {} - - virtual void SetFrameSize(unsigned int width, unsigned int height) - { - m_width = width; - m_height = height; - m_size = width*height; - } - - unsigned int &Width() { return m_width; } - unsigned int &Height() { return m_height; } - unsigned int &Size() { return m_size; } - - protected: - unsigned int m_width; - unsigned int m_height; - unsigned int m_size; - }; - } +namespace BackgroundSubtraction +{ +class BgsParams +{ +public: + virtual ~BgsParams() {} + + virtual void SetFrameSize(unsigned int width, unsigned int height) + { + m_width = width; + m_height = height; + m_size = width*height; + } + + unsigned int &Width() { return m_width; } + unsigned int &Height() { return m_height; } + unsigned int &Size() { return m_size; } + +protected: + unsigned int m_width; + unsigned int m_height; + unsigned int m_size; +}; +} } - -#endif diff --git a/package_bgs/dp/DPAdaptiveMedianBGS.cpp b/package_bgs/dp/DPAdaptiveMedianBGS.cpp deleted file mode 100644 index 0ae68fe..0000000 --- a/package_bgs/dp/DPAdaptiveMedianBGS.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#include "DPAdaptiveMedianBGS.h" - -DPAdaptiveMedianBGS::DPAdaptiveMedianBGS() : firstTime(true), frameNumber(0), threshold(40), samplingRate(7), learningFrames(30), showOutput(true) -{ - std::cout << "DPAdaptiveMedianBGS()" << std::endl; -} - -DPAdaptiveMedianBGS::~DPAdaptiveMedianBGS() -{ - std::cout << "~DPAdaptiveMedianBGS()" << std::endl; -} - -void DPAdaptiveMedianBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) -{ - if(img_input.empty()) - return; - - loadConfig(); - - if(firstTime) - saveConfig(); - - frame = new IplImage(img_input); - - if(firstTime) - frame_data.ReleaseMemory(false); - frame_data = frame; - - if(firstTime) - { - int width = img_input.size().width; - int height = img_input.size().height; - - lowThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); - lowThresholdMask.Ptr()->origin = IPL_ORIGIN_BL; - - highThresholdMask = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); - highThresholdMask.Ptr()->origin = IPL_ORIGIN_BL; - - params.SetFrameSize(width, height); - params.LowThreshold() = threshold; - params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing - params.SamplingRate() = samplingRate; - params.LearningFrames() = learningFrames; - - bgs.Initalize(params); - bgs.InitModel(frame_data); - - std::cout << "threshold: " << threshold << std::endl; - std::cout << "samplingRate: " << samplingRate << std::endl; - std::cout << "learningFrames: " << learningFrames << std::endl; - std::cout << "showOutput: " << showOutput << std::endl; - } - - bgs.Subtract(frameNumber, frame_data, lowThresholdMask, highThresholdMask); - lowThresholdMask.Clear(); - bgs.Update(frameNumber, frame_data, lowThresholdMask); - - cv::Mat foreground(highThresholdMask.Ptr()); - cv::Mat background(bgs.Background()->Ptr()); - - if(showOutput){ - cv::imshow("Adaptive Median FG (McFarlane&Schofield)", foreground); - cv::imshow("Adaptive Median BG (McFarlane&Schofield)", background); - } - - foreground.copyTo(img_output); - background.copyTo(img_bgmodel); - - delete frame; - firstTime = false; - frameNumber++; -} - -void DPAdaptiveMedianBGS::saveConfig() -{ - CvFileStorage* fs = cvOpenFileStorage("./config/DPAdaptiveMedianBGS.xml", 0, CV_STORAGE_WRITE); - - cvWriteInt(fs, "threshold", threshold); - cvWriteInt(fs, "samplingRate", samplingRate); - cvWriteInt(fs, "learningFrames", learningFrames); - cvWriteInt(fs, "showOutput", showOutput); - - cvReleaseFileStorage(&fs); -} - -void DPAdaptiveMedianBGS::loadConfig() -{ - CvFileStorage* fs = cvOpenFileStorage("./config/DPAdaptiveMedianBGS.xml", 0, CV_STORAGE_READ); - - threshold = cvReadIntByName(fs, 0, "threshold", 40); - samplingRate = cvReadIntByName(fs, 0, "samplingRate", 7); - learningFrames = cvReadIntByName(fs, 0, "learningFrames", 30); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - - cvReleaseFileStorage(&fs); -} diff --git a/package_bgs/dp/DPAdaptiveMedianBGS.h b/package_bgs/dp/DPAdaptiveMedianBGS.h deleted file mode 100644 index a5dd387..0000000 --- a/package_bgs/dp/DPAdaptiveMedianBGS.h +++ /dev/null @@ -1,56 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#include -#include - - -#include "../IBGS.h" -#include "AdaptiveMedianBGS.h" - -using namespace Algorithms::BackgroundSubtraction; - -class DPAdaptiveMedianBGS : public IBGS -{ -private: - bool firstTime; - long frameNumber; - IplImage* frame; - RgbImage frame_data; - - AdaptiveMedianParams params; - AdaptiveMedianBGS bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - int threshold; - int samplingRate; - int learningFrames; - bool showOutput; - -public: - DPAdaptiveMedianBGS(); - ~DPAdaptiveMedianBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - diff --git a/package_bgs/dp/DPPratiMediodBGS.h b/package_bgs/dp/DPPratiMediodBGS.h deleted file mode 100644 index 8aa6416..0000000 --- a/package_bgs/dp/DPPratiMediodBGS.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#include -#include - - -#include "../IBGS.h" -#include "PratiMediodBGS.h" - -using namespace Algorithms::BackgroundSubtraction; - -class DPPratiMediodBGS : public IBGS -{ -private: - bool firstTime; - long frameNumber; - IplImage* frame; - RgbImage frame_data; - - PratiParams params; - PratiMediodBGS bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - int threshold; - int samplingRate; - int historySize; - int weight; - bool showOutput; - -public: - DPPratiMediodBGS(); - ~DPPratiMediodBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - diff --git a/package_bgs/dp/DPTextureBGS.h b/package_bgs/dp/DPTextureBGS.h deleted file mode 100644 index c29e7e9..0000000 --- a/package_bgs/dp/DPTextureBGS.h +++ /dev/null @@ -1,60 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#include -#include - - -#include "../IBGS.h" -#include "TextureBGS.h" -//#include "ConnectedComponents.h" - -class DPTextureBGS : public IBGS -{ -private: - bool firstTime; - bool showOutput; - - int width; - int height; - int size; - TextureBGS bgs; - IplImage* frame; - RgbImage image; - BwImage fgMask; - BwImage tempMask; - TextureArray* bgModel; - RgbImage texture; - unsigned char* modeArray; - TextureHistogram* curTextureHist; - //ConnectedComponents cc; - //CBlobResult largeBlobs; - //IplConvKernel* dilateElement; - //IplConvKernel* erodeElement; - //bool enableFiltering; - -public: - DPTextureBGS(); - ~DPTextureBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; diff --git a/package_bgs/dp/Eigenbackground.cpp b/package_bgs/dp/Eigenbackground.cpp index 2d2c3ef..dceb4cd 100644 --- a/package_bgs/dp/Eigenbackground.cpp +++ b/package_bgs/dp/Eigenbackground.cpp @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * Eigenbackground.cpp * -* Purpose: Implementation of the Eigenbackground background subtraction +* Purpose: Implementation of the Eigenbackground background subtraction * algorithm developed by Oliver et al. * * Author: Donovan Parks, September 2007 @@ -34,157 +34,157 @@ using namespace Algorithms::BackgroundSubtraction; Eigenbackground::Eigenbackground() { - m_pcaData = NULL; - m_pcaAvg = NULL; - m_eigenValues = NULL; - m_eigenVectors = NULL; + m_pcaData = NULL; + m_pcaAvg = NULL; + m_eigenValues = NULL; + m_eigenVectors = NULL; } Eigenbackground::~Eigenbackground() { - if(m_pcaData != NULL) cvReleaseMat(&m_pcaData); - if(m_pcaAvg != NULL) cvReleaseMat(&m_pcaAvg); - if(m_eigenValues != NULL) cvReleaseMat(&m_eigenValues); - if(m_eigenVectors != NULL) cvReleaseMat(&m_eigenVectors); + if (m_pcaData != NULL) cvReleaseMat(&m_pcaData); + if (m_pcaAvg != NULL) cvReleaseMat(&m_pcaAvg); + if (m_eigenValues != NULL) cvReleaseMat(&m_eigenValues); + if (m_eigenVectors != NULL) cvReleaseMat(&m_eigenVectors); } void Eigenbackground::Initalize(const BgsParams& param) { - m_params = (EigenbackgroundParams&)param; - - m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); - m_background.Clear(); + m_params = (EigenbackgroundParams&)param; + + m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); + m_background.Clear(); } void Eigenbackground::InitModel(const RgbImage& data) { - if(m_pcaData != NULL) cvReleaseMat(&m_pcaData); - if(m_pcaAvg != NULL) cvReleaseMat(&m_pcaAvg); - if(m_eigenValues != NULL) cvReleaseMat(&m_eigenValues); - if(m_eigenVectors != NULL) cvReleaseMat(&m_eigenVectors); + if (m_pcaData != NULL) cvReleaseMat(&m_pcaData); + if (m_pcaAvg != NULL) cvReleaseMat(&m_pcaAvg); + if (m_eigenValues != NULL) cvReleaseMat(&m_eigenValues); + if (m_eigenVectors != NULL) cvReleaseMat(&m_eigenVectors); - m_pcaData = cvCreateMat(m_params.HistorySize(), m_params.Size()*3, CV_8UC1); + m_pcaData = cvCreateMat(m_params.HistorySize(), m_params.Size() * 3, CV_8UC1); - m_background.Clear(); + m_background.Clear(); } -void Eigenbackground::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) +void Eigenbackground::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) { - // the eigenbackground model is not updated (serious limitation!) + // the eigenbackground model is not updated (serious limitation!) } -void Eigenbackground::Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask) +void Eigenbackground::Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask) { - // create eigenbackground - if(frame_num == m_params.HistorySize()) - { - // create the eigenspace - m_pcaAvg = cvCreateMat( 1, m_pcaData->cols, CV_32F ); - m_eigenValues = cvCreateMat( m_pcaData->rows, 1, CV_32F ); - m_eigenVectors = cvCreateMat( m_pcaData->rows, m_pcaData->cols, CV_32F ); - cvCalcPCA(m_pcaData, m_pcaAvg, m_eigenValues, m_eigenVectors, CV_PCA_DATA_AS_ROW); - - int index = 0; - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - for(int ch = 0; ch < m_background.Ptr()->nChannels; ++ch) - { - m_background(r,c,0) = static_cast(cvmGet(m_pcaAvg,0,index)+0.5); - index++; - } - } - } - } - - if(frame_num >= m_params.HistorySize()) - { - // project new image into the eigenspace - int w = data.Ptr()->width; - int h = data.Ptr()->height; - int ch = data.Ptr()->nChannels; - CvMat* dataPt = cvCreateMat(1, w*h*ch, CV_8UC1); - CvMat data_row; - cvGetRow(dataPt, &data_row, 0); - cvReshape(&data_row, &data_row, 3, data.Ptr()->height); - cvCopy(data.Ptr(), &data_row); - - CvMat* proj = cvCreateMat(1, m_params.EmbeddedDim(), CV_32F); - cvProjectPCA(dataPt, m_pcaAvg, m_eigenVectors, proj); - - // reconstruct point - CvMat* result = cvCreateMat(1, m_pcaData->cols, CV_32F); - cvBackProjectPCA(proj, m_pcaAvg, m_eigenVectors, result); - - // calculate Euclidean distance between new image and its eigenspace projection - int index = 0; - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - double dist = 0; - bool bgLow = true; - bool bgHigh = true; - for(int ch = 0; ch < 3; ++ch) - { - dist = (data(r,c,ch) - cvmGet(result,0,index))*(data(r,c,ch) - cvmGet(result,0,index)); - if(dist > m_params.LowThreshold()) - bgLow = false; - if(dist > m_params.HighThreshold()) - bgHigh = false; - index++; - } - - if(!bgLow) - { - low_threshold_mask(r,c) = FOREGROUND; - } - else - { - low_threshold_mask(r,c) = BACKGROUND; - } - - if(!bgHigh) - { - high_threshold_mask(r,c) = FOREGROUND; - } - else - { - high_threshold_mask(r,c) = BACKGROUND; - } - } - } - - cvReleaseMat(&result); - cvReleaseMat(&proj); - cvReleaseMat(&dataPt); - } - else - { - // set entire image to background since there is not enough information yet - // to start performing background subtraction - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - low_threshold_mask(r,c) = BACKGROUND; - high_threshold_mask(r,c) = BACKGROUND; - } - } - } - - UpdateHistory(frame_num, data); + // create eigenbackground + if (frame_num == m_params.HistorySize()) + { + // create the eigenspace + m_pcaAvg = cvCreateMat(1, m_pcaData->cols, CV_32F); + m_eigenValues = cvCreateMat(m_pcaData->rows, 1, CV_32F); + m_eigenVectors = cvCreateMat(m_pcaData->rows, m_pcaData->cols, CV_32F); + cvCalcPCA(m_pcaData, m_pcaAvg, m_eigenValues, m_eigenVectors, CV_PCA_DATA_AS_ROW); + + int index = 0; + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + for (int ch = 0; ch < m_background.Ptr()->nChannels; ++ch) + { + m_background(r, c, 0) = static_cast(cvmGet(m_pcaAvg, 0, index) + 0.5); + index++; + } + } + } + } + + if (frame_num >= m_params.HistorySize()) + { + // project new image into the eigenspace + int w = data.Ptr()->width; + int h = data.Ptr()->height; + int ch = data.Ptr()->nChannels; + CvMat* dataPt = cvCreateMat(1, w*h*ch, CV_8UC1); + CvMat data_row; + cvGetRow(dataPt, &data_row, 0); + cvReshape(&data_row, &data_row, 3, data.Ptr()->height); + cvCopy(data.Ptr(), &data_row); + + CvMat* proj = cvCreateMat(1, m_params.EmbeddedDim(), CV_32F); + cvProjectPCA(dataPt, m_pcaAvg, m_eigenVectors, proj); + + // reconstruct point + CvMat* result = cvCreateMat(1, m_pcaData->cols, CV_32F); + cvBackProjectPCA(proj, m_pcaAvg, m_eigenVectors, result); + + // calculate Euclidean distance between new image and its eigenspace projection + int index = 0; + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + double dist = 0; + bool bgLow = true; + bool bgHigh = true; + for (int ch = 0; ch < 3; ++ch) + { + dist = (data(r, c, ch) - cvmGet(result, 0, index))*(data(r, c, ch) - cvmGet(result, 0, index)); + if (dist > m_params.LowThreshold()) + bgLow = false; + if (dist > m_params.HighThreshold()) + bgHigh = false; + index++; + } + + if (!bgLow) + { + low_threshold_mask(r, c) = FOREGROUND; + } + else + { + low_threshold_mask(r, c) = BACKGROUND; + } + + if (!bgHigh) + { + high_threshold_mask(r, c) = FOREGROUND; + } + else + { + high_threshold_mask(r, c) = BACKGROUND; + } + } + } + + cvReleaseMat(&result); + cvReleaseMat(&proj); + cvReleaseMat(&dataPt); + } + else + { + // set entire image to background since there is not enough information yet + // to start performing background subtraction + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + low_threshold_mask(r, c) = BACKGROUND; + high_threshold_mask(r, c) = BACKGROUND; + } + } + } + + UpdateHistory(frame_num, data); } void Eigenbackground::UpdateHistory(int frame_num, const RgbImage& new_frame) { - if(frame_num < m_params.HistorySize()) - { - CvMat src_row; - cvGetRow(m_pcaData, &src_row, frame_num); - cvReshape(&src_row, &src_row, 3, new_frame.Ptr()->height); - cvCopy(new_frame.Ptr(), &src_row); - } + if (frame_num < m_params.HistorySize()) + { + CvMat src_row; + cvGetRow(m_pcaData, &src_row, frame_num); + cvReshape(&src_row, &src_row, 3, new_frame.Ptr()->height); + cvCopy(new_frame.Ptr(), &src_row); + } } diff --git a/package_bgs/dp/Eigenbackground.h b/package_bgs/dp/Eigenbackground.h index 721b735..10ed189 100644 --- a/package_bgs/dp/Eigenbackground.h +++ b/package_bgs/dp/Eigenbackground.h @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * Eigenbackground.hpp * -* Purpose: Implementation of the Eigenbackground background subtraction +* Purpose: Implementation of the Eigenbackground background subtraction * algorithm developed by Oliver et al. * * Author: Donovan Parks, September 2007 @@ -30,16 +30,14 @@ along with BGSLibrary. If not, see . Algorithms::BackgroundSubtraction::EigenbackgroundParams params; params.SetFrameSize(width, height); params.LowThreshold() = 15*15; -params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing +params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing params.HistorySize() = 100; params.EmbeddedDim() = 20; Algorithms::BackgroundSubtraction::Eigenbackground bgs; bgs.Initalize(params); ******************************************************************************/ - -#ifndef _ELGAMMAL_H_ -#define _ELGAMMAL_H_ +#pragma once #include "Bgs.h" @@ -77,9 +75,9 @@ namespace Algorithms void Initalize(const BgsParams& param); void InitModel(const RgbImage& data); - void Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask); - void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); + void Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask); + void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); RgbImage* Background() { return &m_background; } @@ -90,12 +88,10 @@ namespace Algorithms CvMat* m_pcaData; CvMat* m_pcaAvg; - CvMat* m_eigenValues; + CvMat* m_eigenValues; CvMat* m_eigenVectors; RgbImage m_background; }; } } - -#endif diff --git a/package_bgs/dp/Error.h b/package_bgs/dp/Error.h index cdbb222..81cbbeb 100644 --- a/package_bgs/dp/Error.h +++ b/package_bgs/dp/Error.h @@ -23,14 +23,9 @@ along with BGSLibrary. If not, see . * Author: Donovan Parks, July 2007 * ******************************************************************************/ - -#ifndef ERROR_H -#define ERROR_H +#pragma once bool Error(const char* msg, const char* code, int data); - bool TraceInit(const char* filename); void Trace(const char* msg); void TraceClose(); - -#endif diff --git a/package_bgs/dp/GrimsonGMM.cpp b/package_bgs/dp/GrimsonGMM.cpp index ff9c262..4cf6f35 100644 --- a/package_bgs/dp/GrimsonGMM.cpp +++ b/package_bgs/dp/GrimsonGMM.cpp @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * GrimsonGMM.cpp * -* Purpose: Implementation of the Gaussian mixture model (GMM) background +* Purpose: Implementation of the Gaussian mixture model (GMM) background * subtraction described in: * "Adaptive background mixture models for real-time tracking" * by Chris Stauffer and W.E.L Grimson @@ -26,16 +26,16 @@ along with BGSLibrary. If not, see . * Author: Donovan Parks, September 2007 * * This code is based on code by Z. Zivkovic's written for his enhanced GMM -* background subtraction algorithm: +* background subtraction algorithm: * * "Improved adaptive Gausian mixture model for background subtraction" -* Z.Zivkovic +* Z.Zivkovic * International Conference Pattern Recognition, UK, August, 2004 * * -* "Efficient Adaptive Density Estimapion per Image Pixel for the +* "Efficient Adaptive Density Estimapion per Image Pixel for the * Task of Background Subtraction" -* Z.Zivkovic, F. van der Heijden +* Z.Zivkovic, F. van der Heijden * Pattern Recognition Letters, vol. 27, no. 7, pages 773-780, 2006. * * Zivkovic's code can be obtained at: www.zoranz.net @@ -47,252 +47,252 @@ using namespace Algorithms::BackgroundSubtraction; int compareGMM(const void* _gmm1, const void* _gmm2) { - GMM gmm1 = *(GMM*)_gmm1; - GMM gmm2 = *(GMM*)_gmm2; - - if(gmm1.significants < gmm2.significants) - return 1; - else if(gmm1.significants == gmm2.significants) - return 0; - else - return -1; + GMM gmm1 = *(GMM*)_gmm1; + GMM gmm2 = *(GMM*)_gmm2; + + if (gmm1.significants < gmm2.significants) + return 1; + else if (gmm1.significants == gmm2.significants) + return 0; + else + return -1; } GrimsonGMM::GrimsonGMM() { - m_modes = NULL; + m_modes = NULL; } GrimsonGMM::~GrimsonGMM() { - delete[] m_modes; + delete[] m_modes; } void GrimsonGMM::Initalize(const BgsParams& param) { - m_params = (GrimsonParams&)param; + m_params = (GrimsonParams&)param; - // Tbf - the threshold - m_bg_threshold = 0.75f; // 1-cf from the paper + // Tbf - the threshold + m_bg_threshold = 0.75f; // 1-cf from the paper - // Tgenerate - the threshold - m_variance = 36.0f; // sigma for the new mode + // Tgenerate - the threshold + m_variance = 36.0f; // sigma for the new mode - // GMM for each pixel - m_modes = new GMM[m_params.Size()*m_params.MaxModes()]; + // GMM for each pixel + m_modes = new GMM[m_params.Size()*m_params.MaxModes()]; - // used modes per pixel - m_modes_per_pixel = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 1); + // used modes per pixel + m_modes_per_pixel = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 1); - m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); + m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); } RgbImage* GrimsonGMM::Background() { - return &m_background; + return &m_background; } void GrimsonGMM::InitModel(const RgbImage& data) { - m_modes_per_pixel.Clear(); - - for(unsigned int i = 0; i < m_params.Size()*m_params.MaxModes(); ++i) - { - m_modes[i].weight = 0; - m_modes[i].variance = 0; - m_modes[i].muR = 0; - m_modes[i].muG = 0; - m_modes[i].muB = 0; - m_modes[i].significants = 0; - } + m_modes_per_pixel.Clear(); + + for (unsigned int i = 0; i < m_params.Size()*m_params.MaxModes(); ++i) + { + m_modes[i].weight = 0; + m_modes[i].variance = 0; + m_modes[i].muR = 0; + m_modes[i].muG = 0; + m_modes[i].muB = 0; + m_modes[i].significants = 0; + } } -void GrimsonGMM::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) +void GrimsonGMM::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) { - // it doesn't make sense to have conditional updates in the GMM framework + // it doesn't make sense to have conditional updates in the GMM framework } -void GrimsonGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& numModes, - unsigned char& low_threshold, unsigned char& high_threshold) +void GrimsonGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& numModes, + unsigned char& low_threshold, unsigned char& high_threshold) { - // calculate distances to the modes (+ sort???) - // here we need to go in descending order!!! - long pos; - bool bFitsPDF=false; - bool bBackgroundLow=false; - bool bBackgroundHigh=false; - - float fOneMinAlpha = 1-m_params.Alpha(); - - float totalWeight = 0.0f; - - // calculate number of Gaussians to include in the background model - int backgroundGaussians = 0; - double sum = 0.0; - for(int i = 0; i < numModes; ++i) - { - if(sum < m_bg_threshold) - { - backgroundGaussians++; - sum += m_modes[posPixel+i].weight; - } - else - { - break; - } - } - - // update all distributions and check for match with current pixel - for (int iModes=0; iModes < numModes; iModes++) - { - pos=posPixel+iModes; - float weight = m_modes[pos].weight; - - // fit not found yet - if (!bFitsPDF) - { - //check if it belongs to some of the modes - //calculate distance - float var = m_modes[pos].variance; - float muR = m_modes[pos].muR; - float muG = m_modes[pos].muG; - float muB = m_modes[pos].muB; - - float dR=muR - pixel(0); - float dG=muG - pixel(1); - float dB=muB - pixel(2); - - // calculate the squared distance - float dist = (dR*dR + dG*dG + dB*dB); - - if(dist < m_params.HighThreshold()*var && iModes < backgroundGaussians) - bBackgroundHigh = true; - - // a match occurs when the pixel is within sqrt(fTg) standard deviations of the distribution - if(dist < m_params.LowThreshold()*var) - { - bFitsPDF=true; - - // check if this Gaussian is part of the background model - if(iModes < backgroundGaussians) - bBackgroundLow = true; - - //update distribution - float k = m_params.Alpha()/weight; - weight = fOneMinAlpha*weight + m_params.Alpha(); - m_modes[pos].weight = weight; - m_modes[pos].muR = muR - k*(dR); - m_modes[pos].muG = muG - k*(dG); - m_modes[pos].muB = muB - k*(dB); - - //limit the variance - float sigmanew = var + k*(dist-var); - m_modes[pos].variance = sigmanew < 4 ? 4 : sigmanew > 5*m_variance ? 5*m_variance : sigmanew; - m_modes[pos].significants = m_modes[pos].weight / sqrt(m_modes[pos].variance); - } - else - { - weight = fOneMinAlpha*weight; - if (weight < 0.0) - { - weight=0.0; - numModes--; - } - - m_modes[pos].weight = weight; - m_modes[pos].significants = m_modes[pos].weight / sqrt(m_modes[pos].variance); - } - } - else - { - weight = fOneMinAlpha*weight; - if (weight < 0.0) - { - weight=0.0; - numModes--; - } - m_modes[pos].weight = weight; - m_modes[pos].significants = m_modes[pos].weight / sqrt(m_modes[pos].variance); - } - - totalWeight += weight; - } - - // renormalize weights so they add to one - double invTotalWeight = 1.0 / totalWeight; - for (int iLocal = 0; iLocal < numModes; iLocal++) - { - m_modes[posPixel + iLocal].weight *= (float)invTotalWeight; - m_modes[posPixel + iLocal].significants = m_modes[posPixel + iLocal].weight - / sqrt(m_modes[posPixel + iLocal].variance); - } - - // Sort significance values so they are in desending order. - qsort(&m_modes[posPixel], numModes, sizeof(GMM), compareGMM); - - // make new mode if needed and exit - if (!bFitsPDF) - { - if (numModes < m_params.MaxModes()) - { - numModes++; - } - else - { - // the weakest mode will be replaced - } - - pos = posPixel + numModes-1; - - m_modes[pos].muR = pixel.ch[0]; - m_modes[pos].muG = pixel.ch[1]; - m_modes[pos].muB = pixel.ch[2]; - m_modes[pos].variance = m_variance; - m_modes[pos].significants = 0; // will be set below - - if (numModes==1) - m_modes[pos].weight = 1; - else - m_modes[pos].weight = m_params.Alpha(); - - //renormalize weights - int iLocal; - float sum = 0.0; - for (iLocal = 0; iLocal < numModes; iLocal++) - { - sum += m_modes[posPixel+ iLocal].weight; - } - - double invSum = 1.0/sum; - for (iLocal = 0; iLocal < numModes; iLocal++) - { - m_modes[posPixel + iLocal].weight *= (float)invSum; - m_modes[posPixel + iLocal].significants = m_modes[posPixel + iLocal].weight - / sqrt(m_modes[posPixel + iLocal].variance); - - } - } - - // Sort significance values so they are in desending order. - qsort(&(m_modes[posPixel]), numModes, sizeof(GMM), compareGMM); - - if(bBackgroundLow) - { - low_threshold = BACKGROUND; - } - else - { - low_threshold = FOREGROUND; - } - - if(bBackgroundHigh) - { - high_threshold = BACKGROUND; - } - else - { - high_threshold = FOREGROUND; - } + // calculate distances to the modes (+ sort???) + // here we need to go in descending order!!! + long pos; + bool bFitsPDF = false; + bool bBackgroundLow = false; + bool bBackgroundHigh = false; + + float fOneMinAlpha = 1 - m_params.Alpha(); + + float totalWeight = 0.0f; + + // calculate number of Gaussians to include in the background model + int backgroundGaussians = 0; + double sum = 0.0; + for (int i = 0; i < numModes; ++i) + { + if (sum < m_bg_threshold) + { + backgroundGaussians++; + sum += m_modes[posPixel + i].weight; + } + else + { + break; + } + } + + // update all distributions and check for match with current pixel + for (int iModes = 0; iModes < numModes; iModes++) + { + pos = posPixel + iModes; + float weight = m_modes[pos].weight; + + // fit not found yet + if (!bFitsPDF) + { + //check if it belongs to some of the modes + //calculate distance + float var = m_modes[pos].variance; + float muR = m_modes[pos].muR; + float muG = m_modes[pos].muG; + float muB = m_modes[pos].muB; + + float dR = muR - pixel(0); + float dG = muG - pixel(1); + float dB = muB - pixel(2); + + // calculate the squared distance + float dist = (dR*dR + dG*dG + dB*dB); + + if (dist < m_params.HighThreshold()*var && iModes < backgroundGaussians) + bBackgroundHigh = true; + + // a match occurs when the pixel is within sqrt(fTg) standard deviations of the distribution + if (dist < m_params.LowThreshold()*var) + { + bFitsPDF = true; + + // check if this Gaussian is part of the background model + if (iModes < backgroundGaussians) + bBackgroundLow = true; + + //update distribution + float k = m_params.Alpha() / weight; + weight = fOneMinAlpha*weight + m_params.Alpha(); + m_modes[pos].weight = weight; + m_modes[pos].muR = muR - k*(dR); + m_modes[pos].muG = muG - k*(dG); + m_modes[pos].muB = muB - k*(dB); + + //limit the variance + float sigmanew = var + k*(dist - var); + m_modes[pos].variance = sigmanew < 4 ? 4 : sigmanew > 5 * m_variance ? 5 * m_variance : sigmanew; + m_modes[pos].significants = m_modes[pos].weight / sqrt(m_modes[pos].variance); + } + else + { + weight = fOneMinAlpha*weight; + if (weight < 0.0) + { + weight = 0.0; + numModes--; + } + + m_modes[pos].weight = weight; + m_modes[pos].significants = m_modes[pos].weight / sqrt(m_modes[pos].variance); + } + } + else + { + weight = fOneMinAlpha*weight; + if (weight < 0.0) + { + weight = 0.0; + numModes--; + } + m_modes[pos].weight = weight; + m_modes[pos].significants = m_modes[pos].weight / sqrt(m_modes[pos].variance); + } + + totalWeight += weight; + } + + // renormalize weights so they add to one + double invTotalWeight = 1.0 / totalWeight; + for (int iLocal = 0; iLocal < numModes; iLocal++) + { + m_modes[posPixel + iLocal].weight *= (float)invTotalWeight; + m_modes[posPixel + iLocal].significants = m_modes[posPixel + iLocal].weight + / sqrt(m_modes[posPixel + iLocal].variance); + } + + // Sort significance values so they are in desending order. + qsort(&m_modes[posPixel], numModes, sizeof(GMM), compareGMM); + + // make new mode if needed and exit + if (!bFitsPDF) + { + if (numModes < m_params.MaxModes()) + { + numModes++; + } + else + { + // the weakest mode will be replaced + } + + pos = posPixel + numModes - 1; + + m_modes[pos].muR = pixel.ch[0]; + m_modes[pos].muG = pixel.ch[1]; + m_modes[pos].muB = pixel.ch[2]; + m_modes[pos].variance = m_variance; + m_modes[pos].significants = 0; // will be set below + + if (numModes == 1) + m_modes[pos].weight = 1; + else + m_modes[pos].weight = m_params.Alpha(); + + //renormalize weights + int iLocal; + float sum = 0.0; + for (iLocal = 0; iLocal < numModes; iLocal++) + { + sum += m_modes[posPixel + iLocal].weight; + } + + double invSum = 1.0 / sum; + for (iLocal = 0; iLocal < numModes; iLocal++) + { + m_modes[posPixel + iLocal].weight *= (float)invSum; + m_modes[posPixel + iLocal].significants = m_modes[posPixel + iLocal].weight + / sqrt(m_modes[posPixel + iLocal].variance); + + } + } + + // Sort significance values so they are in desending order. + qsort(&(m_modes[posPixel]), numModes, sizeof(GMM), compareGMM); + + if (bBackgroundLow) + { + low_threshold = BACKGROUND; + } + else + { + low_threshold = FOREGROUND; + } + + if (bBackgroundHigh) + { + high_threshold = BACKGROUND; + } + else + { + high_threshold = FOREGROUND; + } } /////////////////////////////////////////////////////////////////////////////// @@ -303,29 +303,29 @@ void GrimsonGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned ch // (the memory should already be reserved) // values: 255-foreground, 125-shadow, 0-background /////////////////////////////////////////////////////////////////////////////// -void GrimsonGMM::Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask) +void GrimsonGMM::Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask) { - unsigned char low_threshold, high_threshold; - long posPixel; - - // update each pixel of the image - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - // update model + background subtract - posPixel=(r*m_params.Width()+c)*m_params.MaxModes(); - - SubtractPixel(posPixel, data(r,c), m_modes_per_pixel(r,c), low_threshold, high_threshold); - - low_threshold_mask(r,c) = low_threshold; - high_threshold_mask(r,c) = high_threshold; - - m_background(r,c,0) = (unsigned char)m_modes[posPixel].muR; - m_background(r,c,1) = (unsigned char)m_modes[posPixel].muG; - m_background(r,c,2) = (unsigned char)m_modes[posPixel].muB; - } - } + unsigned char low_threshold, high_threshold; + long posPixel; + + // update each pixel of the image + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + // update model + background subtract + posPixel = (r*m_params.Width() + c)*m_params.MaxModes(); + + SubtractPixel(posPixel, data(r, c), m_modes_per_pixel(r, c), low_threshold, high_threshold); + + low_threshold_mask(r, c) = low_threshold; + high_threshold_mask(r, c) = high_threshold; + + m_background(r, c, 0) = (unsigned char)m_modes[posPixel].muR; + m_background(r, c, 1) = (unsigned char)m_modes[posPixel].muG; + m_background(r, c, 2) = (unsigned char)m_modes[posPixel].muB; + } + } } diff --git a/package_bgs/dp/GrimsonGMM.h b/package_bgs/dp/GrimsonGMM.h index a177a80..2c0f772 100644 --- a/package_bgs/dp/GrimsonGMM.h +++ b/package_bgs/dp/GrimsonGMM.h @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * GrimsonGMM.cpp * -* Purpose: Implementation of the Gaussian mixture model (GMM) background +* Purpose: Implementation of the Gaussian mixture model (GMM) background * subtraction described in: * "Adaptive background mixture models for real-time tracking" * by Chris Stauffer and W.E.L Grimson @@ -26,125 +26,121 @@ along with BGSLibrary. If not, see . * Author: Donovan Parks, September 2007 * * This code is based on code by Z. Zivkovic's written for his enhanced GMM -* background subtraction algorithm: +* background subtraction algorithm: * * "Improved adaptive Gausian mixture model for background subtraction" -* Z.Zivkovic +* Z.Zivkovic * International Conference Pattern Recognition, UK, August, 2004 * * -* "Efficient Adaptive Density Estimapion per Image Pixel for the +* "Efficient Adaptive Density Estimapion per Image Pixel for the * Task of Background Subtraction" -* Z.Zivkovic, F. van der Heijden +* Z.Zivkovic, F. van der Heijden * Pattern Recognition Letters, vol. 27, no. 7, pages 773-780, 2006. * * Zivkovic's code can be obtained at: www.zoranz.net Example: - Algorithms::BackgroundSubtraction::GrimsonParams params; - params.SetFrameSize(width, height); - params.LowThreshold() = 3.0f*3.0f; - params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing - params.Alpha() = 0.001f; - params.MaxModes() = 3; - - Algorithms::BackgroundSubtraction::GrimsonGMM bgs; - bgs.Initalize(params); + Algorithms::BackgroundSubtraction::GrimsonParams params; + params.SetFrameSize(width, height); + params.LowThreshold() = 3.0f*3.0f; + params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing + params.Alpha() = 0.001f; + params.MaxModes() = 3; + + Algorithms::BackgroundSubtraction::GrimsonGMM bgs; + bgs.Initalize(params); ******************************************************************************/ - -#ifndef GRIMSON_GMM_ -#define GRIMSON_GMM_ +#pragma once #include "Bgs.h" namespace Algorithms { - namespace BackgroundSubtraction - { - typedef struct GMMGaussian - { - float variance; - float muR; - float muG; - float muB; - float weight; - float significants; // this is equal to weight / standard deviation and is used to - // determine which Gaussians should be part of the background model - } GMM; - - // --- User adjustable parameters used by the Grimson GMM BGS algorithm --- - class GrimsonParams : public BgsParams - { - public: - float &LowThreshold() { return m_low_threshold; } - float &HighThreshold() { return m_high_threshold; } - - float &Alpha() { return m_alpha; } - int &MaxModes() { return m_max_modes; } - - private: - // Threshold on the squared dist. to decide when a sample is close to an existing - // components. If it is not close to any a new component will be generated. - // Smaller threshold values lead to more generated components and higher threshold values - // lead to a small number of components but they can grow too large. - // - // It is usual easiest to think of these thresholds as being the number of variances away - // from the mean of a pixel before it is considered to be from the foreground. - float m_low_threshold; - float m_high_threshold; - - // alpha - speed of update - if the time interval you want to average over is T - // set alpha=1/T. - float m_alpha; - - // Maximum number of modes (Gaussian components) that will be used per pixel - int m_max_modes; - }; - - // --- Grimson GMM BGS algorithm --- - class GrimsonGMM : public Bgs - { - public: - GrimsonGMM(); - ~GrimsonGMM(); - - void Initalize(const BgsParams& param); - - void InitModel(const RgbImage& data); - void Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask); - void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); - - RgbImage* Background(); - - private: - void SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& numModes, - unsigned char& lowThreshold, unsigned char& highThreshold); - - // User adjustable parameters - GrimsonParams m_params; - - // Threshold when the component becomes significant enough to be included into - // the background model. It is the TB = 1-cf from the paper. So I use cf=0.1 => TB=0.9 - // For alpha=0.001 it means that the mode should exist for approximately 105 frames before - // it is considered foreground - float m_bg_threshold; //1-cf from the paper - - // Initial variance for the newly generated components. - // It will will influence the speed of adaptation. A good guess should be made. - // A simple way is to estimate the typical standard deviation from the images. - float m_variance; - - // Dynamic array for the mixture of Gaussians - GMM* m_modes; - - // Number of Gaussian components per pixel - BwImage m_modes_per_pixel; - - // Current background model - RgbImage m_background; - }; - } + namespace BackgroundSubtraction + { + typedef struct GMMGaussian + { + float variance; + float muR; + float muG; + float muB; + float weight; + float significants; // this is equal to weight / standard deviation and is used to + // determine which Gaussians should be part of the background model + } GMM; + + // --- User adjustable parameters used by the Grimson GMM BGS algorithm --- + class GrimsonParams : public BgsParams + { + public: + float &LowThreshold() { return m_low_threshold; } + float &HighThreshold() { return m_high_threshold; } + + float &Alpha() { return m_alpha; } + int &MaxModes() { return m_max_modes; } + + private: + // Threshold on the squared dist. to decide when a sample is close to an existing + // components. If it is not close to any a new component will be generated. + // Smaller threshold values lead to more generated components and higher threshold values + // lead to a small number of components but they can grow too large. + // + // It is usual easiest to think of these thresholds as being the number of variances away + // from the mean of a pixel before it is considered to be from the foreground. + float m_low_threshold; + float m_high_threshold; + + // alpha - speed of update - if the time interval you want to average over is T + // set alpha=1/T. + float m_alpha; + + // Maximum number of modes (Gaussian components) that will be used per pixel + int m_max_modes; + }; + + // --- Grimson GMM BGS algorithm --- + class GrimsonGMM : public Bgs + { + public: + GrimsonGMM(); + ~GrimsonGMM(); + + void Initalize(const BgsParams& param); + + void InitModel(const RgbImage& data); + void Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask); + void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); + + RgbImage* Background(); + + private: + void SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char& numModes, + unsigned char& lowThreshold, unsigned char& highThreshold); + + // User adjustable parameters + GrimsonParams m_params; + + // Threshold when the component becomes significant enough to be included into + // the background model. It is the TB = 1-cf from the paper. So I use cf=0.1 => TB=0.9 + // For alpha=0.001 it means that the mode should exist for approximately 105 frames before + // it is considered foreground + float m_bg_threshold; //1-cf from the paper + + // Initial variance for the newly generated components. + // It will will influence the speed of adaptation. A good guess should be made. + // A simple way is to estimate the typical standard deviation from the images. + float m_variance; + + // Dynamic array for the mixture of Gaussians + GMM* m_modes; + + // Number of Gaussian components per pixel + BwImage m_modes_per_pixel; + + // Current background model + RgbImage m_background; + }; + } } - -#endif diff --git a/package_bgs/dp/Image.cpp b/package_bgs/dp/Image.cpp index f00febd..92119c2 100644 --- a/package_bgs/dp/Image.cpp +++ b/package_bgs/dp/Image.cpp @@ -18,59 +18,59 @@ along with BGSLibrary. If not, see . * * Image.hpp * -* Purpose: C++ wrapper for OpenCV IplImage which supports simple and +* Purpose: C++ wrapper for OpenCV IplImage which supports simple and * efficient access to the image data * * Author: Donovan Parks, September 2007 * -* Based on code from: +* Based on code from: * http://www.cs.iit.edu/~agam/cs512/lect-notes/opencv-intro/opencv-intro.hpptml ******************************************************************************/ #include "Image.h" ImageBase::~ImageBase() -{ - if(imgp != NULL && m_bReleaseMemory) +{ + if (imgp != NULL && m_bReleaseMemory) cvReleaseImage(&imgp); - imgp = NULL; + imgp = NULL; } void DensityFilter(BwImage& image, BwImage& filtered, int minDensity, unsigned char fgValue) { - for(int r = 1; r < image.Ptr()->height-1; ++r) + for (int r = 1; r < image.Ptr()->height - 1; ++r) { - for(int c = 1; c < image.Ptr()->width-1; ++c) + for (int c = 1; c < image.Ptr()->width - 1; ++c) { int count = 0; - if(image(r,c) == fgValue) + if (image(r, c) == fgValue) { - if(image(r-1,c-1) == fgValue) + if (image(r - 1, c - 1) == fgValue) count++; - if(image(r-1,c) == fgValue) + if (image(r - 1, c) == fgValue) count++; - if(image(r-1,c+1) == fgValue) + if (image(r - 1, c + 1) == fgValue) count++; - if(image(r,c-1) == fgValue) + if (image(r, c - 1) == fgValue) count++; - if(image(r,c+1) == fgValue) + if (image(r, c + 1) == fgValue) count++; - if(image(r+1,c-1) == fgValue) + if (image(r + 1, c - 1) == fgValue) count++; - if(image(r+1,c) == fgValue) + if (image(r + 1, c) == fgValue) count++; - if(image(r+1,c+1) == fgValue) + if (image(r + 1, c + 1) == fgValue) count++; - if(count < minDensity) - filtered(r,c) = 0; + if (count < minDensity) + filtered(r, c) = 0; else - filtered(r,c) = fgValue; + filtered(r, c) = fgValue; } else { - filtered(r,c) = 0; + filtered(r, c) = 0; } } } -} \ No newline at end of file +} diff --git a/package_bgs/dp/Image.h b/package_bgs/dp/Image.h index 58fe50d..6c0b5b3 100644 --- a/package_bgs/dp/Image.h +++ b/package_bgs/dp/Image.h @@ -18,17 +18,15 @@ along with BGSLibrary. If not, see . * * Image.h * -* Purpose: C++ wrapper for OpenCV IplImage which supports simple and +* Purpose: C++ wrapper for OpenCV IplImage which supports simple and * efficient access to the image data * * Author: Donovan Parks, September 2007 * -* Based on code from: +* Based on code from: * http://www.cs.iit.edu/~agam/cs512/lect-notes/opencv-intro/opencv-intro.html ******************************************************************************/ - -#ifndef _IMAGE_H_ -#define _IMAGE_H_ +#pragma once #include //#include @@ -36,30 +34,30 @@ along with BGSLibrary. If not, see . // --- Image Iterator --------------------------------------------------------- template -class ImageIterator +class ImageIterator { -public: - ImageIterator(IplImage* image, int x=0, int y=0, int dx= 0, int dy=0) : - i(x), j(y), i0(0) - { +public: + ImageIterator(IplImage* image, int x = 0, int y = 0, int dx = 0, int dy = 0) : + i(x), j(y), i0(0) + { data = reinterpret_cast(image->imageData); step = image->widthStep / sizeof(T); - nl= image->height; - if ((y+dy)>0 && (y+dy) < nl) - nl= y+dy; + nl = image->height; + if ((y + dy) > 0 && (y + dy) < nl) + nl = y + dy; - if (y<0) - j=0; + if (y < 0) + j = 0; data += step*j; nc = image->width; - if ((x+dx) > 0 && (x+dx) < nc) - nc = x+dx; + if ((x + dx) > 0 && (x + dx) < nc) + nc = x + dx; nc *= image->nChannels; - if (x>0) + if (x > 0) i0 = x*image->nChannels; i = i0; @@ -71,26 +69,26 @@ class ImageIterator bool operator!() const { return j < nl; } /* next pixel */ - ImageIterator& operator++() + ImageIterator& operator++() { i++; - if (i >= nc) - { - i=i0; - j++; - data += step; + if (i >= nc) + { + i = i0; + j++; + data += step; } return *this; } - ImageIterator& operator+=(int s) + ImageIterator& operator+=(int s) { - i+=s; - if (i >= nc) - { - i=i0; - j++; - data += step; + i += s; + if (i >= nc) + { + i = i0; + j++; + data += step; } return *this; } @@ -101,18 +99,18 @@ class ImageIterator const T operator*() const { return data[i]; } const T neighbor(int dx, int dy) const - { - return *(data+dy*step+i+dx); + { + return *(data + dy*step + i + dx); } - T* operator&() const { return data+i; } + T* operator&() const { return data + i; } /* current pixel coordinates */ - int column() const { return i/nch; } + int column() const { return i / nch; } int line() const { return j; } private: - int i, i0,j; + int i, i0, j; T* data; int step; int nl, nc; @@ -128,7 +126,7 @@ const unsigned char NUM_CHANNELS = 3; class RgbPixel { public: - RgbPixel() {;} + RgbPixel() { ; } RgbPixel(unsigned char _r, unsigned char _g, unsigned char _b) { ch[0] = _r; ch[1] = _g; ch[2] = _b; @@ -156,7 +154,7 @@ class RgbPixel class RgbPixelFloat { public: - RgbPixelFloat() {;} + RgbPixelFloat() { ; } RgbPixelFloat(float _r, float _g, float _b) { ch[0] = _r; ch[1] = _g; ch[2] = _b; @@ -199,14 +197,14 @@ class ImageBase cvReleaseImage(&imgp); } - void operator=(IplImage* img) - { + void operator=(IplImage* img) + { imgp = img; } // copy-constructor ImageBase(const ImageBase& rhs) - { + { // it is very inefficent if this copy-constructor is called assert(false); } @@ -237,31 +235,31 @@ class RgbImage : public ImageBase cvZero(imgp); } - void operator=(IplImage* img) - { + void operator=(IplImage* img) + { imgp = img; } // channel-level access using image(row, col, channel) inline unsigned char& operator()(const int r, const int c, const int ch) { - return (unsigned char &)imgp->imageData[r*imgp->widthStep+c*imgp->nChannels+ch]; + return (unsigned char &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels + ch]; } inline const unsigned char& operator()(const int r, const int c, const int ch) const { - return (unsigned char &)imgp->imageData[r*imgp->widthStep+c*imgp->nChannels+ch]; + return (unsigned char &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels + ch]; } // RGB pixel-level access using image(row, col) - inline RgbPixel& operator()(const int r, const int c) + inline RgbPixel& operator()(const int r, const int c) { - return (RgbPixel &)imgp->imageData[r*imgp->widthStep+c*imgp->nChannels]; + return (RgbPixel &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels]; } inline const RgbPixel& operator()(const int r, const int c) const { - return (RgbPixel &)imgp->imageData[r*imgp->widthStep+c*imgp->nChannels]; + return (RgbPixel &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels]; } }; @@ -275,31 +273,31 @@ class RgbImageFloat : public ImageBase cvZero(imgp); } - void operator=(IplImage* img) - { + void operator=(IplImage* img) + { imgp = img; } // channel-level access using image(row, col, channel) inline float& operator()(const int r, const int c, const int ch) { - return (float &)imgp->imageData[r*imgp->widthStep+(c*imgp->nChannels+ch)*sizeof(float)]; + return (float &)imgp->imageData[r*imgp->widthStep + (c*imgp->nChannels + ch) * sizeof(float)]; } inline float operator()(const int r, const int c, const int ch) const { - return (float)imgp->imageData[r*imgp->widthStep+(c*imgp->nChannels+ch)*sizeof(float)]; + return (float)imgp->imageData[r*imgp->widthStep + (c*imgp->nChannels + ch) * sizeof(float)]; } // RGB pixel-level access using image(row, col) - inline RgbPixelFloat& operator()(const int r, const int c) + inline RgbPixelFloat& operator()(const int r, const int c) { - return (RgbPixelFloat &)imgp->imageData[r*imgp->widthStep+c*imgp->nChannels*sizeof(float)]; + return (RgbPixelFloat &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels * sizeof(float)]; } inline const RgbPixelFloat& operator()(const int r, const int c) const { - return (RgbPixelFloat &)imgp->imageData[r*imgp->widthStep+c*imgp->nChannels*sizeof(float)]; + return (RgbPixelFloat &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels * sizeof(float)]; } }; @@ -313,20 +311,20 @@ class BwImage : public ImageBase cvZero(imgp); } - void operator=(IplImage* img) - { + void operator=(IplImage* img) + { imgp = img; } // pixel-level access using image(row, col) inline unsigned char& operator()(const int r, const int c) { - return (unsigned char &)imgp->imageData[r*imgp->widthStep+c]; + return (unsigned char &)imgp->imageData[r*imgp->widthStep + c]; } inline unsigned char operator()(const int r, const int c) const { - return (unsigned char)imgp->imageData[r*imgp->widthStep+c]; + return (unsigned char)imgp->imageData[r*imgp->widthStep + c]; } }; @@ -340,25 +338,23 @@ class BwImageFloat : public ImageBase cvZero(imgp); } - void operator=(IplImage* img) - { + void operator=(IplImage* img) + { imgp = img; } // pixel-level access using image(row, col) inline float& operator()(const int r, const int c) { - return (float &)imgp->imageData[r*imgp->widthStep+c*sizeof(float)]; + return (float &)imgp->imageData[r*imgp->widthStep + c * sizeof(float)]; } inline float operator()(const int r, const int c) const { - return (float)imgp->imageData[r*imgp->widthStep+c*sizeof(float)]; + return (float)imgp->imageData[r*imgp->widthStep + c * sizeof(float)]; } }; // --- Image Functions -------------------------------------------------------- void DensityFilter(BwImage& image, BwImage& filtered, int minDensity, unsigned char fgValue); - -#endif diff --git a/package_bgs/dp/MeanBGS.cpp b/package_bgs/dp/MeanBGS.cpp index b8df895..5178158 100644 --- a/package_bgs/dp/MeanBGS.cpp +++ b/package_bgs/dp/MeanBGS.cpp @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * MeanBGS.h * -* Purpose: Implementation of a simple temporal mean background +* Purpose: Implementation of a simple temporal mean background * subtraction algorithm. * * Author: Donovan Parks, September 2007 @@ -31,72 +31,72 @@ using namespace Algorithms::BackgroundSubtraction; void MeanBGS::Initalize(const BgsParams& param) { - m_params = (MeanParams&)param; + m_params = (MeanParams&)param; - m_mean = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_32F, 3); - m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); + m_mean = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_32F, 3); + m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); } void MeanBGS::InitModel(const RgbImage& data) { - for (unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - for(int ch = 0; ch < NUM_CHANNELS; ++ch) - { - m_mean(r,c,ch) = (float)data(r,c,ch); - } - } - } + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + for (int ch = 0; ch < NUM_CHANNELS; ++ch) + { + m_mean(r, c, ch) = (float)data(r, c, ch); + } + } + } } -void MeanBGS::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) +void MeanBGS::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) { - // update background model - for (unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - // perform conditional updating only if we are passed the learning phase - if(update_mask(r,c) == BACKGROUND || frame_num < m_params.LearningFrames()) - { - // update B/G model - float mean; - for(int ch = 0; ch < NUM_CHANNELS; ++ch) - { - mean = m_params.Alpha() * m_mean(r,c,ch) + (1.0f-m_params.Alpha()) * data(r,c,ch); - m_mean(r,c,ch) = mean; - m_background(r,c,ch) = (unsigned char)(mean + 0.5); - } - } - } - } + // update background model + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + // perform conditional updating only if we are passed the learning phase + if (update_mask(r, c) == BACKGROUND || frame_num < m_params.LearningFrames()) + { + // update B/G model + float mean; + for (int ch = 0; ch < NUM_CHANNELS; ++ch) + { + mean = m_params.Alpha() * m_mean(r, c, ch) + (1.0f - m_params.Alpha()) * data(r, c, ch); + m_mean(r, c, ch) = mean; + m_background(r, c, ch) = (unsigned char)(mean + 0.5); + } + } + } + } } -void MeanBGS::SubtractPixel(int r, int c, const RgbPixel& pixel, - unsigned char& low_threshold, - unsigned char& high_threshold) +void MeanBGS::SubtractPixel(int r, int c, const RgbPixel& pixel, + unsigned char& low_threshold, + unsigned char& high_threshold) { - // calculate distance to sample point - float dist = 0; - for(int ch = 0; ch < NUM_CHANNELS; ++ch) - { - dist += (pixel(ch)-m_mean(r,c,ch))*(pixel(ch)-m_mean(r,c,ch)); - } - - // determine if sample point is F/G or B/G pixel - low_threshold = BACKGROUND; - if(dist > m_params.LowThreshold()) - { - low_threshold = FOREGROUND; - } - - high_threshold = BACKGROUND; - if(dist > m_params.HighThreshold()) - { - high_threshold = FOREGROUND; - } + // calculate distance to sample point + float dist = 0; + for (int ch = 0; ch < NUM_CHANNELS; ++ch) + { + dist += (pixel(ch) - m_mean(r, c, ch))*(pixel(ch) - m_mean(r, c, ch)); + } + + // determine if sample point is F/G or B/G pixel + low_threshold = BACKGROUND; + if (dist > m_params.LowThreshold()) + { + low_threshold = FOREGROUND; + } + + high_threshold = BACKGROUND; + if (dist > m_params.HighThreshold()) + { + high_threshold = FOREGROUND; + } } /////////////////////////////////////////////////////////////////////////////// @@ -106,26 +106,22 @@ void MeanBGS::SubtractPixel(int r, int c, const RgbPixel& pixel, // output - a pointer to the data of a gray value image of the same size // values: 255-foreground, 0-background /////////////////////////////////////////////////////////////////////////////// -void MeanBGS::Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask) +void MeanBGS::Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask) { - unsigned char low_threshold, high_threshold; - - // update each pixel of the image - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - // perform background subtraction + update background model - SubtractPixel(r, c, data(r,c), low_threshold, high_threshold); - - // setup silhouette mask - low_threshold_mask(r,c) = low_threshold; - high_threshold_mask(r,c) = high_threshold; - } - } + unsigned char low_threshold, high_threshold; + + // update each pixel of the image + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + // perform background subtraction + update background model + SubtractPixel(r, c, data(r, c), low_threshold, high_threshold); + + // setup silhouette mask + low_threshold_mask(r, c) = low_threshold; + high_threshold_mask(r, c) = high_threshold; + } + } } - - - - diff --git a/package_bgs/dp/MeanBGS.h b/package_bgs/dp/MeanBGS.h index 247e094..306af1a 100644 --- a/package_bgs/dp/MeanBGS.h +++ b/package_bgs/dp/MeanBGS.h @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * MeanBGS.hpp * -* Purpose: Implementation of a simple temporal mean background +* Purpose: Implementation of a simple temporal mean background * subtraction algorithm. * * Author: Donovan Parks, September 2007 @@ -28,13 +28,14 @@ along with BGSLibrary. If not, see . Algorithms::BackgroundSubtraction::MeanParams params; params.SetFrameSize(width, height); params.LowThreshold() = 3*30*30; -params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing +params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing params.Alpha() = 1e-6f; params.LearningFrames() = 30; Algorithms::BackgroundSubtraction::MeanBGS bgs; bgs.Initalize(params); ******************************************************************************/ +#pragma once #include "Bgs.h" @@ -54,7 +55,7 @@ namespace Algorithms int &LearningFrames() { return m_learning_frames; } private: - // A pixel is considered to be from the background if the squared distance between + // A pixel is considered to be from the background if the squared distance between // it and the background model is less than the threshold. unsigned int m_low_threshold; unsigned int m_high_threshold; @@ -73,14 +74,14 @@ namespace Algorithms void Initalize(const BgsParams& param); void InitModel(const RgbImage& data); - void Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask); - void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); + void Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask); + void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); RgbImage* Background() { return &m_background; } - private: - void SubtractPixel(int r, int c, const RgbPixel& pixel, + private: + void SubtractPixel(int r, int c, const RgbPixel& pixel, unsigned char& lowThreshold, unsigned char& highThreshold); MeanParams m_params; @@ -91,8 +92,3 @@ namespace Algorithms } } - - - - - diff --git a/package_bgs/dp/PratiMediodBGS.cpp b/package_bgs/dp/PratiMediodBGS.cpp index 2256ceb..5a15cec 100644 --- a/package_bgs/dp/PratiMediodBGS.cpp +++ b/package_bgs/dp/PratiMediodBGS.cpp @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * PratiMediodBGS.h * -* Purpose: Implementation of the temporal median background +* Purpose: Implementation of the temporal median background * subtraction algorithm described in: * * [1] "Detecting Moving Objects, Shosts, and Shadows in Video Stream" @@ -29,7 +29,7 @@ along with BGSLibrary. If not, see . * * Author: Donovan Parks, September 2007 * -* Please note that this is not an implementation of the complete system +* Please note that this is not an implementation of the complete system * given in the above papers. It simply implements the temporal media background * subtraction algorithm. ******************************************************************************/ @@ -40,202 +40,202 @@ using namespace Algorithms::BackgroundSubtraction; PratiMediodBGS::PratiMediodBGS() { - m_median_buffer = NULL; + m_median_buffer = NULL; } PratiMediodBGS::~PratiMediodBGS() { - delete[] m_median_buffer; + delete[] m_median_buffer; } void PratiMediodBGS::Initalize(const BgsParams& param) { - m_params = (PratiParams&)param; + m_params = (PratiParams&)param; - m_mask_low_threshold = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 1); - m_mask_high_threshold = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 1); + m_mask_low_threshold = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 1); + m_mask_high_threshold = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 1); - m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); + m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); - m_median_buffer = new MEDIAN_BUFFER[m_params.Size()]; + m_median_buffer = new MEDIAN_BUFFER[m_params.Size()]; } void PratiMediodBGS::InitModel(const RgbImage& data) { - // there is no need to initialize the mode since it needs a buffer of frames - // before it can performing background subtraction + // there is no need to initialize the mode since it needs a buffer of frames + // before it can performing background subtraction } -void PratiMediodBGS::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) +void PratiMediodBGS::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) { - // update the image buffer with the new frame and calculate new median values - if(frame_num % m_params.SamplingRate() == 0) - { - if(m_median_buffer[0].dist.size() == m_params.HistorySize()) - { - // subtract distance to sample being removed from all distances - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - int i = r*m_params.Width()+c; - - if(update_mask(r,c) == BACKGROUND) - { - int oldPos = m_median_buffer[i].pos; - for(unsigned int s = 0; s < m_median_buffer[i].pixels.size(); ++s) - { - int maxDist = 0; - for(int ch = 0; ch < NUM_CHANNELS; ++ch) - { - int tempDist = abs(m_median_buffer[i].pixels.at(oldPos)(ch) - - m_median_buffer[i].pixels.at(s)(ch)); - if(tempDist > maxDist) - maxDist = tempDist; - } - - m_median_buffer[i].dist.at(s) -= maxDist; - } - - int dist; - UpdateMediod(r, c, data, dist); - m_median_buffer[i].dist.at(oldPos) = dist; - m_median_buffer[i].pixels.at(oldPos) = data(r,c); - m_median_buffer[i].pos++; - if(m_median_buffer[i].pos >= m_params.HistorySize()) - m_median_buffer[i].pos = 0; - } - } - } - } - else - { - // calculate sum of L-inf distances for new point and - // add distance from each sample point to this point to their L-inf sum - int dist; - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - int index = r*m_params.Width()+c; - UpdateMediod(r, c, data, dist); - m_median_buffer[index].dist.push_back(dist); - m_median_buffer[index].pos = 0; - m_median_buffer[index].pixels.push_back(data(r,c)); - } - } - } - } + // update the image buffer with the new frame and calculate new median values + if (frame_num % m_params.SamplingRate() == 0) + { + if (m_median_buffer[0].dist.size() == m_params.HistorySize()) + { + // subtract distance to sample being removed from all distances + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + int i = r*m_params.Width() + c; + + if (update_mask(r, c) == BACKGROUND) + { + int oldPos = m_median_buffer[i].pos; + for (unsigned int s = 0; s < m_median_buffer[i].pixels.size(); ++s) + { + int maxDist = 0; + for (int ch = 0; ch < NUM_CHANNELS; ++ch) + { + int tempDist = abs(m_median_buffer[i].pixels.at(oldPos)(ch) + - m_median_buffer[i].pixels.at(s)(ch)); + if (tempDist > maxDist) + maxDist = tempDist; + } + + m_median_buffer[i].dist.at(s) -= maxDist; + } + + int dist; + UpdateMediod(r, c, data, dist); + m_median_buffer[i].dist.at(oldPos) = dist; + m_median_buffer[i].pixels.at(oldPos) = data(r, c); + m_median_buffer[i].pos++; + if (m_median_buffer[i].pos >= m_params.HistorySize()) + m_median_buffer[i].pos = 0; + } + } + } + } + else + { + // calculate sum of L-inf distances for new point and + // add distance from each sample point to this point to their L-inf sum + int dist; + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + int index = r*m_params.Width() + c; + UpdateMediod(r, c, data, dist); + m_median_buffer[index].dist.push_back(dist); + m_median_buffer[index].pos = 0; + m_median_buffer[index].pixels.push_back(data(r, c)); + } + } + } + } } void PratiMediodBGS::UpdateMediod(int r, int c, const RgbImage& new_frame, int& dist) { - // calculate sum of L-inf distances for new point and - // add distance from each sample point to this point to their L-inf sum - unsigned int i = (r*m_params.Width()+c); - - m_median_buffer[i].medianDist = INT_MAX; - - int L_inf_dist = 0; - for(unsigned int s = 0; s < m_median_buffer[i].dist.size(); ++s) - { - int maxDist = 0; - for(int ch = 0; ch < NUM_CHANNELS; ++ch) - { - int tempDist = abs(m_median_buffer[i].pixels.at(s)(ch) - new_frame(r,c,ch)); - if(tempDist > maxDist) - maxDist = tempDist; - } - - // check if point from this frame in the image buffer is the median - m_median_buffer[i].dist.at(s) += maxDist; - if(m_median_buffer[i].dist.at(s) < m_median_buffer[i].medianDist) - { - m_median_buffer[i].medianDist = m_median_buffer[i].dist.at(s); - m_median_buffer[i].median = m_median_buffer[i].pixels.at(s); - } - - L_inf_dist += maxDist; - } - - dist = L_inf_dist; - - // check if the new point is the median - if(L_inf_dist < m_median_buffer[i].medianDist) - { - m_median_buffer[i].medianDist = L_inf_dist; - m_median_buffer[i].median = new_frame(r,c); - } + // calculate sum of L-inf distances for new point and + // add distance from each sample point to this point to their L-inf sum + unsigned int i = (r*m_params.Width() + c); + + m_median_buffer[i].medianDist = INT_MAX; + + int L_inf_dist = 0; + for (unsigned int s = 0; s < m_median_buffer[i].dist.size(); ++s) + { + int maxDist = 0; + for (int ch = 0; ch < NUM_CHANNELS; ++ch) + { + int tempDist = abs(m_median_buffer[i].pixels.at(s)(ch) - new_frame(r, c, ch)); + if (tempDist > maxDist) + maxDist = tempDist; + } + + // check if point from this frame in the image buffer is the median + m_median_buffer[i].dist.at(s) += maxDist; + if (m_median_buffer[i].dist.at(s) < m_median_buffer[i].medianDist) + { + m_median_buffer[i].medianDist = m_median_buffer[i].dist.at(s); + m_median_buffer[i].median = m_median_buffer[i].pixels.at(s); + } + + L_inf_dist += maxDist; + } + + dist = L_inf_dist; + + // check if the new point is the median + if (L_inf_dist < m_median_buffer[i].medianDist) + { + m_median_buffer[i].medianDist = L_inf_dist; + m_median_buffer[i].median = new_frame(r, c); + } } void PratiMediodBGS::Combine(const BwImage& low_mask, const BwImage& high_mask, BwImage& output) { - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - output(r,c) = BACKGROUND; - - if(r == 0 || c == 0 || r == m_params.Height()-1 || c == m_params.Width()-1) - continue; - - if(high_mask(r,c) == FOREGROUND) - { - output(r,c) = FOREGROUND; - } - else if(low_mask(r,c) == FOREGROUND) - { - // consider the pixel to be a F/G pixel if it is 8-connected to - // a F/G pixel in the high mask - // check if there is an 8-connected foreground pixel - if(high_mask(r-1,c-1)) - output(r,c) = FOREGROUND; - else if(high_mask(r-1,c)) - output(r,c) = FOREGROUND; - else if(high_mask(r-1,c+1)) - output(r,c) = FOREGROUND; - else if(high_mask(r,c-1)) - output(r,c) = FOREGROUND; - else if(high_mask(r,c+1)) - output(r,c) = FOREGROUND; - else if(high_mask(r+1,c-1)) - output(r,c) = FOREGROUND; - else if(high_mask(r+1,c)) - output(r,c) = FOREGROUND; - else if(high_mask(r+1,c+1)) - output(r,c) = FOREGROUND; - } - } - } + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + output(r, c) = BACKGROUND; + + if (r == 0 || c == 0 || r == m_params.Height() - 1 || c == m_params.Width() - 1) + continue; + + if (high_mask(r, c) == FOREGROUND) + { + output(r, c) = FOREGROUND; + } + else if (low_mask(r, c) == FOREGROUND) + { + // consider the pixel to be a F/G pixel if it is 8-connected to + // a F/G pixel in the high mask + // check if there is an 8-connected foreground pixel + if (high_mask(r - 1, c - 1)) + output(r, c) = FOREGROUND; + else if (high_mask(r - 1, c)) + output(r, c) = FOREGROUND; + else if (high_mask(r - 1, c + 1)) + output(r, c) = FOREGROUND; + else if (high_mask(r, c - 1)) + output(r, c) = FOREGROUND; + else if (high_mask(r, c + 1)) + output(r, c) = FOREGROUND; + else if (high_mask(r + 1, c - 1)) + output(r, c) = FOREGROUND; + else if (high_mask(r + 1, c)) + output(r, c) = FOREGROUND; + else if (high_mask(r + 1, c + 1)) + output(r, c) = FOREGROUND; + } + } + } } void PratiMediodBGS::CalculateMasks(int r, int c, const RgbPixel& pixel) { - int pos = r*m_params.Width()+c; - - // calculate l-inf distance between current value and median value - unsigned char dist = 0; - for(int ch = 0; ch < NUM_CHANNELS; ++ch) - { - int tempDist = abs(pixel(ch) - m_median_buffer[pos].median(ch)); - if(tempDist > dist) - dist = tempDist; - } - m_background(r,c) = m_median_buffer[pos].median; - - // check if pixel is a B/G or F/G pixel according to the low threshold B/G model - m_mask_low_threshold(r,c) = BACKGROUND; - if(dist > m_params.LowThreshold()) - { - m_mask_low_threshold(r,c) = FOREGROUND; - } - - // check if pixel is a B/G or F/G pixel according to the high threshold B/G model - m_mask_high_threshold(r,c)= BACKGROUND; - if(dist > m_params.HighThreshold()) - { - m_mask_high_threshold(r,c) = FOREGROUND; - } + int pos = r*m_params.Width() + c; + + // calculate l-inf distance between current value and median value + unsigned char dist = 0; + for (int ch = 0; ch < NUM_CHANNELS; ++ch) + { + int tempDist = abs(pixel(ch) - m_median_buffer[pos].median(ch)); + if (tempDist > dist) + dist = tempDist; + } + m_background(r, c) = m_median_buffer[pos].median; + + // check if pixel is a B/G or F/G pixel according to the low threshold B/G model + m_mask_low_threshold(r, c) = BACKGROUND; + if (dist > m_params.LowThreshold()) + { + m_mask_low_threshold(r, c) = FOREGROUND; + } + + // check if pixel is a B/G or F/G pixel according to the high threshold B/G model + m_mask_high_threshold(r, c) = BACKGROUND; + if (dist > m_params.HighThreshold()) + { + m_mask_high_threshold(r, c) = FOREGROUND; + } } /////////////////////////////////////////////////////////////////////////////// @@ -245,29 +245,29 @@ void PratiMediodBGS::CalculateMasks(int r, int c, const RgbPixel& pixel) // output - a pointer to the data of a gray value image of the same size // values: 255-foreground, 0-background /////////////////////////////////////////////////////////////////////////////// -void PratiMediodBGS::Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mark, BwImage& high_threshold_mark) +void PratiMediodBGS::Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mark, BwImage& high_threshold_mark) { - if(frame_num < m_params.HistorySize()) - { - low_threshold_mark.Clear(); - high_threshold_mark.Clear(); - return; - } - - // update each pixel of the image - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - // need at least one frame of data before we can start calculating the masks - CalculateMasks(r, c, data(r,c)); - } - } - - // combine low and high threshold masks - Combine(m_mask_low_threshold, m_mask_high_threshold, low_threshold_mark); - Combine(m_mask_low_threshold, m_mask_high_threshold, high_threshold_mark); + if (frame_num < m_params.HistorySize()) + { + low_threshold_mark.Clear(); + high_threshold_mark.Clear(); + return; + } + + // update each pixel of the image + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + // need at least one frame of data before we can start calculating the masks + CalculateMasks(r, c, data(r, c)); + } + } + + // combine low and high threshold masks + Combine(m_mask_low_threshold, m_mask_high_threshold, low_threshold_mark); + Combine(m_mask_low_threshold, m_mask_high_threshold, high_threshold_mark); } diff --git a/package_bgs/dp/PratiMediodBGS.h b/package_bgs/dp/PratiMediodBGS.h index bc8cb7f..6bc8f92 100644 --- a/package_bgs/dp/PratiMediodBGS.h +++ b/package_bgs/dp/PratiMediodBGS.h @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * PratiMediodBGS.hpp * -* Purpose: Implementation of the temporal median background +* Purpose: Implementation of the temporal median background * subtraction algorithm described in: * * [1] "Detecting Moving Objects, Shosts, and Shadows in Video Stream" @@ -29,7 +29,7 @@ along with BGSLibrary. If not, see . * * Author: Donovan Parks, September 2007 * -* Please note that this is not an implementation of the complete system +* Please note that this is not an implementation of the complete system * given in the above papers. It simply implements the temporal media background * subtraction algorithm. @@ -37,7 +37,7 @@ along with BGSLibrary. If not, see . Algorithms::BackgroundSubtraction::PratiParams params; params.SetFrameSize(width, height); params.LowThreshold() = 30; -params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing +params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing params.SamplingRate() = 5; params.HistorySize() = 16; params.Weight() = 5; @@ -45,9 +45,7 @@ params.Weight() = 5; Algorithms::BackgroundSubtraction::PratiMediodBGS bgs; bgs.Initalize(params); ******************************************************************************/ - -#ifndef PRATI_MEDIA_BGS_H -#define PRATI_MEDIA_BGS_H +#pragma once #include #include "Bgs.h" @@ -68,9 +66,9 @@ namespace Algorithms int &HistorySize() { return m_history_size; } private: - // The low threshold is used to supress noise. The high thresohld is used - // to find pixels highly likely to be foreground. This implementation uses an L-inf - // distance measure and a pixel p is considered F/G if D(I(p), B(p)) > threshold. + // The low threshold is used to supress noise. The high thresohld is used + // to find pixels highly likely to be foreground. This implementation uses an L-inf + // distance measure and a pixel p is considered F/G if D(I(p), B(p)) > threshold. // The two threshold maps are combined as in [2]. unsigned int m_low_threshold; unsigned int m_high_threshold; @@ -88,9 +86,9 @@ namespace Algorithms }; // --- Prati Mediod BGS algorithm --- - class PratiMediodBGS : public Bgs + class PratiMediodBGS : public Bgs { - private: + private: // sum of L-inf distances from a sample point to all other sample points struct MEDIAN_BUFFER { @@ -109,13 +107,13 @@ namespace Algorithms void Initalize(const BgsParams& param); void InitModel(const RgbImage& data); - void Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask); - void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); + void Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask); + void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); RgbImage* Background() { return &m_background; } - private: + private: MEDIAN_BUFFER* m_median_buffer; void CalculateMasks(int r, int c, const RgbPixel& pixel); @@ -132,11 +130,3 @@ namespace Algorithms } } - -#endif - - - - - - diff --git a/package_bgs/dp/TextureBGS.cpp b/package_bgs/dp/TextureBGS.cpp index 62684f8..920f140 100644 --- a/package_bgs/dp/TextureBGS.cpp +++ b/package_bgs/dp/TextureBGS.cpp @@ -16,40 +16,40 @@ along with BGSLibrary. If not, see . */ #include "TextureBGS.h" -TextureBGS::TextureBGS(){} -TextureBGS::~TextureBGS(){} +TextureBGS::TextureBGS() {} +TextureBGS::~TextureBGS() {} void TextureBGS::LBP(RgbImage& image, RgbImage& texture) { - for(int y = TEXTURE_R; y < image.Ptr()->height-TEXTURE_R; ++y) + for (int y = TEXTURE_R; y < image.Ptr()->height - TEXTURE_R; ++y) { - for(int x = TEXTURE_R; x < image.Ptr()->width-TEXTURE_R; ++x) - { - for(int ch = 0; ch < NUM_CHANNELS; ++ch) + for (int x = TEXTURE_R; x < image.Ptr()->width - TEXTURE_R; ++x) + { + for (int ch = 0; ch < NUM_CHANNELS; ++ch) { unsigned char textureCode = 0; int centerValue = (int)image(y, x, ch); // this only works for a texture radius of 2 - if(centerValue - (int)image(y-2, x, ch) + HYSTERSIS >= 0) + if (centerValue - (int)image(y - 2, x, ch) + HYSTERSIS >= 0) textureCode += 1; - if(centerValue - (int)image(y-1, x-2, ch) + HYSTERSIS >= 0) + if (centerValue - (int)image(y - 1, x - 2, ch) + HYSTERSIS >= 0) textureCode += 2; - if(centerValue - (int)image(y-1, x+2, ch) + HYSTERSIS >= 0) + if (centerValue - (int)image(y - 1, x + 2, ch) + HYSTERSIS >= 0) textureCode += 4; - if(centerValue - (int)image(y+1, x-2, ch) + HYSTERSIS >= 0) + if (centerValue - (int)image(y + 1, x - 2, ch) + HYSTERSIS >= 0) textureCode += 8; - if(centerValue - (int)image(y+1, x+2, ch) + HYSTERSIS >= 0) + if (centerValue - (int)image(y + 1, x + 2, ch) + HYSTERSIS >= 0) textureCode += 16; - if(centerValue - (int)image(y+2, x, ch) + HYSTERSIS >= 0) + if (centerValue - (int)image(y + 2, x, ch) + HYSTERSIS >= 0) textureCode += 32; - texture(y,x,ch) = textureCode; + texture(y, x, ch) = textureCode; } } } @@ -58,14 +58,14 @@ void TextureBGS::LBP(RgbImage& image, RgbImage& texture) void TextureBGS::Histogram(RgbImage& texture, TextureHistogram* curTextureHist) { // calculate histogram within a 2*REGION_R square - for(int y = REGION_R+TEXTURE_R; y < texture.Ptr()->height-REGION_R-TEXTURE_R; ++y) + for (int y = REGION_R + TEXTURE_R; y < texture.Ptr()->height - REGION_R - TEXTURE_R; ++y) { - for(int x = REGION_R+TEXTURE_R; x < texture.Ptr()->width-REGION_R-TEXTURE_R; ++x) - { - int index = x+y*(texture.Ptr()->width); + for (int x = REGION_R + TEXTURE_R; x < texture.Ptr()->width - REGION_R - TEXTURE_R; ++x) + { + int index = x + y*(texture.Ptr()->width); // clear histogram - for(int i = 0; i < NUM_BINS; ++i) + for (int i = 0; i < NUM_BINS; ++i) { curTextureHist[index].r[i] = 0; curTextureHist[index].g[i] = 0; @@ -73,13 +73,13 @@ void TextureBGS::Histogram(RgbImage& texture, TextureHistogram* curTextureHist) } // calculate histogram - for(int j = -REGION_R; j <= REGION_R; ++j) + for (int j = -REGION_R; j <= REGION_R; ++j) { - for(int i = -REGION_R; i <= REGION_R; ++i) + for (int i = -REGION_R; i <= REGION_R; ++i) { - curTextureHist[index].r[texture(y+j,x+i,2)]++; - curTextureHist[index].g[texture(y+j,x+i,1)]++; - curTextureHist[index].b[texture(y+j,x+i,0)]++; + curTextureHist[index].r[texture(y + j, x + i, 2)]++; + curTextureHist[index].g[texture(y + j, x + i, 1)]++; + curTextureHist[index].b[texture(y + j, x + i, 0)]++; } } } @@ -88,66 +88,66 @@ void TextureBGS::Histogram(RgbImage& texture, TextureHistogram* curTextureHist) int TextureBGS::ProximityMeasure(TextureHistogram& bgTexture, TextureHistogram& curTextureHist) { - int proximity = 0; - for(int i = 0; i < NUM_BINS; ++i) + int proximity = 0; + for (int i = 0; i < NUM_BINS; ++i) { proximity += std::min(bgTexture.r[i], curTextureHist.r[i]); proximity += std::min(bgTexture.g[i], curTextureHist.g[i]); proximity += std::min(bgTexture.b[i], curTextureHist.b[i]); } - return proximity; + return proximity; } -void TextureBGS::BgsCompare(TextureArray* bgModel, TextureHistogram* curTextureHist, - unsigned char* modeArray, float threshold, BwImage& fgMask) +void TextureBGS::BgsCompare(TextureArray* bgModel, TextureHistogram* curTextureHist, + unsigned char* modeArray, float threshold, BwImage& fgMask) { cvZero(fgMask.Ptr()); - for(int y = REGION_R+TEXTURE_R; y < fgMask.Ptr()->height-REGION_R-TEXTURE_R; ++y) + for (int y = REGION_R + TEXTURE_R; y < fgMask.Ptr()->height - REGION_R - TEXTURE_R; ++y) { - for(int x = REGION_R+TEXTURE_R; x < fgMask.Ptr()->width-REGION_R-TEXTURE_R; ++x) - { - int index = x+y*(fgMask.Ptr()->width); + for (int x = REGION_R + TEXTURE_R; x < fgMask.Ptr()->width - REGION_R - TEXTURE_R; ++x) + { + int index = x + y*(fgMask.Ptr()->width); // find closest matching texture in background model int maxProximity = -1; - for(int m = 0; m < NUM_MODES; ++m) + for (int m = 0; m < NUM_MODES; ++m) { int proximity = ProximityMeasure(bgModel[index].mode[m], curTextureHist[index]); - if(proximity > maxProximity) + if (proximity > maxProximity) { maxProximity = proximity; modeArray[index] = m; } } - if(maxProximity < threshold) - fgMask(y,x) = 255; + if (maxProximity < threshold) + fgMask(y, x) = 255; } } } -void TextureBGS::UpdateModel(BwImage& fgMask, TextureArray* bgModel, - TextureHistogram* curTextureHist, unsigned char* modeArray) +void TextureBGS::UpdateModel(BwImage& fgMask, TextureArray* bgModel, + TextureHistogram* curTextureHist, unsigned char* modeArray) { - for(int y = REGION_R+TEXTURE_R; y < fgMask.Ptr()->height-REGION_R-TEXTURE_R; ++y) + for (int y = REGION_R + TEXTURE_R; y < fgMask.Ptr()->height - REGION_R - TEXTURE_R; ++y) { - for(int x = REGION_R+TEXTURE_R; x < fgMask.Ptr()->width-REGION_R-TEXTURE_R; ++x) - { - int index = x+y*(fgMask.Ptr()->width); + for (int x = REGION_R + TEXTURE_R; x < fgMask.Ptr()->width - REGION_R - TEXTURE_R; ++x) + { + int index = x + y*(fgMask.Ptr()->width); - if(fgMask(y,x) == 0) + if (fgMask(y, x) == 0) { - for(int i = 0; i < NUM_BINS; ++i) + for (int i = 0; i < NUM_BINS; ++i) { bgModel[index].mode[modeArray[index]].r[i] - = (unsigned char)(ALPHA*curTextureHist[index].r[i] - + (1-ALPHA)*bgModel[index].mode[modeArray[index]].r[i] + 0.5); + = (unsigned char)(ALPHA*curTextureHist[index].r[i] + + (1 - ALPHA)*bgModel[index].mode[modeArray[index]].r[i] + 0.5); } - } + } } - } + } } diff --git a/package_bgs/dp/TextureBGS.h b/package_bgs/dp/TextureBGS.h index 573e6f9..2650f04 100644 --- a/package_bgs/dp/TextureBGS.h +++ b/package_bgs/dp/TextureBGS.h @@ -14,6 +14,8 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ +#pragma once + #include #include "Image.h" @@ -23,7 +25,7 @@ const int TEXTURE_R = 2; // Note: the code currently assumes this value is 2 const int NUM_BINS = 64; // 2^TEXTURE_POINTS const int HYSTERSIS = 3; const double ALPHA = 0.05f; -const double THRESHOLD = 0.5*(REGION_R+REGION_R+1)*(REGION_R+REGION_R+1)*NUM_CHANNELS; +const double THRESHOLD = 0.5*(REGION_R + REGION_R + 1)*(REGION_R + REGION_R + 1)*NUM_CHANNELS; const int NUM_MODES = 1; // The paper describes how multiple modes can be maintained, // but this implementation does not fully support more than one @@ -48,8 +50,8 @@ class TextureBGS void LBP(RgbImage& image, RgbImage& texture); void Histogram(RgbImage& texture, TextureHistogram* curTextureHist); int ProximityMeasure(TextureHistogram& bgTexture, TextureHistogram& curTextureHist); - void BgsCompare(TextureArray* bgModel, TextureHistogram* curTextureHist, - unsigned char* modeArray, float threshold, BwImage& fgMask); - void UpdateModel(BwImage& fgMask, TextureArray* bgModel, - TextureHistogram* curTextureHist, unsigned char* modeArray); + void BgsCompare(TextureArray* bgModel, TextureHistogram* curTextureHist, + unsigned char* modeArray, float threshold, BwImage& fgMask); + void UpdateModel(BwImage& fgMask, TextureArray* bgModel, + TextureHistogram* curTextureHist, unsigned char* modeArray); }; diff --git a/package_bgs/dp/WrenGA.cpp b/package_bgs/dp/WrenGA.cpp index 16e2640..80a9e27 100644 --- a/package_bgs/dp/WrenGA.cpp +++ b/package_bgs/dp/WrenGA.cpp @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * WrenGA.h * -* Purpose: Implementation of the running Gaussian average background +* Purpose: Implementation of the running Gaussian average background * subtraction algorithm described in: * "Pfinder: real-time tracking of the human body" * by C. Wren et al (1997) @@ -36,114 +36,114 @@ using namespace Algorithms::BackgroundSubtraction; WrenGA::WrenGA() { - m_gaussian = NULL; + m_gaussian = NULL; } WrenGA::~WrenGA() { - delete[] m_gaussian; + delete[] m_gaussian; } void WrenGA::Initalize(const BgsParams& param) { - m_params = (WrenParams&)param; - - m_variance = 36.0f; - - // GMM for each pixel - m_gaussian = new GAUSSIAN[m_params.Size()]; - for(unsigned int i = 0; i < m_params.Size(); ++i) - { - for(int ch = 0; ch < NUM_CHANNELS; ++ch) - { - m_gaussian[i].mu[ch] = 0; - m_gaussian[i].var[ch] = 0; - } - } - - m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); + m_params = (WrenParams&)param; + + m_variance = 36.0f; + + // GMM for each pixel + m_gaussian = new GAUSSIAN[m_params.Size()]; + for (unsigned int i = 0; i < m_params.Size(); ++i) + { + for (int ch = 0; ch < NUM_CHANNELS; ++ch) + { + m_gaussian[i].mu[ch] = 0; + m_gaussian[i].var[ch] = 0; + } + } + + m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); } void WrenGA::InitModel(const RgbImage& data) { - int pos = 0; - - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - for(int ch = 0; ch < NUM_CHANNELS; ++ch) - { - m_gaussian[pos].mu[ch] = data(r,c,ch); - m_gaussian[pos].var[ch] = m_variance; - } - - pos++; - } - } + int pos = 0; + + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + for (int ch = 0; ch < NUM_CHANNELS; ++ch) + { + m_gaussian[pos].mu[ch] = data(r, c, ch); + m_gaussian[pos].var[ch] = m_variance; + } + + pos++; + } + } } -void WrenGA::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) +void WrenGA::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) { - int pos = 0; - - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - // perform conditional updating only if we are passed the learning phase - if(update_mask(r,c) == BACKGROUND || frame_num < m_params.LearningFrames()) - { - float dR = m_gaussian[pos].mu[0] - data(r,c,0); - float dG = m_gaussian[pos].mu[1] - data(r,c,1); - float dB = m_gaussian[pos].mu[2] - data(r,c,2); - - float dist = (dR*dR + dG*dG + dB*dB); - - m_gaussian[pos].mu[0] -= m_params.Alpha()*(dR); - m_gaussian[pos].mu[1] -= m_params.Alpha()*(dG); - m_gaussian[pos].mu[2] -= m_params.Alpha()*(dB); - - float sigmanew = m_gaussian[pos].var[0] + m_params.Alpha()*(dist-m_gaussian[pos].var[0]); - m_gaussian[pos].var[0] = sigmanew < 4 ? 4 : sigmanew > 5*m_variance ? 5*m_variance : sigmanew; - - m_background(r, c, 0) = (unsigned char)(m_gaussian[pos].mu[0] + 0.5); - m_background(r, c, 1) = (unsigned char)(m_gaussian[pos].mu[1] + 0.5); - m_background(r, c, 2) = (unsigned char)(m_gaussian[pos].mu[2] + 0.5); - } - - pos++; - } - } + int pos = 0; + + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + // perform conditional updating only if we are passed the learning phase + if (update_mask(r, c) == BACKGROUND || frame_num < m_params.LearningFrames()) + { + float dR = m_gaussian[pos].mu[0] - data(r, c, 0); + float dG = m_gaussian[pos].mu[1] - data(r, c, 1); + float dB = m_gaussian[pos].mu[2] - data(r, c, 2); + + float dist = (dR*dR + dG*dG + dB*dB); + + m_gaussian[pos].mu[0] -= m_params.Alpha()*(dR); + m_gaussian[pos].mu[1] -= m_params.Alpha()*(dG); + m_gaussian[pos].mu[2] -= m_params.Alpha()*(dB); + + float sigmanew = m_gaussian[pos].var[0] + m_params.Alpha()*(dist - m_gaussian[pos].var[0]); + m_gaussian[pos].var[0] = sigmanew < 4 ? 4 : sigmanew > 5 * m_variance ? 5 * m_variance : sigmanew; + + m_background(r, c, 0) = (unsigned char)(m_gaussian[pos].mu[0] + 0.5); + m_background(r, c, 1) = (unsigned char)(m_gaussian[pos].mu[1] + 0.5); + m_background(r, c, 2) = (unsigned char)(m_gaussian[pos].mu[2] + 0.5); + } + + pos++; + } + } } -void WrenGA::SubtractPixel(int r, int c, const RgbPixel& pixel, - unsigned char& low_threshold, - unsigned char& high_threshold) +void WrenGA::SubtractPixel(int r, int c, const RgbPixel& pixel, + unsigned char& low_threshold, + unsigned char& high_threshold) { - unsigned int pos = r*m_params.Width()+c; - - // calculate distance between model and pixel - float mu[NUM_CHANNELS]; - float var[1]; - float delta[NUM_CHANNELS]; - float dist = 0; - for(int ch = 0; ch < NUM_CHANNELS; ++ch) - { - mu[ch] = m_gaussian[pos].mu[ch]; - var[0] = m_gaussian[pos].var[0]; - delta[ch] = mu[ch] - pixel(ch); - dist += delta[ch]*delta[ch]; - } - - // calculate the squared distance and see if pixel fits the B/G model - low_threshold = BACKGROUND; - high_threshold = BACKGROUND; - - if(dist > m_params.LowThreshold()*var[0]) - low_threshold = FOREGROUND; - if(dist > m_params.HighThreshold()*var[0]) - high_threshold = FOREGROUND; + unsigned int pos = r*m_params.Width() + c; + + // calculate distance between model and pixel + float mu[NUM_CHANNELS]; + float var[1]; + float delta[NUM_CHANNELS]; + float dist = 0; + for (int ch = 0; ch < NUM_CHANNELS; ++ch) + { + mu[ch] = m_gaussian[pos].mu[ch]; + var[0] = m_gaussian[pos].var[0]; + delta[ch] = mu[ch] - pixel(ch); + dist += delta[ch] * delta[ch]; + } + + // calculate the squared distance and see if pixel fits the B/G model + low_threshold = BACKGROUND; + high_threshold = BACKGROUND; + + if (dist > m_params.LowThreshold()*var[0]) + low_threshold = FOREGROUND; + if (dist > m_params.HighThreshold()*var[0]) + high_threshold = FOREGROUND; } /////////////////////////////////////////////////////////////////////////////// @@ -154,20 +154,20 @@ void WrenGA::SubtractPixel(int r, int c, const RgbPixel& pixel, // (the memory should already be reserved) // values: 255-foreground, 125-shadow, 0-background /////////////////////////////////////////////////////////////////////////////// -void WrenGA::Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask) +void WrenGA::Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask) { - unsigned char low_threshold, high_threshold; - - // update each pixel of the image - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - SubtractPixel(r, c, data(r,c), low_threshold, high_threshold); - low_threshold_mask(r,c) = low_threshold; - high_threshold_mask(r,c) = high_threshold; - } - } + unsigned char low_threshold, high_threshold; + + // update each pixel of the image + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + SubtractPixel(r, c, data(r, c), low_threshold, high_threshold); + low_threshold_mask(r, c) = low_threshold; + high_threshold_mask(r, c) = high_threshold; + } + } } diff --git a/package_bgs/dp/WrenGA.h b/package_bgs/dp/WrenGA.h index 116c292..97e97ee 100644 --- a/package_bgs/dp/WrenGA.h +++ b/package_bgs/dp/WrenGA.h @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * WrenGA.hpp * -* Purpose: Implementation of the running Gaussian average background +* Purpose: Implementation of the running Gaussian average background * subtraction algorithm described in: * "Pfinder: real-time tracking of the human body" * by C. Wren et al (1997) @@ -33,16 +33,14 @@ along with BGSLibrary. If not, see . Algorithms::BackgroundSubtraction::WrenParams params; params.SetFrameSize(width, height); params.LowThreshold() = 3.5f*3.5f; -params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing +params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing params.Alpha() = 0.005f; params.LearningFrames() = 30; Algorithms::BackgroundSubtraction::WrenGA bgs; bgs.Initalize(params); ******************************************************************************/ - -#ifndef WREN_GA_H -#define WREN_GA_H +#pragma once #include "Bgs.h" @@ -74,7 +72,7 @@ namespace Algorithms // --- Mean BGS algorithm --- class WrenGA : public Bgs { - private: + private: struct GAUSSIAN { float mu[NUM_CHANNELS]; @@ -88,19 +86,19 @@ namespace Algorithms void Initalize(const BgsParams& param); void InitModel(const RgbImage& data); - void Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask); - void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); + void Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask); + void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); RgbImage* Background() { return &m_background; } - private: - void SubtractPixel(int r, int c, const RgbPixel& pixel, + private: + void SubtractPixel(int r, int c, const RgbPixel& pixel, unsigned char& lowThreshold, unsigned char& highThreshold); WrenParams m_params; - // Initial variance for the newly generated components. + // Initial variance for the newly generated components. float m_variance; // dynamic array for the mixture of Gaussians @@ -110,11 +108,3 @@ namespace Algorithms }; } } - -#endif - - - - - - diff --git a/package_bgs/dp/ZivkovicAGMM.cpp b/package_bgs/dp/ZivkovicAGMM.cpp index 041ffc4..f73d5a1 100644 --- a/package_bgs/dp/ZivkovicAGMM.cpp +++ b/package_bgs/dp/ZivkovicAGMM.cpp @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * ZivkovicAGMM.cpp * -* Purpose: Implementation of the Gaussian mixture model (GMM) background +* Purpose: Implementation of the Gaussian mixture model (GMM) background * subtraction algorithm developed by Z. Zivkovic. * * Author: Donovan Parks, September 2007 @@ -28,13 +28,13 @@ along with BGSLibrary. If not, see . * following papers: * * "Improved adaptive Gausian mixture model for background subtraction" -* Z.Zivkovic +* Z.Zivkovic * International Conference Pattern Recognition, UK, August, 2004 * * -* "Efficient Adaptive Density Estimapion per Image Pixel for the +* "Efficient Adaptive Density Estimapion per Image Pixel for the * Task of Background Subtraction" -* Z.Zivkovic, F. van der Heijden +* Z.Zivkovic, F. van der Heijden * Pattern Recognition Letters, vol. 27, no. 7, pages 773-780, 2006. * * Zivkovic's code can be obtained at: www.zoranz.net @@ -46,328 +46,328 @@ using namespace Algorithms::BackgroundSubtraction; ZivkovicAGMM::ZivkovicAGMM() { - m_modes = NULL; - m_modes_per_pixel = NULL; + m_modes = NULL; + m_modes_per_pixel = NULL; } ZivkovicAGMM::~ZivkovicAGMM() { - delete[] m_modes; - delete[] m_modes_per_pixel; + delete[] m_modes; + delete[] m_modes_per_pixel; } void ZivkovicAGMM::Initalize(const BgsParams& param) { - m_params = (ZivkovicParams&)param; + m_params = (ZivkovicParams&)param; - m_num_bands = 3; //always 3 - not implemented for other values! - m_bg_threshold = 0.75f; //1-cf from the paper - m_variance = 36.0f; // variance for the new mode - m_complexity_prior = 0.05f; // complexity reduction prior constant + m_num_bands = 3; //always 3 - not implemented for other values! + m_bg_threshold = 0.75f; //1-cf from the paper + m_variance = 36.0f; // variance for the new mode + m_complexity_prior = 0.05f; // complexity reduction prior constant - // GMM for each pixel - m_modes = new GMM[m_params.Size()*m_params.MaxModes()]; + // GMM for each pixel + m_modes = new GMM[m_params.Size()*m_params.MaxModes()]; - // used modes per pixel - m_modes_per_pixel = new unsigned char[m_params.Size()]; + // used modes per pixel + m_modes_per_pixel = new unsigned char[m_params.Size()]; - m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); + m_background = cvCreateImage(cvSize(m_params.Width(), m_params.Height()), IPL_DEPTH_8U, 3); } void ZivkovicAGMM::InitModel(const RgbImage& data) { - for(unsigned int i = 0; i < m_params.Size(); ++i) - { - m_modes_per_pixel[i] = 0; - } - - for(unsigned int i = 0; i < m_params.Size()*m_params.MaxModes(); ++i) - { - m_modes[i].weight = 0; - m_modes[i].sigma = 0; - m_modes[i].muR = 0; - m_modes[i].muG = 0; - m_modes[i].muB = 0; - } + for (unsigned int i = 0; i < m_params.Size(); ++i) + { + m_modes_per_pixel[i] = 0; + } + + for (unsigned int i = 0; i < m_params.Size()*m_params.MaxModes(); ++i) + { + m_modes[i].weight = 0; + m_modes[i].sigma = 0; + m_modes[i].muR = 0; + m_modes[i].muG = 0; + m_modes[i].muB = 0; + } } -void ZivkovicAGMM::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) +void ZivkovicAGMM::Update(int frame_num, const RgbImage& data, const BwImage& update_mask) { - // it doesn't make sense to have conditional updates in the GMM framework + // it doesn't make sense to have conditional updates in the GMM framework } -void ZivkovicAGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char* pModesUsed, - unsigned char& low_threshold, unsigned char& high_threshold) +void ZivkovicAGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char* pModesUsed, + unsigned char& low_threshold, unsigned char& high_threshold) { - //calculate distances to the modes (+ sort???) - //here we need to go in descending order!!! - long pos; - bool bFitsPDF=0; - bool bBackgroundLow=false; - bool bBackgroundHigh=false; - - float fOneMinAlpha = 1-m_params.Alpha(); - - float prune = -m_params.Alpha()*m_complexity_prior; - - int nModes =* pModesUsed; - float totalWeight = 0.0f; - - // calculate number of Gaussians to include in the background model - int backgroundGaussians = 0; - double sum = 0.0; - for(int i = 0; i < nModes; ++i) - { - if(sum < m_bg_threshold) - { - backgroundGaussians++; - sum += m_modes[posPixel+i].weight; - } - else - { - break; - } - } - - // update all distributions and check for match with current pixel - for (int iModes = 0; iModes < nModes; iModes++) - { - pos=posPixel+iModes; - float weight = m_modes[pos].weight; - - //fit not found yet - if (!bFitsPDF) - { - //check if it belongs to some of the modes - //calculate distance - float var = m_modes[pos].sigma; - float muR = m_modes[pos].muR; - float muG = m_modes[pos].muG; - float muB = m_modes[pos].muB; - - float dR=muR - pixel(0); - float dG=muG - pixel(1); - float dB=muB - pixel(2); - - // calculate the squared distance - float dist = (dR*dR + dG*dG + dB*dB); - - if(dist < m_params.HighThreshold()*var && iModes < backgroundGaussians) - bBackgroundHigh = true; - - //check fit - if (dist < m_params.LowThreshold()*var) - { - ///// - //belongs to the mode - bFitsPDF = true; - - // check if this Gaussian is part of the background model - if(iModes < backgroundGaussians) - bBackgroundLow = true; - - //update distribution - float k = m_params.Alpha()/weight; - weight = fOneMinAlpha*weight+prune; - weight += m_params.Alpha(); - m_modes[pos].weight = weight; - m_modes[pos].muR = muR - k*(dR); - m_modes[pos].muG = muG - k*(dG); - m_modes[pos].muB = muB - k*(dB); - - //limit update speed for cov matrice - //not needed - //k=k>20*m_m_params.Alpha()?20*m_m_params.Alpha():k; - //float sigmanew = var + k*((0.33*(dR*dR+dG*dG+dB*dB))-var); - //float sigmanew = var + k*((dR*dR+dG*dG+dB*dB)-var); - //float sigmanew = var + k*((0.33*dist)-var); - float sigmanew = var + k*(dist-var); - - //limit the variance - m_modes[pos].sigma = sigmanew < 4 ? 4 : sigmanew > 5*m_variance ? 5*m_variance : sigmanew; - - // Sort weights so they are in desending order. Note that only the weight for this - // mode will increase and that the weight for all modes that were previously larger than - // this one have already been modified and will not be modified again. Thus, we just need to - // the correct position of this mode in the already sorted list. - - // Zivkovic implementation has been modified for clarity, but the results are equivalent - /* - for (int iLocal = iModes;iLocal>0;iLocal--) - { - long posLocal=posPixel + iLocal; - if (weight < (m_modes[posLocal-1].weight)) - { - break; - } - else - { - //swap - GMM temp = m_modes[posLocal]; - m_modes[posLocal] = m_modes[posLocal-1]; - m_modes[posLocal-1] = temp; - } - } - */ - - for (int iLocal = iModes; iLocal > 0; iLocal--) - { - long posLocal = posPixel + iLocal; - if (m_modes[posLocal].weight > m_modes[posLocal-1].weight) - { - //swap - GMM temp = m_modes[posLocal]; - m_modes[posLocal] = m_modes[posLocal-1]; - m_modes[posLocal-1] = temp; - } - else - { - break; - } - } - } - else - { - weight = fOneMinAlpha*weight+prune; - //check prune - if (weight < -prune) - { - weight=0.0; - nModes--; - } - m_modes[pos].weight = weight; - } - //check if it fits the current mode (2.5 sigma) - /////// - } - //fit not found yet - ///// - else - { - weight = fOneMinAlpha*weight + prune; - //check prune - if (weight < -prune) - { - weight=0.0; - nModes--; - } - m_modes[pos].weight = weight; - } - totalWeight += weight; - } - - //renormalize weights so they sum to 1 - for (int iLocal = 0; iLocal < nModes; iLocal++) - { - m_modes[posPixel+ iLocal].weight = m_modes[posPixel+ iLocal].weight/totalWeight; - } - - //make new mode if needed and exit - if (!bFitsPDF) - { - if (nModes == m_params.MaxModes()) - { - //replace the weakest - } - else - { - nModes++; - } - pos = posPixel + nModes-1; - - if (nModes==1) - m_modes[pos].weight=1; - else - m_modes[pos].weight=m_params.Alpha(); - - // Zivkovic implementation changes as this will not result in the - // weights adding to 1 - /* - int iLocal; - for (iLocal = 0; iLocal < m_params.MaxModes()odes-1; iLocal++) - { - m_modes[posPixel+ iLocal].weight *= fOneMinAlpha; - } - */ - - // Revised implementation: - //renormalize weights - int iLocal; - float sum = 0.0; - for (iLocal = 0; iLocal < nModes; iLocal++) - { - sum += m_modes[posPixel+ iLocal].weight; - } - - float invSum = 1.0f/sum; - for (iLocal = 0; iLocal < nModes; iLocal++) - { - m_modes[posPixel+ iLocal].weight *= invSum; - } - - m_modes[pos].muR=pixel(0); - m_modes[pos].muG=pixel(1); - m_modes[pos].muB=pixel(2); - m_modes[pos].sigma=m_variance; - - // Zivkovic implementation to sort GMM so they are sorted in descending order according to their weight. - // It has been revised for clarity, but the results are equivalent - /* - for (iLocal = m_params.MaxModes()odes-1; iLocal > 0; iLocal--) - { - long posLocal = posPixel + iLocal; - if (m_params.Alpha() < (m_modes[posLocal-1].weight)) - { - break; - } - else - { - //swap - GMM temp = m_modes[posLocal]; - m_modes[posLocal] = m_modes[posLocal-1]; - m_modes[posLocal-1] = temp; - } - } - */ - - // sort GMM so they are sorted in descending order according to their weight - for (iLocal = nModes-1; iLocal > 0; iLocal--) - { - long posLocal = posPixel + iLocal; - if (m_modes[posLocal].weight > m_modes[posLocal-1].weight) - { - //swap - GMM temp = m_modes[posLocal]; - m_modes[posLocal] = m_modes[posLocal-1]; - m_modes[posLocal-1] = temp; - } - else - { - break; - } - } - } - - //set the number of modes - *pModesUsed=nModes; - - if(bBackgroundLow) - { - low_threshold = BACKGROUND; - } - else - { - low_threshold = FOREGROUND; - } - - if(bBackgroundHigh) - { - high_threshold = BACKGROUND; - } - else - { - high_threshold = FOREGROUND; - } + //calculate distances to the modes (+ sort???) + //here we need to go in descending order!!! + long pos; + bool bFitsPDF = 0; + bool bBackgroundLow = false; + bool bBackgroundHigh = false; + + float fOneMinAlpha = 1 - m_params.Alpha(); + + float prune = -m_params.Alpha()*m_complexity_prior; + + int nModes = *pModesUsed; + float totalWeight = 0.0f; + + // calculate number of Gaussians to include in the background model + int backgroundGaussians = 0; + double sum = 0.0; + for (int i = 0; i < nModes; ++i) + { + if (sum < m_bg_threshold) + { + backgroundGaussians++; + sum += m_modes[posPixel + i].weight; + } + else + { + break; + } + } + + // update all distributions and check for match with current pixel + for (int iModes = 0; iModes < nModes; iModes++) + { + pos = posPixel + iModes; + float weight = m_modes[pos].weight; + + //fit not found yet + if (!bFitsPDF) + { + //check if it belongs to some of the modes + //calculate distance + float var = m_modes[pos].sigma; + float muR = m_modes[pos].muR; + float muG = m_modes[pos].muG; + float muB = m_modes[pos].muB; + + float dR = muR - pixel(0); + float dG = muG - pixel(1); + float dB = muB - pixel(2); + + // calculate the squared distance + float dist = (dR*dR + dG*dG + dB*dB); + + if (dist < m_params.HighThreshold()*var && iModes < backgroundGaussians) + bBackgroundHigh = true; + + //check fit + if (dist < m_params.LowThreshold()*var) + { + ///// + //belongs to the mode + bFitsPDF = true; + + // check if this Gaussian is part of the background model + if (iModes < backgroundGaussians) + bBackgroundLow = true; + + //update distribution + float k = m_params.Alpha() / weight; + weight = fOneMinAlpha*weight + prune; + weight += m_params.Alpha(); + m_modes[pos].weight = weight; + m_modes[pos].muR = muR - k*(dR); + m_modes[pos].muG = muG - k*(dG); + m_modes[pos].muB = muB - k*(dB); + + //limit update speed for cov matrice + //not needed + //k=k>20*m_m_params.Alpha()?20*m_m_params.Alpha():k; + //float sigmanew = var + k*((0.33*(dR*dR+dG*dG+dB*dB))-var); + //float sigmanew = var + k*((dR*dR+dG*dG+dB*dB)-var); + //float sigmanew = var + k*((0.33*dist)-var); + float sigmanew = var + k*(dist - var); + + //limit the variance + m_modes[pos].sigma = sigmanew < 4 ? 4 : sigmanew > 5 * m_variance ? 5 * m_variance : sigmanew; + + // Sort weights so they are in desending order. Note that only the weight for this + // mode will increase and that the weight for all modes that were previously larger than + // this one have already been modified and will not be modified again. Thus, we just need to + // the correct position of this mode in the already sorted list. + + // Zivkovic implementation has been modified for clarity, but the results are equivalent + /* + for (int iLocal = iModes;iLocal>0;iLocal--) + { + long posLocal=posPixel + iLocal; + if (weight < (m_modes[posLocal-1].weight)) + { + break; + } + else + { + //swap + GMM temp = m_modes[posLocal]; + m_modes[posLocal] = m_modes[posLocal-1]; + m_modes[posLocal-1] = temp; + } + } + */ + + for (int iLocal = iModes; iLocal > 0; iLocal--) + { + long posLocal = posPixel + iLocal; + if (m_modes[posLocal].weight > m_modes[posLocal - 1].weight) + { + //swap + GMM temp = m_modes[posLocal]; + m_modes[posLocal] = m_modes[posLocal - 1]; + m_modes[posLocal - 1] = temp; + } + else + { + break; + } + } + } + else + { + weight = fOneMinAlpha*weight + prune; + //check prune + if (weight < -prune) + { + weight = 0.0; + nModes--; + } + m_modes[pos].weight = weight; + } + //check if it fits the current mode (2.5 sigma) + /////// + } + //fit not found yet + ///// + else + { + weight = fOneMinAlpha*weight + prune; + //check prune + if (weight < -prune) + { + weight = 0.0; + nModes--; + } + m_modes[pos].weight = weight; + } + totalWeight += weight; + } + + //renormalize weights so they sum to 1 + for (int iLocal = 0; iLocal < nModes; iLocal++) + { + m_modes[posPixel + iLocal].weight = m_modes[posPixel + iLocal].weight / totalWeight; + } + + //make new mode if needed and exit + if (!bFitsPDF) + { + if (nModes == m_params.MaxModes()) + { + //replace the weakest + } + else + { + nModes++; + } + pos = posPixel + nModes - 1; + + if (nModes == 1) + m_modes[pos].weight = 1; + else + m_modes[pos].weight = m_params.Alpha(); + + // Zivkovic implementation changes as this will not result in the + // weights adding to 1 + /* + int iLocal; + for (iLocal = 0; iLocal < m_params.MaxModes()odes-1; iLocal++) + { + m_modes[posPixel+ iLocal].weight *= fOneMinAlpha; + } + */ + + // Revised implementation: + //renormalize weights + int iLocal; + float sum = 0.0; + for (iLocal = 0; iLocal < nModes; iLocal++) + { + sum += m_modes[posPixel + iLocal].weight; + } + + float invSum = 1.0f / sum; + for (iLocal = 0; iLocal < nModes; iLocal++) + { + m_modes[posPixel + iLocal].weight *= invSum; + } + + m_modes[pos].muR = pixel(0); + m_modes[pos].muG = pixel(1); + m_modes[pos].muB = pixel(2); + m_modes[pos].sigma = m_variance; + + // Zivkovic implementation to sort GMM so they are sorted in descending order according to their weight. + // It has been revised for clarity, but the results are equivalent + /* + for (iLocal = m_params.MaxModes()odes-1; iLocal > 0; iLocal--) + { + long posLocal = posPixel + iLocal; + if (m_params.Alpha() < (m_modes[posLocal-1].weight)) + { + break; + } + else + { + //swap + GMM temp = m_modes[posLocal]; + m_modes[posLocal] = m_modes[posLocal-1]; + m_modes[posLocal-1] = temp; + } + } + */ + + // sort GMM so they are sorted in descending order according to their weight + for (iLocal = nModes - 1; iLocal > 0; iLocal--) + { + long posLocal = posPixel + iLocal; + if (m_modes[posLocal].weight > m_modes[posLocal - 1].weight) + { + //swap + GMM temp = m_modes[posLocal]; + m_modes[posLocal] = m_modes[posLocal - 1]; + m_modes[posLocal - 1] = temp; + } + else + { + break; + } + } + } + + //set the number of modes + *pModesUsed = nModes; + + if (bBackgroundLow) + { + low_threshold = BACKGROUND; + } + else + { + low_threshold = FOREGROUND; + } + + if (bBackgroundHigh) + { + high_threshold = BACKGROUND; + } + else + { + high_threshold = FOREGROUND; + } } @@ -379,30 +379,30 @@ void ZivkovicAGMM::SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned // (the memory should already be reserved) // values: 255-foreground, 125-shadow, 0-background /////////////////////////////////////////////////////////////////////////////// -void ZivkovicAGMM::Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask) +void ZivkovicAGMM::Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask) { - unsigned char low_threshold, high_threshold; - - // update each pixel of the image - long posPixel; - unsigned char* pUsedModes=m_modes_per_pixel; - for(unsigned int r = 0; r < m_params.Height(); ++r) - { - for(unsigned int c = 0; c < m_params.Width(); ++c) - { - //update model+ background subtract - posPixel=(r*m_params.Width()+c)*m_params.MaxModes(); - SubtractPixel(posPixel, data(r,c), pUsedModes, low_threshold, high_threshold); - low_threshold_mask(r,c) = low_threshold; - high_threshold_mask(r,c) = high_threshold; - - m_background(r,c,0) = (unsigned char)m_modes[posPixel].muR; - m_background(r,c,1) = (unsigned char)m_modes[posPixel].muG; - m_background(r,c,2) = (unsigned char)m_modes[posPixel].muB; - - pUsedModes++; - } - } + unsigned char low_threshold, high_threshold; + + // update each pixel of the image + long posPixel; + unsigned char* pUsedModes = m_modes_per_pixel; + for (unsigned int r = 0; r < m_params.Height(); ++r) + { + for (unsigned int c = 0; c < m_params.Width(); ++c) + { + //update model+ background subtract + posPixel = (r*m_params.Width() + c)*m_params.MaxModes(); + SubtractPixel(posPixel, data(r, c), pUsedModes, low_threshold, high_threshold); + low_threshold_mask(r, c) = low_threshold; + high_threshold_mask(r, c) = high_threshold; + + m_background(r, c, 0) = (unsigned char)m_modes[posPixel].muR; + m_background(r, c, 1) = (unsigned char)m_modes[posPixel].muG; + m_background(r, c, 2) = (unsigned char)m_modes[posPixel].muB; + + pUsedModes++; + } + } } diff --git a/package_bgs/dp/ZivkovicAGMM.h b/package_bgs/dp/ZivkovicAGMM.h index 1cd99b0..07da9c3 100644 --- a/package_bgs/dp/ZivkovicAGMM.h +++ b/package_bgs/dp/ZivkovicAGMM.h @@ -18,7 +18,7 @@ along with BGSLibrary. If not, see . * * ZivkovicAGMM.hpp * -* Purpose: Implementation of the Gaussian mixture model (GMM) background +* Purpose: Implementation of the Gaussian mixture model (GMM) background * subtraction algorithm developed by Z. Zivkovic. * * Author: Donovan Parks, September 2007 @@ -28,13 +28,13 @@ along with BGSLibrary. If not, see . * following papers: * * "Improved adaptive Gausian mixture model for background subtraction" -* Z.Zivkovic +* Z.Zivkovic * International Conference Pattern Recognition, UK, August, 2004 * * -* "Efficient Adaptive Density Estimapion per Image Pixel for the +* "Efficient Adaptive Density Estimapion per Image Pixel for the * Task of Background Subtraction" -* Z.Zivkovic, F. van der Heijden +* Z.Zivkovic, F. van der Heijden * Pattern Recognition Letters, vol. 27, no. 7, pages 773-780, 2006. * * Zivkovic's code can be obtained at: www.zoranz.net @@ -43,16 +43,14 @@ along with BGSLibrary. If not, see . Algorithms::BackgroundSubtraction::ZivkovicParams params; params.SetFrameSize(width, height); params.LowThreshold() = 5.0f*5.0f; -params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing +params.HighThreshold() = 2*params.LowThreshold(); // Note: high threshold is used by post-processing params.Alpha() = 0.001f; params.MaxModes() = 3; Algorithms::BackgroundSubtraction::ZivkovicAGMM bgs; bgs.Initalize(params); ******************************************************************************/ - -#ifndef ZIVKOVIC_AGMM_H -#define ZIVKOVIC_AGMM_H +#pragma once #include "Bgs.h" @@ -71,18 +69,18 @@ namespace Algorithms int &MaxModes() { return m_max_modes; } private: - // Threshold on the squared dist. to decide when a sample is close to an existing - // components. If it is not close to any a new component will be generated. - // Smaller threshold values lead to more generated components and higher threshold values + // Threshold on the squared dist. to decide when a sample is close to an existing + // components. If it is not close to any a new component will be generated. + // Smaller threshold values lead to more generated components and higher threshold values // lead to a small number of components but they can grow too large. // - // It is usual easiest to think of these thresholds as being the number of variances (not standard deviations) + // It is usual easiest to think of these thresholds as being the number of variances (not standard deviations) // away from the mean of a pixel before it is considered to be from the foreground. float m_low_threshold; float m_high_threshold; // alpha - speed of update - if the time interval you want to average over is T - // set alpha=1/T. + // set alpha=1/T. float m_alpha; // Maximum number of modes (Gaussian components) that will be used per pixel @@ -109,14 +107,14 @@ namespace Algorithms void Initalize(const BgsParams& param); void InitModel(const RgbImage& data); - void Subtract(int frame_num, const RgbImage& data, - BwImage& low_threshold_mask, BwImage& high_threshold_mask); - void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); + void Subtract(int frame_num, const RgbImage& data, + BwImage& low_threshold_mask, BwImage& high_threshold_mask); + void Update(int frame_num, const RgbImage& data, const BwImage& update_mask); RgbImage* Background() { return &m_background; } private: - void SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char* pModesUsed, + void SubtractPixel(long posPixel, const RgbPixel& pixel, unsigned char* pModesUsed, unsigned char& lowThreshold, unsigned char& highThreshold); // User adjustable parameters @@ -128,13 +126,13 @@ namespace Algorithms // it is considered foreground float m_bg_threshold; //1-cf from the paper - // Initial variance for the newly generated components. - // It will will influence the speed of adaptation. A good guess should be made. + // Initial variance for the newly generated components. + // It will will influence the speed of adaptation. A good guess should be made. // A simple way is to estimate the typical standard deviation from the images. float m_variance; // This is related to the number of samples needed to accept that a component - // actually exists. + // actually exists. float m_complexity_prior; //data @@ -150,11 +148,3 @@ namespace Algorithms }; } } - -#endif - - - - - - diff --git a/package_bgs/jmo/BlobResult.cpp b/package_bgs/jmo/BlobResult.cpp deleted file mode 100644 index 1a047c7..0000000 --- a/package_bgs/jmo/BlobResult.cpp +++ /dev/null @@ -1,847 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -/* --- --- --- -* Copyright (C) 2008--2010 Idiap Research Institute (.....@idiap.ch) -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions -* are met: -* 1. Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* 2. 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. -* 3. The name of the author may not be used to endorse or promote products -* derived from this software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. -*/ -/************************************************************************ -BlobResult.cpp - -FUNCIONALITAT: Implementació de la classe CBlobResult -AUTOR: Inspecta S.L. -MODIFICACIONS (Modificació, Autor, Data): - -**************************************************************************/ - -#include -#include -#include -#include -#include "BlobResult.h" -#include "BlobExtraction.h" - -/************************************************************************** -Constructors / Destructors -**************************************************************************/ - -namespace Blob -{ - - /** - - FUNCIÓ: CBlobResult - - FUNCIONALITAT: Constructor estandard. - - PARÀMETRES: - - RESULTAT: - - Crea un CBlobResult sense cap blob - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 20-07-2004. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlobResult - - FUNCTIONALITY: Standard constructor - - PARAMETERS: - - RESULT: - - creates an empty set of blobs - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlobResult::CBlobResult() - { - m_blobs = blob_vector(); - } - - /** - - FUNCIÓ: CBlobResult - - FUNCIONALITAT: Constructor a partir d'una imatge. Inicialitza la seqüència de blobs - amb els blobs resultants de l'anàlisi de blobs de la imatge. - - PARÀMETRES: - - source: imatge d'on s'extreuran els blobs - - mask: màscara a aplicar. Només es calcularan els blobs on la màscara sigui - diferent de 0. Els blobs que toquin a un pixel 0 de la màscara seran - considerats exteriors. - - threshold: llindar que s'aplicarà a la imatge source abans de calcular els blobs - - findmoments: indica si s'han de calcular els moments de cada blob - - RESULTAT: - - objecte CBlobResult amb els blobs de la imatge source - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlob - - FUNCTIONALITY: Constructor from an image. Fills an object with all the blobs in - the image - - PARAMETERS: - - source: image to extract the blobs from - - mask: optional mask to apply. The blobs will be extracted where the mask is - not 0. All the neighbouring blobs where the mask is 0 will be extern blobs - - threshold: threshold level to apply to the image before computing blobs - - findmoments: true to calculate the blob moments (slower) - - RESULT: - - object with all the blobs in the image. It throws an EXCEPCIO_CALCUL_BLOBS - if some error appears in the BlobAnalysis function - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlobResult::CBlobResult(IplImage *source, IplImage *mask, int threshold, bool findmoments) - { - bool success; - - try - { - // cridem la funció amb el marc a true=1=blanc (així no unirà els blobs externs) - success = BlobAnalysis(source, (uchar)threshold, mask, true, findmoments, m_blobs); - } - catch (...) - { - success = false; - } - - if (!success) throw EXCEPCIO_CALCUL_BLOBS; - } - - /** - - FUNCIÓ: CBlobResult - - FUNCIONALITAT: Constructor de còpia. Inicialitza la seqüència de blobs - amb els blobs del paràmetre. - - PARÀMETRES: - - source: objecte que es copiarà - - RESULTAT: - - objecte CBlobResult amb els blobs de l'objecte source - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlobResult - - FUNCTIONALITY: Copy constructor - - PARAMETERS: - - source: object to copy - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlobResult::CBlobResult(const CBlobResult &source) - { - m_blobs = blob_vector(source.GetNumBlobs()); - - // creem el nou a partir del passat com a paràmetre - m_blobs = blob_vector(source.GetNumBlobs()); - // copiem els blobs de l'origen a l'actual - blob_vector::const_iterator pBlobsSrc = source.m_blobs.begin(); - blob_vector::iterator pBlobsDst = m_blobs.begin(); - - while (pBlobsSrc != source.m_blobs.end()) - { - // no podem cridar a l'operador = ja que blob_vector és un - // vector de CBlob*. Per tant, creem un blob nou a partir del - // blob original - *pBlobsDst = new CBlob(**pBlobsSrc); - ++pBlobsSrc; - ++pBlobsDst; - } - } - - - - /** - - FUNCIÓ: ~CBlobResult - - FUNCIONALITAT: Destructor estandard. - - PARÀMETRES: - - RESULTAT: - - Allibera la memòria reservada de cadascun dels blobs de la classe - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: ~CBlobResult - - FUNCTIONALITY: Destructor - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlobResult::~CBlobResult() - { - ClearBlobs(); - } - - /************************************************************************** - Operadors / Operators - **************************************************************************/ - - - /** - - FUNCIÓ: operador = - - FUNCIONALITAT: Assigna un objecte source a l'actual - - PARÀMETRES: - - source: objecte a assignar - - RESULTAT: - - Substitueix els blobs actuals per els de l'objecte source - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: Assigment operator - - FUNCTIONALITY: - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlobResult& CBlobResult::operator=(const CBlobResult& source) - { - // si ja són el mateix, no cal fer res - if (this != &source) - { - // alliberem el conjunt de blobs antic - for (int i = 0; i < GetNumBlobs(); i++) - { - delete m_blobs[i]; - } - m_blobs.clear(); - // creem el nou a partir del passat com a paràmetre - m_blobs = blob_vector(source.GetNumBlobs()); - // copiem els blobs de l'origen a l'actual - blob_vector::const_iterator pBlobsSrc = source.m_blobs.begin(); - blob_vector::iterator pBlobsDst = m_blobs.begin(); - - while (pBlobsSrc != source.m_blobs.end()) - { - // no podem cridar a l'operador = ja que blob_vector és un - // vector de CBlob*. Per tant, creem un blob nou a partir del - // blob original - *pBlobsDst = new CBlob(**pBlobsSrc); - ++pBlobsSrc; - ++pBlobsDst; - } - } - return *this; - } - - - /** - - FUNCIÓ: operador + - - FUNCIONALITAT: Concatena els blobs de dos CBlobResult - - PARÀMETRES: - - source: d'on s'agafaran els blobs afegits a l'actual - - RESULTAT: - - retorna un nou CBlobResult amb els dos CBlobResult concatenats - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - NOTA: per la implementació, els blobs del paràmetre es posen en ordre invers - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: + operator - - FUNCTIONALITY: Joins the blobs in source with the current ones - - PARAMETERS: - - source: object to copy the blobs - - RESULT: - - object with the actual blobs and the source blobs - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlobResult CBlobResult::operator+(const CBlobResult& source) - { - //creem el resultat a partir dels blobs actuals - CBlobResult resultat(*this); - - // reservem memòria per als nous blobs - resultat.m_blobs.resize(resultat.GetNumBlobs() + source.GetNumBlobs()); - - // declarem els iterador per recòrrer els blobs d'origen i desti - blob_vector::const_iterator pBlobsSrc = source.m_blobs.begin(); - blob_vector::iterator pBlobsDst = resultat.m_blobs.end(); - - // insertem els blobs de l'origen a l'actual - while (pBlobsSrc != source.m_blobs.end()) - { - --pBlobsDst; - *pBlobsDst = new CBlob(**pBlobsSrc); - ++pBlobsSrc; - } - - return resultat; - } - - /************************************************************************** - Operacions / Operations - **************************************************************************/ - - /** - - FUNCIÓ: AddBlob - - FUNCIONALITAT: Afegeix un blob al conjunt - - PARÀMETRES: - - blob: blob a afegir - - RESULTAT: - - modifica el conjunt de blobs actual - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 2006/03/01 - - MODIFICACIÓ: Data. Autor. Descripció. - */ - void CBlobResult::AddBlob(CBlob *blob) - { - if (blob != NULL) - m_blobs.push_back(new CBlob(blob)); - } - - - /** - - FUNCIÓ: GetSTLResult - - FUNCIONALITAT: Calcula el resultat especificat sobre tots els blobs de la classe - - PARÀMETRES: - - evaluador: Qualsevol objecte derivat de COperadorBlob - - RESULTAT: - - Retorna un array de double's STL amb el resultat per cada blob - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: GetResult - - FUNCTIONALITY: Computes the function evaluador on all the blobs of the class - and returns a vector with the result - - PARAMETERS: - - evaluador: function to apply to each blob (any object derived from the - COperadorBlob class ) - - RESULT: - - vector with all the results in the same order as the blobs - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double_stl_vector CBlobResult::GetSTLResult(funcio_calculBlob *evaluador) const - { - if (GetNumBlobs() <= 0) - { - return double_stl_vector(); - } - - // definim el resultat - double_stl_vector result = double_stl_vector(GetNumBlobs()); - // i iteradors sobre els blobs i el resultat - double_stl_vector::iterator itResult = result.begin(); - blob_vector::const_iterator itBlobs = m_blobs.begin(); - - // avaluem la funció en tots els blobs - while (itBlobs != m_blobs.end()) - { - *itResult = (*evaluador)(**itBlobs); - ++itBlobs; - ++itResult; - } - return result; - } - - /** - - FUNCIÓ: GetNumber - - FUNCIONALITAT: Calcula el resultat especificat sobre un únic blob de la classe - - PARÀMETRES: - - evaluador: Qualsevol objecte derivat de COperadorBlob - - indexblob: número de blob del que volem calcular el resultat. - - RESULTAT: - - Retorna un double amb el resultat - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: GetNumber - - FUNCTIONALITY: Computes the function evaluador on a blob of the class - - PARAMETERS: - - indexBlob: index of the blob to compute the function - - evaluador: function to apply to each blob (any object derived from the - COperadorBlob class ) - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobResult::GetNumber(int indexBlob, funcio_calculBlob *evaluador) const - { - if (indexBlob < 0 || indexBlob >= GetNumBlobs()) - RaiseError(EXCEPTION_BLOB_OUT_OF_BOUNDS); - return (*evaluador)(*m_blobs[indexBlob]); - } - - /////////////////////////// FILTRAT DE BLOBS //////////////////////////////////// - - /** - - FUNCIÓ: Filter - - FUNCIONALITAT: Filtra els blobs de la classe i deixa el resultat amb només - els blobs que han passat el filtre. - El filtrat es basa en especificar condicions sobre un resultat dels blobs - i seleccionar (o excloure) aquells blobs que no compleixen una determinada - condicio - - PARÀMETRES: - - dst: variable per deixar els blobs filtrats - - filterAction: acció de filtrat. Incloure els blobs trobats (B_INCLUDE), - o excloure els blobs trobats (B_EXCLUDE) - - evaluador: Funció per evaluar els blobs (qualsevol objecte derivat de COperadorBlob - - Condition: tipus de condició que ha de superar la mesura (FilterType) - sobre cada blob per a ser considerat. - B_EQUAL,B_NOT_EQUAL,B_GREATER,B_LESS,B_GREATER_OR_EQUAL, - B_LESS_OR_EQUAL,B_INSIDE,B_OUTSIDE - - LowLimit: valor numèric per a la comparació (Condition) de la mesura (FilterType) - - HighLimit: valor numèric per a la comparació (Condition) de la mesura (FilterType) - (només té sentit per a aquelles condicions que tenen dos valors - (B_INSIDE, per exemple). - - RESULTAT: - - Deixa els blobs resultants del filtrat a destination - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: Filter - - FUNCTIONALITY: Get some blobs from the class based on conditions on measures - of the blobs. - - PARAMETERS: - - dst: where to store the selected blobs - - filterAction: B_INCLUDE: include the blobs which pass the filter in the result - B_EXCLUDE: exclude the blobs which pass the filter in the result - - evaluador: Object to evaluate the blob - - Condition: How to decide if the result returned by evaluador on each blob - is included or not. It can be: - B_EQUAL,B_NOT_EQUAL,B_GREATER,B_LESS,B_GREATER_OR_EQUAL, - B_LESS_OR_EQUAL,B_INSIDE,B_OUTSIDE - - LowLimit: numerical value to evaluate the Condition on evaluador(blob) - - HighLimit: numerical value to evaluate the Condition on evaluador(blob). - Only useful for B_INSIDE and B_OUTSIDE - - RESULT: - - It returns on dst the blobs that accomplish (B_INCLUDE) or discards (B_EXCLUDE) - the Condition on the result returned by evaluador on each blob - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - void CBlobResult::Filter(CBlobResult &dst, - int filterAction, - funcio_calculBlob *evaluador, - int condition, - double lowLimit, double highLimit /*=0*/) - - { - int i, numBlobs; - bool resultavaluacio; - double_stl_vector avaluacioBlobs; - double_stl_vector::iterator itavaluacioBlobs; - - if (GetNumBlobs() <= 0) return; - if (!evaluador) return; - //avaluem els blobs amb la funció pertinent - avaluacioBlobs = GetSTLResult(evaluador); - itavaluacioBlobs = avaluacioBlobs.begin(); - numBlobs = GetNumBlobs(); - switch (condition) - { - case B_EQUAL: - for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) - { - resultavaluacio = *itavaluacioBlobs == lowLimit; - if ((resultavaluacio && filterAction == B_INCLUDE) || - (!resultavaluacio && filterAction == B_EXCLUDE)) - { - dst.m_blobs.push_back(new CBlob(GetBlob(i))); - } - } - break; - case B_NOT_EQUAL: - for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) - { - resultavaluacio = *itavaluacioBlobs != lowLimit; - if ((resultavaluacio && filterAction == B_INCLUDE) || - (!resultavaluacio && filterAction == B_EXCLUDE)) - { - dst.m_blobs.push_back(new CBlob(GetBlob(i))); - } - } - break; - case B_GREATER: - for (i = 0; i lowLimit; - if ((resultavaluacio && filterAction == B_INCLUDE) || - (!resultavaluacio && filterAction == B_EXCLUDE)) - { - dst.m_blobs.push_back(new CBlob(GetBlob(i))); - } - } - break; - case B_LESS: - for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) - { - resultavaluacio = *itavaluacioBlobs < lowLimit; - if ((resultavaluacio && filterAction == B_INCLUDE) || - (!resultavaluacio && filterAction == B_EXCLUDE)) - { - dst.m_blobs.push_back(new CBlob(GetBlob(i))); - } - } - break; - case B_GREATER_OR_EQUAL: - for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) - { - resultavaluacio = *itavaluacioBlobs >= lowLimit; - if ((resultavaluacio && filterAction == B_INCLUDE) || - (!resultavaluacio && filterAction == B_EXCLUDE)) - { - dst.m_blobs.push_back(new CBlob(GetBlob(i))); - } - } - break; - case B_LESS_OR_EQUAL: - for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) - { - resultavaluacio = *itavaluacioBlobs <= lowLimit; - if ((resultavaluacio && filterAction == B_INCLUDE) || - (!resultavaluacio && filterAction == B_EXCLUDE)) - { - dst.m_blobs.push_back(new CBlob(GetBlob(i))); - } - } - break; - case B_INSIDE: - for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) - { - resultavaluacio = (*itavaluacioBlobs >= lowLimit) && (*itavaluacioBlobs <= highLimit); - if ((resultavaluacio && filterAction == B_INCLUDE) || - (!resultavaluacio && filterAction == B_EXCLUDE)) - { - dst.m_blobs.push_back(new CBlob(GetBlob(i))); - } - } - break; - case B_OUTSIDE: - for (i = 0; i < numBlobs; i++, itavaluacioBlobs++) - { - resultavaluacio = (*itavaluacioBlobs < lowLimit) || (*itavaluacioBlobs > highLimit); - if ((resultavaluacio && filterAction == B_INCLUDE) || - (!resultavaluacio && filterAction == B_EXCLUDE)) - { - dst.m_blobs.push_back(new CBlob(GetBlob(i))); - } - } - break; - } - - - // en cas de voler filtrar un CBlobResult i deixar-ho en el mateix CBlobResult - // ( operacio inline ) - if (&dst == this) - { - // esborrem els primers blobs ( que són els originals ) - // ja que els tindrem replicats al final si passen el filtre - blob_vector::iterator itBlobs = m_blobs.begin(); - for (int i = 0; i < numBlobs; i++) - { - delete *itBlobs; - ++itBlobs; - } - m_blobs.erase(m_blobs.begin(), itBlobs); - } - } - - - /** - - FUNCIÓ: GetBlob - - FUNCIONALITAT: Retorna un blob si aquest existeix (index != -1) - - PARÀMETRES: - - indexblob: index del blob a retornar - - RESULTAT: - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /* - - FUNCTION: GetBlob - - FUNCTIONALITY: Gets the n-th blob (without ordering the blobs) - - PARAMETERS: - - indexblob: index in the blob array - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlob CBlobResult::GetBlob(int indexblob) const - { - if (indexblob < 0 || indexblob >= GetNumBlobs()) - RaiseError(EXCEPTION_BLOB_OUT_OF_BOUNDS); - - return *m_blobs[indexblob]; - } - CBlob *CBlobResult::GetBlob(int indexblob) - { - if (indexblob < 0 || indexblob >= GetNumBlobs()) - RaiseError(EXCEPTION_BLOB_OUT_OF_BOUNDS); - - return m_blobs[indexblob]; - } - - /** - - FUNCIÓ: GetNthBlob - - FUNCIONALITAT: Retorna l'enèssim blob segons un determinat criteri - - PARÀMETRES: - - criteri: criteri per ordenar els blobs (objectes derivats de COperadorBlob) - - nBlob: index del blob a retornar - - dst: on es retorna el resultat - - RESULTAT: - - retorna el blob nBlob a dst ordenant els blobs de la classe segons el criteri - en ordre DESCENDENT. Per exemple, per obtenir el blob major: - GetNthBlob( CBlobGetArea(), 0, blobMajor ); - GetNthBlob( CBlobGetArea(), 1, blobMajor ); (segon blob més gran) - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /* - - FUNCTION: GetNthBlob - - FUNCTIONALITY: Gets the n-th blob ordering first the blobs with some criteria - - PARAMETERS: - - criteri: criteria to order the blob array - - nBlob: index of the returned blob in the ordered blob array - - dst: where to store the result - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - void CBlobResult::GetNthBlob(funcio_calculBlob *criteri, int nBlob, CBlob &dst) const - { - // verifiquem que no estem accedint fora el vector de blobs - if (nBlob < 0 || nBlob >= GetNumBlobs()) - { - //RaiseError( EXCEPTION_BLOB_OUT_OF_BOUNDS ); - dst = CBlob(); - return; - } - - double_stl_vector avaluacioBlobs, avaluacioBlobsOrdenat; - double valorEnessim; - - //avaluem els blobs amb la funció pertinent - avaluacioBlobs = GetSTLResult(criteri); - - avaluacioBlobsOrdenat = double_stl_vector(GetNumBlobs()); - - // obtenim els nBlob primers resultats (en ordre descendent) - std::partial_sort_copy(avaluacioBlobs.begin(), - avaluacioBlobs.end(), - avaluacioBlobsOrdenat.begin(), - avaluacioBlobsOrdenat.end(), - std::greater()); - - valorEnessim = avaluacioBlobsOrdenat[nBlob]; - - // busquem el primer blob que té el valor n-ssim - double_stl_vector::const_iterator itAvaluacio = avaluacioBlobs.begin(); - - bool trobatBlob = false; - int indexBlob = 0; - while (itAvaluacio != avaluacioBlobs.end() && !trobatBlob) - { - if (*itAvaluacio == valorEnessim) - { - trobatBlob = true; - dst = CBlob(GetBlob(indexBlob)); - } - ++itAvaluacio; - indexBlob++; - } - } - - /** - - FUNCIÓ: ClearBlobs - - FUNCIONALITAT: Elimina tots els blobs de l'objecte - - PARÀMETRES: - - RESULTAT: - - Allibera tota la memòria dels blobs - - RESTRICCIONS: - - AUTOR: Ricard Borràs Navarra - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /* - - FUNCTION: ClearBlobs - - FUNCTIONALITY: Clears all the blobs from the object and releases all its memory - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - void CBlobResult::ClearBlobs() - { - /*for( int i = 0; i < GetNumBlobs(); i++ ) - { - delete m_blobs[i]; - }*/ - blob_vector::iterator itBlobs = m_blobs.begin(); - while (itBlobs != m_blobs.end()) - { - delete *itBlobs; - ++itBlobs; - } - - m_blobs.clear(); - } - - /** - - FUNCIÓ: RaiseError - - FUNCIONALITAT: Funció per a notificar errors al l'usuari (en debug) i llença - les excepcions - - PARÀMETRES: - - errorCode: codi d'error - - RESULTAT: - - Ensenya un missatge a l'usuari (en debug) i llença una excepció - - RESTRICCIONS: - - AUTOR: Ricard Borràs Navarra - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /* - - FUNCTION: RaiseError - - FUNCTIONALITY: Error handling function - - PARAMETERS: - - errorCode: reason of the error - - RESULT: - - in _DEBUG version, shows a message box with the error. In release is silent. - In both cases throws an exception with the error. - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - void CBlobResult::RaiseError(const int errorCode) const - { - throw errorCode; - } - - - - /************************************************************************** - Auxiliars / Auxiliary functions - **************************************************************************/ - - - /** - - FUNCIÓ: PrintBlobs - - FUNCIONALITAT: Escriu els paràmetres (àrea, perímetre, exterior, mitjana) - de tots els blobs a un fitxer. - - PARÀMETRES: - - nom_fitxer: path complet del fitxer amb el resultat - - RESULTAT: - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /* - - FUNCTION: PrintBlobs - - FUNCTIONALITY: Prints some blob features in an ASCII file - - PARAMETERS: - - nom_fitxer: full path + filename to generate - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - void CBlobResult::PrintBlobs(char *nom_fitxer) const - { - double_stl_vector area, /*perimetre,*/ exterior, mitjana, compacitat, longitud, - externPerimeter, perimetreConvex, perimetre; - int i; - FILE *fitxer_sortida; - - area = GetSTLResult(CBlobGetArea()); - perimetre = GetSTLResult(CBlobGetPerimeter()); - exterior = GetSTLResult(CBlobGetExterior()); - mitjana = GetSTLResult(CBlobGetMean()); - compacitat = GetSTLResult(CBlobGetCompactness()); - longitud = GetSTLResult(CBlobGetLength()); - externPerimeter = GetSTLResult(CBlobGetExternPerimeter()); - perimetreConvex = GetSTLResult(CBlobGetHullPerimeter()); - - fitxer_sortida = fopen(nom_fitxer, "w"); - - for (i = 0; i < GetNumBlobs(); i++) - { - fprintf(fitxer_sortida, "blob %d ->\t a=%7.0f\t p=%8.2f (%8.2f extern)\t pconvex=%8.2f\t ext=%.0f\t m=%7.2f\t c=%3.2f\t l=%8.2f\n", - i, area[i], perimetre[i], externPerimeter[i], perimetreConvex[i], exterior[i], mitjana[i], compacitat[i], longitud[i]); - } - fclose(fitxer_sortida); - - } - -} diff --git a/package_bgs/jmo/MultiLayerBGS.h b/package_bgs/jmo/MultiLayerBGS.h deleted file mode 100644 index 29db946..0000000 --- a/package_bgs/jmo/MultiLayerBGS.h +++ /dev/null @@ -1,101 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#include -#include - - -#include "../IBGS.h" -#include "CMultiLayerBGS.h" - -class MultiLayerBGS : public IBGS -{ -public: - enum Status - { - MLBGS_NONE = -1, - MLBGS_LEARN = 0, - MLBGS_DETECT = 1 - }; - -private: - bool firstTime; - long long frameNumber; - cv::Mat img_foreground; - cv::Mat img_merged; - cv::Mat img_background; - bool showOutput; - bool saveModel; - bool disableDetectMode; - bool disableLearning; - int detectAfter; - CMultiLayerBGS* BGS; - Status status; - IplImage* img; - IplImage* org_img; - IplImage* fg_img; - IplImage* bg_img; - IplImage* fg_prob_img; - IplImage* fg_mask_img; - IplImage* fg_prob_img3; - IplImage* merged_img; - std::string bg_model_preload; - - bool loadDefaultParams; - - int max_mode_num; - float weight_updating_constant; - float texture_weight; - float bg_mode_percent; - int pattern_neig_half_size; - float pattern_neig_gaus_sigma; - float bg_prob_threshold; - float bg_prob_updating_threshold; - int robust_LBP_constant; - float min_noised_angle; - float shadow_rate; - float highlight_rate; - float bilater_filter_sigma_s; - float bilater_filter_sigma_r; - - float frame_duration; - - float mode_learn_rate_per_second; - float weight_learn_rate_per_second; - float init_mode_weight; - - float learn_mode_learn_rate_per_second; - float learn_weight_learn_rate_per_second; - float learn_init_mode_weight; - - float detect_mode_learn_rate_per_second; - float detect_weight_learn_rate_per_second; - float detect_init_mode_weight; - -public: - MultiLayerBGS(); - ~MultiLayerBGS(); - - void setStatus(Status status); - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void finish(void); - void saveConfig(); - void loadConfig(); -}; \ No newline at end of file diff --git a/package_bgs/jmo/blob.cpp b/package_bgs/jmo/blob.cpp deleted file mode 100644 index 3629714..0000000 --- a/package_bgs/jmo/blob.cpp +++ /dev/null @@ -1,1149 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -/* --- --- --- -* Copyright (C) 2008--2010 Idiap Research Institute (.....@idiap.ch) -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions -* are met: -* 1. Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* 2. 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. -* 3. The name of the author may not be used to endorse or promote products -* derived from this software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. -*/ -/************************************************************************ -Blob.cpp - -- FUNCIONALITAT: Implementació de la classe CBlob -- AUTOR: Inspecta S.L. -MODIFICACIONS (Modificació, Autor, Data): - - -FUNCTIONALITY: Implementation of the CBlob class and some helper classes to perform -some calculations on it -AUTHOR: Inspecta S.L. -MODIFICATIONS (Modification, Author, Date): - -**************************************************************************/ - - -#include -#include "blob.h" -#include - -namespace Blob -{ - - /** - - FUNCIÓ: CBlob - - FUNCIONALITAT: Constructor estàndard - - PARÀMETRES: - - RESULTAT: - - inicialització de totes les variables internes i de l'storage i la sequencia - per a les cantonades del blob - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlob - - FUNCTIONALITY: Standard constructor - - PARAMETERS: - - RESULT: - - memory allocation for the blob edges and initialization of member variables - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlob::CBlob() - { - etiqueta = -1; // Flag indicates null region - exterior = 0; - area = 0.0f; - perimeter = 0.0f; - parent = -1; - minx = LONG_MAX; - maxx = 0; - miny = LONG_MAX; - maxy = 0; - sumx = 0; - sumy = 0; - sumxx = 0; - sumyy = 0; - sumxy = 0; - mean = 0; - stddev = 0; - externPerimeter = 0; - - m_storage = cvCreateMemStorage(0); - edges = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, - sizeof(CvContour), - sizeof(CvPoint), m_storage); - } - - /** - - FUNCIÓ: CBlob - - FUNCIONALITAT: Constructor de còpia - - PARÀMETRES: - - RESULTAT: - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlob - - FUNCTIONALITY: Copy constructor - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlob::CBlob(const CBlob &src) - { - // copiem les propietats del blob origen a l'actual - etiqueta = src.etiqueta; - exterior = src.exterior; - area = src.Area(); - perimeter = src.Perimeter(); - parent = src.parent; - minx = src.minx; - maxx = src.maxx; - miny = src.miny; - maxy = src.maxy; - sumx = src.sumx; - sumy = src.sumy; - sumxx = src.sumxx; - sumyy = src.sumyy; - sumxy = src.sumxy; - mean = src.mean; - stddev = src.stddev; - externPerimeter = src.externPerimeter; - - // copiem els edges del blob origen a l'actual - CvSeqReader reader; - CvSeqWriter writer; - CvPoint edgeactual; - - // creem una sequencia buida per als edges - m_storage = cvCreateMemStorage(0); - edges = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, - sizeof(CvContour), - sizeof(CvPoint), m_storage); - - cvStartReadSeq(src.Edges(), &reader); - cvStartAppendToSeq(edges, &writer); - - for (int i = 0; i < src.Edges()->total; i++) - { - CV_READ_SEQ_ELEM(edgeactual, reader); - CV_WRITE_SEQ_ELEM(edgeactual, writer); - } - - cvEndWriteSeq(&writer); - } - CBlob::CBlob(const CBlob *src) - { - // copiem les propietats del blob origen a l'actual - etiqueta = src->etiqueta; - exterior = src->exterior; - area = src->Area(); - perimeter = src->Perimeter(); - parent = src->parent; - minx = src->minx; - maxx = src->maxx; - miny = src->miny; - maxy = src->maxy; - sumx = src->sumx; - sumy = src->sumy; - sumxx = src->sumxx; - sumyy = src->sumyy; - sumxy = src->sumxy; - mean = src->mean; - stddev = src->stddev; - externPerimeter = src->externPerimeter; - - // copiem els edges del blob origen a l'actual - CvSeqReader reader; - CvSeqWriter writer; - CvPoint edgeactual; - - // creem una sequencia buida per als edges - m_storage = cvCreateMemStorage(0); - edges = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, - sizeof(CvContour), - sizeof(CvPoint), m_storage); - - cvStartReadSeq(src->Edges(), &reader); - cvStartAppendToSeq(edges, &writer); - - for (int i = 0; i < src->Edges()->total; i++) - { - CV_READ_SEQ_ELEM(edgeactual, reader); - CV_WRITE_SEQ_ELEM(edgeactual, writer); - } - - cvEndWriteSeq(&writer); - } - - /** - - FUNCIÓ: ~CBlob - - FUNCIONALITAT: Destructor estàndard - - PARÀMETRES: - - RESULTAT: - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlob - - FUNCTIONALITY: Standard destructor - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlob::~CBlob() - { - // Eliminar vèrtexs del blob - cvClearSeq(edges); - // i la zona de memòria on són - cvReleaseMemStorage(&m_storage); - } - - /** - - FUNCIÓ: operator= - - FUNCIONALITAT: Operador d'assignació - - PARÀMETRES: - - src: blob a assignar a l'actual - - RESULTAT: - - Substitueix el blob actual per el src - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: Assigment operator - - FUNCTIONALITY: Assigns a blob to the current - - PARAMETERS: - - src: blob to assign - - RESULT: - - the current blob is replaced by the src blob - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CBlob& CBlob::operator=(const CBlob &src) - { - // si ja són el mateix, no cal fer res - if (this != &src) - { - // Eliminar vèrtexs del blob - cvClearSeq(edges); - // i la zona de memòria on són - cvReleaseMemStorage(&m_storage); - - // creem una sequencia buida per als edges - m_storage = cvCreateMemStorage(0); - edges = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, - sizeof(CvContour), - sizeof(CvPoint), m_storage); - - // copiem les propietats del blob origen a l'actual - etiqueta = src.etiqueta; - exterior = src.exterior; - area = src.Area(); - perimeter = src.Perimeter(); - parent = src.parent; - minx = src.minx; - maxx = src.maxx; - miny = src.miny; - maxy = src.maxy; - sumx = src.sumx; - sumy = src.sumy; - sumxx = src.sumxx; - sumyy = src.sumyy; - sumxy = src.sumxy; - mean = src.mean; - stddev = src.stddev; - externPerimeter = src.externPerimeter; - - // copiem els edges del blob origen a l'actual - CvSeqReader reader; - CvSeqWriter writer; - CvPoint edgeactual; - - cvStartReadSeq(src.Edges(), &reader); - cvStartAppendToSeq(edges, &writer); - - for (int i = 0; i < src.Edges()->total; i++) - { - CV_READ_SEQ_ELEM(edgeactual, reader); - CV_WRITE_SEQ_ELEM(edgeactual, writer); - } - - cvEndWriteSeq(&writer); - } - return *this; - } - - /** - - FUNCIÓ: FillBlob - - FUNCIONALITAT: Pinta l'interior d'un blob amb el color especificat - - PARÀMETRES: - - imatge: imatge on es vol pintar el el blob - - color: color amb que es vol pintar el blob - - RESULTAT: - - retorna la imatge d'entrada amb el blob pintat - - RESTRICCIONS: - - AUTOR: - - Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: FillBlob - - FUNCTIONALITY: - - Fills the blob with a specified colour - - PARAMETERS: - - imatge: where to paint - - color: colour to paint the blob - - RESULT: - - modifies input image and returns the seed point used to fill the blob - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - void CBlob::FillBlob(IplImage *imatge, CvScalar color, int offsetX /*=0*/, int offsetY /*=0*/) const - { - - //verifiquem que existeixi el blob i que tingui cantonades - if (edges == NULL || edges->total == 0) return; - - CvPoint edgeactual, pt1, pt2; - CvSeqReader reader; - vectorPunts vectorEdges = vectorPunts(edges->total); - vectorPunts::iterator itEdges, itEdgesSeguent; - bool dinsBlob; - int yActual; - - // passem els punts del blob a un vector de punts de les STL - cvStartReadSeq(edges, &reader); - itEdges = vectorEdges.begin(); - while (itEdges != vectorEdges.end()) - { - CV_READ_SEQ_ELEM(edgeactual, reader); - *itEdges = edgeactual; - ++itEdges; - } - // ordenem el vector per les Y's i les X's d'esquerra a dreta - std::sort(vectorEdges.begin(), vectorEdges.end(), comparaCvPoint()); - - // recorrem el vector ordenat i fem linies entre punts consecutius - itEdges = vectorEdges.begin(); - itEdgesSeguent = vectorEdges.begin() + 1; - dinsBlob = true; - while (itEdges != (vectorEdges.end() - 1)) - { - yActual = (*itEdges).y; - - if (((*itEdges).x != (*itEdgesSeguent).x) && - ((*itEdgesSeguent).y == yActual) - ) - { - if (dinsBlob) - { - pt1 = *itEdges; - pt1.x += offsetX; - pt1.y += offsetY; - - pt2 = *itEdgesSeguent; - pt2.x += offsetX; - pt2.y += offsetY; - - cvLine(imatge, pt1, pt2, color); - } - dinsBlob = !dinsBlob; - } - ++itEdges; - ++itEdgesSeguent; - if ((*itEdges).y != yActual) dinsBlob = true; - } - vectorEdges.clear(); - } - - /** - - FUNCIÓ: CopyEdges - - FUNCIONALITAT: Afegeix els vèrtexs del blob al blob destination - - PARÀMETRES: - - destination: blob al que volem afegir els vèrtexs - - RESULTAT: - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CopyEdges - - FUNCTIONALITY: Adds the blob edges to destination - - PARAMETERS: - - destination: where to add the edges - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - void CBlob::CopyEdges(CBlob &destination) const - { - CvSeqReader reader; - CvSeqWriter writer; - CvPoint edgeactual; - - cvStartReadSeq(edges, &reader); - cvStartAppendToSeq(destination.Edges(), &writer); - - for (int i = 0; i < edges->total; i++) - { - CV_READ_SEQ_ELEM(edgeactual, reader); - CV_WRITE_SEQ_ELEM(edgeactual, writer); - } - - cvEndWriteSeq(&writer); - } - - /** - - FUNCIÓ: ClearEdges - - FUNCIONALITAT: Elimina els vèrtexs del blob - - PARÀMETRES: - - RESULTAT: - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: ClearEdges - - FUNCTIONALITY: Delete current blob edges - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - void CBlob::ClearEdges() - { - // Eliminar vèrtexs del blob eliminat - cvClearSeq(edges); - } - - /** - - FUNCIÓ: GetConvexHull - - FUNCIONALITAT: Retorna el poligon convex del blob - - PARÀMETRES: - - dst: sequencia on desarem el resultat (no ha d'estar inicialitzada) - - RESULTAT: - - true si tot ha anat bé - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: GetConvexHull - - FUNCTIONALITY: Calculates the convex hull polygon of the blob - - PARAMETERS: - - dst: where to store the result - - RESULT: - - true if no error ocurred - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - bool CBlob::GetConvexHull(CvSeq **dst) const - { - if (edges != NULL && edges->total > 0) - { - *dst = cvConvexHull2(edges, 0, CV_CLOCKWISE, 0); - return true; - } - return false; - } - - /** - - FUNCIÓ: GetEllipse - - FUNCIONALITAT: Retorna l'ellipse que s'ajusta millor a les cantonades del blob - - PARÀMETRES: - - RESULTAT: - - estructura amb l'ellipse - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 25-05-2005. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: GetEllipse - - FUNCTIONALITY: Calculates the ellipse that best fits the edges of the blob - - PARAMETERS: - - RESULT: - - CvBox2D struct with the calculated ellipse - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - CvBox2D CBlob::GetEllipse() const - { - CvBox2D elipse; - // necessitem 6 punts per calcular l'elipse - if (edges != NULL && edges->total > 6) - { - elipse = cvFitEllipse2(edges); - } - else - { - elipse.center.x = 0.0; - elipse.center.y = 0.0; - elipse.size.width = 0.0; - elipse.size.height = 0.0; - elipse.angle = 0.0; - } - return elipse; - } - - - - /*************************************************************************** - Implementació de les classes per al càlcul de característiques sobre el blob - - Implementation of the helper classes to perform operations on blobs - **************************************************************************/ - - /** - - FUNCIÓ: Moment - - FUNCIONALITAT: Calcula el moment pq del blob - - RESULTAT: - - retorna el moment pq especificat o 0 si el moment no està implementat - - RESTRICCIONS: - - Implementats els moments pq: 00, 01, 10, 20, 02 - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 20-07-2004. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: Moment - - FUNCTIONALITY: Calculates the pq moment of the blob - - PARAMETERS: - - RESULT: - - returns the pq moment or 0 if the moment it is not implemented - - RESTRICTIONS: - - Currently, only implemented the 00, 01, 10, 20, 02 pq moments - - AUTHOR: Ricard Borràs - - CREATION DATE: 20-07-2004. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetMoment::operator()(const CBlob &blob) const - { - //Moment 00 - if ((m_p == 0) && (m_q == 0)) - return blob.Area(); - - //Moment 10 - if ((m_p == 1) && (m_q == 0)) - return blob.SumX(); - - //Moment 01 - if ((m_p == 0) && (m_q == 1)) - return blob.SumY(); - - //Moment 20 - if ((m_p == 2) && (m_q == 0)) - return blob.SumXX(); - - //Moment 02 - if ((m_p == 0) && (m_q == 2)) - return blob.SumYY(); - - return 0; - } - - /** - - FUNCIÓ: HullPerimeter - - FUNCIONALITAT: Calcula la longitud del perimetre convex del blob. - Fa servir la funció d'OpenCV cvConvexHull2 per a - calcular el perimetre convex. - - - PARÀMETRES: - - RESULTAT: - - retorna la longitud del perímetre convex del blob. Si el blob no té coordenades - associades retorna el perímetre normal del blob. - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 20-07-2004. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlobGetHullPerimeter - - FUNCTIONALITY: Calculates the convex hull perimeter of the blob - - PARAMETERS: - - RESULT: - - returns the convex hull perimeter of the blob or the perimeter if the - blob edges could not be retrieved - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetHullPerimeter::operator()(const CBlob &blob) const - { - if (blob.Edges() != NULL && blob.Edges()->total > 0) - { - CvSeq *hull = cvConvexHull2(blob.Edges(), 0, CV_CLOCKWISE, 1); - return fabs(cvArcLength(hull, CV_WHOLE_SEQ, 1)); - } - return blob.Perimeter(); - } - - double CBlobGetHullArea::operator()(const CBlob &blob) const - { - if (blob.Edges() != NULL && blob.Edges()->total > 0) - { - CvSeq *hull = cvConvexHull2(blob.Edges(), 0, CV_CLOCKWISE, 1); - return fabs(cvContourArea(hull)); - } - return blob.Perimeter(); - } - - /** - - FUNCIÓ: MinX_at_MinY - - FUNCIONALITAT: Calcula el valor MinX a MinY. - - PARÀMETRES: - - blob: blob del que volem calcular el valor - - RESULTAT: - - retorna la X minima en la Y minima. - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 20-07-2004. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlobGetMinXatMinY - - FUNCTIONALITY: Calculates the minimum X on the minimum Y - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetMinXatMinY::operator()(const CBlob &blob) const - { - double MinX_at_MinY = LONG_MAX; - - CvSeqReader reader; - CvPoint edgeactual; - - cvStartReadSeq(blob.Edges(), &reader); - - for (int j = 0; j < blob.Edges()->total; j++) - { - CV_READ_SEQ_ELEM(edgeactual, reader); - if ((edgeactual.y == blob.MinY()) && (edgeactual.x < MinX_at_MinY)) - { - MinX_at_MinY = edgeactual.x; - } - } - - return MinX_at_MinY; - } - - /** - - FUNCIÓ: MinY_at_MaxX - - FUNCIONALITAT: Calcula el valor MinX a MaxX. - - PARÀMETRES: - - blob: blob del que volem calcular el valor - - RESULTAT: - - retorna la Y minima en la X maxima. - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 20-07-2004. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlobGetMinXatMinY - - FUNCTIONALITY: Calculates the minimum Y on the maximum X - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetMinYatMaxX::operator()(const CBlob &blob) const - { - double MinY_at_MaxX = LONG_MAX; - - CvSeqReader reader; - CvPoint edgeactual; - - cvStartReadSeq(blob.Edges(), &reader); - - for (int j = 0; j < blob.Edges()->total; j++) - { - CV_READ_SEQ_ELEM(edgeactual, reader); - if ((edgeactual.x == blob.MaxX()) && (edgeactual.y < MinY_at_MaxX)) - { - MinY_at_MaxX = edgeactual.y; - } - } - - return MinY_at_MaxX; - } - - /** - - FUNCIÓ: MaxX_at_MaxY - - FUNCIONALITAT: Calcula el valor MaxX a MaxY. - - PARÀMETRES: - - blob: blob del que volem calcular el valor - - RESULTAT: - - retorna la X maxima en la Y maxima. - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 20-07-2004. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlobGetMaxXatMaxY - - FUNCTIONALITY: Calculates the maximum X on the maximum Y - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetMaxXatMaxY::operator()(const CBlob &blob) const - { - double MaxX_at_MaxY = LONG_MIN; - - CvSeqReader reader; - CvPoint edgeactual; - - cvStartReadSeq(blob.Edges(), &reader); - - for (int j = 0; jtotal; j++) - { - CV_READ_SEQ_ELEM(edgeactual, reader); - if ((edgeactual.y == blob.MaxY()) && (edgeactual.x > MaxX_at_MaxY)) - { - MaxX_at_MaxY = edgeactual.x; - } - } - - return MaxX_at_MaxY; - } - - /** - - FUNCIÓ: MaxY_at_MinX - - FUNCIONALITAT: Calcula el valor MaxY a MinX. - - PARÀMETRES: - - blob: blob del que volem calcular el valor - - RESULTAT: - - retorna la Y maxima en la X minima. - - RESTRICCIONS: - - AUTOR: Ricard Borràs - - DATA DE CREACIÓ: 20-07-2004. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: CBlobGetMaxYatMinX - - FUNCTIONALITY: Calculates the maximum Y on the minimum X - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetMaxYatMinX::operator()(const CBlob &blob) const - { - double MaxY_at_MinX = LONG_MIN; - - CvSeqReader reader; - CvPoint edgeactual; - - cvStartReadSeq(blob.Edges(), &reader); - - for (int j = 0; jtotal; j++) - { - CV_READ_SEQ_ELEM(edgeactual, reader); - if ((edgeactual.x == blob.MinY()) && (edgeactual.y > MaxY_at_MinX)) - { - MaxY_at_MinX = edgeactual.y; - } - } - - return MaxY_at_MinX; - } - - /** - Retorna l'elongació del blob (longitud/amplada) - */ - /** - - FUNCTION: CBlobGetElongation - - FUNCTIONALITY: Calculates the elongation of the blob ( length/breadth ) - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - See below to see how the lenght and the breadth are aproximated - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetElongation::operator()(const CBlob &blob) const - { - double ampladaC, longitudC, amplada, longitud; - - ampladaC = (double)(blob.Perimeter() + sqrt(pow(blob.Perimeter(), 2) - 16 * blob.Area())) / 4; - if (ampladaC <= 0.0) return 0; - longitudC = (double)blob.Area() / ampladaC; - - longitud = MAX(longitudC, ampladaC); - amplada = MIN(longitudC, ampladaC); - - return (double)longitud / amplada; - } - - /** - Retorna la compacitat del blob - */ - /** - - FUNCTION: CBlobGetCompactness - - FUNCTIONALITY: Calculates the compactness of the blob - ( maximum for circle shaped blobs, minimum for the rest) - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetCompactness::operator()(const CBlob &blob) const - { - if (blob.Area() != 0.0) - return (double)pow(blob.Perimeter(), 2) / (4 * CV_PI*blob.Area()); - else - return 0.0; - } - - /** - Retorna la rugositat del blob - */ - /** - - FUNCTION: CBlobGetRoughness - - FUNCTIONALITY: Calculates the roughness of the blob - ( ratio between perimeter and convex hull perimeter) - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetRoughness::operator()(const CBlob &blob) const - { - CBlobGetHullPerimeter getHullPerimeter = CBlobGetHullPerimeter(); - - double hullPerimeter = getHullPerimeter(blob); - - if (hullPerimeter != 0.0) - return blob.Perimeter() / hullPerimeter;//HullPerimeter(); - - return 0.0; - } - - /** - Retorna la longitud del blob - */ - /** - - FUNCTION: CBlobGetLength - - FUNCTIONALITY: Calculates the lenght of the blob (the biggest axis of the blob) - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - The lenght is an aproximation to the real lenght - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetLength::operator()(const CBlob &blob) const - { - double ampladaC, longitudC; - double tmp; - - tmp = blob.Perimeter()*blob.Perimeter() - 16 * blob.Area(); - - if (tmp > 0.0) - ampladaC = (double)(blob.Perimeter() + sqrt(tmp)) / 4; - // error intrínsec en els càlculs de l'àrea i el perímetre - else - ampladaC = (double)(blob.Perimeter()) / 4; - - if (ampladaC <= 0.0) return 0; - longitudC = (double)blob.Area() / ampladaC; - - return MAX(longitudC, ampladaC); - } - - /** - Retorna l'amplada del blob - */ - /** - - FUNCTION: CBlobGetBreadth - - FUNCTIONALITY: Calculates the breadth of the blob (the smallest axis of the blob) - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - The breadth is an aproximation to the real breadth - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetBreadth::operator()(const CBlob &blob) const - { - double ampladaC, longitudC; - double tmp; - - tmp = blob.Perimeter()*blob.Perimeter() - 16 * blob.Area(); - - if (tmp > 0.0) - ampladaC = (double)(blob.Perimeter() + sqrt(tmp)) / 4; - // error intrínsec en els càlculs de l'àrea i el perímetre - else - ampladaC = (double)(blob.Perimeter()) / 4; - - if (ampladaC <= 0.0) return 0; - longitudC = (double)blob.Area() / ampladaC; - - return MIN(longitudC, ampladaC); - } - - /** - Calcula la distància entre un punt i el centre del blob - */ - /** - - FUNCTION: CBlobGetDistanceFromPoint - - FUNCTIONALITY: Calculates the euclidean distance between the blob center and - the point specified in the constructor - - PARAMETERS: - - RESULT: - - RESTRICTIONS: - - AUTHOR: Ricard Borràs - - CREATION DATE: 25-05-2005. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetDistanceFromPoint::operator()(const CBlob &blob) const - { - double xmitjana, ymitjana; - CBlobGetXCenter getXCenter; - CBlobGetYCenter getYCenter; - - xmitjana = m_x - getXCenter(blob); - ymitjana = m_y - getYCenter(blob); - - return sqrt((xmitjana*xmitjana) + (ymitjana*ymitjana)); - } - - /** - - FUNCIÓ: BlobGetXYInside - - FUNCIONALITAT: Calcula si un punt cau dins de la capsa rectangular - del blob - - RESULTAT: - - retorna 1 si hi està; 0 si no - - RESTRICCIONS: - - AUTOR: Francesc Pinyol Margalef - - DATA DE CREACIÓ: 16-01-2006. - - MODIFICACIÓ: Data. Autor. Descripció. - */ - /** - - FUNCTION: BlobGetXYInside - - FUNCTIONALITY: Calculates whether a point is inside the - rectangular bounding box of a blob - - PARAMETERS: - - RESULT: - - returns 1 if it is inside; o if not - - RESTRICTIONS: - - AUTHOR: Francesc Pinyol Margalef - - CREATION DATE: 16-01-2006. - - MODIFICATION: Date. Author. Description. - */ - double CBlobGetXYInside::operator()(const CBlob &blob) const - { - if (blob.Edges() == NULL || blob.Edges()->total == 0) return 0.0; - - // passem els punts del blob a un vector de punts de les STL - CvSeqReader reader; - CBlob::vectorPunts vectorEdges; - CBlob::vectorPunts::iterator itEdges, itEdgesSeguent; - CvPoint edgeactual; - bool dinsBlob; - - // agafem tots els punts amb la mateixa y que l'actual - cvStartReadSeq(blob.Edges(), &reader); - - for (int i = 0; i < blob.Edges()->total; i++) - { - CV_READ_SEQ_ELEM(edgeactual, reader); - if (edgeactual.y == m_p.y) - vectorEdges.push_back(edgeactual); - } - - if (vectorEdges.empty()) return 0.0; - - // ordenem el vector per les Y's i les X's d'esquerra a dreta - std::sort(vectorEdges.begin(), vectorEdges.end(), CBlob::comparaCvPoint()); - - // recorrem el punts del blob de la mateixa fila que el punt d'entrada - // i mirem si la X del punt d'entrada està entre dos coordenades "plenes" - // del blob - itEdges = vectorEdges.begin(); - itEdgesSeguent = vectorEdges.begin() + 1; - dinsBlob = true; - - while (itEdges != (vectorEdges.end() - 1)) - { - if ((*itEdges).x <= m_p.x && (*itEdgesSeguent).x >= m_p.x && dinsBlob) - { - vectorEdges.clear(); - return 1.0; - } - - ++itEdges; - ++itEdgesSeguent; - dinsBlob = !dinsBlob; - } - - vectorEdges.clear(); - return 0.0; - } - -#ifdef BLOB_OBJECT_FACTORY - - /** - - FUNCIÓ: RegistraTotsOperadors - - FUNCIONALITAT: Registrar tots els operadors definits a blob.h - - PARÀMETRES: - - fabricaOperadorsBlob: fàbrica on es registraran els operadors - - RESULTAT: - - Modifica l'objecte fabricaOperadorsBlob - - RESTRICCIONS: - - Només es registraran els operadors de blob.h. Si se'n volen afegir, cal afegir-los amb - el mètode Register de la fàbrica. - - AUTOR: rborras - - DATA DE CREACIÓ: 2006/05/18 - - MODIFICACIÓ: Data. Autor. Descripció. - */ - void RegistraTotsOperadors( t_OperadorBlobFactory &fabricaOperadorsBlob ) - { - // blob shape - fabricaOperadorsBlob.Register( CBlobGetArea().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetBreadth().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetCompactness().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetElongation().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetExterior().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetLength().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetPerimeter().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetRoughness().GetNom(), Type2Type()); - - // extern pixels - fabricaOperadorsBlob.Register( CBlobGetExternPerimeterRatio().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetExternHullPerimeterRatio().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetExternPerimeter().GetNom(), Type2Type()); - - - // hull - fabricaOperadorsBlob.Register( CBlobGetHullPerimeter().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetHullArea().GetNom(), Type2Type()); - - - // elipse info - fabricaOperadorsBlob.Register( CBlobGetMajorAxisLength().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetMinorAxisLength().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetAxisRatio().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetOrientation().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetOrientationCos().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetAreaElipseRatio().GetNom(), Type2Type()); - - // min an max - fabricaOperadorsBlob.Register( CBlobGetMaxX().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetMaxY().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetMinX().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetMinY().GetNom(), Type2Type()); - - fabricaOperadorsBlob.Register( CBlobGetMaxXatMaxY().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetMaxYatMinX().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetMinXatMinY().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetMinYatMaxX().GetNom(), Type2Type()); - - // grey level stats - fabricaOperadorsBlob.Register( CBlobGetMean().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetStdDev().GetNom(), Type2Type()); - - // coordinate info - fabricaOperadorsBlob.Register( CBlobGetXYInside().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetDiffY().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetDiffX().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetXCenter().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetYCenter().GetNom(), Type2Type()); - fabricaOperadorsBlob.Register( CBlobGetDistanceFromPoint().GetNom(), Type2Type()); - - // moments - fabricaOperadorsBlob.Register( CBlobGetMoment().GetNom(), Type2Type()); - - } - -#endif - -} - diff --git a/package_bgs/lb/BGModel.cpp b/package_bgs/lb/BGModel.cpp index c42bec7..3c73b53 100644 --- a/package_bgs/lb/BGModel.cpp +++ b/package_bgs/lb/BGModel.cpp @@ -14,9 +14,9 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModel.cpp - + Copyright (C) 2011 Laurence Bender This program is free software; you can redistribute it and/or modify @@ -38,11 +38,11 @@ along with BGSLibrary. If not, see . namespace lb_library { - BGModel::BGModel(int width, int height): m_width(width), m_height(height) + BGModel::BGModel(int width, int height) : m_width(width), m_height(height) { - m_SrcImage = cvCreateImage(cvSize(m_width,m_height), IPL_DEPTH_8U, 3); - m_BGImage = cvCreateImage(cvSize(m_width,m_height), IPL_DEPTH_8U, 3); - m_FGImage = cvCreateImage(cvSize(m_width,m_height), IPL_DEPTH_8U, 3); + m_SrcImage = cvCreateImage(cvSize(m_width, m_height), IPL_DEPTH_8U, 3); + m_BGImage = cvCreateImage(cvSize(m_width, m_height), IPL_DEPTH_8U, 3); + m_FGImage = cvCreateImage(cvSize(m_width, m_height), IPL_DEPTH_8U, 3); cvZero(m_SrcImage); cvZero(m_BGImage); @@ -51,9 +51,9 @@ namespace lb_library BGModel::~BGModel() { - if (m_SrcImage!=NULL) cvReleaseImage(&m_SrcImage); - if (m_BGImage!=NULL) cvReleaseImage(&m_BGImage); - if (m_FGImage!=NULL) cvReleaseImage(&m_FGImage); + if (m_SrcImage != NULL) cvReleaseImage(&m_SrcImage); + if (m_BGImage != NULL) cvReleaseImage(&m_BGImage); + if (m_FGImage != NULL) cvReleaseImage(&m_FGImage); } IplImage* BGModel::GetSrc() @@ -73,15 +73,15 @@ namespace lb_library void BGModel::InitModel(IplImage* image) { - cvCopy(image,m_SrcImage); + cvCopy(image, m_SrcImage); Init(); return; } void BGModel::UpdateModel(IplImage* image) { - cvCopy(image,m_SrcImage); + cvCopy(image, m_SrcImage); Update(); return; } -} \ No newline at end of file +} diff --git a/package_bgs/lb/BGModel.h b/package_bgs/lb/BGModel.h index 77f0490..d47ad62 100644 --- a/package_bgs/lb/BGModel.h +++ b/package_bgs/lb/BGModel.h @@ -14,9 +14,9 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModel.h - + Copyright (C) 2011 Laurence Bender This program is free software; you can redistribute it and/or modify @@ -33,9 +33,7 @@ along with BGSLibrary. If not, see . along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -#ifndef BGMODEL_H -#define BGMODEL_H +#pragma once #include #include @@ -54,7 +52,7 @@ namespace lb_library void InitModel(IplImage* image); void UpdateModel(IplImage* image); - + virtual void setBGModelParameter(int id, int value) {}; virtual IplImage* GetSrc(); @@ -62,17 +60,15 @@ namespace lb_library virtual IplImage* GetBG(); protected: - + IplImage* m_SrcImage; IplImage* m_BGImage; IplImage* m_FGImage; const int m_width; const int m_height; - + virtual void Init() = 0; virtual void Update() = 0; }; } - -#endif diff --git a/package_bgs/lb/BGModelFuzzyGauss.cpp b/package_bgs/lb/BGModelFuzzyGauss.cpp index 8706b13..b249052 100644 --- a/package_bgs/lb/BGModelFuzzyGauss.cpp +++ b/package_bgs/lb/BGModelFuzzyGauss.cpp @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModelFuzzyGauss.cpp Copyright (C) 2011 Laurence Bender @@ -40,7 +40,7 @@ namespace lb_library { namespace FuzzyGaussian { - BGModelFuzzyGauss::BGModelFuzzyGauss(int width, int height) : BGModel(width,height) + BGModelFuzzyGauss::BGModelFuzzyGauss(int width, int height) : BGModel(width, height) { m_alphamax = ALPHAFUZZYGAUSS; m_threshold = THRESHOLDFUZZYGAUSS * THRESHOLDFUZZYGAUSS; @@ -53,7 +53,7 @@ namespace lb_library DBLRGB *pMu = m_pMu; DBLRGB *pVar = m_pVar; - for(int k = 0; k < (m_width * m_height); k++) + for (int k = 0; k < (m_width * m_height); k++) { pMu->Red = 0.0; pMu->Green = 0.0; @@ -70,15 +70,15 @@ namespace lb_library BGModelFuzzyGauss::~BGModelFuzzyGauss() { - delete [] m_pMu; - delete [] m_pVar; + delete[] m_pMu; + delete[] m_pVar; } void BGModelFuzzyGauss::setBGModelParameter(int id, int value) { - double dvalue = (double)value/255.0; + double dvalue = (double)value / 255.0; - switch(id) + switch (id) { case 0: m_threshold = 100.0*dvalue*dvalue; @@ -107,9 +107,9 @@ namespace lb_library Image prgbSrc(m_SrcImage); - for(int i = 0; i < m_height; i++) + for (int i = 0; i < m_height; i++) { - for(int j = 0; j < m_width; j++) + for (int j = 0; j < m_width; j++) { pMu->Red = prgbSrc[i][j].Red; pMu->Green = prgbSrc[i][j].Green; @@ -136,13 +136,13 @@ namespace lb_library Image prgbBG(m_BGImage); Image prgbFG(m_FGImage); - for(int i = 0; i < m_height; i++) + for (int i = 0; i < m_height; i++) { - for(int j = 0; j < m_width; j++) + for (int j = 0; j < m_width; j++) { - double srcR = (double) prgbSrc[i][j].Red; - double srcG = (double) prgbSrc[i][j].Green; - double srcB = (double) prgbSrc[i][j].Blue; + double srcR = (double)prgbSrc[i][j].Red; + double srcG = (double)prgbSrc[i][j].Green; + double srcB = (double)prgbSrc[i][j].Blue; // Fuzzy background subtraction (Mahalanobis distance) @@ -150,53 +150,53 @@ namespace lb_library double dg = srcG - pMu->Green; double db = srcB - pMu->Blue; - double d2 = dr*dr/pVar->Red + dg*dg/pVar->Green + db*db/pVar->Blue; + double d2 = dr*dr / pVar->Red + dg*dg / pVar->Green + db*db / pVar->Blue; double fuzzyBG = 1.0; - if(d2 < m_threshold) - fuzzyBG = d2/m_threshold; + if (d2 < m_threshold) + fuzzyBG = d2 / m_threshold; // Fuzzy running average double alpha = m_alphamax*exp(FUZZYEXP*fuzzyBG); - if(dr*dr > DBL_MIN) + if (dr*dr > DBL_MIN) pMu->Red += alpha*dr; - if(dg*dg > DBL_MIN) + if (dg*dg > DBL_MIN) pMu->Green += alpha*dg; - if(db*db > DBL_MIN) + if (db*db > DBL_MIN) pMu->Blue += alpha*db; double d; d = (srcR - pMu->Red)*(srcR - pMu->Red) - pVar->Red; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pVar->Red += alpha*d; d = (srcG - pMu->Green)*(srcG - pMu->Green) - pVar->Green; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pVar->Green += alpha*d; d = (srcB - pMu->Blue)*(srcB - pMu->Blue) - pVar->Blue; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pVar->Blue += alpha*d; - pVar->Red = (std::max)(pVar->Red,m_noise); - pVar->Green = (std::max)(pVar->Green,m_noise); - pVar->Blue = (std::max)(pVar->Blue,m_noise); + pVar->Red = (std::max)(pVar->Red, m_noise); + pVar->Green = (std::max)(pVar->Green, m_noise); + pVar->Blue = (std::max)(pVar->Blue, m_noise); // Set foreground and background - if(fuzzyBG >= m_threshBG) + if (fuzzyBG >= m_threshBG) prgbFG[i][j].Red = prgbFG[i][j].Green = prgbFG[i][j].Blue = 255; else prgbFG[i][j].Red = prgbFG[i][j].Green = prgbFG[i][j].Blue = 0; - prgbBG[i][j].Red = (unsigned char)pMu->Red; - prgbBG[i][j].Green = (unsigned char)pMu->Green; + prgbBG[i][j].Red = (unsigned char)pMu->Red; + prgbBG[i][j].Green = (unsigned char)pMu->Green; prgbBG[i][j].Blue = (unsigned char)pMu->Blue; pMu++; @@ -207,4 +207,4 @@ namespace lb_library return; } } -} \ No newline at end of file +} diff --git a/package_bgs/lb/BGModelFuzzyGauss.h b/package_bgs/lb/BGModelFuzzyGauss.h index 98a92e5..a73b716 100644 --- a/package_bgs/lb/BGModelFuzzyGauss.h +++ b/package_bgs/lb/BGModelFuzzyGauss.h @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModelFuzzyGauss.h Copyright (C) 2011 Laurence Bender @@ -33,9 +33,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -#ifndef BGMODELFUZZYGAUSS_H -#define BGMODELFUZZYGAUSS_H +#pragma once #include "BGModel.h" @@ -71,5 +69,3 @@ namespace lb_library }; } } - -#endif diff --git a/package_bgs/lb/BGModelFuzzySom.cpp b/package_bgs/lb/BGModelFuzzySom.cpp index 189cb1a..e7e59dd 100644 --- a/package_bgs/lb/BGModelFuzzySom.cpp +++ b/package_bgs/lb/BGModelFuzzySom.cpp @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModelFuzzySom.cpp Copyright (C) 2011 Laurence Bender @@ -40,27 +40,27 @@ namespace lb_library { namespace FuzzyAdaptiveSOM { - BGModelFuzzySom::BGModelFuzzySom(int width, int height) : BGModel(width,height) + BGModelFuzzySom::BGModelFuzzySom(int width, int height) : BGModel(width, height) { - m_offset = (KERNEL - 1)/2; + m_offset = (KERNEL - 1) / 2; - if(SPAN_NEIGHBORS) - m_pad = 0; + if (SPAN_NEIGHBORS) + m_pad = 0; else m_pad = m_offset; // SOM models - m_widthSOM = m_width*M + 2*m_offset + (m_width-1)*m_pad; - m_heightSOM = m_height*N + 2*m_offset + (m_height-1)*m_pad; + m_widthSOM = m_width*M + 2 * m_offset + (m_width - 1)*m_pad; + m_heightSOM = m_height*N + 2 * m_offset + (m_height - 1)*m_pad; - m_ppSOM = new DBLRGB*[m_heightSOM]; - for(int n = 0; n < m_heightSOM; n++) + m_ppSOM = new DBLRGB*[m_heightSOM]; + for (int n = 0; n < m_heightSOM; n++) m_ppSOM[n] = new DBLRGB[m_widthSOM]; - for(int j = 0; j < m_heightSOM; j++) + for (int j = 0; j < m_heightSOM; j++) { - for(int i = 0; i < m_widthSOM; i++) + for (int i = 0; i < m_widthSOM; i++) { m_ppSOM[j][i].Red = 0.0; m_ppSOM[j][i].Green = 0.0; @@ -71,7 +71,7 @@ namespace lb_library // Create weights m_ppW = new double*[KERNEL]; - for(int n = 0; n < KERNEL; n++) + for (int n = 0; n < KERNEL; n++) m_ppW[n] = new double[KERNEL]; // Construct Gaussian kernel using Pascal's triangle @@ -81,15 +81,15 @@ namespace lb_library m_Wmax = DBL_MIN; cN = 1; - for(int j = 0; j < KERNEL; j++) + for (int j = 0; j < KERNEL; j++) { - cM = 1; + cM = 1; - for(int i = 0; i < KERNEL; i++) + for (int i = 0; i < KERNEL; i++) { m_ppW[j][i] = cN*cM; - if(m_ppW[j][i] > m_Wmax) + if (m_ppW[j][i] > m_Wmax) m_Wmax = m_ppW[j][i]; cM = cM * (KERNEL - 1 - i) / (i + 1); @@ -103,8 +103,8 @@ namespace lb_library m_epsilon1 = EPS1*EPS1; m_epsilon2 = EPS2*EPS2; - m_alpha1 = C1/m_Wmax; - m_alpha2 = C2/m_Wmax; + m_alpha1 = C1 / m_Wmax; + m_alpha2 = C2 / m_Wmax; m_K = 0; m_TSteps = TRAINING_STEPS; @@ -112,22 +112,22 @@ namespace lb_library BGModelFuzzySom::~BGModelFuzzySom() { - for(int n = 0; n < m_heightSOM; n++) - delete [] m_ppSOM[n]; + for (int n = 0; n < m_heightSOM; n++) + delete[] m_ppSOM[n]; - delete [] m_ppSOM; + delete[] m_ppSOM; - for(int n = 0; n < KERNEL; n++) - delete [] m_ppW[n]; + for (int n = 0; n < KERNEL; n++) + delete[] m_ppW[n]; - delete [] m_ppW; + delete[] m_ppW; } void BGModelFuzzySom::setBGModelParameter(int id, int value) { - double dvalue = (double)value/255.0; + double dvalue = (double)value / 255.0; - switch(id) + switch (id) { case 0: m_epsilon2 = 255.0*255.0*dvalue*dvalue*dvalue*dvalue; @@ -138,11 +138,11 @@ namespace lb_library break; case 2: - m_alpha2 = dvalue*dvalue*dvalue/m_Wmax; + m_alpha2 = dvalue*dvalue*dvalue / m_Wmax; break; case 3: - m_alpha1 = dvalue*dvalue*dvalue/m_Wmax; + m_alpha1 = dvalue*dvalue*dvalue / m_Wmax; break; case 5: @@ -157,21 +157,21 @@ namespace lb_library { Image prgbSrc(m_SrcImage); - for(int j = 0; j < m_height; j++) + for (int j = 0; j < m_height; j++) { int jj = m_offset + j*(N + m_pad); - for(int i = 0; i < m_width; i++) + for (int i = 0; i < m_width; i++) { - int ii = m_offset + i*(M + m_pad); + int ii = m_offset + i*(M + m_pad); - for(int l = 0; l < N; l++) + for (int l = 0; l < N; l++) { - for(int k = 0; k < M; k++) + for (int k = 0; k < M; k++) { - m_ppSOM[jj+l][ii+k].Red = (double)prgbSrc[j][i].Red; - m_ppSOM[jj+l][ii+k].Green = (double)prgbSrc[j][i].Green; - m_ppSOM[jj+l][ii+k].Blue = (double)prgbSrc[j][i].Blue; + m_ppSOM[jj + l][ii + k].Red = (double)prgbSrc[j][i].Red; + m_ppSOM[jj + l][ii + k].Green = (double)prgbSrc[j][i].Green; + m_ppSOM[jj + l][ii + k].Blue = (double)prgbSrc[j][i].Blue; } } } @@ -184,11 +184,11 @@ namespace lb_library void BGModelFuzzySom::Update() { - double alpha,a; + double alpha, a; double epsilon; // calibration phase - if(m_K <= m_TSteps) + if (m_K <= m_TSteps) { epsilon = m_epsilon1; alpha = (m_alpha1 - m_K * (m_alpha1 - m_alpha2) / m_TSteps); @@ -204,11 +204,11 @@ namespace lb_library Image prgbBG(m_BGImage); Image prgbFG(m_FGImage); - for(int j = 0; j < m_height; j++) + for (int j = 0; j < m_height; j++) { int jj = m_offset + j*(N + m_pad); - for(int i = 0; i < m_width; i++) + for (int i = 0; i < m_width; i++) { int ii = m_offset + i*(M + m_pad); @@ -222,17 +222,17 @@ namespace lb_library int iiHit = ii; int jjHit = jj; - for(int l = 0; l < N; l++) + for (int l = 0; l < N; l++) { - for(int k = 0; k < M; k++) + for (int k = 0; k < M; k++) { - double dr = srcR - m_ppSOM[jj+l][ii+k].Red; - double dg = srcG - m_ppSOM[jj+l][ii+k].Green; - double db = srcB - m_ppSOM[jj+l][ii+k].Blue; + double dr = srcR - m_ppSOM[jj + l][ii + k].Red; + double dg = srcG - m_ppSOM[jj + l][ii + k].Green; + double db = srcB - m_ppSOM[jj + l][ii + k].Blue; double d2 = dr*dr + dg*dg + db*db; - if(d2 < d2min) + if (d2 < d2min) { d2min = d2; iiHit = ii + k; @@ -243,16 +243,16 @@ namespace lb_library double fuzzyBG = 1.0; - if(d2min < epsilon) - fuzzyBG = d2min/epsilon; + if (d2min < epsilon) + fuzzyBG = d2min / epsilon; // Update SOM double alphamax = alpha*exp(FUZZYEXP*fuzzyBG); - for(int l = (jjHit - m_offset); l <= (jjHit + m_offset); l++) + for (int l = (jjHit - m_offset); l <= (jjHit + m_offset); l++) { - for(int k = (iiHit - m_offset); k <= (iiHit + m_offset); k++) + for (int k = (iiHit - m_offset); k <= (iiHit + m_offset); k++) { a = alphamax * m_ppW[l - jjHit + m_offset][k - iiHit + m_offset]; @@ -261,20 +261,20 @@ namespace lb_library double d; d = srcR - m_ppSOM[l][k].Red; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) m_ppSOM[l][k].Red += a*d; d = srcG - m_ppSOM[l][k].Green; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) m_ppSOM[l][k].Green += a*d; d = srcB - m_ppSOM[l][k].Blue; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) m_ppSOM[l][k].Blue += a*d; } } - if(fuzzyBG >= FUZZYTHRESH) + if (fuzzyBG >= FUZZYTHRESH) { // Set foreground image prgbFG[j][i].Red = prgbFG[j][i].Green = prgbFG[j][i].Blue = 255; @@ -284,7 +284,7 @@ namespace lb_library // Set background image prgbBG[j][i].Red = m_ppSOM[jjHit][iiHit].Red; prgbBG[j][i].Green = m_ppSOM[jjHit][iiHit].Green; - prgbBG[j][i].Blue = m_ppSOM[jjHit][iiHit].Blue; + prgbBG[j][i].Blue = m_ppSOM[jjHit][iiHit].Blue; // Set foreground image prgbFG[j][i].Red = prgbFG[j][i].Green = prgbFG[j][i].Blue = 0; @@ -295,4 +295,4 @@ namespace lb_library return; } } -} \ No newline at end of file +} diff --git a/package_bgs/lb/BGModelFuzzySom.h b/package_bgs/lb/BGModelFuzzySom.h index 2f21d3e..ed0bf21 100644 --- a/package_bgs/lb/BGModelFuzzySom.h +++ b/package_bgs/lb/BGModelFuzzySom.h @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModelFuzzySom.h Copyright (C) 2011 Laurence Bender @@ -33,9 +33,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -#ifndef BGMODELFUZZYSOM_H -#define BGMODELFUZZYSOM_H +#pragma once #include "BGModel.h" @@ -47,9 +45,9 @@ namespace lb_library const int M = 3; // width SOM (per pixel) const int N = 3; // height SOM (per pixel) - const int KERNEL = 3; // size Gaussian kernel + const int KERNEL = 3; // size Gaussian kernel - const bool SPAN_NEIGHBORS = false; // true if update neighborhood spans different pixels // + const bool SPAN_NEIGHBORS = false; // true if update neighborhood spans different pixels // const int TRAINING_STEPS = 100; // number of training steps const double EPS1 = 100.0; // model match distance during training @@ -84,12 +82,10 @@ namespace lb_library double m_alpha2; DBLRGB** m_ppSOM; // SOM grid - double** m_ppW; // Weights + double** m_ppW; // Weights void Init(); void Update(); }; } } - -#endif diff --git a/package_bgs/lb/BGModelGauss.cpp b/package_bgs/lb/BGModelGauss.cpp index 6892d13..28a8b93 100644 --- a/package_bgs/lb/BGModelGauss.cpp +++ b/package_bgs/lb/BGModelGauss.cpp @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModelGauss.cpp Copyright (C) 2011 Laurence Bender @@ -40,7 +40,7 @@ namespace lb_library { namespace SimpleGaussian { - BGModelGauss::BGModelGauss(int width, int height) : BGModel(width,height) + BGModelGauss::BGModelGauss(int width, int height) : BGModel(width, height) { m_alpha = ALPHAGAUSS; m_threshold = THRESHGAUSS*THRESHGAUSS; @@ -52,7 +52,7 @@ namespace lb_library DBLRGB *pMu = m_pMu; DBLRGB *pVar = m_pVar; - for(int k = 0; k < (m_width * m_height); k++) + for (int k = 0; k < (m_width * m_height); k++) { pMu->Red = 0.0; pMu->Green = 0.0; @@ -69,15 +69,15 @@ namespace lb_library BGModelGauss::~BGModelGauss() { - delete [] m_pMu; - delete [] m_pVar; + delete[] m_pMu; + delete[] m_pVar; } void BGModelGauss::setBGModelParameter(int id, int value) { - double dvalue = (double)value/255.0; + double dvalue = (double)value / 255.0; - switch(id) + switch (id) { case 0: m_threshold = 100.0*dvalue*dvalue; @@ -87,7 +87,7 @@ namespace lb_library m_noise = 100.0*dvalue; break; - case 2: + case 2: m_alpha = dvalue*dvalue*dvalue; break; } @@ -102,9 +102,9 @@ namespace lb_library Image prgbSrc(m_SrcImage); - for(int i = 0; i < m_height; i++) + for (int i = 0; i < m_height; i++) { - for(int j = 0; j < m_width; j++) + for (int j = 0; j < m_width; j++) { pMu->Red = prgbSrc[i][j].Red; pMu->Green = prgbSrc[i][j].Green; @@ -131,62 +131,62 @@ namespace lb_library Image prgbBG(m_BGImage); Image prgbFG(m_FGImage); - for(int i = 0; i < m_height; i++) + for (int i = 0; i < m_height; i++) { - for(int j = 0; j < m_width; j++) + for (int j = 0; j < m_width; j++) { - double srcR = (double) prgbSrc[i][j].Red; - double srcG = (double) prgbSrc[i][j].Green; - double srcB = (double) prgbSrc[i][j].Blue; + double srcR = (double)prgbSrc[i][j].Red; + double srcG = (double)prgbSrc[i][j].Green; + double srcB = (double)prgbSrc[i][j].Blue; - // Mahalanobis distance + // Mahalanobis distance double dr = srcR - pMu->Red; double dg = srcG - pMu->Green; double db = srcB - pMu->Blue; - double d2 = dr*dr/pVar->Red + dg*dg/pVar->Green + db*db/pVar->Blue; + double d2 = dr*dr / pVar->Red + dg*dg / pVar->Green + db*db / pVar->Blue; // Classify - if(d2 < m_threshold) - prgbFG[i][j].Red = prgbFG[i][j].Green = prgbFG[i][j].Blue = 0; + if (d2 < m_threshold) + prgbFG[i][j].Red = prgbFG[i][j].Green = prgbFG[i][j].Blue = 0; else - prgbFG[i][j].Red = prgbFG[i][j].Green = prgbFG[i][j].Blue = 255; + prgbFG[i][j].Red = prgbFG[i][j].Green = prgbFG[i][j].Blue = 255; // Update parameters - if(dr*dr > DBL_MIN) + if (dr*dr > DBL_MIN) pMu->Red += m_alpha*dr; - if(dg*dg > DBL_MIN) + if (dg*dg > DBL_MIN) pMu->Green += m_alpha*dg; - if(db*db > DBL_MIN) + if (db*db > DBL_MIN) pMu->Blue += m_alpha*db; double d; d = (srcR - pMu->Red)*(srcR - pMu->Red) - pVar->Red; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pVar->Red += m_alpha*d; d = (srcG - pMu->Green)*(srcG - pMu->Green) - pVar->Green; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pVar->Green += m_alpha*d; d = (srcB - pMu->Blue)*(srcB - pMu->Blue) - pVar->Blue; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pVar->Blue += m_alpha*d; - pVar->Red = (std::min)(pVar->Red,m_noise); - pVar->Green = (std::min)(pVar->Green,m_noise); - pVar->Blue = (std::min)(pVar->Blue,m_noise); + pVar->Red = (std::min)(pVar->Red, m_noise); + pVar->Green = (std::min)(pVar->Green, m_noise); + pVar->Blue = (std::min)(pVar->Blue, m_noise); // Set background - prgbBG[i][j].Red = (unsigned char)pMu->Red; - prgbBG[i][j].Green = (unsigned char)pMu->Green; + prgbBG[i][j].Red = (unsigned char)pMu->Red; + prgbBG[i][j].Green = (unsigned char)pMu->Green; prgbBG[i][j].Blue = (unsigned char)pMu->Blue; pMu++; @@ -197,4 +197,4 @@ namespace lb_library return; } } -} \ No newline at end of file +} diff --git a/package_bgs/lb/BGModelGauss.h b/package_bgs/lb/BGModelGauss.h index d36a716..0af6efe 100644 --- a/package_bgs/lb/BGModelGauss.h +++ b/package_bgs/lb/BGModelGauss.h @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModelGauss.h Copyright (C) 2011 Laurence Bender @@ -33,9 +33,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -#ifndef BGMODELGAUSS_H -#define BGMODELGAUSS_H +#pragma once #include "BGModel.h" @@ -69,5 +67,3 @@ namespace lb_library }; } } - -#endif diff --git a/package_bgs/lb/BGModelMog.cpp b/package_bgs/lb/BGModelMog.cpp index df6986c..036feab 100644 --- a/package_bgs/lb/BGModelMog.cpp +++ b/package_bgs/lb/BGModelMog.cpp @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModelMog.cpp Copyright (C) 2011 Laurence Bender @@ -54,9 +54,9 @@ namespace lb_library MOGDATA *pMOG = m_pMOG; int *pK = m_pK; - for(int i = 0; i < (m_width * m_height); i++) + for (int i = 0; i < (m_width * m_height); i++) { - for(int k = 0; k < NUMBERGAUSSIANS; k++) + for (int k = 0; k < NUMBERGAUSSIANS; k++) { pMOG->mu.Red = 0.0; pMOG->mu.Green = 0.0; @@ -72,27 +72,27 @@ namespace lb_library pMOG++; } - pK[i] = 0; + pK[i] = 0; } } BGModelMog::~BGModelMog() { - delete [] m_pMOG; - delete [] m_pK; + delete[] m_pMOG; + delete[] m_pK; } void BGModelMog::setBGModelParameter(int id, int value) { - double dvalue = (double)value/255.0; + double dvalue = (double)value / 255.0; - switch(id) + switch (id) { case 0: m_threshold = 100.0*dvalue*dvalue; break; - case 1: + case 1: m_T = dvalue; break; @@ -100,7 +100,7 @@ namespace lb_library m_alpha = dvalue*dvalue*dvalue; break; - case 3: + case 3: m_noise = 100.0*dvalue;; break; } @@ -116,9 +116,9 @@ namespace lb_library Image prgbSrc(m_SrcImage); int n = 0; - for(int i = 0; i < m_height; i++) + for (int i = 0; i < m_height; i++) { - for(int j = 0; j < m_width; j++) + for (int j = 0; j < m_width; j++) { pMOG[0].mu.Red = prgbSrc[i][j].Red; pMOG[0].mu.Green = prgbSrc[i][j].Green; @@ -129,7 +129,7 @@ namespace lb_library pMOG[0].var.Blue = m_noise; pMOG[0].w = 1.0; - pMOG[0].sortKey = pMOG[0].w/sqrt(pMOG[0].var.Red+pMOG[0].var.Green+pMOG[0].var.Blue); + pMOG[0].sortKey = pMOG[0].w / sqrt(pMOG[0].var.Red + pMOG[0].var.Green + pMOG[0].var.Blue); pK[n] = 1; n++; @@ -153,27 +153,27 @@ namespace lb_library Image prgbFG(m_FGImage); int n = 0; - for(int i = 0; i < m_height; i++) + for (int i = 0; i < m_height; i++) { - for(int j = 0; j < m_width; j++) + for (int j = 0; j < m_width; j++) { - double srcR = (double) prgbSrc[i][j].Red; - double srcG = (double) prgbSrc[i][j].Green; - double srcB = (double) prgbSrc[i][j].Blue; + double srcR = (double)prgbSrc[i][j].Red; + double srcG = (double)prgbSrc[i][j].Green; + double srcB = (double)prgbSrc[i][j].Blue; // Find matching distribution int kHit = -1; - for(int k = 0; k < pK[n]; k++) + for (int k = 0; k < pK[n]; k++) { // Mahalanobis distance double dr = srcR - pMOG[k].mu.Red; double dg = srcG - pMOG[k].mu.Green; double db = srcB - pMOG[k].mu.Blue; - double d2 = dr*dr/pMOG[k].var.Red + dg*dg/pMOG[k].var.Green + db*db/pMOG[k].var.Blue; + double d2 = dr*dr / pMOG[k].var.Red + dg*dg / pMOG[k].var.Green + db*db / pMOG[k].var.Blue; - if(d2 < m_threshold) + if (d2 < m_threshold) { kHit = k; break; @@ -183,43 +183,43 @@ namespace lb_library // Adjust parameters // matching distribution found - if(kHit != -1) + if (kHit != -1) { - for(int k = 0; k < pK[n]; k++) + for (int k = 0; k < pK[n]; k++) { - if(k == kHit) + if (k == kHit) { pMOG[k].w = pMOG[k].w + m_alpha*(1.0f - pMOG[k].w); double d; d = srcR - pMOG[k].mu.Red; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pMOG[k].mu.Red += m_alpha*d; d = srcG - pMOG[k].mu.Green; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pMOG[k].mu.Green += m_alpha*d; d = srcB - pMOG[k].mu.Blue; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pMOG[k].mu.Blue += m_alpha*d; d = (srcR - pMOG[k].mu.Red)*(srcR - pMOG[k].mu.Red) - pMOG[k].var.Red; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pMOG[k].var.Red += m_alpha*d; d = (srcG - pMOG[k].mu.Green)*(srcG - pMOG[k].mu.Green) - pMOG[k].var.Green; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pMOG[k].var.Green += m_alpha*d; d = (srcB - pMOG[k].mu.Blue)*(srcB - pMOG[k].mu.Blue) - pMOG[k].var.Blue; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) pMOG[k].var.Blue += m_alpha*d; - pMOG[k].var.Red = (std::max)(pMOG[k].var.Red,m_noise); - pMOG[k].var.Green = (std::max)(pMOG[k].var.Green,m_noise); - pMOG[k].var.Blue = (std::max)(pMOG[k].var.Blue,m_noise); + pMOG[k].var.Red = (std::max)(pMOG[k].var.Red, m_noise); + pMOG[k].var.Green = (std::max)(pMOG[k].var.Green, m_noise); + pMOG[k].var.Blue = (std::max)(pMOG[k].var.Blue, m_noise); } else pMOG[k].w = (1.0 - m_alpha)*pMOG[k].w; @@ -228,12 +228,12 @@ namespace lb_library // no match found... create new one else { - if(pK[n] < NUMBERGAUSSIANS) + if (pK[n] < NUMBERGAUSSIANS) pK[n]++; kHit = pK[n] - 1; - if(pK[n] == 1) + if (pK[n] == 1) pMOG[kHit].w = 1.0; else pMOG[kHit].w = LEARNINGRATEMOG; @@ -251,24 +251,24 @@ namespace lb_library double wsum = 0.0; - for(int k = 0; k < pK[n]; k++) + for (int k = 0; k < pK[n]; k++) wsum += pMOG[k].w; - double wfactor = 1.0/wsum; + double wfactor = 1.0 / wsum; - for(int k = 0; k < pK[n]; k++) + for (int k = 0; k < pK[n]; k++) { pMOG[k].w *= wfactor; - pMOG[k].sortKey = pMOG[k].w/sqrt(pMOG[k].var.Red+pMOG[k].var.Green+pMOG[k].var.Blue); + pMOG[k].sortKey = pMOG[k].w / sqrt(pMOG[k].var.Red + pMOG[k].var.Green + pMOG[k].var.Blue); } // Sort distributions for (int k = 0; k < kHit; k++) { - if(pMOG[kHit].sortKey > pMOG[k].sortKey) + if (pMOG[kHit].sortKey > pMOG[k].sortKey) { - std::swap(pMOG[kHit],pMOG[k]); + std::swap(pMOG[kHit], pMOG[k]); break; } } @@ -277,21 +277,21 @@ namespace lb_library wsum = 0.0; - for(int k = 0; k < pK[n]; k++) + for (int k = 0; k < pK[n]; k++) { wsum += pMOG[k].w; - if(wsum > m_T) + if (wsum > m_T) { kBG = k; break; } } - if(kHit > kBG) - prgbFG[i][j].Red = prgbFG[i][j].Green = prgbFG[i][j].Blue = 255; + if (kHit > kBG) + prgbFG[i][j].Red = prgbFG[i][j].Green = prgbFG[i][j].Blue = 255; else - prgbFG[i][j].Red = prgbFG[i][j].Green = prgbFG[i][j].Blue = 0; + prgbFG[i][j].Red = prgbFG[i][j].Green = prgbFG[i][j].Blue = 0; prgbBG[i][j].Red = (unsigned char)pMOG[0].mu.Red; prgbBG[i][j].Green = (unsigned char)pMOG[0].mu.Green; @@ -306,4 +306,4 @@ namespace lb_library return; } } -} \ No newline at end of file +} diff --git a/package_bgs/lb/BGModelMog.h b/package_bgs/lb/BGModelMog.h index c4e51a4..c75d1fd 100644 --- a/package_bgs/lb/BGModelMog.h +++ b/package_bgs/lb/BGModelMog.h @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModelMog.h Copyright (C) 2011 Laurence Bender @@ -33,9 +33,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -#ifndef BGMODELMOGRGB_H -#define BGMODELMOGRGB_H +#pragma once #include "BGModel.h" @@ -79,5 +77,3 @@ namespace lb_library }; } } - -#endif diff --git a/package_bgs/lb/BGModelSom.cpp b/package_bgs/lb/BGModelSom.cpp index 256d485..735af6e 100644 --- a/package_bgs/lb/BGModelSom.cpp +++ b/package_bgs/lb/BGModelSom.cpp @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModelSom.cpp Copyright (C) 2011 Laurence Bender @@ -40,28 +40,28 @@ namespace lb_library { namespace AdaptiveSOM { - BGModelSom::BGModelSom(int width, int height) : BGModel(width,height) + BGModelSom::BGModelSom(int width, int height) : BGModel(width, height) { - m_offset = (KERNEL - 1)/2; + m_offset = (KERNEL - 1) / 2; - if(SPAN_NEIGHBORS) - m_pad = 0; + if (SPAN_NEIGHBORS) + m_pad = 0; else m_pad = m_offset; // SOM models - m_widthSOM = m_width*M + 2*m_offset + (m_width-1)*m_pad; - m_heightSOM = m_height*N + 2*m_offset + (m_height-1)*m_pad; + m_widthSOM = m_width*M + 2 * m_offset + (m_width - 1)*m_pad; + m_heightSOM = m_height*N + 2 * m_offset + (m_height - 1)*m_pad; - m_ppSOM = new DBLRGB*[m_heightSOM]; - for(int n = 0; n < m_heightSOM; n++) + m_ppSOM = new DBLRGB*[m_heightSOM]; + for (int n = 0; n < m_heightSOM; n++) m_ppSOM[n] = new DBLRGB[m_widthSOM]; - for(int j = 0; j < m_heightSOM; j++) + for (int j = 0; j < m_heightSOM; j++) { - for(int i = 0; i < m_widthSOM; i++) - { + for (int i = 0; i < m_widthSOM; i++) + { m_ppSOM[j][i].Red = 0.0; m_ppSOM[j][i].Green = 0.0; m_ppSOM[j][i].Blue = 0.0; @@ -71,7 +71,7 @@ namespace lb_library // Create weights m_ppW = new double*[KERNEL]; - for(int n = 0; n < KERNEL; n++) + for (int n = 0; n < KERNEL; n++) m_ppW[n] = new double[KERNEL]; // Construct Gaussian kernel using Pascal's triangle @@ -81,16 +81,16 @@ namespace lb_library m_Wmax = DBL_MIN; cN = 1; - for(int j = 0; j < KERNEL; j++) + for (int j = 0; j < KERNEL; j++) { - cM = 1; + cM = 1; - for(int i = 0; i < KERNEL; i++) + for (int i = 0; i < KERNEL; i++) { m_ppW[j][i] = cN*cM; - if(m_ppW[j][i] > m_Wmax) - m_Wmax = m_ppW[j][i]; + if (m_ppW[j][i] > m_Wmax) + m_Wmax = m_ppW[j][i]; cM = cM * (KERNEL - 1 - i) / (i + 1); } @@ -103,8 +103,8 @@ namespace lb_library m_epsilon1 = EPS1*EPS1; m_epsilon2 = EPS2*EPS2; - m_alpha1 = C1/m_Wmax; - m_alpha2 = C2/m_Wmax; + m_alpha1 = C1 / m_Wmax; + m_alpha2 = C2 / m_Wmax; m_K = 0; m_TSteps = TRAINING_STEPS; @@ -112,22 +112,22 @@ namespace lb_library BGModelSom::~BGModelSom() { - for(int n = 0; n < m_heightSOM; n++) - delete [] m_ppSOM[n]; + for (int n = 0; n < m_heightSOM; n++) + delete[] m_ppSOM[n]; - delete [] m_ppSOM; + delete[] m_ppSOM; - for(int n = 0; n < KERNEL; n++) - delete [] m_ppW[n]; + for (int n = 0; n < KERNEL; n++) + delete[] m_ppW[n]; - delete [] m_ppW; + delete[] m_ppW; } void BGModelSom::setBGModelParameter(int id, int value) { - double dvalue = (double)value/255.0; + double dvalue = (double)value / 255.0; - switch(id) + switch (id) { case 0: m_epsilon2 = 255.0*255.0*dvalue*dvalue*dvalue*dvalue; @@ -138,11 +138,11 @@ namespace lb_library break; case 2: - m_alpha2 = dvalue*dvalue*dvalue/m_Wmax; + m_alpha2 = dvalue*dvalue*dvalue / m_Wmax; break; case 3: - m_alpha1 = dvalue*dvalue*dvalue/m_Wmax; + m_alpha1 = dvalue*dvalue*dvalue / m_Wmax; break; case 5: @@ -157,21 +157,21 @@ namespace lb_library { Image prgbSrc(m_SrcImage); - for(int j = 0; j < m_height; j++) + for (int j = 0; j < m_height; j++) { int jj = m_offset + j*(N + m_pad); - for(int i = 0; i < m_width; i++) + for (int i = 0; i < m_width; i++) { - int ii = m_offset + i*(M + m_pad); + int ii = m_offset + i*(M + m_pad); - for(int l = 0; l < N; l++) + for (int l = 0; l < N; l++) { - for(int k = 0; k < M; k++) + for (int k = 0; k < M; k++) { - m_ppSOM[jj+l][ii+k].Red = (double)prgbSrc[j][i].Red; - m_ppSOM[jj+l][ii+k].Green = (double)prgbSrc[j][i].Green; - m_ppSOM[jj+l][ii+k].Blue = (double)prgbSrc[j][i].Blue; + m_ppSOM[jj + l][ii + k].Red = (double)prgbSrc[j][i].Red; + m_ppSOM[jj + l][ii + k].Green = (double)prgbSrc[j][i].Green; + m_ppSOM[jj + l][ii + k].Blue = (double)prgbSrc[j][i].Blue; } } } @@ -184,18 +184,18 @@ namespace lb_library void BGModelSom::Update() { - double alpha,a; + double alpha, a; double epsilon; // calibration phase - if(m_K <= m_TSteps) - { + if (m_K <= m_TSteps) + { epsilon = m_epsilon1; - alpha = (m_alpha1-m_K*(m_alpha1-m_alpha2)/m_TSteps); + alpha = (m_alpha1 - m_K*(m_alpha1 - m_alpha2) / m_TSteps); m_K++; } else // online phase - { + { epsilon = m_epsilon2; alpha = m_alpha2; } @@ -204,11 +204,11 @@ namespace lb_library Image prgbBG(m_BGImage); Image prgbFG(m_FGImage); - for(int j = 0; j < m_height; j++) + for (int j = 0; j < m_height; j++) { int jj = m_offset + j*(N + m_pad); - for(int i = 0; i < m_width; i++) + for (int i = 0; i < m_width; i++) { int ii = m_offset + i*(M + m_pad); @@ -222,17 +222,17 @@ namespace lb_library int iiHit = ii; int jjHit = jj; - for(int l = 0; l < N; l++) + for (int l = 0; l < N; l++) { - for(int k = 0; k < M; k++) + for (int k = 0; k < M; k++) { - double dr = srcR - m_ppSOM[jj+l][ii+k].Red; - double dg = srcG - m_ppSOM[jj+l][ii+k].Green; - double db = srcB - m_ppSOM[jj+l][ii+k].Blue; + double dr = srcR - m_ppSOM[jj + l][ii + k].Red; + double dg = srcG - m_ppSOM[jj + l][ii + k].Green; + double db = srcB - m_ppSOM[jj + l][ii + k].Blue; double d2 = dr*dr + dg*dg + db*db; - if(d2 < d2min) + if (d2 < d2min) { d2min = d2; iiHit = ii + k; @@ -243,28 +243,28 @@ namespace lb_library // Update SOM - if(d2min <= epsilon) // matching model found + if (d2min <= epsilon) // matching model found { - for(int l = (jjHit - m_offset); l <= (jjHit + m_offset); l++) + for (int l = (jjHit - m_offset); l <= (jjHit + m_offset); l++) { - for(int k = (iiHit - m_offset); k <= (iiHit + m_offset); k++) + for (int k = (iiHit - m_offset); k <= (iiHit + m_offset); k++) { - a = alpha*m_ppW[l-jjHit+m_offset][k-iiHit+m_offset]; + a = alpha*m_ppW[l - jjHit + m_offset][k - iiHit + m_offset]; // speed hack.. avoid very small increment values. abs() is sloooow. double d; d = srcR - m_ppSOM[l][k].Red; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) m_ppSOM[l][k].Red += a*d; d = srcG - m_ppSOM[l][k].Green; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) m_ppSOM[l][k].Green += a*d; d = srcB - m_ppSOM[l][k].Blue; - if(d*d > DBL_MIN) + if (d*d > DBL_MIN) m_ppSOM[l][k].Blue += a*d; } } @@ -272,7 +272,7 @@ namespace lb_library // Set background image prgbBG[j][i].Red = m_ppSOM[jjHit][iiHit].Red; prgbBG[j][i].Green = m_ppSOM[jjHit][iiHit].Green; - prgbBG[j][i].Blue = m_ppSOM[jjHit][iiHit].Blue; + prgbBG[j][i].Blue = m_ppSOM[jjHit][iiHit].Blue; // Set foreground image prgbFG[j][i].Red = prgbFG[j][i].Green = prgbFG[j][i].Blue = 0; @@ -288,4 +288,4 @@ namespace lb_library return; } } -} \ No newline at end of file +} diff --git a/package_bgs/lb/BGModelSom.h b/package_bgs/lb/BGModelSom.h index dcd7158..0975b0b 100644 --- a/package_bgs/lb/BGModelSom.h +++ b/package_bgs/lb/BGModelSom.h @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BGSLibrary. If not, see . */ -/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments +/* Scene 1.0.1 -- Background subtraction and object tracking for complex environments BGModelSom.h Copyright (C) 2011 Laurence Bender @@ -33,9 +33,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -#ifndef BGMODELSOM_H -#define BGMODELSOM_H +#pragma once #include "BGModel.h" @@ -47,9 +45,9 @@ namespace lb_library const int M = 3; // width SOM (per pixel) const int N = 3; // height SOM (per pixel) - const int KERNEL = 3; // size Gaussian kernel + const int KERNEL = 3; // size Gaussian kernel - const bool SPAN_NEIGHBORS = false; // true if update neighborhood spans different pixels // + const bool SPAN_NEIGHBORS = false; // true if update neighborhood spans different pixels // const int TRAINING_STEPS = 100; // number of training steps const float EPS1 = 100.0; // model match distance during training @@ -81,12 +79,10 @@ namespace lb_library double m_alpha2; DBLRGB** m_ppSOM; // SOM grid - double** m_ppW; // Weights + double** m_ppW; // Weights void Init(); void Update(); }; } } - -#endif diff --git a/package_bgs/lb/Types.h b/package_bgs/lb/Types.h index cb318a2..bd59c41 100644 --- a/package_bgs/lb/Types.h +++ b/package_bgs/lb/Types.h @@ -33,57 +33,55 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -#ifndef TYPES_H -#define TYPES_H +#pragma once #include namespace lb_library { - template class Image - { - private: - IplImage* imgp; +template class Image +{ +private: + IplImage* imgp; - public: - Image(IplImage* img=0) {imgp=img;} - ~Image(){imgp=0;} +public: + Image(IplImage* img=0) {imgp=img;} + ~Image(){imgp=0;} - void operator=(IplImage* img) {imgp=img;} + void operator=(IplImage* img) {imgp=img;} - inline T* operator[](const int rowIndx) - { - return ((T *)(imgp->imageData + rowIndx*imgp->widthStep)); - } - }; - - typedef struct{ - unsigned char b,g,r; - } RgbPixel; - - typedef struct{ - unsigned char Blue,Green,Red; - } BYTERGB; - - typedef struct{ - unsigned int Blue,Green,Red; - } INTRGB; - - typedef struct{ - float b,g,r; - }RgbPixelFloat; - - typedef struct{ - double Blue,Green,Red; - } DBLRGB; - - typedef Image RgbImage; - typedef Image RgbImageFloat; - typedef Image BwImage; - typedef Image BwImageFloat; - - /* + inline T* operator[](const int rowIndx) + { + return ((T *)(imgp->imageData + rowIndx*imgp->widthStep)); + } +}; + +typedef struct{ + unsigned char b,g,r; +} RgbPixel; + +typedef struct{ + unsigned char Blue,Green,Red; +} BYTERGB; + +typedef struct{ + unsigned int Blue,Green,Red; +} INTRGB; + +typedef struct{ + float b,g,r; +}RgbPixelFloat; + +typedef struct{ + double Blue,Green,Red; +} DBLRGB; + +typedef Image RgbImage; +typedef Image RgbImageFloat; +typedef Image BwImage; +typedef Image BwImageFloat; + +/* IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_32F, 3); RgbImageFloat imgA(img); for(int i = 0; i < m_height; i++) @@ -93,7 +91,3 @@ namespace lb_library imgA[i][j].r = 111; */ } - -//--------------------------------------------- - -#endif diff --git a/package_bgs/my/MyBGS.cpp b/package_bgs/my/MyBGS.cpp deleted file mode 100644 index 07b51cd..0000000 --- a/package_bgs/my/MyBGS.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "MyBGS.h" - -MyBGS::MyBGS(){} -MyBGS::~MyBGS(){} - -void MyBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) -{ - if(img_input.empty()) - return; - - if(img_previous.empty()) - img_input.copyTo(img_previous); - - cv::Mat img_foreground; - cv::absdiff(img_previous, img_input, img_foreground); - - if(img_foreground.channels() == 3) - cv::cvtColor(img_foreground, img_foreground, CV_BGR2GRAY); - - cv::threshold(img_foreground, img_foreground, 15, 255, cv::THRESH_BINARY); - - img_foreground.copyTo(img_output); - img_previous.copyTo(img_bgmodel); - - img_input.copyTo(img_previous); -} diff --git a/package_bgs/my/MyBGS.h b/package_bgs/my/MyBGS.h deleted file mode 100644 index fa27484..0000000 --- a/package_bgs/my/MyBGS.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - - -#include "../IBGS.h" - -class MyBGS : public IBGS -{ -private: - cv::Mat img_previous; - -public: - MyBGS(); - ~MyBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(){} - void loadConfig(){} -}; \ No newline at end of file diff --git a/package_bgs/pl/BackgroundSubtractorLBSP.cpp b/package_bgs/pl/BackgroundSubtractorLBSP.cpp deleted file mode 100644 index 6752f76..0000000 --- a/package_bgs/pl/BackgroundSubtractorLBSP.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "BackgroundSubtractorLBSP.h" -#include "DistanceUtils.h" -#include "RandUtils.h" -#include -#include -#include -#include -#include - -#ifndef SIZE_MAX -# if __WORDSIZE == 64 -# define SIZE_MAX (18446744073709551615UL) -# else -# define SIZE_MAX (4294967295U) -# endif -#endif - -// local define used to determine the default median blur kernel size -#define DEFAULT_MEDIAN_BLUR_KERNEL_SIZE (9) - -BackgroundSubtractorLBSP::BackgroundSubtractorLBSP(float fRelLBSPThreshold, size_t nLBSPThresholdOffset) - : m_nImgChannels(0) - ,m_nImgType(0) - ,m_nLBSPThresholdOffset(nLBSPThresholdOffset) - ,m_fRelLBSPThreshold(fRelLBSPThreshold) - ,m_nTotPxCount(0) - ,m_nTotRelevantPxCount(0) - ,m_nFrameIndex(SIZE_MAX) - ,m_nFramesSinceLastReset(0) - ,m_nModelResetCooldown(0) - ,m_aPxIdxLUT(nullptr) - ,m_aPxInfoLUT(nullptr) - ,m_nDefaultMedianBlurKernelSize(DEFAULT_MEDIAN_BLUR_KERNEL_SIZE) - ,m_bInitialized(false) - ,m_bAutoModelResetEnabled(true) - ,m_bUsingMovingCamera(false) - ,nDebugCoordX(0),nDebugCoordY(0) { - CV_Assert(m_fRelLBSPThreshold>=0); -} - -BackgroundSubtractorLBSP::~BackgroundSubtractorLBSP() {} - -void BackgroundSubtractorLBSP::initialize(const cv::Mat& oInitImg) { - this->initialize(oInitImg,cv::Mat()); -} - -/*cv::AlgorithmInfo* BackgroundSubtractorLBSP::info() const { - return nullptr; -}*/ - -cv::Mat BackgroundSubtractorLBSP::getROICopy() const { - return m_oROI.clone(); -} - -void BackgroundSubtractorLBSP::setROI(cv::Mat& oROI) { - LBSP::validateROI(oROI); - CV_Assert(cv::countNonZero(oROI)>0); - if(m_bInitialized) { - cv::Mat oLatestBackgroundImage; - getBackgroundImage(oLatestBackgroundImage); - initialize(oLatestBackgroundImage,oROI); - } - else - m_oROI = oROI.clone(); -} - -void BackgroundSubtractorLBSP::setAutomaticModelReset(bool bVal) { - m_bAutoModelResetEnabled = bVal; -} diff --git a/package_bgs/pl/BackgroundSubtractorLBSP.h b/package_bgs/pl/BackgroundSubtractorLBSP.h deleted file mode 100644 index 5602d34..0000000 --- a/package_bgs/pl/BackgroundSubtractorLBSP.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -#include -#include -#include "LBSP.h" - -/*! - Local Binary Similarity Pattern (LBSP)-based change detection algorithm (abstract version/base class). - - For more details on the different parameters, see P.-L. St-Charles and G.-A. Bilodeau, "Improving Background - Subtraction using Local Binary Similarity Patterns", in WACV 2014, or G.-A. Bilodeau et al, "Change Detection - in Feature Space Using Local Binary Similarity Patterns", in CRV 2013. - - This algorithm is currently NOT thread-safe. - */ -class BackgroundSubtractorLBSP : public cv::BackgroundSubtractor { -public: - //! full constructor - BackgroundSubtractorLBSP(float fRelLBSPThreshold, size_t nLBSPThresholdOffset=0); - //! default destructor - virtual ~BackgroundSubtractorLBSP(); - //! (re)initiaization method; needs to be called before starting background subtraction - virtual void initialize(const cv::Mat& oInitImg); - //! (re)initiaization method; needs to be called before starting background subtraction - virtual void initialize(const cv::Mat& oInitImg, const cv::Mat& oROI)=0; - //! primary model update function; the learning param is used to override the internal learning speed (ignored when <= 0) - virtual void operator()(cv::InputArray image, cv::OutputArray fgmask, double learningRate=0)=0; - //! unused, always returns nullptr - //virtual cv::AlgorithmInfo* info() const; - //! returns a copy of the ROI used for descriptor extraction - virtual cv::Mat getROICopy() const; - //! sets the ROI to be used for descriptor extraction (note: this function will reinit the model and return the usable ROI) - virtual void setROI(cv::Mat& oROI); - //! turns automatic model reset on or off - void setAutomaticModelReset(bool); - -protected: - struct PxInfoBase { - int nImgCoord_Y; - int nImgCoord_X; - size_t nModelIdx; - }; - //! background model ROI used for LBSP descriptor extraction (specific to the input image size) - cv::Mat m_oROI; - //! input image size - cv::Size m_oImgSize; - //! input image channel size - size_t m_nImgChannels; - //! input image type - int m_nImgType; - //! LBSP internal threshold offset value, used to reduce texture noise in dark regions - const size_t m_nLBSPThresholdOffset; - //! LBSP relative internal threshold (kept here since we don't keep an LBSP object) - const float m_fRelLBSPThreshold; - //! total number of pixels (depends on the input frame size) & total number of relevant pixels - size_t m_nTotPxCount, m_nTotRelevantPxCount; - //! current frame index, frame count since last model reset & model reset cooldown counters - size_t m_nFrameIndex, m_nFramesSinceLastReset, m_nModelResetCooldown; - //! pre-allocated internal LBSP threshold values LUT for all possible 8-bit intensities - size_t m_anLBSPThreshold_8bitLUT[UCHAR_MAX+1]; - //! internal pixel index LUT for all relevant analysis regions (based on the provided ROI) - size_t* m_aPxIdxLUT; - //! internal pixel info LUT for all possible pixel indexes - PxInfoBase* m_aPxInfoLUT; - //! default kernel size for median blur post-proc filtering - const int m_nDefaultMedianBlurKernelSize; - //! specifies whether the algorithm is fully initialized or not - bool m_bInitialized; - //! specifies whether automatic model resets are enabled or not - bool m_bAutoModelResetEnabled; - //! specifies whether the camera is considered moving or not - bool m_bUsingMovingCamera; - //! copy of latest pixel intensities (used when refreshing model) - cv::Mat m_oLastColorFrame; - //! copy of latest descriptors (used when refreshing model) - cv::Mat m_oLastDescFrame; - //! the foreground mask generated by the method at [t-1] - cv::Mat m_oLastFGMask; - -public: - // ######## DEBUG PURPOSES ONLY ########## - int nDebugCoordX, nDebugCoordY; - std::string sDebugName; -}; - diff --git a/package_bgs/pl/BackgroundSubtractorLOBSTER.cpp b/package_bgs/pl/BackgroundSubtractorLOBSTER.cpp deleted file mode 100644 index 78938ad..0000000 --- a/package_bgs/pl/BackgroundSubtractorLOBSTER.cpp +++ /dev/null @@ -1,326 +0,0 @@ -#include "BackgroundSubtractorLOBSTER.h" -#include "DistanceUtils.h" -#include "RandUtils.h" -#include -#include -#include -#include - -BackgroundSubtractorLOBSTER::BackgroundSubtractorLOBSTER( float fRelLBSPThreshold - ,size_t nLBSPThresholdOffset - ,size_t nDescDistThreshold - ,size_t nColorDistThreshold - ,size_t nBGSamples - ,size_t nRequiredBGSamples) - : BackgroundSubtractorLBSP(fRelLBSPThreshold,nLBSPThresholdOffset) - ,m_nColorDistThreshold(nColorDistThreshold) - ,m_nDescDistThreshold(nDescDistThreshold) - ,m_nBGSamples(nBGSamples) - ,m_nRequiredBGSamples(nRequiredBGSamples) { - CV_Assert(m_nRequiredBGSamples<=m_nBGSamples); - m_bAutoModelResetEnabled = false; // @@@@@@ not supported here for now -} - -BackgroundSubtractorLOBSTER::~BackgroundSubtractorLOBSTER() { - if(m_aPxIdxLUT) - delete[] m_aPxIdxLUT; - if(m_aPxInfoLUT) - delete[] m_aPxInfoLUT; -} - -void BackgroundSubtractorLOBSTER::initialize(const cv::Mat& oInitImg, const cv::Mat& oROI) { - CV_Assert(!oInitImg.empty() && oInitImg.cols>0 && oInitImg.rows>0); - CV_Assert(oInitImg.isContinuous()); - CV_Assert(oInitImg.type()==CV_8UC1 || oInitImg.type()==CV_8UC3); - if(oInitImg.type()==CV_8UC3) { - std::vector voInitImgChannels; - cv::split(oInitImg,voInitImgChannels); - if(!cv::countNonZero((voInitImgChannels[0]!=voInitImgChannels[1])|(voInitImgChannels[2]!=voInitImgChannels[1]))) - std::cout << std::endl << "\tBackgroundSubtractorLOBSTER : Warning, grayscale images should always be passed in CV_8UC1 format for optimal performance." << std::endl; - } - cv::Mat oNewBGROI; - if(oROI.empty() && (m_oROI.empty() || oROI.size()!=oInitImg.size())) { - oNewBGROI.create(oInitImg.size(),CV_8UC1); - oNewBGROI = cv::Scalar_(UCHAR_MAX); - } - else if(oROI.empty()) - oNewBGROI = m_oROI; - else { - CV_Assert(oROI.size()==oInitImg.size() && oROI.type()==CV_8UC1); - CV_Assert(cv::countNonZero((oROI0))==0); - oNewBGROI = oROI.clone(); - } - LBSP::validateROI(oNewBGROI); - const size_t nROIPxCount = (size_t)cv::countNonZero(oNewBGROI); - CV_Assert(nROIPxCount>0); - m_oROI = oNewBGROI; - m_oImgSize = oInitImg.size(); - m_nImgType = oInitImg.type(); - m_nImgChannels = oInitImg.channels(); - m_nTotPxCount = m_oImgSize.area(); - m_nTotRelevantPxCount = nROIPxCount; - m_nFrameIndex = 0; - m_nFramesSinceLastReset = 0; - m_nModelResetCooldown = 0; - m_oLastFGMask.create(m_oImgSize,CV_8UC1); - m_oLastFGMask = cv::Scalar_(0); - m_oLastColorFrame.create(m_oImgSize,CV_8UC((int)m_nImgChannels)); - m_oLastColorFrame = cv::Scalar_::all(0); - m_oLastDescFrame.create(m_oImgSize,CV_16UC((int)m_nImgChannels)); - m_oLastDescFrame = cv::Scalar_::all(0); - m_voBGColorSamples.resize(m_nBGSamples); - m_voBGDescSamples.resize(m_nBGSamples); - for(size_t s=0; s::all(0); - m_voBGDescSamples[s].create(m_oImgSize,CV_16UC((int)m_nImgChannels)); - m_voBGDescSamples[s] = cv::Scalar_::all(0); - } - if(m_aPxIdxLUT) - delete[] m_aPxIdxLUT; - if(m_aPxInfoLUT) - delete[] m_aPxInfoLUT; - m_aPxIdxLUT = new size_t[m_nTotRelevantPxCount]; - m_aPxInfoLUT = new PxInfoBase[m_nTotPxCount]; - if(m_nImgChannels==1) { - CV_Assert(m_oLastColorFrame.step.p[0]==(size_t)m_oImgSize.width && m_oLastColorFrame.step.p[1]==1); - CV_Assert(m_oLastDescFrame.step.p[0]==m_oLastColorFrame.step.p[0]*2 && m_oLastDescFrame.step.p[1]==m_oLastColorFrame.step.p[1]*2); - for(size_t t=0; t<=UCHAR_MAX; ++t) - m_anLBSPThreshold_8bitLUT[t] = cv::saturate_cast((t*m_fRelLBSPThreshold+m_nLBSPThresholdOffset)/2); - for(size_t nPxIter=0, nModelIter=0; nPxIter(t*m_fRelLBSPThreshold+m_nLBSPThresholdOffset); - for(size_t nPxIter=0, nModelIter=0; nPxIter0.0f && fSamplesRefreshFrac<=1.0f); - const size_t nModelsToRefresh = fSamplesRefreshFrac<1.0f?(size_t)(fSamplesRefreshFrac*m_nBGSamples):m_nBGSamples; - const size_t nRefreshStartPos = fSamplesRefreshFrac<1.0f?rand()%m_nBGSamples:0; - if(m_nImgChannels==1) { - for(size_t nModelIter=0; nModelIter0); - cv::Mat oInputImg = _image.getMat(); - CV_Assert(oInputImg.type()==m_nImgType && oInputImg.size()==m_oImgSize); - CV_Assert(oInputImg.isContinuous()); - _fgmask.create(m_oImgSize,CV_8UC1); - cv::Mat oCurrFGMask = _fgmask.getMat(); - oCurrFGMask = cv::Scalar_(0); - const size_t nLearningRate = (size_t)ceil(learningRate); - if(m_nImgChannels==1) { - for(size_t nModelIter=0; nModelIterm_nColorDistThreshold/2) - goto failedcheck1ch; - LBSP::computeGrayscaleDescriptor(oInputImg,nBGColor,nCurrImgCoord_X,nCurrImgCoord_Y,m_anLBSPThreshold_8bitLUT[nBGColor],nCurrInputDesc); - const size_t nDescDist = hdist(nCurrInputDesc,*((ushort*)(m_voBGDescSamples[nModelIdx].data+nDescIter))); - if(nDescDist>m_nDescDistThreshold) - goto failedcheck1ch; - nGoodSamplesCount++; - } - failedcheck1ch: - nModelIdx++; - } - if(nGoodSamplesCount(nSampleImgCoord_Y,nSampleImgCoord_X); - LBSP::computeGrayscaleDescriptor(oInputImg,nCurrColor,nCurrImgCoord_X,nCurrImgCoord_Y,m_anLBSPThreshold_8bitLUT[nCurrColor],nRandInputDesc); - m_voBGColorSamples[nSampleModelIdx].at(nSampleImgCoord_Y,nSampleImgCoord_X) = nCurrColor; - } - } - } - } - else { //m_nImgChannels==3 - const size_t nCurrDescDistThreshold = m_nDescDistThreshold*3; - const size_t nCurrColorDistThreshold = m_nColorDistThreshold*3; - const size_t nCurrSCDescDistThreshold = nCurrDescDistThreshold/2; - const size_t nCurrSCColorDistThreshold = nCurrColorDistThreshold/2; - const size_t desc_row_step = m_voBGDescSamples[0].step.p[0]; - const size_t img_row_step = m_voBGColorSamples[0].step.p[0]; - for(size_t nModelIter=0; nModelIternCurrSCColorDistThreshold) - goto failedcheck3ch; - LBSP::computeSingleRGBDescriptor(oInputImg,anBGColor[c],nCurrImgCoord_X,nCurrImgCoord_Y,c,m_anLBSPThreshold_8bitLUT[anBGColor[c]],anCurrInputDesc[c]); - const size_t nDescDist = hdist(anCurrInputDesc[c],anBGDesc[c]); - if(nDescDist>nCurrSCDescDistThreshold) - goto failedcheck3ch; - nTotColorDist += nColorDist; - nTotDescDist += nDescDist; - } - if(nTotDescDist<=nCurrDescDistThreshold && nTotColorDist<=nCurrColorDistThreshold) - nGoodSamplesCount++; - failedcheck3ch: - nModelIdx++; - } - if(nGoodSamplesCount 0 (smaller values == faster adaptation) - virtual void operator()(cv::InputArray image, cv::OutputArray fgmask, double learningRate=BGSLOBSTER_DEFAULT_LEARNING_RATE); - //! returns a copy of the latest reconstructed background image - void getBackgroundImage(cv::OutputArray backgroundImage) const; - //! returns a copy of the latest reconstructed background descriptors image - virtual void getBackgroundDescriptorsImage(cv::OutputArray backgroundDescImage) const; - -protected: - //! absolute color distance threshold - const size_t m_nColorDistThreshold; - //! absolute descriptor distance threshold - const size_t m_nDescDistThreshold; - //! number of different samples per pixel/block to be taken from input frames to build the background model - const size_t m_nBGSamples; - //! number of similar samples needed to consider the current pixel/block as 'background' - const size_t m_nRequiredBGSamples; - //! background model pixel intensity samples - std::vector m_voBGColorSamples; - //! background model descriptors samples - std::vector m_voBGDescSamples; -}; - diff --git a/package_bgs/pl/BackgroundSubtractorSuBSENSE.cpp b/package_bgs/pl/BackgroundSubtractorSuBSENSE.cpp deleted file mode 100644 index c7d5dac..0000000 --- a/package_bgs/pl/BackgroundSubtractorSuBSENSE.cpp +++ /dev/null @@ -1,737 +0,0 @@ -#include "BackgroundSubtractorSuBSENSE.h" -#include "DistanceUtils.h" -#include "RandUtils.h" -#include -#include -#include -#include - -/* - * - * Intrinsic parameters for our method are defined here; tuning these for better - * performance should not be required in most cases -- although improvements in - * very specific scenarios are always possible. - * - */ -//! defines the threshold value(s) used to detect long-term ghosting and trigger the fast edge-based absorption heuristic -#define GHOSTDET_D_MAX (0.010f) // defines 'negligible' change here -#define GHOSTDET_S_MIN (0.995f) // defines the required minimum local foreground saturation value -//! parameter used to scale dynamic distance threshold adjustments ('R(x)') -#define FEEDBACK_R_VAR (0.01f) -//! parameters used to adjust the variation step size of 'v(x)' -#define FEEDBACK_V_INCR (1.000f) -#define FEEDBACK_V_DECR (0.100f) -//! parameters used to scale dynamic learning rate adjustments ('T(x)') -#define FEEDBACK_T_DECR (0.2500f) -#define FEEDBACK_T_INCR (0.5000f) -#define FEEDBACK_T_LOWER (2.0000f) -#define FEEDBACK_T_UPPER (256.00f) -//! parameters used to define 'unstable' regions, based on segm noise/bg dynamics and local dist threshold values -#define UNSTABLE_REG_RATIO_MIN (0.100f) -#define UNSTABLE_REG_RDIST_MIN (3.000f) -//! parameters used to scale the relative LBSP intensity threshold used for internal comparisons -#define LBSPDESC_NONZERO_RATIO_MIN (0.100f) -#define LBSPDESC_NONZERO_RATIO_MAX (0.500f) -//! parameters used to define model reset/learning rate boosts in our frame-level component -#define FRAMELEVEL_MIN_COLOR_DIFF_THRESHOLD (m_nMinColorDistThreshold/2) -#define FRAMELEVEL_ANALYSIS_DOWNSAMPLE_RATIO (8) - -// local define used to display debug information -#define DISPLAY_SUBSENSE_DEBUG_INFO 0 -// local define used to specify the default frame size (320x240 = QVGA) -#define DEFAULT_FRAME_SIZE cv::Size(320,240) -// local define used to specify the color dist threshold offset used for unstable regions -#define STAB_COLOR_DIST_OFFSET (m_nMinColorDistThreshold/5) -// local define used to specify the desc dist threshold offset used for unstable regions -#define UNSTAB_DESC_DIST_OFFSET (m_nDescDistThresholdOffset) - -static const size_t s_nColorMaxDataRange_1ch = UCHAR_MAX; -static const size_t s_nDescMaxDataRange_1ch = LBSP::DESC_SIZE*8; -static const size_t s_nColorMaxDataRange_3ch = s_nColorMaxDataRange_1ch*3; -static const size_t s_nDescMaxDataRange_3ch = s_nDescMaxDataRange_1ch*3; - -BackgroundSubtractorSuBSENSE::BackgroundSubtractorSuBSENSE( float fRelLBSPThreshold - ,size_t nDescDistThresholdOffset - ,size_t nMinColorDistThreshold - ,size_t nBGSamples - ,size_t nRequiredBGSamples - ,size_t nSamplesForMovingAvgs) - : BackgroundSubtractorLBSP(fRelLBSPThreshold) - ,m_nMinColorDistThreshold(nMinColorDistThreshold) - ,m_nDescDistThresholdOffset(nDescDistThresholdOffset) - ,m_nBGSamples(nBGSamples) - ,m_nRequiredBGSamples(nRequiredBGSamples) - ,m_nSamplesForMovingAvgs(nSamplesForMovingAvgs) - ,m_fLastNonZeroDescRatio(0.0f) - ,m_bLearningRateScalingEnabled(true) - ,m_fCurrLearningRateLowerCap(FEEDBACK_T_LOWER) - ,m_fCurrLearningRateUpperCap(FEEDBACK_T_UPPER) - ,m_nMedianBlurKernelSize(m_nDefaultMedianBlurKernelSize) - ,m_bUse3x3Spread(true) { - CV_Assert(m_nBGSamples>0 && m_nRequiredBGSamples<=m_nBGSamples); - CV_Assert(m_nMinColorDistThreshold>=STAB_COLOR_DIST_OFFSET); -} - -BackgroundSubtractorSuBSENSE::~BackgroundSubtractorSuBSENSE() { - if(m_aPxIdxLUT) - delete[] m_aPxIdxLUT; - if(m_aPxInfoLUT) - delete[] m_aPxInfoLUT; -} - -void BackgroundSubtractorSuBSENSE::initialize(const cv::Mat& oInitImg, const cv::Mat& oROI) { - // == init - CV_Assert(!oInitImg.empty() && oInitImg.cols>0 && oInitImg.rows>0); - CV_Assert(oInitImg.isContinuous()); - CV_Assert(oInitImg.type()==CV_8UC3 || oInitImg.type()==CV_8UC1); - if(oInitImg.type()==CV_8UC3) { - std::vector voInitImgChannels; - cv::split(oInitImg,voInitImgChannels); - if(!cv::countNonZero((voInitImgChannels[0]!=voInitImgChannels[1])|(voInitImgChannels[2]!=voInitImgChannels[1]))) - std::cout << std::endl << "\tBackgroundSubtractorSuBSENSE : Warning, grayscale images should always be passed in CV_8UC1 format for optimal performance." << std::endl; - } - cv::Mat oNewBGROI; - if(oROI.empty() && (m_oROI.empty() || oROI.size()!=oInitImg.size())) { - oNewBGROI.create(oInitImg.size(),CV_8UC1); - oNewBGROI = cv::Scalar_(UCHAR_MAX); - } - else if(oROI.empty()) - oNewBGROI = m_oROI; - else { - CV_Assert(oROI.size()==oInitImg.size() && oROI.type()==CV_8UC1); - CV_Assert(cv::countNonZero((oROI0))==0); - oNewBGROI = oROI.clone(); - cv::Mat oTempROI; - cv::dilate(oNewBGROI,oTempROI,cv::Mat(),cv::Point(-1,-1),LBSP::PATCH_SIZE/2); - cv::bitwise_or(oNewBGROI,oTempROI/2,oNewBGROI); - } - const size_t nOrigROIPxCount = (size_t)cv::countNonZero(oNewBGROI); - CV_Assert(nOrigROIPxCount>0); - LBSP::validateROI(oNewBGROI); - const size_t nFinalROIPxCount = (size_t)cv::countNonZero(oNewBGROI); - CV_Assert(nFinalROIPxCount>0); - m_oROI = oNewBGROI; - m_oImgSize = oInitImg.size(); - m_nImgType = oInitImg.type(); - m_nImgChannels = oInitImg.channels(); - m_nTotPxCount = m_oImgSize.area(); - m_nTotRelevantPxCount = nFinalROIPxCount; - m_nFrameIndex = 0; - m_nFramesSinceLastReset = 0; - m_nModelResetCooldown = 0; - m_fLastNonZeroDescRatio = 0.0f; - const int nTotImgPixels = m_oImgSize.height*m_oImgSize.width; - if(nOrigROIPxCount>=m_nTotPxCount/2 && (int)m_nTotPxCount>=DEFAULT_FRAME_SIZE.area()) { - m_bLearningRateScalingEnabled = true; - m_bAutoModelResetEnabled = true; - m_bUse3x3Spread = !(nTotImgPixels>DEFAULT_FRAME_SIZE.area()*2); - const int nRawMedianBlurKernelSize = std::min((int)floor((float)nTotImgPixels/DEFAULT_FRAME_SIZE.area()+0.5f)+m_nDefaultMedianBlurKernelSize,14); - m_nMedianBlurKernelSize = (nRawMedianBlurKernelSize%2)?nRawMedianBlurKernelSize:nRawMedianBlurKernelSize-1; - m_fCurrLearningRateLowerCap = FEEDBACK_T_LOWER; - m_fCurrLearningRateUpperCap = FEEDBACK_T_UPPER; - } - else { - m_bLearningRateScalingEnabled = false; - m_bAutoModelResetEnabled = false; - m_bUse3x3Spread = true; - m_nMedianBlurKernelSize = m_nDefaultMedianBlurKernelSize; - m_fCurrLearningRateLowerCap = FEEDBACK_T_LOWER*2; - m_fCurrLearningRateUpperCap = FEEDBACK_T_UPPER*2; - } - m_oUpdateRateFrame.create(m_oImgSize,CV_32FC1); - m_oUpdateRateFrame = cv::Scalar(m_fCurrLearningRateLowerCap); - m_oDistThresholdFrame.create(m_oImgSize,CV_32FC1); - m_oDistThresholdFrame = cv::Scalar(1.0f); - m_oVariationModulatorFrame.create(m_oImgSize,CV_32FC1); - m_oVariationModulatorFrame = cv::Scalar(10.0f); // should always be >= FEEDBACK_V_DECR - m_oMeanLastDistFrame.create(m_oImgSize,CV_32FC1); - m_oMeanLastDistFrame = cv::Scalar(0.0f); - m_oMeanMinDistFrame_LT.create(m_oImgSize,CV_32FC1); - m_oMeanMinDistFrame_LT = cv::Scalar(0.0f); - m_oMeanMinDistFrame_ST.create(m_oImgSize,CV_32FC1); - m_oMeanMinDistFrame_ST = cv::Scalar(0.0f); - m_oDownSampledFrameSize = cv::Size(m_oImgSize.width/FRAMELEVEL_ANALYSIS_DOWNSAMPLE_RATIO,m_oImgSize.height/FRAMELEVEL_ANALYSIS_DOWNSAMPLE_RATIO); - m_oMeanDownSampledLastDistFrame_LT.create(m_oDownSampledFrameSize,CV_32FC((int)m_nImgChannels)); - m_oMeanDownSampledLastDistFrame_LT = cv::Scalar(0.0f); - m_oMeanDownSampledLastDistFrame_ST.create(m_oDownSampledFrameSize,CV_32FC((int)m_nImgChannels)); - m_oMeanDownSampledLastDistFrame_ST = cv::Scalar(0.0f); - m_oMeanRawSegmResFrame_LT.create(m_oImgSize,CV_32FC1); - m_oMeanRawSegmResFrame_LT = cv::Scalar(0.0f); - m_oMeanRawSegmResFrame_ST.create(m_oImgSize,CV_32FC1); - m_oMeanRawSegmResFrame_ST = cv::Scalar(0.0f); - m_oMeanFinalSegmResFrame_LT.create(m_oImgSize,CV_32FC1); - m_oMeanFinalSegmResFrame_LT = cv::Scalar(0.0f); - m_oMeanFinalSegmResFrame_ST.create(m_oImgSize,CV_32FC1); - m_oMeanFinalSegmResFrame_ST = cv::Scalar(0.0f); - m_oUnstableRegionMask.create(m_oImgSize,CV_8UC1); - m_oUnstableRegionMask = cv::Scalar_(0); - m_oBlinksFrame.create(m_oImgSize,CV_8UC1); - m_oBlinksFrame = cv::Scalar_(0); - m_oDownSampledFrame_MotionAnalysis.create(m_oDownSampledFrameSize,CV_8UC((int)m_nImgChannels)); - m_oDownSampledFrame_MotionAnalysis = cv::Scalar_::all(0); - m_oLastColorFrame.create(m_oImgSize,CV_8UC((int)m_nImgChannels)); - m_oLastColorFrame = cv::Scalar_::all(0); - m_oLastDescFrame.create(m_oImgSize,CV_16UC((int)m_nImgChannels)); - m_oLastDescFrame = cv::Scalar_::all(0); - m_oLastRawFGMask.create(m_oImgSize,CV_8UC1); - m_oLastRawFGMask = cv::Scalar_(0); - m_oLastFGMask.create(m_oImgSize,CV_8UC1); - m_oLastFGMask = cv::Scalar_(0); - m_oLastFGMask_dilated.create(m_oImgSize,CV_8UC1); - m_oLastFGMask_dilated = cv::Scalar_(0); - m_oLastFGMask_dilated_inverted.create(m_oImgSize,CV_8UC1); - m_oLastFGMask_dilated_inverted = cv::Scalar_(0); - m_oFGMask_FloodedHoles.create(m_oImgSize,CV_8UC1); - m_oFGMask_FloodedHoles = cv::Scalar_(0); - m_oFGMask_PreFlood.create(m_oImgSize,CV_8UC1); - m_oFGMask_PreFlood = cv::Scalar_(0); - m_oCurrRawFGBlinkMask.create(m_oImgSize,CV_8UC1); - m_oCurrRawFGBlinkMask = cv::Scalar_(0); - m_oLastRawFGBlinkMask.create(m_oImgSize,CV_8UC1); - m_oLastRawFGBlinkMask = cv::Scalar_(0); - m_voBGColorSamples.resize(m_nBGSamples); - m_voBGDescSamples.resize(m_nBGSamples); - for(size_t s=0; s::all(0); - m_voBGDescSamples[s].create(m_oImgSize,CV_16UC((int)m_nImgChannels)); - m_voBGDescSamples[s] = cv::Scalar_::all(0); - } - if(m_aPxIdxLUT) - delete[] m_aPxIdxLUT; - if(m_aPxInfoLUT) - delete[] m_aPxInfoLUT; - m_aPxIdxLUT = new size_t[m_nTotRelevantPxCount]; - m_aPxInfoLUT = new PxInfoBase[m_nTotPxCount]; - if(m_nImgChannels==1) { - CV_Assert(m_oLastColorFrame.step.p[0]==(size_t)m_oImgSize.width && m_oLastColorFrame.step.p[1]==1); - CV_Assert(m_oLastDescFrame.step.p[0]==m_oLastColorFrame.step.p[0]*2 && m_oLastDescFrame.step.p[1]==m_oLastColorFrame.step.p[1]*2); - for(size_t t=0; t<=UCHAR_MAX; ++t) - m_anLBSPThreshold_8bitLUT[t] = cv::saturate_cast((m_nLBSPThresholdOffset+t*m_fRelLBSPThreshold)/3); - for(size_t nPxIter=0, nModelIter=0; nPxIter(m_nLBSPThresholdOffset+t*m_fRelLBSPThreshold); - for(size_t nPxIter=0, nModelIter=0; nPxIter0.0f && fSamplesRefreshFrac<=1.0f); - const size_t nModelsToRefresh = fSamplesRefreshFrac<1.0f?(size_t)(fSamplesRefreshFrac*m_nBGSamples):m_nBGSamples; - const size_t nRefreshStartPos = fSamplesRefreshFrac<1.0f?rand()%m_nBGSamples:0; - if(m_nImgChannels==1) { - for(size_t nModelIter=0; nModelIterUNSTABLE_REG_RDIST_MIN || (*pfCurrMeanRawSegmRes_LT-*pfCurrMeanFinalSegmRes_LT)>UNSTABLE_REG_RATIO_MIN || (*pfCurrMeanRawSegmRes_ST-*pfCurrMeanFinalSegmRes_ST)>UNSTABLE_REG_RATIO_MIN)?1:0; - size_t nGoodSamplesCount=0, nSampleIdx=0; - while(nGoodSamplesCountnCurrColorDistThreshold) - goto failedcheck1ch; - const ushort& nBGIntraDesc = *((ushort*)(m_voBGDescSamples[nSampleIdx].data+nDescIter)); - const size_t nIntraDescDist = hdist(nCurrIntraDesc,nBGIntraDesc); - LBSP::computeGrayscaleDescriptor(oInputImg,nBGColor,nCurrImgCoord_X,nCurrImgCoord_Y,m_anLBSPThreshold_8bitLUT[nBGColor],nCurrInterDesc); - const size_t nInterDescDist = hdist(nCurrInterDesc,nBGIntraDesc); - const size_t nDescDist = (nIntraDescDist+nInterDescDist)/2; - if(nDescDist>nCurrDescDistThreshold) - goto failedcheck1ch; - const size_t nSumDist = std::min((nDescDist/4)*(s_nColorMaxDataRange_1ch/s_nDescMaxDataRange_1ch)+nColorDist,s_nColorMaxDataRange_1ch); - if(nSumDist>nCurrColorDistThreshold) - goto failedcheck1ch; - if(nMinDescDist>nDescDist) - nMinDescDist = nDescDist; - if(nMinSumDist>nSumDist) - nMinSumDist = nSumDist; - nGoodSamplesCount++; - } - failedcheck1ch: - nSampleIdx++; - } - const float fNormalizedLastDist = ((float)L1dist(nLastColor,nCurrColor)/s_nColorMaxDataRange_1ch+(float)hdist(nLastIntraDesc,nCurrIntraDesc)/s_nDescMaxDataRange_1ch)/2; - *pfCurrMeanLastDist = (*pfCurrMeanLastDist)*(1.0f-fRollAvgFactor_ST) + fNormalizedLastDist*fRollAvgFactor_ST; - if(nGoodSamplesCount0?(size_t)ceil(learningRateOverride):(size_t)ceil(*pfCurrLearningRate); - if((rand()%nLearningRate)==0) { - const size_t s_rand = rand()%m_nBGSamples; - *((ushort*)(m_voBGDescSamples[s_rand].data+nDescIter)) = nCurrIntraDesc; - m_voBGColorSamples[s_rand].data[nPxIter] = nCurrColor; - } - int nSampleImgCoord_Y, nSampleImgCoord_X; - const bool bCurrUsing3x3Spread = m_bUse3x3Spread && !m_oUnstableRegionMask.data[nPxIter]; - if(bCurrUsing3x3Spread) - getRandNeighborPosition_3x3(nSampleImgCoord_X,nSampleImgCoord_Y,nCurrImgCoord_X,nCurrImgCoord_Y,LBSP::PATCH_SIZE/2,m_oImgSize); - else - getRandNeighborPosition_5x5(nSampleImgCoord_X,nSampleImgCoord_Y,nCurrImgCoord_X,nCurrImgCoord_Y,LBSP::PATCH_SIZE/2,m_oImgSize); - const size_t n_rand = rand(); - const size_t idx_rand_uchar = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; - const size_t idx_rand_flt32 = idx_rand_uchar*4; - const float fRandMeanLastDist = *((float*)(m_oMeanLastDistFrame.data+idx_rand_flt32)); - const float fRandMeanRawSegmRes = *((float*)(m_oMeanRawSegmResFrame_ST.data+idx_rand_flt32)); - if((n_rand%(bCurrUsing3x3Spread?nLearningRate:(nLearningRate/2+1)))==0 - || (fRandMeanRawSegmRes>GHOSTDET_S_MIN && fRandMeanLastDistm_fCurrLearningRateLowerCap) - *pfCurrLearningRate -= FEEDBACK_T_DECR*(*pfCurrVariationFactor)/std::max(*pfCurrMeanMinDist_LT,*pfCurrMeanMinDist_ST); - if((*pfCurrLearningRate)m_fCurrLearningRateUpperCap) - *pfCurrLearningRate = m_fCurrLearningRateUpperCap; - if(std::max(*pfCurrMeanMinDist_LT,*pfCurrMeanMinDist_ST)>UNSTABLE_REG_RATIO_MIN && m_oBlinksFrame.data[nPxIter]) - (*pfCurrVariationFactor) += FEEDBACK_V_INCR; - else if((*pfCurrVariationFactor)>FEEDBACK_V_DECR) { - (*pfCurrVariationFactor) -= m_oLastFGMask.data[nPxIter]?FEEDBACK_V_DECR/4:m_oUnstableRegionMask.data[nPxIter]?FEEDBACK_V_DECR/2:FEEDBACK_V_DECR; - if((*pfCurrVariationFactor)=2) - ++nNonZeroDescCount; - nLastIntraDesc = nCurrIntraDesc; - nLastColor = nCurrColor; - } - } - else { //m_nImgChannels==3 - for(size_t nModelIter=0; nModelIterUNSTABLE_REG_RDIST_MIN || (*pfCurrMeanRawSegmRes_LT-*pfCurrMeanFinalSegmRes_LT)>UNSTABLE_REG_RATIO_MIN || (*pfCurrMeanRawSegmRes_ST-*pfCurrMeanFinalSegmRes_ST)>UNSTABLE_REG_RATIO_MIN)?1:0; - size_t nGoodSamplesCount=0, nSampleIdx=0; - while(nGoodSamplesCountnCurrSCColorDistThreshold) - goto failedcheck3ch; - const size_t nIntraDescDist = hdist(anCurrIntraDesc[c],anBGIntraDesc[c]); - LBSP::computeSingleRGBDescriptor(oInputImg,anBGColor[c],nCurrImgCoord_X,nCurrImgCoord_Y,c,m_anLBSPThreshold_8bitLUT[anBGColor[c]],anCurrInterDesc[c]); - const size_t nInterDescDist = hdist(anCurrInterDesc[c],anBGIntraDesc[c]); - const size_t nDescDist = (nIntraDescDist+nInterDescDist)/2; - const size_t nSumDist = std::min((nDescDist/2)*(s_nColorMaxDataRange_1ch/s_nDescMaxDataRange_1ch)+nColorDist,s_nColorMaxDataRange_1ch); - if(nSumDist>nCurrSCColorDistThreshold) - goto failedcheck3ch; - nTotDescDist += nDescDist; - nTotSumDist += nSumDist; - } - if(nTotDescDist>nCurrTotDescDistThreshold || nTotSumDist>nCurrTotColorDistThreshold) - goto failedcheck3ch; - if(nMinTotDescDist>nTotDescDist) - nMinTotDescDist = nTotDescDist; - if(nMinTotSumDist>nTotSumDist) - nMinTotSumDist = nTotSumDist; - nGoodSamplesCount++; - failedcheck3ch: - nSampleIdx++; - } - const float fNormalizedLastDist = ((float)L1dist<3>(anLastColor,anCurrColor)/s_nColorMaxDataRange_3ch+(float)hdist<3>(anLastIntraDesc,anCurrIntraDesc)/s_nDescMaxDataRange_3ch)/2; - *pfCurrMeanLastDist = (*pfCurrMeanLastDist)*(1.0f-fRollAvgFactor_ST) + fNormalizedLastDist*fRollAvgFactor_ST; - if(nGoodSamplesCount0?(size_t)ceil(learningRateOverride):(size_t)ceil(*pfCurrLearningRate); - if((rand()%nLearningRate)==0) { - const size_t s_rand = rand()%m_nBGSamples; - for(size_t c=0; c<3; ++c) { - *((ushort*)(m_voBGDescSamples[s_rand].data+nDescIterRGB+2*c)) = anCurrIntraDesc[c]; - *(m_voBGColorSamples[s_rand].data+nPxIterRGB+c) = anCurrColor[c]; - } - } - int nSampleImgCoord_Y, nSampleImgCoord_X; - const bool bCurrUsing3x3Spread = m_bUse3x3Spread && !m_oUnstableRegionMask.data[nPxIter]; - if(bCurrUsing3x3Spread) - getRandNeighborPosition_3x3(nSampleImgCoord_X,nSampleImgCoord_Y,nCurrImgCoord_X,nCurrImgCoord_Y,LBSP::PATCH_SIZE/2,m_oImgSize); - else - getRandNeighborPosition_5x5(nSampleImgCoord_X,nSampleImgCoord_Y,nCurrImgCoord_X,nCurrImgCoord_Y,LBSP::PATCH_SIZE/2,m_oImgSize); - const size_t n_rand = rand(); - const size_t idx_rand_uchar = m_oImgSize.width*nSampleImgCoord_Y + nSampleImgCoord_X; - const size_t idx_rand_flt32 = idx_rand_uchar*4; - const float fRandMeanLastDist = *((float*)(m_oMeanLastDistFrame.data+idx_rand_flt32)); - const float fRandMeanRawSegmRes = *((float*)(m_oMeanRawSegmResFrame_ST.data+idx_rand_flt32)); - if((n_rand%(bCurrUsing3x3Spread?nLearningRate:(nLearningRate/2+1)))==0 - || (fRandMeanRawSegmRes>GHOSTDET_S_MIN && fRandMeanLastDistm_fCurrLearningRateLowerCap) - *pfCurrLearningRate -= FEEDBACK_T_DECR*(*pfCurrVariationFactor)/std::max(*pfCurrMeanMinDist_LT,*pfCurrMeanMinDist_ST); - if((*pfCurrLearningRate)m_fCurrLearningRateUpperCap) - *pfCurrLearningRate = m_fCurrLearningRateUpperCap; - if(std::max(*pfCurrMeanMinDist_LT,*pfCurrMeanMinDist_ST)>UNSTABLE_REG_RATIO_MIN && m_oBlinksFrame.data[nPxIter]) - (*pfCurrVariationFactor) += FEEDBACK_V_INCR; - else if((*pfCurrVariationFactor)>FEEDBACK_V_DECR) { - (*pfCurrVariationFactor) -= m_oLastFGMask.data[nPxIter]?FEEDBACK_V_DECR/4:m_oUnstableRegionMask.data[nPxIter]?FEEDBACK_V_DECR/2:FEEDBACK_V_DECR; - if((*pfCurrVariationFactor)(anCurrIntraDesc)>=4) - ++nNonZeroDescCount; - for(size_t c=0; c<3; ++c) { - anLastIntraDesc[c] = anCurrIntraDesc[c]; - anLastColor[c] = anCurrColor[c]; - } - } - } -#if DISPLAY_SUBSENSE_DEBUG_INFO - std::cout << std::endl; - cv::Point dbgpt(nDebugCoordX,nDebugCoordY); - cv::Mat oMeanMinDistFrameNormalized; m_oMeanMinDistFrame_ST.copyTo(oMeanMinDistFrameNormalized); - cv::circle(oMeanMinDistFrameNormalized,dbgpt,5,cv::Scalar(1.0f)); - cv::resize(oMeanMinDistFrameNormalized,oMeanMinDistFrameNormalized,DEFAULT_FRAME_SIZE); - cv::imshow("d_min(x)",oMeanMinDistFrameNormalized); - std::cout << std::fixed << std::setprecision(5) << " d_min(" << dbgpt << ") = " << m_oMeanMinDistFrame_ST.at(dbgpt) << std::endl; - cv::Mat oMeanLastDistFrameNormalized; m_oMeanLastDistFrame.copyTo(oMeanLastDistFrameNormalized); - cv::circle(oMeanLastDistFrameNormalized,dbgpt,5,cv::Scalar(1.0f)); - cv::resize(oMeanLastDistFrameNormalized,oMeanLastDistFrameNormalized,DEFAULT_FRAME_SIZE); - cv::imshow("d_last(x)",oMeanLastDistFrameNormalized); - std::cout << std::fixed << std::setprecision(5) << " d_last(" << dbgpt << ") = " << m_oMeanLastDistFrame.at(dbgpt) << std::endl; - cv::Mat oMeanRawSegmResFrameNormalized; m_oMeanRawSegmResFrame_ST.copyTo(oMeanRawSegmResFrameNormalized); - cv::circle(oMeanRawSegmResFrameNormalized,dbgpt,5,cv::Scalar(1.0f)); - cv::resize(oMeanRawSegmResFrameNormalized,oMeanRawSegmResFrameNormalized,DEFAULT_FRAME_SIZE); - cv::imshow("s_avg(x)",oMeanRawSegmResFrameNormalized); - std::cout << std::fixed << std::setprecision(5) << " s_avg(" << dbgpt << ") = " << m_oMeanRawSegmResFrame_ST.at(dbgpt) << std::endl; - cv::Mat oMeanFinalSegmResFrameNormalized; m_oMeanFinalSegmResFrame_ST.copyTo(oMeanFinalSegmResFrameNormalized); - cv::circle(oMeanFinalSegmResFrameNormalized,dbgpt,5,cv::Scalar(1.0f)); - cv::resize(oMeanFinalSegmResFrameNormalized,oMeanFinalSegmResFrameNormalized,DEFAULT_FRAME_SIZE); - cv::imshow("z_avg(x)",oMeanFinalSegmResFrameNormalized); - std::cout << std::fixed << std::setprecision(5) << " z_avg(" << dbgpt << ") = " << m_oMeanFinalSegmResFrame_ST.at(dbgpt) << std::endl; - cv::Mat oDistThresholdFrameNormalized; m_oDistThresholdFrame.convertTo(oDistThresholdFrameNormalized,CV_32FC1,0.25f,-0.25f); - cv::circle(oDistThresholdFrameNormalized,dbgpt,5,cv::Scalar(1.0f)); - cv::resize(oDistThresholdFrameNormalized,oDistThresholdFrameNormalized,DEFAULT_FRAME_SIZE); - cv::imshow("r(x)",oDistThresholdFrameNormalized); - std::cout << std::fixed << std::setprecision(5) << " r(" << dbgpt << ") = " << m_oDistThresholdFrame.at(dbgpt) << std::endl; - cv::Mat oVariationModulatorFrameNormalized; cv::normalize(m_oVariationModulatorFrame,oVariationModulatorFrameNormalized,0,255,cv::NORM_MINMAX,CV_8UC1); - cv::circle(oVariationModulatorFrameNormalized,dbgpt,5,cv::Scalar(255)); - cv::resize(oVariationModulatorFrameNormalized,oVariationModulatorFrameNormalized,DEFAULT_FRAME_SIZE); - cv::imshow("v(x)",oVariationModulatorFrameNormalized); - std::cout << std::fixed << std::setprecision(5) << " v(" << dbgpt << ") = " << m_oVariationModulatorFrame.at(dbgpt) << std::endl; - cv::Mat oUpdateRateFrameNormalized; m_oUpdateRateFrame.convertTo(oUpdateRateFrameNormalized,CV_32FC1,1.0f/FEEDBACK_T_UPPER,-FEEDBACK_T_LOWER/FEEDBACK_T_UPPER); - cv::circle(oUpdateRateFrameNormalized,dbgpt,5,cv::Scalar(1.0f)); - cv::resize(oUpdateRateFrameNormalized,oUpdateRateFrameNormalized,DEFAULT_FRAME_SIZE); - cv::imshow("t(x)",oUpdateRateFrameNormalized); - std::cout << std::fixed << std::setprecision(5) << " t(" << dbgpt << ") = " << m_oUpdateRateFrame.at(dbgpt) << std::endl; -#endif //DISPLAY_SUBSENSE_DEBUG_INFO - cv::bitwise_xor(oCurrFGMask,m_oLastRawFGMask,m_oCurrRawFGBlinkMask); - cv::bitwise_or(m_oCurrRawFGBlinkMask,m_oLastRawFGBlinkMask,m_oBlinksFrame); - m_oCurrRawFGBlinkMask.copyTo(m_oLastRawFGBlinkMask); - oCurrFGMask.copyTo(m_oLastRawFGMask); - cv::morphologyEx(oCurrFGMask,m_oFGMask_PreFlood,cv::MORPH_CLOSE,cv::Mat()); - m_oFGMask_PreFlood.copyTo(m_oFGMask_FloodedHoles); - cv::floodFill(m_oFGMask_FloodedHoles,cv::Point(0,0),UCHAR_MAX); - cv::bitwise_not(m_oFGMask_FloodedHoles,m_oFGMask_FloodedHoles); - cv::erode(m_oFGMask_PreFlood,m_oFGMask_PreFlood,cv::Mat(),cv::Point(-1,-1),3); - cv::bitwise_or(oCurrFGMask,m_oFGMask_FloodedHoles,oCurrFGMask); - cv::bitwise_or(oCurrFGMask,m_oFGMask_PreFlood,oCurrFGMask); - cv::medianBlur(oCurrFGMask,m_oLastFGMask,m_nMedianBlurKernelSize); - cv::dilate(m_oLastFGMask,m_oLastFGMask_dilated,cv::Mat(),cv::Point(-1,-1),3); - cv::bitwise_and(m_oBlinksFrame,m_oLastFGMask_dilated_inverted,m_oBlinksFrame); - cv::bitwise_not(m_oLastFGMask_dilated,m_oLastFGMask_dilated_inverted); - cv::bitwise_and(m_oBlinksFrame,m_oLastFGMask_dilated_inverted,m_oBlinksFrame); - m_oLastFGMask.copyTo(oCurrFGMask); - cv::addWeighted(m_oMeanFinalSegmResFrame_LT,(1.0f-fRollAvgFactor_LT),m_oLastFGMask,(1.0/UCHAR_MAX)*fRollAvgFactor_LT,0,m_oMeanFinalSegmResFrame_LT,CV_32F); - cv::addWeighted(m_oMeanFinalSegmResFrame_ST,(1.0f-fRollAvgFactor_ST),m_oLastFGMask,(1.0/UCHAR_MAX)*fRollAvgFactor_ST,0,m_oMeanFinalSegmResFrame_ST,CV_32F); - const float fCurrNonZeroDescRatio = (float)nNonZeroDescCount/m_nTotRelevantPxCount; - if(fCurrNonZeroDescRatiocv::saturate_cast(m_nLBSPThresholdOffset+ceil(t*m_fRelLBSPThreshold/4))) - --m_anLBSPThreshold_8bitLUT[t]; - } - else if(fCurrNonZeroDescRatio>LBSPDESC_NONZERO_RATIO_MAX && m_fLastNonZeroDescRatio>LBSPDESC_NONZERO_RATIO_MAX) { - for(size_t t=0; t<=UCHAR_MAX; ++t) - if(m_anLBSPThreshold_8bitLUT[t](m_nLBSPThresholdOffset+UCHAR_MAX*m_fRelLBSPThreshold)) - ++m_anLBSPThreshold_8bitLUT[t]; - } - m_fLastNonZeroDescRatio = fCurrNonZeroDescRatio; - if(m_bLearningRateScalingEnabled) { - cv::resize(oInputImg,m_oDownSampledFrame_MotionAnalysis,m_oDownSampledFrameSize,0,0,cv::INTER_AREA); - cv::accumulateWeighted(m_oDownSampledFrame_MotionAnalysis,m_oMeanDownSampledLastDistFrame_LT,fRollAvgFactor_LT); - cv::accumulateWeighted(m_oDownSampledFrame_MotionAnalysis,m_oMeanDownSampledLastDistFrame_ST,fRollAvgFactor_ST); - size_t nTotColorDiff = 0; - for(int i=0; i1000) - m_bAutoModelResetEnabled = false; - else if(fCurrColorDiffRatio>=FRAMELEVEL_MIN_COLOR_DIFF_THRESHOLD && m_nModelResetCooldown==0) { - m_nFramesSinceLastReset = 0; - refreshModel(0.1f); // reset 10% of the bg model - m_nModelResetCooldown = m_nSamplesForMovingAvgs/4; - m_oUpdateRateFrame = cv::Scalar(1.0f); - } - else - ++m_nFramesSinceLastReset; - } - else if(fCurrColorDiffRatio>=FRAMELEVEL_MIN_COLOR_DIFF_THRESHOLD*2) { - m_nFramesSinceLastReset = 0; - m_bAutoModelResetEnabled = true; - } - if(fCurrColorDiffRatio>=FRAMELEVEL_MIN_COLOR_DIFF_THRESHOLD/2) { - m_fCurrLearningRateLowerCap = (float)std::max((int)FEEDBACK_T_LOWER>>(int)(fCurrColorDiffRatio/2),1); - m_fCurrLearningRateUpperCap = (float)std::max((int)FEEDBACK_T_UPPER>>(int)(fCurrColorDiffRatio/2),1); - } - else { - m_fCurrLearningRateLowerCap = FEEDBACK_T_LOWER; - m_fCurrLearningRateUpperCap = FEEDBACK_T_UPPER; - } - if(m_nModelResetCooldown>0) - --m_nModelResetCooldown; - } -} - -void BackgroundSubtractorSuBSENSE::getBackgroundImage(cv::OutputArray backgroundImage) const { - CV_Assert(m_bInitialized); - cv::Mat oAvgBGImg = cv::Mat::zeros(m_oImgSize,CV_32FC((int)m_nImgChannels)); - for(size_t s=0; s m_voBGColorSamples; - //! background model descriptors samples - std::vector m_voBGDescSamples; - - //! per-pixel update rates ('T(x)' in PBAS, which contains pixel-level 'sigmas', as referred to in ViBe) - cv::Mat m_oUpdateRateFrame; - //! per-pixel distance thresholds (equivalent to 'R(x)' in PBAS, but used as a relative value to determine both intensity and descriptor variation thresholds) - cv::Mat m_oDistThresholdFrame; - //! per-pixel distance variation modulators ('v(x)', relative value used to modulate 'R(x)' and 'T(x)' variations) - cv::Mat m_oVariationModulatorFrame; - //! per-pixel mean distances between consecutive frames ('D_last(x)', used to detect ghosts and high variation regions in the sequence) - cv::Mat m_oMeanLastDistFrame; - //! per-pixel mean minimal distances from the model ('D_min(x)' in PBAS, used to control variation magnitude and direction of 'T(x)' and 'R(x)') - cv::Mat m_oMeanMinDistFrame_LT, m_oMeanMinDistFrame_ST; - //! per-pixel mean downsampled distances between consecutive frames (used to analyze camera movement and control max learning rates globally) - cv::Mat m_oMeanDownSampledLastDistFrame_LT, m_oMeanDownSampledLastDistFrame_ST; - //! per-pixel mean raw segmentation results (used to detect unstable segmentation regions) - cv::Mat m_oMeanRawSegmResFrame_LT, m_oMeanRawSegmResFrame_ST; - //! per-pixel mean raw segmentation results (used to detect unstable segmentation regions) - cv::Mat m_oMeanFinalSegmResFrame_LT, m_oMeanFinalSegmResFrame_ST; - //! a lookup map used to keep track of unstable regions (based on segm. noise & local dist. thresholds) - cv::Mat m_oUnstableRegionMask; - //! per-pixel blink detection map ('Z(x)') - cv::Mat m_oBlinksFrame; - //! pre-allocated matrix used to downsample the input frame when needed - cv::Mat m_oDownSampledFrame_MotionAnalysis; - //! the foreground mask generated by the method at [t-1] (without post-proc, used for blinking px detection) - cv::Mat m_oLastRawFGMask; - - //! pre-allocated CV_8UC1 matrices used to speed up morph ops - cv::Mat m_oFGMask_PreFlood; - cv::Mat m_oFGMask_FloodedHoles; - cv::Mat m_oLastFGMask_dilated; - cv::Mat m_oLastFGMask_dilated_inverted; - cv::Mat m_oCurrRawFGBlinkMask; - cv::Mat m_oLastRawFGBlinkMask; -}; - diff --git a/package_bgs/pl/DistanceUtils.h b/package_bgs/pl/DistanceUtils.h deleted file mode 100644 index 54d1cec..0000000 --- a/package_bgs/pl/DistanceUtils.h +++ /dev/null @@ -1,316 +0,0 @@ -#pragma once - -#include - -//! computes the L1 distance between two integer values -template static inline typename std::enable_if::value,size_t>::type L1dist(T a, T b) { - return (size_t)abs((int)a-b); -} - -//! computes the L1 distance between two float values -template static inline typename std::enable_if::value,float>::type L1dist(T a, T b) { - return fabs((float)a-(float)b); -} - -//! computes the L1 distance between two generic arrays -template static inline auto L1dist(const T* a, const T* b) -> decltype(L1dist(*a,*b)) { - decltype(L1dist(*a,*b)) oResult = 0; - for(size_t c=0; c static inline auto L1dist(const T* a, const T* b, size_t nElements, const uchar* m=NULL) -> decltype(L1dist(a,b)) { - decltype(L1dist(a,b)) oResult = 0; - size_t nTotElements = nElements*nChannels; - if(m) { - for(size_t n=0,i=0; n(a+n,b+n); - } - else { - for(size_t n=0; n(a+n,b+n); - } - return oResult; -} - -//! computes the L1 distance between two generic arrays -template static inline auto L1dist(const T* a, const T* b, size_t nElements, size_t nChannels, const uchar* m=NULL) -> decltype(L1dist<3>(a,b,nElements,m)) { - CV_Assert(nChannels>0 && nChannels<=4); - switch(nChannels) { - case 1: return L1dist<1>(a,b,nElements,m); - case 2: return L1dist<2>(a,b,nElements,m); - case 3: return L1dist<3>(a,b,nElements,m); - case 4: return L1dist<4>(a,b,nElements,m); - default: return 0; - } -} - -//! computes the L1 distance between two opencv vectors -template static inline auto L1dist_(const cv::Vec& a, const cv::Vec& b) -> decltype(L1dist((T*)(0),(T*)(0))) { - T a_array[nChannels], b_array[nChannels]; - for(size_t c=0; c(a_array,b_array); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//! computes the squared L2 distance between two generic variables -template static inline auto L2sqrdist(T a, T b) -> decltype(L1dist(a,b)) { - auto oResult = L1dist(a,b); - return oResult*oResult; -} - -//! computes the squared L2 distance between two generic arrays -template static inline auto L2sqrdist(const T* a, const T* b) -> decltype(L2sqrdist(*a,*b)) { - decltype(L2sqrdist(*a,*b)) oResult = 0; - for(size_t c=0; c static inline auto L2sqrdist(const T* a, const T* b, size_t nElements, const uchar* m=NULL) -> decltype(L2sqrdist(a,b)) { - decltype(L2sqrdist(a,b)) oResult = 0; - size_t nTotElements = nElements*nChannels; - if(m) { - for(size_t n=0,i=0; n(a+n,b+n); - } - else { - for(size_t n=0; n(a+n,b+n); - } - return oResult; -} - -//! computes the squared L2 distance between two generic arrays -template static inline auto L2sqrdist(const T* a, const T* b, size_t nElements, size_t nChannels, const uchar* m=NULL) -> decltype(L2sqrdist<3>(a,b,nElements,m)) { - CV_Assert(nChannels>0 && nChannels<=4); - switch(nChannels) { - case 1: return L2sqrdist<1>(a,b,nElements,m); - case 2: return L2sqrdist<2>(a,b,nElements,m); - case 3: return L2sqrdist<3>(a,b,nElements,m); - case 4: return L2sqrdist<4>(a,b,nElements,m); - default: return 0; - } -} - -//! computes the squared L2 distance between two opencv vectors -template static inline auto L2sqrdist_(const cv::Vec& a, const cv::Vec& b) -> decltype(L2sqrdist((T*)(0),(T*)(0))) { - T a_array[nChannels], b_array[nChannels]; - for(size_t c=0; c(a_array,b_array); -} - -//! computes the L2 distance between two generic arrays -template static inline float L2dist(const T* a, const T* b) { - decltype(L2sqrdist(*a,*b)) oResult = 0; - for(size_t c=0; c static inline float L2dist(const T* a, const T* b, size_t nElements, const uchar* m=NULL) { - decltype(L2sqrdist(a,b)) oResult = 0; - size_t nTotElements = nElements*nChannels; - if(m) { - for(size_t n=0,i=0; n(a+n,b+n); - } - else { - for(size_t n=0; n(a+n,b+n); - } - return sqrt((float)oResult); -} - -//! computes the squared L2 distance between two generic arrays -template static inline float L2dist(const T* a, const T* b, size_t nElements, size_t nChannels, const uchar* m=NULL) { - CV_Assert(nChannels>0 && nChannels<=4); - switch(nChannels) { - case 1: return L2dist<1>(a,b,nElements,m); - case 2: return L2dist<2>(a,b,nElements,m); - case 3: return L2dist<3>(a,b,nElements,m); - case 4: return L2dist<4>(a,b,nElements,m); - default: return 0; - } -} - -//! computes the L2 distance between two opencv vectors -template static inline float L2dist_(const cv::Vec& a, const cv::Vec& b) { - T a_array[nChannels], b_array[nChannels]; - for(size_t c=0; c(a_array,b_array); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//! computes the color distortion between two integer arrays -template static inline typename std::enable_if::value,size_t>::type cdist(const T* curr, const T* bg) { - static_assert(nChannels>1,"cdist: requires more than one channel"); - size_t curr_sqr = 0; - bool bSkip = true; - for(size_t c=0; c static inline typename std::enable_if::value,float>::type cdist(const T* curr, const T* bg) { - static_assert(nChannels>1,"cdist: requires more than one channel"); - float curr_sqr = 0; - bool bSkip = true; - for(size_t c=0; c static inline auto cdist(const T* a, const T* b, size_t nElements, const uchar* m=NULL) -> decltype(cdist(a,b)) { - decltype(cdist(a,b)) oResult = 0; - size_t nTotElements = nElements*nChannels; - if(m) { - for(size_t n=0,i=0; n(a+n,b+n); - } - else { - for(size_t n=0; n(a+n,b+n); - } - return oResult; -} - -//! computes the color distortion between two generic arrays -template static inline auto cdist(const T* a, const T* b, size_t nElements, size_t nChannels, const uchar* m=NULL) -> decltype(cdist<3>(a,b,nElements,m)) { - CV_Assert(nChannels>1 && nChannels<=4); - switch(nChannels) { - case 2: return cdist<2>(a,b,nElements,m); - case 3: return cdist<3>(a,b,nElements,m); - case 4: return cdist<4>(a,b,nElements,m); - default: return 0; - } -} - -//! computes the color distortion between two opencv vectors -template static inline auto cdist_(const cv::Vec& a, const cv::Vec& b) -> decltype(cdist((T*)(0),(T*)(0))) { - T a_array[nChannels], b_array[nChannels]; - for(size_t c=0; c(a_array,b_array); -} - -//! computes a color distortion-distance mix using two generic distances -template static inline T cmixdist(T oL1Distance, T oCDistortion) { - return (oL1Distance/2+oCDistortion*4); -} - -//! computes a color distoirtion-distance mix using two generic arrays -template static inline typename std::enable_if::value,size_t>::type cmixdist(const T* curr, const T* bg) { - return cmixdist(L1dist(curr,bg),cdist(curr,bg)); -} - -template static inline typename std::enable_if::value,float>::type cmixdist(const T* curr, const T* bg) { - return cmixdist(L1dist(curr,bg),cdist(curr,bg)); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//! popcount LUT for 8-bit vectors -static const uchar popcount_LUT8[256] = { - 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, - 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, - 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, - 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, - 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, - 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, -}; - -//! computes the population count of an N-byte vector using an 8-bit popcount LUT -template static inline size_t popcount(T x) { - size_t nBytes = sizeof(T); - size_t nResult = 0; - for(size_t l=0; l>l*8)]; - return nResult; -} - -//! computes the hamming distance between two N-byte vectors using an 8-bit popcount LUT -template static inline size_t hdist(T a, T b) { - return popcount(a^b); -} - -//! computes the gradient magnitude distance between two N-byte vectors using an 8-bit popcount LUT -template static inline size_t gdist(T a, T b) { - return L1dist(popcount(a),popcount(b)); -} - -//! computes the population count of a (nChannels*N)-byte vector using an 8-bit popcount LUT -template static inline size_t popcount(const T* x) { - size_t nBytes = sizeof(T); - size_t nResult = 0; - for(size_t c=0; c>l*8)]; - return nResult; -} - -//! computes the hamming distance between two (nChannels*N)-byte vectors using an 8-bit popcount LUT -template static inline size_t hdist(const T* a, const T* b) { - T xor_array[nChannels]; - for(size_t c=0; c(xor_array); -} - -//! computes the gradient magnitude distance between two (nChannels*N)-byte vectors using an 8-bit popcount LUT -template static inline size_t gdist(const T* a, const T* b) { - return L1dist(popcount(a),popcount(b)); -} diff --git a/package_bgs/pl/LBSP.cpp b/package_bgs/pl/LBSP.cpp deleted file mode 100644 index de35f1a..0000000 --- a/package_bgs/pl/LBSP.cpp +++ /dev/null @@ -1,318 +0,0 @@ -#include "LBSP.h" - -LBSP::LBSP(size_t nThreshold) - : m_bOnlyUsingAbsThreshold(true) - ,m_fRelThreshold(0) // unused - ,m_nThreshold(nThreshold) - ,m_oRefImage() {} - -LBSP::LBSP(float fRelThreshold, size_t nThresholdOffset) - : m_bOnlyUsingAbsThreshold(false) - ,m_fRelThreshold(fRelThreshold) - ,m_nThreshold(nThresholdOffset) - ,m_oRefImage() { - CV_Assert(m_fRelThreshold>=0); -} - -LBSP::~LBSP() {} - -void LBSP::read(const cv::FileNode& /*fn*/) { - // ... = fn["..."]; -} - -void LBSP::write(cv::FileStorage& /*fs*/) const { - //fs << "..." << ...; -} - -void LBSP::setReference(const cv::Mat& img) { - CV_DbgAssert(img.empty() || img.type()==CV_8UC1 || img.type()==CV_8UC3); - m_oRefImage = img; -} - -int LBSP::descriptorSize() const { - return DESC_SIZE; -} - -int LBSP::descriptorType() const { - return CV_16U; -} - -bool LBSP::isUsingRelThreshold() const { - return !m_bOnlyUsingAbsThreshold; -} - -float LBSP::getRelThreshold() const { - return m_fRelThreshold; -} - -size_t LBSP::getAbsThreshold() const { - return m_nThreshold; -} - -static inline void lbsp_computeImpl( const cv::Mat& oInputImg, - const cv::Mat& oRefImg, - const std::vector& voKeyPoints, - cv::Mat& oDesc, - size_t _t) { - CV_DbgAssert(oRefImg.empty() || (oRefImg.size==oInputImg.size && oRefImg.type()==oInputImg.type())); - CV_DbgAssert(oInputImg.type()==CV_8UC1 || oInputImg.type()==CV_8UC3); - CV_DbgAssert(LBSP::DESC_SIZE==2); // @@@ also relies on a constant desc size - const size_t nChannels = (size_t)oInputImg.channels(); - const size_t _step_row = oInputImg.step.p[0]; - const uchar* _data = oInputImg.data; - const uchar* _refdata = oRefImg.empty()?oInputImg.data:oRefImg.data; - const size_t nKeyPoints = voKeyPoints.size(); - if(nChannels==1) { - oDesc.create((int)nKeyPoints,1,CV_16UC1); - for(size_t k=0; k((int)k); - #include "LBSP_16bits_dbcross_1ch.i" - } - } - else { //nChannels==3 - oDesc.create((int)nKeyPoints,1,CV_16UC3); - for(size_t k=0; k& voKeyPoints, - cv::Mat& oDesc, - float fThreshold, - size_t nThresholdOffset) { - CV_DbgAssert(oRefImg.empty() || (oRefImg.size==oInputImg.size && oRefImg.type()==oInputImg.type())); - CV_DbgAssert(oInputImg.type()==CV_8UC1 || oInputImg.type()==CV_8UC3); - CV_DbgAssert(LBSP::DESC_SIZE==2); // @@@ also relies on a constant desc size - CV_DbgAssert(fThreshold>=0); - const size_t nChannels = (size_t)oInputImg.channels(); - const size_t _step_row = oInputImg.step.p[0]; - const uchar* _data = oInputImg.data; - const uchar* _refdata = oRefImg.empty()?oInputImg.data:oRefImg.data; - const size_t nKeyPoints = voKeyPoints.size(); - if(nChannels==1) { - oDesc.create((int)nKeyPoints,1,CV_16UC1); - for(size_t k=0; k((int)k); - const size_t _t = (size_t)(_ref*fThreshold)+nThresholdOffset; - #include "LBSP_16bits_dbcross_1ch.i" - } - } - else { //nChannels==3 - oDesc.create((int)nKeyPoints,1,CV_16UC3); - for(size_t k=0; k& voKeyPoints, - cv::Mat& oDesc, - size_t _t) { - CV_DbgAssert(oRefImg.empty() || (oRefImg.size==oInputImg.size && oRefImg.type()==oInputImg.type())); - CV_DbgAssert(oInputImg.type()==CV_8UC1 || oInputImg.type()==CV_8UC3); - CV_DbgAssert(LBSP::DESC_SIZE==2); // @@@ also relies on a constant desc size - const size_t nChannels = (size_t)oInputImg.channels(); - const size_t _step_row = oInputImg.step.p[0]; - const uchar* _data = oInputImg.data; - const uchar* _refdata = oRefImg.empty()?oInputImg.data:oRefImg.data; - const size_t nKeyPoints = voKeyPoints.size(); - if(nChannels==1) { - oDesc.create(oInputImg.size(),CV_16UC1); - for(size_t k=0; k(_y,_x); - #include "LBSP_16bits_dbcross_1ch.i" - } - } - else { //nChannels==3 - oDesc.create(oInputImg.size(),CV_16UC3); - for(size_t k=0; k& voKeyPoints, - cv::Mat& oDesc, - float fThreshold, - size_t nThresholdOffset) { - CV_DbgAssert(oRefImg.empty() || (oRefImg.size==oInputImg.size && oRefImg.type()==oInputImg.type())); - CV_DbgAssert(oInputImg.type()==CV_8UC1 || oInputImg.type()==CV_8UC3); - CV_DbgAssert(LBSP::DESC_SIZE==2); // @@@ also relies on a constant desc size - CV_DbgAssert(fThreshold>=0); - const size_t nChannels = (size_t)oInputImg.channels(); - const size_t _step_row = oInputImg.step.p[0]; - const uchar* _data = oInputImg.data; - const uchar* _refdata = oRefImg.empty()?oInputImg.data:oRefImg.data; - const size_t nKeyPoints = voKeyPoints.size(); - if(nChannels==1) { - oDesc.create(oInputImg.size(),CV_16UC1); - for(size_t k=0; k(_y,_x); - const size_t _t = (size_t)(_ref*fThreshold)+nThresholdOffset; - #include "LBSP_16bits_dbcross_1ch.i" - } - } - else { //nChannels==3 - oDesc.create(oInputImg.size(),CV_16UC3); - for(size_t k=0; k& voKeypoints, cv::Mat& oDescriptors) const { - CV_Assert(!oImage.empty()); - cv::KeyPointsFilter::runByImageBorder(voKeypoints,oImage.size(),PATCH_SIZE/2); - cv::KeyPointsFilter::runByKeypointSize(voKeypoints,std::numeric_limits::epsilon()); - if(voKeypoints.empty()) { - oDescriptors.release(); - return; - } - if(m_bOnlyUsingAbsThreshold) - lbsp_computeImpl2(oImage,m_oRefImage,voKeypoints,oDescriptors,m_nThreshold); - else - lbsp_computeImpl2(oImage,m_oRefImage,voKeypoints,oDescriptors,m_fRelThreshold,m_nThreshold); -} - -void LBSP::compute2(const std::vector& voImageCollection, std::vector >& vvoPointCollection, std::vector& voDescCollection) const { - CV_Assert(voImageCollection.size() == vvoPointCollection.size()); - voDescCollection.resize(voImageCollection.size()); - for(size_t i=0; i& voKeypoints, cv::Mat& oDescriptors) const { - CV_Assert(!oImage.empty()); - cv::KeyPointsFilter::runByImageBorder(voKeypoints,oImage.size(),PATCH_SIZE/2); - cv::KeyPointsFilter::runByKeypointSize(voKeypoints,std::numeric_limits::epsilon()); - if(voKeypoints.empty()) { - oDescriptors.release(); - return; - } - if(m_bOnlyUsingAbsThreshold) - lbsp_computeImpl(oImage,m_oRefImage,voKeypoints,oDescriptors,m_nThreshold); - else - lbsp_computeImpl(oImage,m_oRefImage,voKeypoints,oDescriptors,m_fRelThreshold,m_nThreshold); -} - -void LBSP::reshapeDesc(cv::Size oSize, const std::vector& voKeypoints, const cv::Mat& oDescriptors, cv::Mat& oOutput) { - CV_DbgAssert(!voKeypoints.empty()); - CV_DbgAssert(!oDescriptors.empty() && oDescriptors.cols==1); - CV_DbgAssert(oSize.width>0 && oSize.height>0); - CV_DbgAssert(DESC_SIZE==2); // @@@ also relies on a constant desc size - CV_DbgAssert(oDescriptors.type()==CV_16UC1 || oDescriptors.type()==CV_16UC3); - const size_t nChannels = (size_t)oDescriptors.channels(); - const size_t nKeyPoints = voKeypoints.size(); - if(nChannels==1) { - oOutput.create(oSize,CV_16UC1); - oOutput = cv::Scalar_(0); - for(size_t k=0; k(voKeypoints[k].pt) = oDescriptors.at((int)k); - } - else { //nChannels==3 - oOutput.create(oSize,CV_16UC3); - oOutput = cv::Scalar_(0,0,0); - for(size_t k=0; k(i,j) = (uchar)(fScaleFactor*hdist(desc1_ptr[j],desc2_ptr[j])); - } - } - else { //nChannels==3 - if(bForceMergeChannels) - oOutput.create(oDesc1.size(),CV_8UC1); - else - oOutput.create(oDesc1.size(),CV_8UC3); - oOutput = cv::Scalar::all(0); - for(int i=0; i& voKeypoints, cv::Size oImgSize) { - cv::KeyPointsFilter::runByImageBorder(voKeypoints,oImgSize,PATCH_SIZE/2); -} - -void LBSP::validateROI(cv::Mat& oROI) { - CV_Assert(!oROI.empty() && oROI.type()==CV_8UC1); - cv::Mat oROI_new(oROI.size(),CV_8UC1,cv::Scalar_(0)); - const size_t nBorderSize = PATCH_SIZE/2; - const cv::Rect nROI_inner(nBorderSize,nBorderSize,oROI.cols-nBorderSize*2,oROI.rows-nBorderSize*2); - cv::Mat(oROI,nROI_inner).copyTo(cv::Mat(oROI_new,nROI_inner)); - oROI = oROI_new; -} diff --git a/package_bgs/pl/LBSP.h b/package_bgs/pl/LBSP.h deleted file mode 100644 index 1ca17a0..0000000 --- a/package_bgs/pl/LBSP.h +++ /dev/null @@ -1,118 +0,0 @@ -#pragma once - -#include -#include -#include -#include "DistanceUtils.h" - -/*! - Local Binary Similarity Pattern (LBSP) feature extractor - - Note 1: both grayscale and RGB/BGR images may be used with this extractor. - Note 2: using LBSP::compute2(...) is logically equivalent to using LBSP::compute(...) followed by LBSP::reshapeDesc(...). - - For more details on the different parameters, see G.-A. Bilodeau et al, "Change Detection in Feature Space Using Local - Binary Similarity Patterns", in CRV 2013. - - This algorithm is currently NOT thread-safe. - */ -class LBSP : public cv::DescriptorExtractor { -public: - //! constructor 1, threshold = absolute intensity 'similarity' threshold used when computing comparisons - LBSP(size_t nThreshold); - //! constructor 2, threshold = relative intensity 'similarity' threshold used when computing comparisons - LBSP(float fRelThreshold, size_t nThresholdOffset=0); - //! default destructor - virtual ~LBSP(); - //! loads extractor params from the specified file node @@@@ not impl - virtual void read(const cv::FileNode&); - //! writes extractor params to the specified file storage @@@@ not impl - virtual void write(cv::FileStorage&) const; - //! sets the 'reference' image to be used for inter-frame comparisons (note: if no image is set or if the image is empty, the algorithm will default back to intra-frame comparisons) - virtual void setReference(const cv::Mat&); - //! returns the current descriptor size, in bytes - virtual int descriptorSize() const; - //! returns the current descriptor data type - virtual int descriptorType() const; - //! returns whether this extractor is using a relative threshold or not - virtual bool isUsingRelThreshold() const; - //! returns the current relative threshold used for comparisons (-1 = invalid/not used) - virtual float getRelThreshold() const; - //! returns the current absolute threshold used for comparisons (-1 = invalid/not used) - virtual size_t getAbsThreshold() const; - - //! similar to DescriptorExtractor::compute(const cv::Mat& image, ...), but in this case, the descriptors matrix has the same shape as the input matrix (possibly slower, but the result can be displayed) - void compute2(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const; - //! batch version of LBSP::compute2(const cv::Mat& image, ...), also similar to DescriptorExtractor::compute(const std::vector& imageCollection, ...) - void compute2(const std::vector& voImageCollection, std::vector >& vvoPointCollection, std::vector& voDescCollection) const; - - //! utility function, shortcut/lightweight/direct single-point LBSP computation function for extra flexibility (1-channel version) - inline static void computeGrayscaleDescriptor(const cv::Mat& oInputImg, const uchar _ref, const int _x, const int _y, const size_t _t, ushort& _res) { - CV_DbgAssert(!oInputImg.empty()); - CV_DbgAssert(oInputImg.type()==CV_8UC1); - CV_DbgAssert(LBSP::DESC_SIZE==2); // @@@ also relies on a constant desc size - CV_DbgAssert(_x>=(int)LBSP::PATCH_SIZE/2 && _y>=(int)LBSP::PATCH_SIZE/2); - CV_DbgAssert(_x=(int)LBSP::PATCH_SIZE/2 && _y>=(int)LBSP::PATCH_SIZE/2); - CV_DbgAssert(_x=(int)LBSP::PATCH_SIZE/2 && _y>=(int)LBSP::PATCH_SIZE/2); - CV_DbgAssert(_x=(int)LBSP::PATCH_SIZE/2 && _y>=(int)LBSP::PATCH_SIZE/2); - CV_DbgAssert(_x& voKeypoints, const cv::Mat& oDescriptors, cv::Mat& oOutput); - //! utility function, used to illustrate the difference between two descriptor images - static void calcDescImgDiff(const cv::Mat& oDesc1, const cv::Mat& oDesc2, cv::Mat& oOutput, bool bForceMergeChannels=false); - //! utility function, used to filter out bad keypoints that would trigger out of bounds error because they're too close to the image border - static void validateKeyPoints(std::vector& voKeypoints, cv::Size oImgSize); - //! utility function, used to filter out bad pixels in a ROI that would trigger out of bounds error because they're too close to the image border - static void validateROI(cv::Mat& oROI); - //! utility, specifies the pixel size of the pattern used (width and height) - static const size_t PATCH_SIZE = 5; - //! utility, specifies the number of bytes per descriptor (should be the same as calling 'descriptorSize()') - static const size_t DESC_SIZE = 2; - -protected: - //! classic 'compute' implementation, based on the regular DescriptorExtractor::computeImpl arguments & expected output - virtual void computeImpl(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const; - - const bool m_bOnlyUsingAbsThreshold; - const float m_fRelThreshold; - const size_t m_nThreshold; - cv::Mat m_oRefImage; -}; diff --git a/package_bgs/pl/LOBSTER.cpp b/package_bgs/pl/LOBSTER.cpp deleted file mode 100644 index f4e9bf6..0000000 --- a/package_bgs/pl/LOBSTER.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "LOBSTER.h" -#include "BackgroundSubtractorLOBSTER.h" - - -LOBSTERBGS::LOBSTERBGS() : -pLOBSTER(0), firstTime(true), showOutput(true), -fRelLBSPThreshold (BGSLOBSTER_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD), -nLBSPThresholdOffset (BGSLOBSTER_DEFAULT_LBSP_OFFSET_SIMILARITY_THRESHOLD), -nDescDistThreshold (BGSLOBSTER_DEFAULT_DESC_DIST_THRESHOLD), -nColorDistThreshold (BGSLOBSTER_DEFAULT_COLOR_DIST_THRESHOLD), -nBGSamples (BGSLOBSTER_DEFAULT_NB_BG_SAMPLES), -nRequiredBGSamples (BGSLOBSTER_DEFAULT_REQUIRED_NB_BG_SAMPLES) -{ -} - -LOBSTERBGS::~LOBSTERBGS() { - if (pLOBSTER) - delete pLOBSTER; -} - -void LOBSTERBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) -{ - if(img_input.empty()) - return; - - loadConfig(); - - if (firstTime) { - saveConfig(); - pLOBSTER = new BackgroundSubtractorLOBSTER( - fRelLBSPThreshold, nLBSPThresholdOffset, nDescDistThreshold, - nColorDistThreshold, nBGSamples, nRequiredBGSamples); - - pLOBSTER->initialize(img_input, cv::Mat (img_input.size(), CV_8UC1, cv::Scalar_(255))); - firstTime = false; - } - - (*pLOBSTER)(img_input, img_output); - pLOBSTER->getBackgroundImage(img_bgmodel); - - if(showOutput) { - imshow("LOBSTER FG", img_output); - imshow("LOBSTER BG", img_bgmodel); - } -} - -void LOBSTERBGS::saveConfig() -{ - CvFileStorage* fs = cvOpenFileStorage("./config/LOBSTERBGS.xml", 0, CV_STORAGE_WRITE); - - cvWriteReal(fs, "fRelLBSPThreshold", fRelLBSPThreshold); - cvWriteInt(fs, "nLBSPThresholdOffset", nLBSPThresholdOffset); - cvWriteInt(fs, "nDescDistThreshold", nDescDistThreshold); - cvWriteInt(fs, "nColorDistThreshold", nColorDistThreshold); - cvWriteInt(fs, "nBGSamples", nBGSamples); - cvWriteInt(fs, "nRequiredBGSamples", nRequiredBGSamples); - cvWriteInt(fs, "showOutput", showOutput); - - cvReleaseFileStorage(&fs); -} - -void LOBSTERBGS::loadConfig() -{ - CvFileStorage* fs = cvOpenFileStorage("./config/LOBSTERBGS.xml", 0, CV_STORAGE_READ); - - fRelLBSPThreshold = cvReadRealByName(fs, 0, "fRelLBSPThreshold", BGSLOBSTER_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD); - nLBSPThresholdOffset = cvReadIntByName(fs, 0, "nLBSPThresholdOffset", BGSLOBSTER_DEFAULT_LBSP_OFFSET_SIMILARITY_THRESHOLD); - nDescDistThreshold = cvReadIntByName(fs, 0, "nDescDistThreshold", BGSLOBSTER_DEFAULT_DESC_DIST_THRESHOLD); - nColorDistThreshold = cvReadIntByName(fs, 0, "nColorDistThreshold", BGSLOBSTER_DEFAULT_COLOR_DIST_THRESHOLD); - nBGSamples = cvReadIntByName(fs, 0, "nBGSamples", BGSLOBSTER_DEFAULT_NB_BG_SAMPLES); - nRequiredBGSamples = cvReadIntByName(fs, 0, "nRequiredBGSamples", BGSLOBSTER_DEFAULT_REQUIRED_NB_BG_SAMPLES); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - - cvReleaseFileStorage(&fs); -} diff --git a/package_bgs/pl/LOBSTER.h b/package_bgs/pl/LOBSTER.h deleted file mode 100644 index f5954ca..0000000 --- a/package_bgs/pl/LOBSTER.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include - - -#include "../IBGS.h" - -class BackgroundSubtractorLOBSTER; - -class LOBSTERBGS : public IBGS -{ -private: - BackgroundSubtractorLOBSTER* pLOBSTER; - bool firstTime; - bool showOutput; - - float fRelLBSPThreshold; - size_t nLBSPThresholdOffset; - size_t nDescDistThreshold; - size_t nColorDistThreshold; - size_t nBGSamples; - size_t nRequiredBGSamples; - -public: - LOBSTERBGS(); - ~LOBSTERBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; diff --git a/package_bgs/pl/RandUtils.h b/package_bgs/pl/RandUtils.h deleted file mode 100644 index 24ca5f6..0000000 --- a/package_bgs/pl/RandUtils.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -/*// gaussian 3x3 pattern, based on 'floor(fspecial('gaussian', 3, 1)*256)' -static const int s_nSamplesInitPatternWidth = 3; -static const int s_nSamplesInitPatternHeight = 3; -static const int s_nSamplesInitPatternTot = 256; -static const int s_anSamplesInitPattern[s_nSamplesInitPatternHeight][s_nSamplesInitPatternWidth] = { - {19, 32, 19,}, - {32, 52, 32,}, - {19, 32, 19,}, -};*/ - -// gaussian 7x7 pattern, based on 'floor(fspecial('gaussian',7,2)*512)' -static const int s_nSamplesInitPatternWidth = 7; -static const int s_nSamplesInitPatternHeight = 7; -static const int s_nSamplesInitPatternTot = 512; -static const int s_anSamplesInitPattern[s_nSamplesInitPatternHeight][s_nSamplesInitPatternWidth] = { - {2, 4, 6, 7, 6, 4, 2,}, - {4, 8, 12, 14, 12, 8, 4,}, - {6, 12, 21, 25, 21, 12, 6,}, - {7, 14, 25, 28, 25, 14, 7,}, - {6, 12, 21, 25, 21, 12, 6,}, - {4, 8, 12, 14, 12, 8, 4,}, - {2, 4, 6, 7, 6, 4, 2,}, -}; - -//! returns a random init/sampling position for the specified pixel position; also guards against out-of-bounds values via image/border size check. -static inline void getRandSamplePosition(int& x_sample, int& y_sample, const int x_orig, const int y_orig, const int border, const cv::Size& imgsize) { - int r = 1+rand()%s_nSamplesInitPatternTot; - for(x_sample=0; x_sample=imgsize.width-border) - x_sample = imgsize.width-border-1; - if(y_sample=imgsize.height-border) - y_sample = imgsize.height-border-1; -} - -// simple 8-connected (3x3) neighbors pattern -static const int s_anNeighborPatternSize_3x3 = 8; -static const int s_anNeighborPattern_3x3[8][2] = { - {-1, 1}, { 0, 1}, { 1, 1}, - {-1, 0}, { 1, 0}, - {-1,-1}, { 0,-1}, { 1,-1}, -}; - -//! returns a random neighbor position for the specified pixel position; also guards against out-of-bounds values via image/border size check. -static inline void getRandNeighborPosition_3x3(int& x_neighbor, int& y_neighbor, const int x_orig, const int y_orig, const int border, const cv::Size& imgsize) { - int r = rand()%s_anNeighborPatternSize_3x3; - x_neighbor = x_orig+s_anNeighborPattern_3x3[r][0]; - y_neighbor = y_orig+s_anNeighborPattern_3x3[r][1]; - if(x_neighbor=imgsize.width-border) - x_neighbor = imgsize.width-border-1; - if(y_neighbor=imgsize.height-border) - y_neighbor = imgsize.height-border-1; -} - -// 5x5 neighbors pattern -static const int s_anNeighborPatternSize_5x5 = 24; -static const int s_anNeighborPattern_5x5[24][2] = { - {-2, 2}, {-1, 2}, { 0, 2}, { 1, 2}, { 2, 2}, - {-2, 1}, {-1, 1}, { 0, 1}, { 1, 1}, { 2, 1}, - {-2, 0}, {-1, 0}, { 1, 0}, { 2, 0}, - {-2,-1}, {-1,-1}, { 0,-1}, { 1,-1}, { 2,-1}, - {-2,-2}, {-1,-2}, { 0,-2}, { 1,-2}, { 2,-2}, -}; - -//! returns a random neighbor position for the specified pixel position; also guards against out-of-bounds values via image/border size check. -static inline void getRandNeighborPosition_5x5(int& x_neighbor, int& y_neighbor, const int x_orig, const int y_orig, const int border, const cv::Size& imgsize) { - int r = rand()%s_anNeighborPatternSize_5x5; - x_neighbor = x_orig+s_anNeighborPattern_5x5[r][0]; - y_neighbor = y_orig+s_anNeighborPattern_5x5[r][1]; - if(x_neighbor=imgsize.width-border) - x_neighbor = imgsize.width-border-1; - if(y_neighbor=imgsize.height-border) - y_neighbor = imgsize.height-border-1; -} diff --git a/package_bgs/pl/SuBSENSE.cpp b/package_bgs/pl/SuBSENSE.cpp deleted file mode 100644 index a4aab0c..0000000 --- a/package_bgs/pl/SuBSENSE.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "SuBSENSE.h" -#include "BackgroundSubtractorSuBSENSE.h" - - -SuBSENSEBGS::SuBSENSEBGS() : -pSubsense(0), firstTime(true), showOutput(true), -fRelLBSPThreshold (BGSSUBSENSE_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD), -nDescDistThresholdOffset (BGSSUBSENSE_DEFAULT_DESC_DIST_THRESHOLD_OFFSET), -nMinColorDistThreshold (BGSSUBSENSE_DEFAULT_MIN_COLOR_DIST_THRESHOLD), -nBGSamples (BGSSUBSENSE_DEFAULT_NB_BG_SAMPLES), -nRequiredBGSamples (BGSSUBSENSE_DEFAULT_REQUIRED_NB_BG_SAMPLES), -nSamplesForMovingAvgs (BGSSUBSENSE_DEFAULT_N_SAMPLES_FOR_MV_AVGS) -{ -} - -SuBSENSEBGS::~SuBSENSEBGS() { - if (pSubsense) - delete pSubsense; -} - -void SuBSENSEBGS::process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel) -{ - if(img_input.empty()) - return; - - loadConfig(); - - if (firstTime) { - saveConfig(); - pSubsense = new BackgroundSubtractorSuBSENSE( - fRelLBSPThreshold, nDescDistThresholdOffset, nMinColorDistThreshold, - nBGSamples, nRequiredBGSamples, nSamplesForMovingAvgs); - - pSubsense->initialize(img_input, cv::Mat (img_input.size(), CV_8UC1, cv::Scalar_(255))); - firstTime = false; - } - - (*pSubsense)(img_input, img_output); - pSubsense->getBackgroundImage(img_bgmodel); - - if(showOutput) { - imshow("SuBSENSE FG", img_output); - imshow("SuBSENSE BG", img_bgmodel); - } -} - -void SuBSENSEBGS::saveConfig() -{ - CvFileStorage* fs = cvOpenFileStorage("./config/SuBSENSEBGS.xml", 0, CV_STORAGE_WRITE); - - cvWriteReal(fs, "fRelLBSPThreshold", fRelLBSPThreshold); - cvWriteInt(fs, "nDescDistThresholdOffset", nDescDistThresholdOffset); - cvWriteInt(fs, "nMinColorDistThreshold", nMinColorDistThreshold); - cvWriteInt(fs, "nBGSamples", nBGSamples); - cvWriteInt(fs, "nRequiredBGSamples", nRequiredBGSamples); - cvWriteInt(fs, "nSamplesForMovingAvgs", nSamplesForMovingAvgs); - cvWriteInt(fs, "showOutput", showOutput); - - cvReleaseFileStorage(&fs); -} - -void SuBSENSEBGS::loadConfig() -{ - CvFileStorage* fs = cvOpenFileStorage("./config/SuBSENSEBGS.xml", 0, CV_STORAGE_READ); - - fRelLBSPThreshold = cvReadRealByName(fs, 0, "fRelLBSPThreshold", BGSSUBSENSE_DEFAULT_LBSP_REL_SIMILARITY_THRESHOLD); - nDescDistThresholdOffset = cvReadIntByName(fs, 0, "nDescDistThresholdOffset", BGSSUBSENSE_DEFAULT_DESC_DIST_THRESHOLD_OFFSET); - nMinColorDistThreshold = cvReadIntByName(fs, 0, "nMinColorDistThreshold", BGSSUBSENSE_DEFAULT_MIN_COLOR_DIST_THRESHOLD); - nBGSamples = cvReadIntByName(fs, 0, "nBGSamples", BGSSUBSENSE_DEFAULT_NB_BG_SAMPLES); - nRequiredBGSamples = cvReadIntByName(fs, 0, "nRequiredBGSamples", BGSSUBSENSE_DEFAULT_REQUIRED_NB_BG_SAMPLES); - nSamplesForMovingAvgs = cvReadIntByName(fs, 0, "nSamplesForMovingAvgs", BGSSUBSENSE_DEFAULT_N_SAMPLES_FOR_MV_AVGS); - showOutput = cvReadIntByName(fs, 0, "showOutput", true); - - cvReleaseFileStorage(&fs); -} diff --git a/package_bgs/pl/SuBSENSE.h b/package_bgs/pl/SuBSENSE.h deleted file mode 100644 index b527c27..0000000 --- a/package_bgs/pl/SuBSENSE.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include - -#include "../IBGS.h" - -class BackgroundSubtractorSuBSENSE; - -class SuBSENSEBGS: public IBGS { -private: - BackgroundSubtractorSuBSENSE* pSubsense; - bool firstTime; - bool showOutput; - - float fRelLBSPThreshold; - size_t nDescDistThresholdOffset; - size_t nMinColorDistThreshold; - size_t nBGSamples; - size_t nRequiredBGSamples; - size_t nSamplesForMovingAvgs; - -public: - SuBSENSEBGS(); - ~SuBSENSEBGS(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, - cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; diff --git a/package_bgs/sjn/SJN_MultiCueBGS.h b/package_bgs/sjn/SJN_MultiCueBGS.h deleted file mode 100644 index 0891c08..0000000 --- a/package_bgs/sjn/SJN_MultiCueBGS.h +++ /dev/null @@ -1,248 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#define MIN3(x,y,z) ((y) <= (z) ? ((x) <= (y) ? (x) : (y)) : ((x) <= (z) ? (x) : (z))) -#define MAX3(x,y,z) ((y) >= (z) ? ((x) >= (y) ? (x) : (y)) : ((x) >= (z) ? (x) : (z))) - -#ifndef PI - #define PI 3.14159 -#endif - -typedef int BOOL; - -#ifndef FALSE - #define FALSE 0 -#endif - -#ifndef TRUE - #define TRUE 1 -#endif - -#if !defined(__APPLE__) -#include -#endif -#include "math.h" - -#include -using std::vector; - -#include -using std::sort; - -#include - - -#include "../IBGS.h" - -//------------------------------------Structure Lists-------------------------------------// -struct point{ - short m_nX; - short m_nY; -}; - -struct neighbor_pos{ - short m_nX; - short m_nY; -}; -//1) Bounding Box Structure -struct BoundingBoxInfo{ - int m_iBoundBoxNum; //# of bounding boxes for all foreground and false-positive blobs - int m_iArraySize; //the size of the below arrays to store bounding box information - - short *m_aLeft, *m_aRight, *m_aUpper, *m_aBottom; //arrays to store bounding box information for (the original frame size) - short *m_aRLeft, *m_aRRight, *m_aRUpper, *m_aRBottom; //arrays to store bounding box information for (the reduced frame size) - BOOL* m_ValidBox; //If this value is true, the corresponding bounding box is for a foreground blob. - //Else, it is for a false-positive blob -}; - -//2) Texture Model Structure -struct TextureCodeword{ - int m_iMNRL; //the maximum negative run-length - int m_iT_first_time; //the first access time - int m_iT_last_time; //the last access time - - //Æò±Õ MTLBP°ª - float m_fLowThre; //a low threshold for the matching - float m_fHighThre; //a high threshold for the matching - float m_fMean; //mean of the codeword -}; - -struct TextureModel{ - TextureCodeword** m_Codewords; //the texture-codeword Array - - int m_iTotal; //# of learned samples after the last clear process - int m_iElementArraySize; //the array size of m_Codewords - int m_iNumEntries; //# of codewords - - BOOL m_bID; //id=1 --> background model, id=0 --> cachebook -}; - -//3) Color Model Structure -struct ColorCodeword{ - int m_iMNRL; //the maximum negative run-length - int m_iT_first_time; //the first access time - int m_iT_last_time; //the last access time - - double m_dMean[3]; //mean vector of the codeword - -}; - -struct ColorModel{ - ColorCodeword** m_Codewords; //the color-codeword Array - - int m_iTotal; //# of learned samples after the last clear process - int m_iElementArraySize; //the array size of m_Codewords - int m_iNumEntries; //# of codewords - - BOOL m_bID; //id=1 --> background model, id=0 --> cachebookk -}; - - -class SJN_MultiCueBGS : public IBGS -{ -private: - bool firstTime; - bool showOutput; - void saveConfig(); - void loadConfig(); - -public: - SJN_MultiCueBGS(); - ~SJN_MultiCueBGS(void); - -public: - //---------------------------------------------------- - // APIs and User-Adjustable Parameters - //---------------------------------------------------- - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); //the main function to background modeling and subtraction - - void GetForegroundMap(IplImage* return_image, IplImage* input_frame=NULL); //the function returning a foreground binary-map - void Destroy(); //the function to release allocated memories - - int g_iTrainingPeriod; //the training period (The parameter t in the paper) - int g_iT_ModelThreshold; //the threshold for texture-model based BGS. (The parameter tau_T in the paper) - int g_iC_ModelThreshold; //the threshold for appearance based verification. (The parameter tau_A in the paper) - - float g_fLearningRate; //the learning rate for background models. (The parameter alpha in the paper) - - short g_nTextureTrainVolRange; //the codebook size factor for texture models. (The parameter k in the paper) - short g_nColorTrainVolRange; //the codebook size factor for color models. (The parameter eta_1 in the paper) - -public: - //---------------------------------------------------- - // Implemented Function Lists - //---------------------------------------------------- - - //--1) General Functions - void Initialize(IplImage* frame); - - void PreProcessing(IplImage* frame); - void ReduceImageSize(IplImage* SrcImage, IplImage* DstImage); - void GaussianFiltering(IplImage* frame, uchar*** aFilteredFrame); - void BGR2HSVxyz_Par(uchar*** aBGR, uchar*** aXYZ); - - void BackgroundModeling_Par(IplImage* frame); - void ForegroundExtraction(IplImage* frame); - void CreateLandmarkArray_Par(float fConfThre, short nTrainVolRange, float**aConfMap, int iNehborNum, uchar*** aXYZ, - point*** aNeiDir, TextureModel**** TModel, ColorModel*** CModel, uchar**aLandmarkArr); - - void PostProcessing(IplImage* frame); - void MorphologicalOpearions(uchar** aInput, uchar** aOutput, double dThresholdRatio, int iMaskSize, int iWidth, int iHeight); - void Labeling(uchar** aBinaryArray, int* pLabelCount, int** aLabelTable); - void SetBoundingBox(int iLabelCount, int** aLabelTable); - void BoundBoxVerification(IplImage* frame, uchar** aResForeMap, BoundingBoxInfo* BoundBoxInfo); - void EvaluateBoxSize( BoundingBoxInfo* BoundBoxInfo); - void EvaluateOverlapRegionSize(BoundingBoxInfo* SrcBoxInfo); - void EvaluateGhostRegion(IplImage* frame, uchar** aResForeMap, BoundingBoxInfo* BoundBoxInfo); - double CalculateHausdorffDist(IplImage* input_image, IplImage* model_image); - void RemovingInvalidForeRegions(uchar** aResForeMap, BoundingBoxInfo* BoundBoxInfo); - - void UpdateModel_Par(); - void GetEnlargedMap(float** aOriginMap, float** aEnlargedMap); - - //--2) Texture Model Related Functions - void T_AllocateTextureModelRelatedMemory(); - void T_ReleaseTextureModelRelatedMemory(); - void T_SetNeighborDirection(point*** aNeighborPos); - void T_ModelConstruction(short nTrainVolRange,float fLearningRate, uchar*** aXYZ,point center, point* aNei, TextureModel** aModel); - void T_ClearNonEssentialEntries(short nClearNum, TextureModel** aModel); - void T_ClearNonEssentialEntriesForCachebook(uchar bLandmark, short* nReferredIdxArr, short nClearNum, TextureModel** pCachebook); - void T_GetConfidenceMap_Par(uchar*** aXYZ, float** aTextureMap, point*** aNeiDirArr, TextureModel**** aModel); - void T_Absorption(int iAbsorbCnt, point pos, short*** aContinuCnt, short*** aRefferedIndex, TextureModel** pModel, TextureModel** pCache); - - //--3) Color Model Related Functions - void C_AllocateColorModelRelatedMemory(); - void C_ReleaseColorModelRelatedMemory(); - void C_CodebookConstruction(uchar* aP,int iPosX, int iPosY, short nTrainVolRange, float fLearningRate, ColorModel* pC); - void C_ClearNonEssentialEntries(short nClearNum, ColorModel* pModel); - void C_ClearNonEssentialEntriesForCachebook(uchar bLandmark, short nReferredIdx, short nClearNum, ColorModel* pCachebook); - void C_Absorption(int iAbsorbCnt, point pos , short** aContinuCnt, short** aRefferedIndex, ColorModel* pModel, ColorModel* pCache); -public: - //---------------------------------------------------- - // Implemented Variable Lists - //---------------------------------------------------- - - //--1) General Variables - int g_iFrameCount; //the counter of processed frames - - int g_iBackClearPeriod; //the period to clear background models - int g_iCacheClearPeriod; //the period to clear cache-book models - - int g_iAbsortionPeriod; //the period to absorb static ghost regions - BOOL g_bAbsorptionEnable; //If True, procedures for ghost region absorption are activated. - - BOOL g_bModelMemAllocated; //To handle memory.. - BOOL g_bNonModelMemAllocated; //To handle memory.. - - float g_fConfidenceThre; //the final decision threshold - - int g_iWidth, g_iHeight; //width and height of input frames - int g_iRWidth, g_iRHeight; //width and height of reduced frames (For efficiency, the reduced size of frames are processed) - int g_iForegroundNum; //# of detected foreground regions - BOOL g_bForegroundMapEnable; //TRUE only when BGS is successful - - IplImage* g_ResizedFrame; //reduced size of frame (For efficiency, the reduced size of frames are processed) - uchar*** g_aGaussFilteredFrame; - uchar*** g_aXYZFrame; - uchar** g_aLandmarkArray; //the landmark map - uchar** g_aResizedForeMap; //the resized foreground map - uchar** g_aForegroundMap; //the final foreground map - BOOL** g_aUpdateMap; //the location map of update candidate pixels - - BoundingBoxInfo* g_BoundBoxInfo; //the array of bounding boxes of each foreground blob - - //--2) Texture Model Related - TextureModel**** g_TextureModel; //the texture background model - TextureModel**** g_TCacheBook; //the texture cache-book - short*** g_aTReferredIndex; //To handle cache-book - short*** g_aTContinuousCnt; //To handle cache-book - point*** g_aNeighborDirection; - float**g_aTextureConfMap; //the texture confidence map - - short g_nNeighborNum; //# of neighborhoods - short g_nRadius; - short g_nBoundarySize; - - //--3) Texture Model Related - ColorModel*** g_ColorModel; //the color background model - ColorModel*** g_CCacheBook; //the color cache-book - short** g_aCReferredIndex; //To handle cache-book - short** g_aCContinuousCnt; //To handle cache-book -}; - - diff --git a/package_bgs/tb/FuzzyChoquetIntegral.h b/package_bgs/tb/FuzzyChoquetIntegral.h deleted file mode 100644 index 76ba158..0000000 --- a/package_bgs/tb/FuzzyChoquetIntegral.h +++ /dev/null @@ -1,55 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#include -#include - - -#include "../IBGS.h" - -#include "FuzzyUtils.h" - -class FuzzyChoquetIntegral : public IBGS -{ -private: - bool firstTime; - long frameNumber; - bool showOutput; - - int framesToLearn; - double alphaLearn; - double alphaUpdate; - int colorSpace; - int option; - bool smooth; - double threshold; - - FuzzyUtils fu; - cv::Mat img_background_f3; - -public: - FuzzyChoquetIntegral(); - ~FuzzyChoquetIntegral(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - diff --git a/package_bgs/tb/FuzzySugenoIntegral.h b/package_bgs/tb/FuzzySugenoIntegral.h deleted file mode 100644 index cfa91b7..0000000 --- a/package_bgs/tb/FuzzySugenoIntegral.h +++ /dev/null @@ -1,55 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#include -#include - - -#include "../IBGS.h" - -#include "FuzzyUtils.h" - -class FuzzySugenoIntegral : public IBGS -{ -private: - bool firstTime; - long long frameNumber; - bool showOutput; - - int framesToLearn; - double alphaLearn; - double alphaUpdate; - int colorSpace; - int option; - bool smooth; - double threshold; - - FuzzyUtils fu; - cv::Mat img_background_f3; - -public: - FuzzySugenoIntegral(); - ~FuzzySugenoIntegral(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; - diff --git a/package_bgs/tb/FuzzyUtils.cpp b/package_bgs/tb/FuzzyUtils.cpp deleted file mode 100644 index fcb1972..0000000 --- a/package_bgs/tb/FuzzyUtils.cpp +++ /dev/null @@ -1,512 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#include "FuzzyUtils.h" - -FuzzyUtils::FuzzyUtils(void){} - -FuzzyUtils::~FuzzyUtils(void){} - -void FuzzyUtils::LBP(IplImage* InputImage, IplImage* LBPimage) -{ - PixelUtils p; - - float* neighberPixel = (float*) malloc(9*sizeof(float)); - float* BinaryValue = (float*) malloc(9*sizeof(float)); - float* CarreExp = (float*) malloc(9*sizeof(float)); - float* valLBP = (float*) malloc(1*sizeof(float)); - - *valLBP = 0; - - int x = 0, y = 0; - - // on implemente les 8 valeurs puissance de 2 qui correspondent aux 8 elem. d'image voisins au elem. d'image central - *(CarreExp+0)=1.0; - *(CarreExp+1)=2.0; - *(CarreExp+2)=4.0; - *(CarreExp+3)=8.0; - *(CarreExp+4)=0.0; - *(CarreExp+5)=16.0; - *(CarreExp+6)=32.0; - *(CarreExp+7)=64.0; - *(CarreExp+8)=128.0; - - //le calcule de LBP - //pour les 4 coins - /* 1.*/ - if(x==0 && y==0) - { - p.getNeighberhoodGrayPixel(InputImage, x,y,neighberPixel); - getBinValue(neighberPixel,BinaryValue,4,0); - *valLBP=*valLBP+((*(BinaryValue+1))*(*(CarreExp+1))+(*(BinaryValue+2))*(*(CarreExp+2))+(*(BinaryValue+3))*(*(CarreExp+3)))/255.0; - p.PutGrayPixel(LBPimage,x,y,*valLBP); - } - - /* 2.*/ - if(x==0 && y==InputImage->width) - { - *valLBP=0; - p.getNeighberhoodGrayPixel(InputImage, x,y,neighberPixel); - getBinValue(neighberPixel,BinaryValue,4,1); - *valLBP=*valLBP+((*(BinaryValue))*(*(CarreExp))+(*(BinaryValue+2))*(*(CarreExp+2))+(*(BinaryValue+3))*(*(CarreExp+3)))/255.0; - p.PutGrayPixel(LBPimage,x,y,*valLBP); - } - - /* 3.*/ - if(x==InputImage->height && y==0) - { - *valLBP=0; - p.getNeighberhoodGrayPixel(InputImage, x,y,neighberPixel); - getBinValue(neighberPixel,BinaryValue,4,2); - *valLBP=*valLBP+((*(BinaryValue))*(*(CarreExp))+(*(BinaryValue+1))*(*(CarreExp+1))+(*(BinaryValue+3))*(*(CarreExp+3)))/255.0; - p.PutGrayPixel(LBPimage,x,y,*valLBP); - } - - /* 4.*/ - if(x==InputImage->height && y==InputImage->width) - { - *valLBP=0; - p.getNeighberhoodGrayPixel(InputImage, x,y,neighberPixel); - getBinValue(neighberPixel,BinaryValue,4,3); - *valLBP=*valLBP+((*(BinaryValue))*(*(CarreExp))+(*(BinaryValue+1))*(*(CarreExp+1))+(*(BinaryValue+2))*(*(CarreExp+2)))/255.0; - p.PutGrayPixel(LBPimage,x,y,*valLBP); - } - - //le calcul de LBP pour la première ligne : L(0) - if(x==0 && (y!=0 && y!=InputImage->width)) - { - for(int y = 1; y < InputImage->width-1; y++) - { - p.getNeighberhoodGrayPixel(InputImage, x,y,neighberPixel); - getBinValue(neighberPixel,BinaryValue,6,4); - *valLBP=0; - *valLBP=*valLBP+((*(BinaryValue))*(*(CarreExp))+(*(BinaryValue+1))*(*(CarreExp+1))+(*(BinaryValue+2))*(*(CarreExp+2))+(*(BinaryValue+3))*(*(CarreExp+3))+(*(BinaryValue+5))*(*(CarreExp+5)))/255.0; - p.PutGrayPixel(LBPimage,x,y,*valLBP); - } - } - - //le calcul de LBP pour la dernière colonne : C(w) - if((x!=0 && x!=InputImage->height) && y==InputImage->width) - { - for(int x = 1; x < InputImage->height-1; x++) - { - p.getNeighberhoodGrayPixel(InputImage, x,y,neighberPixel); - getBinValue(neighberPixel,BinaryValue,6,4); - *valLBP=0; - *valLBP=*valLBP+((*(BinaryValue))*(*(CarreExp))+(*(BinaryValue+1))*(*(CarreExp+1))+(*(BinaryValue+2))*(*(CarreExp+2))+(*(BinaryValue+3))*(*(CarreExp+3))+(*(BinaryValue+5))*(*(CarreExp+5)))/255.0; - p.PutGrayPixel(LBPimage,x,y,*valLBP); - } - } - - //le calcul de LBP pour la dernière ligne : L(h) - if(x==InputImage->height && (y!=0 && y!=InputImage->width)) - { - for(int y = 1; y < InputImage->width-1; y++) - { - p.getNeighberhoodGrayPixel(InputImage, x,y,neighberPixel); - getBinValue(neighberPixel,BinaryValue,6,1); - *valLBP=0; - *valLBP=*valLBP+((*(BinaryValue))*(*(CarreExp))+(*(BinaryValue+2))*(*(CarreExp+2))+(*(BinaryValue+3))*(*(CarreExp+3))+(*(BinaryValue+4))*(*(CarreExp+4))+(*(BinaryValue+5))*(*(CarreExp+5)))/255.0; - p.PutGrayPixel(LBPimage,x,y,*valLBP); - } - } - - //le calcul de LBP pour la première colonne : C(0) - if((x!=0 && x!=InputImage->height) && y==0) - { - for(int x = 1; x height-1; x++) - { - p.getNeighberhoodGrayPixel(InputImage, x,y,neighberPixel); - getBinValue(neighberPixel,BinaryValue,6,2); - *valLBP=0; - *valLBP=*valLBP+((*(BinaryValue))*(*(CarreExp+5))+(*(BinaryValue+1))*(*(CarreExp+6))+(*(BinaryValue+3))*(*(CarreExp+3))+(*(BinaryValue+4))*(*(CarreExp))+(*(BinaryValue+5))*(*(CarreExp+1)))/255.0; - p.PutGrayPixel(LBPimage,x,y,*valLBP); - } - } - - //pour le reste des elements d'image - for(int y = 1; y < InputImage->height-1; y++) - { - for(int x = 1; x < InputImage->width-1; x++) - { - p.getNeighberhoodGrayPixel(InputImage, x,y,neighberPixel); - getBinValue(neighberPixel,BinaryValue,9,4); - //le calcul de la valeur du LBP pour chaque elem. d'im. - *valLBP=0; - for(int l = 0; l < 9; l++) - *valLBP = *valLBP + ((*(BinaryValue+l)) * (*(CarreExp+l))) / 255.0; - //printf("\nvalLBP(%d,%d)=%f",x,y,*valLBP); - p.PutGrayPixel(LBPimage,x,y,*valLBP); - } - } - - free(neighberPixel); - free(BinaryValue); - free(CarreExp); - free(valLBP); -} - -void FuzzyUtils::getBinValue(float* neighberGrayPixel, float* BinaryValue, int m, int n) -{ - // la comparaison entre la valeur d'elem d'image central et les valeurs des elem. d'im. voisins - // m = le numero des elements (4, 6 ou 9); - // n = la position de l'element central; - - int h = 0; - for(int k = 0; k < m; k++) - { - if(*(neighberGrayPixel+k) >= *(neighberGrayPixel+n)) - { - *(BinaryValue+h)=1; - h++; - } - else - { - *(BinaryValue+h)=0; - h++; - } - } -} - -void FuzzyUtils::SimilarityDegreesImage(IplImage* CurrentImage, IplImage* BGImage, IplImage* DeltaImage, int n, int color_space) -{ - PixelUtils p; - int i, j; - - if(n == 1) - { - float* CurrentGrayPixel = (float*) malloc (1*(sizeof(float))); - float* BGGrayPixel = (float*) malloc (1*(sizeof(float))); - float* DeltaGrayPixel = (float*) malloc (1*(sizeof(float))); - - for(i = 0; i < CurrentImage->width; i++) - { - for(j = 0; j < CurrentImage->height; j++) - { - p.GetGrayPixel(CurrentImage,i,j,CurrentGrayPixel); - p.GetGrayPixel(BGImage,i,j,BGGrayPixel); - RatioPixels(CurrentGrayPixel,BGGrayPixel,DeltaGrayPixel,1); - p.PutGrayPixel(DeltaImage,i,j,*DeltaGrayPixel); - } - } - - free(CurrentGrayPixel); - free(BGGrayPixel); - free(DeltaGrayPixel); - } - - if(n != 1) - { - IplImage* ConvertedCurrentImage = cvCreateImage(cvSize(CurrentImage->width, CurrentImage->height), IPL_DEPTH_32F, 3); - IplImage* ConvertedBGImage = cvCreateImage(cvSize(CurrentImage->width, CurrentImage->height), IPL_DEPTH_32F, 3); - - float* ConvertedCurrentPixel = (float*) malloc(3*(sizeof(float))); - float* ConvertedBGPixel = (float*) malloc(3*(sizeof(float))); - float* DeltaConvertedPixel = (float*) malloc(3*(sizeof(float))); - - p.ColorConversion(CurrentImage,ConvertedCurrentImage,color_space); - p.ColorConversion(BGImage,ConvertedBGImage,color_space); - - for(i = 0; i < CurrentImage->width; i++) - { - for(j = 0; j < CurrentImage->height; j++) - { - p.GetPixel(ConvertedCurrentImage,i,j,ConvertedCurrentPixel); - p.GetPixel(ConvertedBGImage,i,j,ConvertedBGPixel); - RatioPixels(ConvertedCurrentPixel,ConvertedBGPixel,DeltaConvertedPixel,3); - p.PutPixel(DeltaImage,i,j,DeltaConvertedPixel); - } - } - - free(ConvertedCurrentPixel); - free(ConvertedBGPixel); - free(DeltaConvertedPixel); - - cvReleaseImage(&ConvertedCurrentImage); - cvReleaseImage(&ConvertedBGImage); - } -} - -void FuzzyUtils::RatioPixels(float* CurrentPixel, float* BGPixel, float* DeltaPixel, int n) -{ - if(n == 1) - { - if(*CurrentPixel < *BGPixel) - *DeltaPixel = *CurrentPixel / *BGPixel; - - if(*CurrentPixel > *BGPixel) - *DeltaPixel = *BGPixel / *CurrentPixel; - - if(*CurrentPixel == *BGPixel) - *DeltaPixel = 1.0; - } - - if(n == 3) - for(int i = 0; i < 3; i++) - { - if(*(CurrentPixel+i) < *(BGPixel+i)) - *(DeltaPixel+i) = *(CurrentPixel+i) / *(BGPixel+i); - - if(*(CurrentPixel+i) > *(BGPixel+i)) - *(DeltaPixel+i) = *(BGPixel+i) / *(CurrentPixel+i); - - if(*(CurrentPixel+i) == *(BGPixel+i)) - *(DeltaPixel+i) = 1.0; - } -} - -void FuzzyUtils::getFuzzyIntegralSugeno(IplImage* H, IplImage* Delta, int n, float *MeasureG, IplImage* OutputImage) -{ - // MeasureG : est un vecteur contenant 3 mesure g (g1,g2,g3) tel que : g1+g2+g3=1 - // n : =2 cad aggreger les 2 images "H" et "Delta" - // =1 cad aggreger uniquement les valeurs des composantes couleurs de l'image "Delta" - - PixelUtils p; - - float* HTexturePixel = (float*) malloc(1*sizeof(float)); - float* DeltaOhtaPixel = (float*) malloc(3*(sizeof(float))); - int *Indice = (int*) malloc(3*(sizeof(int))); - float *HI = (float*) malloc(3*(sizeof(float))); - float *Integral = (float*) malloc(3*(sizeof(float))); - float* X = (float*) malloc(1*sizeof(float)); - float* XiXj = (float*) malloc(1*sizeof(float)); - float IntegralFlou; - - *Indice = 0; - *(Indice+1) = 1; - *(Indice+2) = 2; - *X = 1.0; - - for(int i = 0; i < H->width; i++) - { - for(int j = 0; j < H->height; j++) - { - p.GetGrayPixel(H,i,j,HTexturePixel); - p.GetPixel(Delta,i,j,DeltaOhtaPixel); - - *(HI+0) = *(HTexturePixel+0); - *(HI+1) = *(DeltaOhtaPixel+0); - *(HI+2) = *(DeltaOhtaPixel+1); - - Trier(HI,3,Indice); - - *XiXj = *(MeasureG + (*(Indice+1))) + (*(MeasureG + (*(Indice+2)))); - - *(Integral+0) = min((HI + (*(Indice+0))), X); - *(Integral+1) = min((HI + (*(Indice+1))), XiXj); - *(Integral+2) = min((HI + (*(Indice+2))), ((MeasureG+(*(Indice+2))))); - - IntegralFlou = max(Integral,3); - p.PutGrayPixel(OutputImage,i,j,IntegralFlou); - } - } - - free(HTexturePixel); - free(DeltaOhtaPixel); - free(Indice); - free(HI); - free(X); - free(XiXj); - free(Integral); -} - -void FuzzyUtils::getFuzzyIntegralChoquet(IplImage* H, IplImage* Delta, int n, float *MeasureG, IplImage* OutputImage) -{ - // MeasureG : est un vecteur contenant 3 mesure g (g1,g2,g3) tel que : g1+g2+g3=1 - // n : =2 cad aggreger les 2 images "H" et "Delta" - // =1 cad aggreger uniquement les valeurs des composantes couleurs de l'image "Delta" - - PixelUtils p; - - float* HTexturePixel = (float*) malloc(1*sizeof(float)); - float* DeltaOhtaPixel = (float*) malloc(3*(sizeof(float))); - int *Indice = (int*) malloc(3*(sizeof(int))); - float *HI = (float*) malloc(3*(sizeof(float))); - float *Integral = (float*) malloc(3*(sizeof(float))); - float* X = (float*) malloc(1*sizeof(float)); - float* XiXj = (float*) malloc(1*sizeof(float)); - float IntegralFlou; - - *Indice = 0; - *(Indice+1) = 1; - *(Indice+2) = 2; - *X = 1.0; - - for(int i = 0; i < Delta->width; i++) - { - for(int j = 0; j < Delta->height; j++) - { - if(n == 2) - { - p.GetGrayPixel(H,i,j,HTexturePixel); - p.GetPixel(Delta,i,j,DeltaOhtaPixel); - - *(HI+0) = *(HTexturePixel+0); - *(HI+1) = *(DeltaOhtaPixel+0); - *(HI+2) = *(DeltaOhtaPixel+1); - } - - if(n==1) - { - //remplir HI par les valeurs des 3 composantes couleurs uniquement - p.GetPixel(Delta,i,j,DeltaOhtaPixel); - - *(HI+0) = *(DeltaOhtaPixel+0); - //*(HI+0) = *(DeltaOhtaPixel+2); - *(HI+1) = *(DeltaOhtaPixel+1); - *(HI+2) = *(DeltaOhtaPixel+2); - } - - Trier(HI,3,Indice); - *XiXj = *(MeasureG + (*(Indice+1))) + (*(MeasureG + (*(Indice+2)))); - - *(Integral+0) = *(HI+(*(Indice+0)))* (*X-*XiXj); - *(Integral+1) = *(HI+(*(Indice+1)))* (*XiXj-*(MeasureG+(*(Indice+2)))); - *(Integral+2) = *(HI+(*(Indice+2)))* (*(MeasureG+(*(Indice+2)))); - - IntegralFlou = *(Integral+0) + *(Integral+1) + *(Integral+2); - p.PutGrayPixel(OutputImage,i,j,IntegralFlou); - } - } - - free(HTexturePixel); - free(DeltaOhtaPixel); - free(Indice); - free(HI); - free(X); - free(XiXj); - free(Integral); -} - -void FuzzyUtils::FuzzyMeasureG(float g1, float g2, float g3, float *G) -{ - *(G+0) = g1; - *(G+1) = g2; - *(G+2) = g3; -} - -void FuzzyUtils::Trier(float* g,int n,int* index) -{ - // Cette fonction trie un vecteur g par ordre croissant et - // sort egalement l'indice des elements selon le trie dans le vecteur "index" supposé initialisé par des valeurs de 1 a n - - float t; - int r,a,b; - - for(a = 1; a <= n; a++) - { - for(b = n-1; b >= a; b--) - if(*(g + b-1) < (*(g + b))) - { - // ordre croissant des élements - t = *(g + b-1); - *(g + b-1) = *(g + b); - *(g + b) = t; - - // ordre des indices des élements du vecteur g - r = *(index + b-1); - *(index + b-1) = *(index + b); - *(index + b) = r; - } - } -} - -float FuzzyUtils::min(float *a,float *b) -{ - float min = 0; - - if(*a >= (*b)) - min = *b; - else - min = *a; - - return min; -} - -float FuzzyUtils::max(float* g , int n) -{ - float max = 0; - - for(int i = 0; i < n; i++) - { - if(*(g+i) >= max) - max = *(g+i); - } - - return max; -} - -void FuzzyUtils::gDeDeux(float* a, float* b, float* lambda) -{ - float* c = (float*) malloc(1*sizeof(float)); - *c = *a + (*b) + (*lambda) * (*a) * (*b); -} - -void FuzzyUtils::getLambda(float* g) -{ - float a,b; - float* lambda = (float*) malloc(1*sizeof(float)); - - a = (*(g+0) * (*(g+1)) + (*(g+1)) * (*(g+2)) + (*(g+0)) * (*(g+2))); - *lambda = -(*(g+0) * (*(g+1)) + (*(g+1)) * (*(g+2)) + (*(g+0)) * (*(g+2))) / (*(g+0) * (*(g+1)) * (*(g+2))); - b = (*(g+0) * (*(g+1)) * (*(g+2))); - - //printf("\na:%f",a); - //printf("\nb:%f",b); - //printf("\nlambda:%f", *lambda); - - free(lambda); -} - -void FuzzyUtils::AdaptativeSelectiveBackgroundModelUpdate(IplImage* CurrentImage, IplImage* BGImage, IplImage* OutputImage, IplImage* Integral, float seuil, float alpha) -{ - PixelUtils p; - - float beta = 0.0; - float* CurentImagePixel = (float*) malloc(3*sizeof(float)); - float* BGImagePixel = (float*) malloc(3*sizeof(float)); - float* OutputImagePixel = (float*) malloc(3*sizeof(float)); - float* IntegralImagePixel = (float*) malloc(1*sizeof(float)); - float *Maximum = (float*) malloc(1*sizeof(float)); - float *Minimum = (float*) malloc(1*sizeof(float)); - - p.ForegroundMaximum(Integral, Maximum, 1); - p.ForegroundMinimum(Integral, Minimum, 1); - - for(int i = 0; i < CurrentImage->width; i++) - { - for(int j = 0; j < CurrentImage->height; j++) - { - p.GetPixel(CurrentImage, i, j, CurentImagePixel); - p.GetPixel(BGImage, i, j, BGImagePixel); - p.GetGrayPixel(Integral, i, j, IntegralImagePixel); - - beta = 1 - ((*IntegralImagePixel) - ((*Minimum / (*Minimum - *Maximum)) * (*IntegralImagePixel) - (*Minimum * (*Maximum) / (*Minimum - *Maximum)))); - - for(int k = 0; k < 3; k++) - *(OutputImagePixel + k) = beta * (*(BGImagePixel + k)) + (1 - beta) * (alpha * (*(CurentImagePixel+k)) + (1-alpha) * (*(BGImagePixel+k))); - - p.PutPixel(OutputImage, i, j, OutputImagePixel); - } - } - - free(CurentImagePixel); - free(BGImagePixel); - free(OutputImagePixel); - free(IntegralImagePixel); - free(Maximum); - free(Minimum); -} diff --git a/package_bgs/tb/PixelUtils.cpp b/package_bgs/tb/PixelUtils.cpp deleted file mode 100644 index d647e05..0000000 --- a/package_bgs/tb/PixelUtils.cpp +++ /dev/null @@ -1,351 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#include "PixelUtils.h" - -PixelUtils::PixelUtils(void){} -PixelUtils::~PixelUtils(void){} - -void PixelUtils::ColorConversion(IplImage* RGBImage, IplImage* ConvertedImage, int color_space) -{ - // Space Color RGB - Nothing to do! - if(color_space == 1) - cvCopy(RGBImage, ConvertedImage); - - // Space Color Ohta - if(color_space == 2) - cvttoOTHA(RGBImage, ConvertedImage); - - // Space Color HSV - V Intensity - (H,S) Chromaticity - if(color_space == 3) - cvCvtColor(RGBImage, ConvertedImage, CV_BGR2HSV); - - // Space Color YCrCb - Y Intensity - (Cr,Cb) Chromaticity - if(color_space == 4) - cvCvtColor(RGBImage,ConvertedImage,CV_BGR2YCrCb); -} - -void PixelUtils::cvttoOTHA(IplImage* RGBImage, IplImage* OthaImage) -{ - float* OhtaPixel = (float*) malloc(3*(sizeof(float))); - float* RGBPixel = (float*) malloc(3*(sizeof(float))); - - for(int i = 0; i < RGBImage->width; i++) - { - for(int j = 0;j < RGBImage->height; j++) - { - GetPixel(RGBImage, i, j, RGBPixel); - - // I1 = (R + G + B) / 3 - *OhtaPixel = (*(RGBPixel) + (*(RGBPixel + 1)) + (*(RGBPixel + 2))) / 3.0; - - // I2 = (R - B) / 2 - *(OhtaPixel+1) = (*RGBPixel - (*(RGBPixel + 2))) / 2.0; - - // I3 = (2G - R - B) / 4 - *(OhtaPixel+2) = (2 * (*(RGBPixel + 1)) - (*RGBPixel) - (*(RGBPixel + 2))) / 4.0; - - PutPixel(OthaImage, i, j, OhtaPixel); - } - } - - free(OhtaPixel); - free(RGBPixel); -} - -void PixelUtils::PostProcessing(IplImage *InputImage) -{ - IplImage *ResultImage = cvCreateImage(cvSize(InputImage->width, InputImage->height), IPL_DEPTH_32F, 3); - - cvErode(InputImage, ResultImage, NULL, 1); - cvDilate(ResultImage, InputImage, NULL, 0); - - cvReleaseImage(&ResultImage); -} - -void PixelUtils::GetPixel(IplImage *image, int m, int n, unsigned char *pixelcourant) -{ - for(int k = 0; k < 3; k++) - pixelcourant[k] = ((unsigned char*)(image->imageData + image->widthStep*n))[m*3 + k]; -} - -void PixelUtils::GetGrayPixel(IplImage *image, int m, int n, unsigned char *pixelcourant) -{ - *pixelcourant = ((unsigned char*)(image->imageData + image->widthStep*n))[m]; -} - -void PixelUtils::PutPixel(IplImage *image,int p,int q,unsigned char *pixelcourant) -{ - for(int r = 0; r < 3; r++) - ((unsigned char*)(image->imageData + image->widthStep*q))[p*3 + r] = pixelcourant[r]; -} - -void PixelUtils::PutGrayPixel(IplImage *image, int p, int q, unsigned char pixelcourant) -{ - ((unsigned char*)(image->imageData + image->widthStep*q))[p] = pixelcourant; -} - -void PixelUtils::GetPixel(IplImage *image, int m, int n, float *pixelcourant) -{ - for(int k = 0; k < 3; k++) - pixelcourant[k] = ((float*)(image->imageData + image->widthStep*n))[m*3 + k]; -} - -void PixelUtils::GetGrayPixel(IplImage *image, int m, int n, float *pixelcourant) -{ - *pixelcourant = ((float*)(image->imageData + image->widthStep*n))[m]; -} - -void PixelUtils::PutPixel(IplImage *image, int p, int q, float *pixelcourant) -{ - for(int r = 0; r < 3; r++) - ((float*)(image->imageData + image->widthStep*q))[p*3 + r] = pixelcourant[r]; -} - -void PixelUtils::PutGrayPixel(IplImage *image,int p,int q,float pixelcourant) -{ - ((float*)(image->imageData + image->widthStep*q))[p] = pixelcourant; -} - -void PixelUtils::getNeighberhoodGrayPixel(IplImage* InputImage, int x, int y, float* neighberPixel) -{ - int i,j,k; - - float* pixelCourant = (float*) malloc(1*(sizeof(float))); - - //le calcul de voisinage pour les 4 coins; - /* 1.*/ - if(x==0 && y==0) - { - k = 0; - for(i = x; i < x+2; i++) - for(j = y; j < y+2; j++) - { - GetGrayPixel(InputImage,i,j,pixelCourant); - *(neighberPixel+k) = *pixelCourant; - k++; - } - } - - /* 2.*/ - if(x==0 && y==InputImage->width) - { - k = 0; - for(i = x; i < x+2; i++) - for(j = y-1; j < y+1; j++) - { - GetGrayPixel(InputImage,i,j,pixelCourant); - *(neighberPixel+k) = *pixelCourant; - k++; - } - } - - /* 3.*/ - if(x==InputImage->height && y==0) - { - k = 0; - for(i = x-1; i < x+1; i++) - for(j = y; j < y+2; j++) - { - GetGrayPixel(InputImage,i,j,pixelCourant); - *(neighberPixel+k) = *pixelCourant; - k++; - } - } - - /* 4.*/ - if(x==InputImage->height && y==InputImage->width) - { - k = 0; - for(i = x-1; i width)) - { - k = 0; - for(i = x+1; i >= x; i--) - for(j = y-1; j < y+2; j++) - { - GetGrayPixel(InputImage,i,j,pixelCourant); - *(neighberPixel+k) = *pixelCourant; - k++; - } - } - - // Voisinage de la dernière colonne : C(w) - if((x!=0 && x!=InputImage->height) && y==InputImage->width) - { - k = 0; - for(i = x+1; i > x-2; i--) - for(j = y-1; j < y+1; j++) - { - GetGrayPixel(InputImage,i,j,pixelCourant); - *(neighberPixel+k) = *pixelCourant; - k++; - } - } - - // Voisinage de la dernière ligne : L(h) - if(x==InputImage->height && (y!=0 && y!=InputImage->width)) - { - k = 0; - for(i = x; i > x-2; i--) - for(j = y-1; j < y+2; j++) - { - GetGrayPixel(InputImage,i,j,pixelCourant); - *(neighberPixel+k) = *pixelCourant; - k++; - } - } - - // Voisinage de la premiere colonne : C(0) - if((x!=0 && x!=InputImage->height) && y==0) - { - k = 0; - for(i = x-1; i < x+2; i++) - for(j = y; j < y+2; j++) - { - GetGrayPixel(InputImage,i,j,pixelCourant); - *(neighberPixel+k) = *pixelCourant; - k++; - } - } - - //le calcul du voisinage pour le reste des elementes d'image - if((x!=0 && x!=InputImage->height)&&(y!=0 && y!=InputImage->width)) - { - k = 0; - for(i = x+1;i > x-2; i--) - for(j = y-1; j < y+2; j++) - { - GetGrayPixel(InputImage,i,j,pixelCourant); - *(neighberPixel+k) = *pixelCourant; - k++; - } - } - - free(pixelCourant); -} - -void PixelUtils::ForegroundMinimum(IplImage *Foreground, float *Minimum, int n) -{ - int i,j,k; - float *pixelcourant; - - pixelcourant = (float *) malloc(n*sizeof(float)); - - for(k = 0; k < n; k++) - *(Minimum + k) = 255; - - for(i = 0; i < Foreground->width; i++) - for(j = 0; j < Foreground->height; j++) - { - if(n == 3) - { - GetPixel(Foreground,i,j,pixelcourant); - - for(k = 0; k < n; k++) - if(*(pixelcourant + k) < *(Minimum + k)) - *(Minimum + k) = *(pixelcourant + k); - } - - if(n==1) - { - GetGrayPixel(Foreground,i,j,pixelcourant); - - if(*pixelcourant < *Minimum) - *Minimum = *pixelcourant; - } - } - - free(pixelcourant); -} - -void PixelUtils::ForegroundMaximum(IplImage *Foreground, float *Maximum, int n) -{ - int i,j,k; - float *pixelcourant; - - pixelcourant = (float *) malloc(n*sizeof(float)); - - for(k = 0; k < n; k++) - *(Maximum + k) = 0; - - for(i = 0; i < Foreground->width; i++) - for(j = 0; j < Foreground->height; j++) - { - if(n == 3) - { - GetPixel(Foreground,i,j,pixelcourant); - - for(k = 0; k < n; k++) - if(*(pixelcourant + k) > *(Maximum + k)) - *(Maximum + k) = *(pixelcourant + k); - } - - if(n == 1) - { - GetGrayPixel(Foreground,i,j,pixelcourant); - - if(*pixelcourant > *Maximum) - *Maximum = *pixelcourant; - } - } - - free(pixelcourant); -} - -void PixelUtils::ComplementaryAlphaImageCreation(IplImage *AlphaImage, IplImage *ComplementaryAlphaImage, int n) -{ - int i,j,k; - float *pixelcourant, *pixelcourant1; - - pixelcourant = (float *) malloc(n * sizeof(float)); - pixelcourant1 = (float *) malloc(n * sizeof(float)); - - for(i = 0; i < AlphaImage->width; i++) - for(j = 0; j < AlphaImage->height; j++) - { - if(n == 1) - { - GetGrayPixel(AlphaImage,i,j,pixelcourant); - *pixelcourant1 = 1 - *(pixelcourant); - PutGrayPixel(ComplementaryAlphaImage,i,j,*pixelcourant1); - } - - if(n == 3) - { - GetPixel(AlphaImage,i,j,pixelcourant); - for(k = 0; k < 3; k++) - { - *pixelcourant1 = 1.0 - *(pixelcourant); - *(pixelcourant1+1) = 1.0 - *(pixelcourant+1); - *(pixelcourant1+2) = 1.0 - *(pixelcourant+2); - } - PutPixel(ComplementaryAlphaImage,i,j,pixelcourant1); - } - } - - free(pixelcourant); - free(pixelcourant1); -} diff --git a/package_bgs/tb/T2FMRF_UM.h b/package_bgs/tb/T2FMRF_UM.h deleted file mode 100644 index fd0da7b..0000000 --- a/package_bgs/tb/T2FMRF_UM.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#include -#include - - -#include "../IBGS.h" -#include "MRF.h" - -using namespace Algorithms::BackgroundSubtraction; - -class T2FMRF_UM : public IBGS -{ -private: - bool firstTime; - long frameNumber; - IplImage *frame; - RgbImage frame_data; - - IplImage *old_labeling; - IplImage *old; - - T2FMRFParams params; - T2FMRF bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - double threshold; - double alpha; - float km; - float kv; - int gaussians; - bool showOutput; - - MRF_TC mrf; - GMM *gmm; - HMM *hmm; - -public: - T2FMRF_UM(); - ~T2FMRF_UM(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; diff --git a/package_bgs/tb/T2FMRF_UV.h b/package_bgs/tb/T2FMRF_UV.h deleted file mode 100644 index 28fcb67..0000000 --- a/package_bgs/tb/T2FMRF_UV.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -This file is part of BGSLibrary. - -BGSLibrary is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -BGSLibrary is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with BGSLibrary. If not, see . -*/ -#pragma once - -#include -#include - - -#include "../IBGS.h" -#include "MRF.h" - -using namespace Algorithms::BackgroundSubtraction; - -class T2FMRF_UV : public IBGS -{ -private: - bool firstTime; - long frameNumber; - IplImage *frame; - RgbImage frame_data; - - IplImage *old_labeling; - IplImage *old; - - T2FMRFParams params; - T2FMRF bgs; - BwImage lowThresholdMask; - BwImage highThresholdMask; - - double threshold; - double alpha; - float km; - float kv; - int gaussians; - bool showOutput; - - MRF_TC mrf; - GMM *gmm; - HMM *hmm; - -public: - T2FMRF_UV(); - ~T2FMRF_UV(); - - void process(const cv::Mat &img_input, cv::Mat &img_output, cv::Mat &img_bgmodel); - -private: - void saveConfig(); - void loadConfig(); -}; diff --git a/vs2010/.gitignore b/vs2010/.gitignore deleted file mode 100644 index affde92..0000000 --- a/vs2010/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Ignore everything in this directory -* -# Except these files -!.gitignore -!README.txt -!bgslibrary.sln -!bgslibrary.suo -!bgslibrary.vcxproj -!bgslibrary.vcxproj.filters -!bgslibrary.vcxproj.user -!bgslibrary_demo.vcxproj -!bgslibrary_demo.vcxproj.filters -!bgslibrary_demo.vcxproj.user -!bgslibrary_demo2.vcxproj -!bgslibrary_demo2.vcxproj.filters -!bgslibrary_demo2.vcxproj.user diff --git a/vs2010/README.txt b/vs2010/README.txt deleted file mode 100644 index 7b0a5cf..0000000 --- a/vs2010/README.txt +++ /dev/null @@ -1,15 +0,0 @@ -VISUAL STUDIO 2010 TEMPLATE PROJECT ------------------------------------ -Change to [Release][Win32] - -Tested with: - VISUAL STUDIO 2010 - VISUAL STUDIO 2012 - VISUAL STUDIO 2013 - VISUAL STUDIO 2015 - -You need to install OpenCV at: - C:\OpenCV2.4.10 -or change the project settings. - -Build and run! ;) \ No newline at end of file diff --git a/vs2010/bgslibrary.sln b/vs2010/bgslibrary.sln deleted file mode 100644 index ab59be3..0000000 --- a/vs2010/bgslibrary.sln +++ /dev/null @@ -1,27 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bgslibrary", "bgslibrary.vcxproj", "{3B6BF763-9CDE-4859-ADD9-8EB7B282659F}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bgslibrary_demo", "bgslibrary_demo.vcxproj", "{EBE7FE0E-9FE6-41D2-B744-D43E7446D50C}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bgslibrary_demo2", "bgslibrary_demo2.vcxproj", "{EAA15CCC-270A-460B-A61C-2DB864D67718}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {3B6BF763-9CDE-4859-ADD9-8EB7B282659F}.Release|Win32.ActiveCfg = Release|Win32 - {3B6BF763-9CDE-4859-ADD9-8EB7B282659F}.Release|Win32.Build.0 = Release|Win32 - {EBE7FE0E-9FE6-41D2-B744-D43E7446D50C}.Release|Win32.ActiveCfg = Release|Win32 - {EBE7FE0E-9FE6-41D2-B744-D43E7446D50C}.Release|Win32.Build.0 = Release|Win32 - {EAA15CCC-270A-460B-A61C-2DB864D67718}.Release|Win32.ActiveCfg = Release|Win32 - {EAA15CCC-270A-460B-A61C-2DB864D67718}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/vs2010/bgslibrary.suo b/vs2010/bgslibrary.suo deleted file mode 100644 index 3dc8e43797f0d1ee8f60b5d84b6328c180a83479..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11776 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 - - - - Release - Win32 - - - - {3B6BF763-9CDE-4859-ADD9-8EB7B282659F} - Win32Proj - bgslibrary - - - - Application - false - true - Unicode - - - - - - - - - - false - ..\build - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - C:\OpenCV2.4.10\build\include;C:\OpenCV2.4.10\build\include\opencv;%(AdditionalIncludeDirectories) - MultiThreaded - - - Console - true - true - true - C:\OpenCV2.4.10\build\x86\vc10\staticlib\*.lib;comctl32.lib;VFW32.lib;%(AdditionalDependencies) - - - - - true - - - true - - - false - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - false - - - false - - - false - - - - - false - - - false - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - false - - - false - - - false - - - - - - - - - - - - \ No newline at end of file diff --git a/vs2010/bgslibrary.vcxproj.filters b/vs2010/bgslibrary.vcxproj.filters deleted file mode 100644 index ab0fbf0..0000000 --- a/vs2010/bgslibrary.vcxproj.filters +++ /dev/null @@ -1,644 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {8cb396e6-81b6-4db9-a1b0-5c2b7c122bd9} - - - {e1ab6d45-3486-42fa-8f51-69a300c0c173} - - - {7992fa8c-e616-4e72-b249-6ede4f4291b4} - - - {667f4048-d125-4453-9f0c-42f9abd4ed3a} - - - {89c4b817-936b-483c-abed-3e7e7c1fc427} - - - {c5e0f44c-6120-4906-917d-c8c8af3eafec} - - - {728fbe82-1489-4878-89ea-a62ba0932204} - - - {6b017402-c47a-49a4-8f57-b5db863e1bde} - - - {e25c1e03-530d-4c7a-b776-26bf17595213} - - - {53f2c4fb-9468-44ce-b76e-e25ea018c084} - - - {23f1cd4a-e9b2-4338-a5e7-128f451d3c89} - - - {52a9f254-d817-4577-96c2-0b3b0a9527b7} - - - {0494c5d4-b4bb-421c-b032-176903ba8e1b} - - - {87961eee-b843-45bd-b642-9dcd9d78b661} - - - {cd33a41f-6151-46a5-95b6-b79022786144} - - - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\db - - - Header Files\package_bgs\db - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\sjn - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files\demo - - - Header Files\package_analysis - - - Source Files\demo - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\db - - - Header Files\package_bgs\db - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\sjn - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Header Files\package_analysis - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - \ No newline at end of file diff --git a/vs2010/bgslibrary.vcxproj.user b/vs2010/bgslibrary.vcxproj.user deleted file mode 100644 index ebebb3b..0000000 --- a/vs2010/bgslibrary.vcxproj.user +++ /dev/null @@ -1,8 +0,0 @@ - - - - ../ - WindowsLocalDebugger - -uf -fn=dataset/video.avi - - \ No newline at end of file diff --git a/vs2010/bgslibrary_demo.vcxproj b/vs2010/bgslibrary_demo.vcxproj deleted file mode 100644 index 966d57b..0000000 --- a/vs2010/bgslibrary_demo.vcxproj +++ /dev/null @@ -1,241 +0,0 @@ - - - - - Release - Win32 - - - - {EBE7FE0E-9FE6-41D2-B744-D43E7446D50C} - Win32Proj - bgslibrary - - - - Application - false - true - Unicode - - - - - - - - - - false - ..\build - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - C:\OpenCV2.4.10\build\include;C:\OpenCV2.4.10\build\include\opencv;%(AdditionalIncludeDirectories) - MultiThreaded - - - Console - true - true - true - C:\OpenCV2.4.10\build\x86\vc10\staticlib\*.lib;comctl32.lib;VFW32.lib;%(AdditionalDependencies) - - - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vs2010/bgslibrary_demo.vcxproj.filters b/vs2010/bgslibrary_demo.vcxproj.filters deleted file mode 100644 index d9f3aa8..0000000 --- a/vs2010/bgslibrary_demo.vcxproj.filters +++ /dev/null @@ -1,596 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {8cb396e6-81b6-4db9-a1b0-5c2b7c122bd9} - - - {e1ab6d45-3486-42fa-8f51-69a300c0c173} - - - {7992fa8c-e616-4e72-b249-6ede4f4291b4} - - - {667f4048-d125-4453-9f0c-42f9abd4ed3a} - - - {89c4b817-936b-483c-abed-3e7e7c1fc427} - - - {c5e0f44c-6120-4906-917d-c8c8af3eafec} - - - {728fbe82-1489-4878-89ea-a62ba0932204} - - - {6b017402-c47a-49a4-8f57-b5db863e1bde} - - - {e25c1e03-530d-4c7a-b776-26bf17595213} - - - {53f2c4fb-9468-44ce-b76e-e25ea018c084} - - - {0494c5d4-b4bb-421c-b032-176903ba8e1b} - - - {87961eee-b843-45bd-b642-9dcd9d78b661} - - - {cd33a41f-6151-46a5-95b6-b79022786144} - - - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\db - - - Header Files\package_bgs\db - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\sjn - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Source Files - - - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\db - - - Header Files\package_bgs\db - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\sjn - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - \ No newline at end of file diff --git a/vs2010/bgslibrary_demo.vcxproj.user b/vs2010/bgslibrary_demo.vcxproj.user deleted file mode 100644 index abe8dd8..0000000 --- a/vs2010/bgslibrary_demo.vcxproj.user +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/vs2010/bgslibrary_demo2.vcxproj b/vs2010/bgslibrary_demo2.vcxproj deleted file mode 100644 index ad0f4c4..0000000 --- a/vs2010/bgslibrary_demo2.vcxproj +++ /dev/null @@ -1,241 +0,0 @@ - - - - - Release - Win32 - - - - {EAA15CCC-270A-460B-A61C-2DB864D67718} - Win32Proj - bgslibrary - - - - Application - false - true - Unicode - - - - - - - - - - false - ..\build - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - C:\OpenCV2.4.10\build\include;C:\OpenCV2.4.10\build\include\opencv;%(AdditionalIncludeDirectories) - MultiThreaded - - - Console - true - true - true - C:\OpenCV2.4.10\build\x86\vc10\staticlib\*.lib;comctl32.lib;VFW32.lib;%(AdditionalDependencies) - - - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vs2010/bgslibrary_demo2.vcxproj.filters b/vs2010/bgslibrary_demo2.vcxproj.filters deleted file mode 100644 index 45de1b6..0000000 --- a/vs2010/bgslibrary_demo2.vcxproj.filters +++ /dev/null @@ -1,596 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {8cb396e6-81b6-4db9-a1b0-5c2b7c122bd9} - - - {e1ab6d45-3486-42fa-8f51-69a300c0c173} - - - {7992fa8c-e616-4e72-b249-6ede4f4291b4} - - - {667f4048-d125-4453-9f0c-42f9abd4ed3a} - - - {89c4b817-936b-483c-abed-3e7e7c1fc427} - - - {c5e0f44c-6120-4906-917d-c8c8af3eafec} - - - {728fbe82-1489-4878-89ea-a62ba0932204} - - - {6b017402-c47a-49a4-8f57-b5db863e1bde} - - - {e25c1e03-530d-4c7a-b776-26bf17595213} - - - {53f2c4fb-9468-44ce-b76e-e25ea018c084} - - - {0494c5d4-b4bb-421c-b032-176903ba8e1b} - - - {87961eee-b843-45bd-b642-9dcd9d78b661} - - - {cd33a41f-6151-46a5-95b6-b79022786144} - - - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\db - - - Header Files\package_bgs\db - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\sjn - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Source Files - - - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\db - - - Header Files\package_bgs\db - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\sjn - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - \ No newline at end of file diff --git a/vs2010/bgslibrary_demo2.vcxproj.user b/vs2010/bgslibrary_demo2.vcxproj.user deleted file mode 100644 index abe8dd8..0000000 --- a/vs2010/bgslibrary_demo2.vcxproj.user +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/vs2010mfc/outputs/background/KEEP_THIS_FOLDER b/vs2010mfc/outputs/background/KEEP_THIS_FOLDER deleted file mode 100644 index e69de29..0000000 diff --git a/vs2010mfc/outputs/foreground/KEEP_THIS_FOLDER b/vs2010mfc/outputs/foreground/KEEP_THIS_FOLDER deleted file mode 100644 index e69de29..0000000 diff --git a/vs2010mfc/outputs/input/KEEP_THIS_FOLDER b/vs2010mfc/outputs/input/KEEP_THIS_FOLDER deleted file mode 100644 index e69de29..0000000 diff --git a/vs2010mfc/src/ReadMe.txt b/vs2010mfc/src/ReadMe.txt deleted file mode 100644 index 6c7fde0..0000000 --- a/vs2010mfc/src/ReadMe.txt +++ /dev/null @@ -1,100 +0,0 @@ -================================================================================ - MICROSOFT FOUNDATION CLASS LIBRARY : bgslibrary_vs2010_mfc Project Overview -=============================================================================== - -The application wizard has created this bgslibrary_vs2010_mfc application for -you. This application not only demonstrates the basics of using the Microsoft -Foundation Classes but is also a starting point for writing your application. - -This file contains a summary of what you will find in each of the files that -make up your bgslibrary_vs2010_mfc application. - -bgslibrary_vs2010_mfc.vcxproj - This is the main project file for VC++ projects generated using an application wizard. - It contains information about the version of Visual C++ that generated the file, and - information about the platforms, configurations, and project features selected with the - application wizard. - -bgslibrary_vs2010_mfc.vcxproj.filters - This is the filters file for VC++ projects generated using an Application Wizard. - It contains information about the association between the files in your project - and the filters. This association is used in the IDE to show grouping of files with - similar extensions under a specific node (for e.g. ".cpp" files are associated with the - "Source Files" filter). - -bgslibrary_vs2010_mfc.h - This is the main header file for the application. It includes other - project specific headers (including Resource.h) and declares the - CApp application class. - -bgslibrary_vs2010_mfc.cpp - This is the main application source file that contains the application - class CApp. - -bgslibrary_vs2010_mfc.rc - This is a listing of all of the Microsoft Windows resources that the - program uses. It includes the icons, bitmaps, and cursors that are stored - in the RES subdirectory. This file can be directly edited in Microsoft - Visual C++. Your project resources are in 1033. - -res\bgslibrary_vs2010_mfc.ico - This is an icon file, which is used as the application's icon. This - icon is included by the main resource file bgslibrary_vs2010_mfc.rc. - -res\bgslibrary_vs2010_mfc.rc2 - This file contains resources that are not edited by Microsoft - Visual C++. You should place all resources not editable by - the resource editor in this file. - - -///////////////////////////////////////////////////////////////////////////// - -The application wizard creates one dialog class: - -Dlg.h, Dlg.cpp - the dialog - These files contain your CDlg class. This class defines - the behavior of your application's main dialog. The dialog's template is - in bgslibrary_vs2010_mfc.rc, which can be edited in Microsoft Visual C++. - - -///////////////////////////////////////////////////////////////////////////// - -Other Features: - -ActiveX Controls - The application includes support to use ActiveX controls. - -///////////////////////////////////////////////////////////////////////////// - -Other standard files: - -StdAfx.h, StdAfx.cpp - These files are used to build a precompiled header (PCH) file - named bgslibrary_vs2010_mfc.pch and a precompiled types file named StdAfx.obj. - -Resource.h - This is the standard header file, which defines new resource IDs. - Microsoft Visual C++ reads and updates this file. - -bgslibrary_vs2010_mfc.manifest - Application manifest files are used by Windows XP to describe an applications - dependency on specific versions of Side-by-Side assemblies. The loader uses this - information to load the appropriate assembly from the assembly cache or private - from the application. The Application manifest maybe included for redistribution - as an external .manifest file that is installed in the same folder as the application - executable or it may be included in the executable in the form of a resource. -///////////////////////////////////////////////////////////////////////////// - -Other notes: - -The application wizard uses "TODO:" to indicate parts of the source code you -should add to or customize. - -If your application uses MFC in a shared DLL, you will need -to redistribute the MFC DLLs. If your application is in a language -other than the operating system's locale, you will also have to -redistribute the corresponding localized resources MFC100XXX.DLL. -For more information on both of these topics, please see the section on -redistributing Visual C++ applications in MSDN documentation. - -///////////////////////////////////////////////////////////////////////////// diff --git a/vs2010mfc/src/bgslibrary_vs2010_mfc.rc b/vs2010mfc/src/bgslibrary_vs2010_mfc.rc deleted file mode 100644 index 7251658acf1cfb47e9ec6c5f95ae0d54e0237ed3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17930 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 D&}ucd diff --git a/vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj b/vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj deleted file mode 100644 index 1ed007d..0000000 --- a/vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj +++ /dev/null @@ -1,296 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {236E77EE-00D6-4B4E-80C7-C38847B1B60E} - bgslibrary_vs2010_mfc - MFCProj - - - - Application - true - Unicode - Static - - - Application - false - true - Unicode - Static - - - - - - - - - - - - - true - - - ../ - mfc_bgslibrary - - - - Use - Level3 - Disabled - WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions) - - - Windows - true - - - false - true - _DEBUG;%(PreprocessorDefinitions) - - - 0x0409 - _DEBUG;%(PreprocessorDefinitions) - $(IntDir);%(AdditionalIncludeDirectories) - - - - - Level3 - NotUsing - MaxSpeed - true - true - WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) - C:\OpenCV2.4.10\build\include;C:\OpenCV2.4.10\build\include\opencv;C:\boost_1_55_0;%(AdditionalIncludeDirectories) - MultiThreaded - - - Windows - true - true - true - C:\OpenCV2.4.10\build\x86\vc10\staticlib\*.lib;C:\boost_1_55_0\stage32\lib\vc10\*.lib;comctl32.lib;VFW32.lib;uafxcw.lib;LIBCMT.lib;%(AdditionalDependencies) - NotSet - uafxcw.lib;LIBCMT.lib - - - false - true - NDEBUG;%(PreprocessorDefinitions) - - - 0x0409 - NDEBUG;%(PreprocessorDefinitions) - $(IntDir);%(AdditionalIncludeDirectories) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - Create - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj.filters b/vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj.filters deleted file mode 100644 index 28d979d..0000000 --- a/vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj.filters +++ /dev/null @@ -1,581 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {be6b45b0-e96c-4347-a65e-a72506b4195f} - - - {c4756493-26d9-46f4-93d6-024b4d1ca61a} - - - {a3dff805-136a-4fc5-a8e9-a7eadebcd6f2} - - - {d9c40f02-d18f-46bb-a956-522e83a8a2e7} - - - {77576fcd-de50-4205-8072-cb25a1aab145} - - - {50f16e47-ef1d-46b0-a0cb-f7c07599cd21} - - - {704bbcb4-9bbe-4fe1-8a80-78ee8d7f49c5} - - - {d365878b-8639-4bfd-8008-adb158c9cd8b} - - - {f7961eef-2755-4712-a9b7-1b840b7936b1} - - - {e23418b4-562b-41ae-bd15-e9ad45ece1d1} - - - {2a0f8129-33e2-4829-a112-edba4c8f5ef6} - - - - - - Resource Files - - - Resource Files - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\sjn - - - Header Files\package_bgs\db - - - Header Files\package_bgs\db - - - Header Files\package_bgs - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - - - Source Files - - - Source Files - - - Source Files - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\sjn - - - Header Files\package_bgs\db - - - Header Files\package_bgs\db - - - Header Files\package_bgs - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - - - Resource Files - - - \ No newline at end of file diff --git a/vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj.user b/vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj.user deleted file mode 100644 index 882b48c..0000000 --- a/vs2010mfc/src/bgslibrary_vs2010_mfc.vcxproj.user +++ /dev/null @@ -1,7 +0,0 @@ - - - - ../ - WindowsLocalDebugger - - \ No newline at end of file diff --git a/vs2010mfc/src/res/bgslibrary_vs2010_mfc.ico b/vs2010mfc/src/res/bgslibrary_vs2010_mfc.ico deleted file mode 100644 index d56fbcdfdf6eac0f4727c34770c26689271d96af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67777 zcmeFYc|4U}`!{@SGetCs%!DQx%1~mPDN~e)khut%$88(;HqRkMBs9@rPAH+2l%z67 zGG~a|g#E0oy6)?`?(6>jp5NzvKkxhe@vJ)cI@em?<5=T)Oy>yz1I!>U&WdsxaG(Qt z3jln4Kg*+301ET~+qV5GV*rGi09aXnmPJvSWj!EzK=cey!~!7AOrif(W&r5hPNDx< zZbAGbgedes%MP>vy1P+_C}DsPT9X0F5wD;8_@HP9Kyc%4eyEJ-c8>q%#|K14fLBYu z`SF1R4S)#^21F4*#0D(a_@YXb@ISM)`@jC+Q6s9s<9}x87u})@5B2r+sDZrpAIN0N z9lFpzN)_ej?};A&_Zmu>-cH@_-|Ok^oNR0Y?ElulU~d=T<<(@TZuyr6db@xCdnX%@ zfB+lUKR3|DjEp$h+d0|U8L9tjpqG?EyW(x{L?p%7+apd`3IV;8prxP)(2q>U$HzxF zdD*Ksp?Z25DQjyfYXfomW?VceF2c(xz|P1LWKgw?Mm$)yXh9<>ZA@GZx{=QSKD{4pGE@ zWHQ2Oq{-gS61*$oL){BSWJDx+h{W#^5jX^IYSM>_LUJh4T}DuHt882lIe-`w5Hn(L zR}X{=5;;E9T1HyZdz6S4p6Kiq;N)cq(V=8A$z4W7%372-5>Fn8s-cDZ42p>fzR!I5D$Ot+RM~R~&gg6{w1dWGSK_(ChGBWw@vZI7i zR2x9RnFqK6k$^gj_H%a^LHq-70WopTE#57NPc#}sl)t;XyR2v8$p9SA+k0dbN5K<) zD*U}=3MGzlaL8@-@@{K!3W#upXks)W+DBF<86^&138NzkbR7km(zm>y3^t3oY*;tvdU z^fWY{KgLh#p1~(FI+EZU85!y8>q@Ekl^BSX1Y&x6dIeghzwoa_wUw1s-iY9Dcw!~u z66yc9eu=0$+B-cngW!w*%^c-uQ3;j4kyVkAY2JV4Pgzv&%5+~}@65D6|CMj})jt|PC2jbX^P(J&%9MQISB^%%R$l&< zQ=pV~^1%PgIqGPQFY0J5Cj*8|>4#j;`!oNDRvy;$3?CW;7F`tmxt5U5{O5%KIUziL zZC;dLbo)QmGk?`E41duq3|@B4PJhxU5yQj?buV=+WjOSbt`OlvBsh6FAzei8F6rjx z8bgW;K-xwfkc@U0bmxmBQ3SW05y<9;ib%Rj`;hEEH%|uGfxjX_%GzDfhd9z~=Vk8% zGDTnAq}<$aWa9Uj7_TOX4kCSbbC>oBB1epj*m(g_mK2JFEYc@25iv3LNdA5GlgW1@ z5=Y~Rao$cqEFqEHL#2I22_v}XIB(QW3Pr-yJ5?hfB4Q*Ch@qjx3K@w+jsx~~IP;UJ z9TkzB;_%&K-)uh$@x)4hByN0?lM^@lpvjRcqY1L<1R{Fk*(j6UK&QcGndy=LYOkzJFY`^0{EcTIrdJ{1mHxX747C&~ z@kbFLvT9A}qZ%@KHHb_eP(+XQ2J$k{uW|wP01Z&1rvJ&MNMVKl`M6Ld14T;wJkI}= z<5AlHHJLo4NT%3R+t4MBuHWaJ57~bIobc#SqZV|y*D~~rZvRK;{I@7V2i3;L9`T~2 z2s;< z3<(W~0d!xHM$BPPq#O{2O84*aL1{nLW^^4IXLpR>wK!~vqb(0byHhI~&&520IdBu2#l4NA!zeU)Ge)kp`@g}wr ziJ{)!an8i(Xz%2V&E5_qV!BUSB3e}>Kz=@n@j2;hPhg#FdLo58!6$m{2_&M233Y>Z zE)6|m{Rm13C7RkbPbXGF=rQ*FhNa zoOZ#r3wt5gNgN8?_Cv9!1Qhy8!%e^aQ0O6x%5qTXCl5FM4?>QM8r%+4hMl3Na3Iti z_C#BQbd(*4B{+a&iX%v0as!!sUpP*11l0^Luu1g?m4YBB2|fsSLk~e2Q4wwhD?@3B zGL%OtL1nBm+zr=)vZ$lzUK<`Hs=_4G zRZBs`?HagTxB=W^4Zth@2zE%fz#fHm*n@ouvYOA}z=`J|ru-TXXubtG{XS5z?1#ff z1Ax_g1}ev1fX2x#IA+lWdJcVHXw?lmu03Gn@d3`b^n;B@4=9@rg1X%>7@ZviZMRW4 z;XMkrKErSUHv)1uL*VdT0%+a{hhsO1png9FG#?~`QB@i^6~%%>NfJ0zW`Ji^HW+8! z23*w@IQb|GOq;KO?bB=E`urxGYrhRHuS&u9O&RzQ-huzc9yk>`0d|pN;2kv%4vCZC znf?VF(`Udldj|ZjeTA&VR0u80ftcz7NO*W1it>t~xsU*D_v7GgO(?WiCO}%#JqUgO z0P-8}LuqR*^gIrSw~Z`v}d5zPGaxnm%+u zM|T$tysU?huO7i*UpIUl=z~YCW6;zw0Xy@xM=}9{l&1-w3mvyj+|+f5l6FSN@0moUEvj06#A;*KU6P zlm7wpyV_CzfVjB0s1SvKOC0?O{BHTDT=$5U)&VJm7u_Mm&(AACq1t}%pO8Q640Lt0 zG${mPs0V(2Ng+WrtnEDJf2C00KtsEAt;)baR|^r?J#B4$ih7U~rS!5L^GlpkAi0Ox z*jQ+3X*biVqLKw7IAS_6i&EVwix0=C0! zEX=etPEJm>D=Q8*0-wbt6yQuh$r2JN-26BOV__(FtV zT$)tLtIwnKph%eoU1Ot@!|Lj)!?Dv(C-meF7#)yATP-R`ITJUFu4ONww761FSyeR( zo}~#9%pd}kCJglt8Ed0Xe&cTz<*iWCw*oY0tm0t`K@%ddpQga4&lw%lw>qXRyGDTc zH#FS5wz~2yKTAbLNePQpqpT}S3x&YR>72u9TT@HpV^&7T4p9hp{KV(2u1;Oc$zI#H zH9T9(F9Iif+p}lwZSAd%C_U_^*s$FV4HSG{_2WmGXrnc-Sk%E!d}HH7CntNGGZvOM zj>blMhomGBzNedlr=+RuObs;+MJ$>@OH0d>3KLUP<1=S$&YZD8$@VlNkX^%f|HMxg zy*=IeQ;+NOi;Ax08x40~lYb#!$N3@mhXbd9Y}Q1FNV&EaNIaZ%p2tA0LSI9yLq$A#bk59BbqFw!FmJqHSwk_J%f>qP^)em#jm zAwUUtdurIjgGiYMA~?_S6Z!M^`&d7kLBq`(*9()A2`S-WZ)acOUe6+YWB`T0)z#(C zaDeZ9y=cBS*6{O-NimDF^Y~Yb!vO)2fxkNVD;`bZC;P&Z)WyY!_T}Zp#Mg^+2#@By z*1_NL=+ND~URZdotYm2^Vt#Rc>FukfIb1*#TG+KI|2rPK*YKq)D{(6;@hi)(R_1WH zD9XZ8I`{`XK+(0L4urqG`YCCC<=ZL+kG5j1gMY-M%8t_2)laK067HbGHy0EXy@p2} z{3rYygkN1PO-rEQ!-8VNesukNOoq@<)I zMMb4Wg$1u2zW>DkG3H~G1!kdmY{7?3Ta7seX%zu$Oe~u;<4TV4;{MXo@Y)aQgpOk#( zzu@|prkAM~+x}AV=K`{U{%?E|nStbi{}bYqzx-5W0{xLFM$a!p33r$tRQE~&r>XQG zKB};#0+=evLy(~;>=ZZ-3I|RDJ5M0+*kOU!L3zzj75L?&?(o(}eyRb8;!c9ByCEF% zGl896b|7)qXU#W_R9^=%+N{VY-2zF5=sLjxsmLeIIL!m;<^pTJXqKZGWIM`3?%ALI zX94m*ueeCTRhL6>)k6}By`BoF zD$WyBuLgkrl_0o{mqY&PVW^BzM7}B(`KX$3CrtGZAN5YSHrz)(YE_&bJV-tQN^qLKH+;>KPsRy)NLq00PJ<1P+=ePXf=L{L^OGtMCeB)S5tA z;|<6i?}o!CJ_1(vDX5%y0h%Y@fR0r!oUnNd76E-=iTqIO!1v%5)DOz0gP>_Y49Cw7 zt@)ycKBHhCi2TqScsN`d4jP$J;1!+)TE$^tcr6T0T@450Vj?J)$ACsnBAlqX2<~}N zU{{_2ZnZfOSd#~O4Hb^s9<)<}&HMO@9YTiDFy7z5R_q-S42AUvks1>qC+aZ6f1FDeEn%08+(-)Ia z)-()-t&4CAz2v_CZW*ePull(2IXwCB3i^9{;p3Zn80hbVHv{jX`{Oh`Ul@fa!z<7? zI15O9!KaZ)m>3^N{^=L^Iy(&$Ulw3`ZUz<>767ULH8JpC_^BqR{ty)Zc2g_C#mOPc z_iy!-ibp4R3-j@8=i=PM!zuXh2nTJAo%#>!M7DE@BY$?=zW`aAX||w}gNF8t>FMc{ z+&mJzJlnVZlutQWNwy8lwDPnn;H3F-q3xuu2=YCVze-_{nRrlozvL#i4J?P5Y3NL? zNndvE78Vxf*A?RD*^VsGjHH9I+57jiv29RbSx;^Dc)3nkLw&cfkghO4FApch?>pEt zElU9?ZeXE3{dlGE=+Ps4b_*K_Qy3tJ^U|T-1=;!uz2cXV{qoA z^%+BZJuMMoK7bE5u1>C8RK-e3U)(Rr_Tbsm78j@bdZ+VF=M1%WBS)vdxuADtX61>T zT)H%BZ+-ggQN4qWm6he0^QVsNMRs`qwaMk48pRB?beRKNQ+&PLnD%KOvpi<0bIifs z7*GtO`0~}fg2IAib{}s~4-a<_+&X#@2@M^Tj-lwl;K#v%?(XZQVL`~+5Abkz^9b>9 zV`LE6siC2*cOGD9XmGIm=Cxb+Ak^#^`pOfvd$_q$37-AAd2{1~{msP%xA1X6xBCdM zrae7EQ73K}{@B{Nfe$x}uHGViBYa+X|9ZhQAk5Fh-ThzfXbQzw^Y7dzmHk-$_G%fm zBU9A zw({-Us;c;ovapaq@4xr|X=HTdU3K}r_?VcOvhWbx-`l^Ab#+mi<6@$tqsyYg{@xCw zUDcK4@rXP+7^QzQE`O5;$8`J~^;*T-dvP%dl#hJ=l>X0pzx2~i!T$L@>YwbCBl`~m zm&HL8Lzf5f-R5hy2)Ba@h*)8P&*@(=^S$nRu)|9qcK8{>&cKr(;e+052AqbyR?ZM$ zZw{6x10h(65rUObyj*Pq5H*;AfMVwat&I??vk{_nwm^(NJH!}mUyGq99p8##=-VN~ zoCgw5@xet4en>VKL2-0ZNVDFtX1}D_?t*MvQOI@@gBa@raLMT)Tumpd(DQ~8)XFtVjVys&IuG#FM#AFS2%FR z8`ROeD$Tg_U>Ir-hVkxjG{pmSGX20h*%#EW1RxuQ5=&Qr61)oB4mk>ULX_YhQ5o4J zs&Fq{2kKHa;by2l6os5bwv92AM<0W7WUrLR8ADCtaTGg04iyO|DAsO@V(q7)_M$D+ zWm+S<#eL0Yxl!Z-B_&>Pw;Trzmvm5k-2h(Pv_p2vd1%VNfNT{nXu9RLX0N=s<$-Jz zZ+LYd2b;Es0|Q$cFl@~P7LKdHyuBFMxQo}~>TG-^uu0%Pu!~d!2a2zA?s@=xa;31- zFcl7IJ_OTKNx;3k9{432VfUeDps3RVl4_lB=x7JX>GXoE!!wXSJ^%{FgP>~g0#prO z!BMj}pmFv&V7;Ehu?sK2#PcPnTEBxMc70%A(+)cR?O=}UfwNw{plmS&sy3ex{xckP z9$B+#j=O#a3)f*d<~a(-1INME^Anu+8$|K;K~PI3fZi` zDF{fN0;dcVm(QI89Ey>r#^*rJjW{T;4Tr8;B6QZ}K-k-Ai0rI^50B!Yza&I+mckdl?=eyQOAu6`HzV z!(hjwHJjx_{}jCMn}zPtkI+9j3QcpLU~pgn#y_KLY!qfDhGAxE6z0CpqVJnX{~S*@ z{F4y-eQO}Jed|^!+26&V`8h4&eaB69a!B*A|87vHUk7kbOQMrRGUAfq+fIo-UzcNM zqNT!8DWBc@X7RKTpU6>u3V{N%Jl%RmS|vs*3!UW`+>(3u>51?myFjscj+vR6QI(0- zQuoK~u02BH$M;e6{k7ty;tkA9YRrshY|W8@wMYBZUNL@v(wnO@ta2>*a!gjQtdAN< zNu0FP-X{WZ?*_S0g-Nd9Aam>6j@Rwa&pVyB*OmZiyG34EU^#sCkleGUfg4X4+gR(L zb1*~7@^*2a#-S^0{(in5E&(*V51=@qt|`*f{cRsVCWVCr;oRdc1baARL?txs(X_sN z|NQBGctXhg-sm}ZBBI&!YY{&6m)(h3O8&MKyW|n+>*4u(ZQ%3WlFHSZ)vu$gK2brw z|EdRgS&nL}S66*gf^dIPa1~_ohe$FRpNkLr;m>%0iRQWo>1h!WnO%Ra z2WY(Xu=`JS|KGkp|K)E}bl2}hx&Ku@E+-0`kRID;DF@rF6k)rq64GT#z;RX;xIOiN zGe94Na7G~Ic^uBWt3&irR-}hE0YwjGm{Z>6^1x-g9gyj;8?w&HBON0I`7WYR=!Se} z*S&Dva~I^DmxTiN15n^93;D=rzUg}i`Ok`Q>8vu+L#l8arwYY>=p8e9mns}<34Spq zpb~BiD$&R{j&g*Pg!7=6a28BrFMv_17o5%tf_q_yke<;+KCm8CCmey&FhjUcG)DIp zfX}dn+Dm7UziSIo#jfxuLkk*nPQvROCh#Q35?Tt~;Mom-=tDm1Y_hKY z4=C5e0o7KJQ*D96TCYLQ1nGiPgP>>j7}QVofWCPr=$(H8Cv7^wz_uUE&b|XH*IrOJ z{Rm1{!=T~t8T7oy;3SgsM-zfU^LiMVT?qrzvScu;O@}iDL@;}i4bDy1z^$zWeBYLX zfA?K5Pn`v;*eUP}{|vs-6M%~u2k+QD11_htmpmh8l+@0!%s7EVM`)C>pn@LdfW)W(T+u(G@ei%aMm=hgqM z_m{s<_W#ZQH&@{A{Pa|w(YXKZ2X}+$O-El8Kkk~d@h{CCc$|7YYsd23dt$SAC?Wn^Pmt| zR&L^MyCd4Z0S6Br!Ud){pARuJ(LAB5qm1F$CrQY=lHz(S&h(urJm;Ra%{m#b0c>H-QVTZ3vMxyQI^MlmoH_cx!G8G5Tk!rqZ))=cX`7+VUDr41&DHR<~Cg6pQ9yd#%2nlAIQ z`8UV4$dB~wI-SsXm_NHZF(WZCF=ICHNB1ECso)Eog}?rP@O>)Z#k{TXRzY$_?Sj$f z*zRK&qgC(6+%>y~b6Fg4L9ZHLdUiLBg=aR=%*g8%;>ONvv}|pTz0(!S;7Vf2$1`W88H3_gp#POu)sN6(7=6dWaFk3mElQkQ5HPz?n{AuB}HN?=DfvU*M_dqGsc>>=MEz(kOGtQUzB~yIy7`I&g&9 zR=O?D%*Mwtp2-r5WgPHLxx891eY$>fa7z4}vr3k$n!vxpBBZccRlLSpp zzCHO@H}Rbkc56)|uV#>9m52I9JR@E&=Df9!>13Tt!}FH*laV*GI9j6tY*f6uo@=#2GHJnb4o>iAMq#O2F%+h8ru1uTxt`3k>iI7v6Csw1Sj@(yWFnFA!a%IOgC);YHo?5ZG+5A zGaQUz5gKl?7|VYR3iA99G@SSIN}aqH9GTMU$nB<7Sji`Z{}?`tY+aRQO@Rp!quz{131cPD*1v zE)OP7N!5CAmCMqx^i&xvE~jjSs_VN4vgotoHt6eazDk8VW5Y*XoudJ2RBbXzke2dw z_YQ@GDh50&w&x33foWKhYcV-Kp7EpaC3G@8b?t+uK8IV44V)LfR6#8q_u^~iLoW;w zpOo?>t}#Jlbtns5KISiT%mBMu0tcN7JGu(AvuQ_Zc z04*Kq;I*&QE5#Gf8z(Ppj?LH4jo;e8laKH|(3X$pFnz8hS5a@UHZ4uK{`ScZCFP6X z3n4BZSh7zxeV4qjKlza6C(y~h9GI-u#sJ8J0}v1a6{bdyry4$ua|a~u%VJY zJw6i6`;u>DtoGCvnj5DL_Uv9&X!RLZu)!!A_|AUa6s=brU4J#<2Jn3;nPWdn6}2wq z$PAN`IwyWOKWH`bNBLJ=Ovk`}Vf=YrcHGE_i{!1bp=%C%3^I-^M==$#;tlAk-q_nF z7~`h4hK*pdE-rFOwF_Z)R&Kfr_X~KBP;oKzh;3}22;RbdTz)B7f0IRPYnn0z;!y#dz&44mWak(Il;u8EBrsD)=RwT2)=5SLQ3S<(9D=puQsp z2YFbZ@@GA0v(D7SCrvWpZS|P$FXwH*xoI)&GM}AVsq?v!Vg7*IW^37wc&O8y zP;``*)9-~ZCwk^Ng8L1aF`+VQ33zTFosYN)9`7?c#o@Fh5HGHBu5@Oc);!a2u)rwdS! zFWT|YXr2C6^JJxWr#0(`@wfPc4`N4z5?aW6lkvKdtuuLb+crHiLd?Y^&M!Z$^OF%~ zca|x|#9$_e88)``U@+BlvxWwi?Hke=QnbjL>p2IsInSBgtTChk%^uL~rQ*9sB8DYm zeh5U+J8I(3J$t+5sb=mYMn?V^-e8`DCK}hovqO5g#e-6{KPq>g(&!-8ej9TqKb*(z z9GWpt1*^-gpC1OMkeTL>Y{4^#9ivsvMstdGI8Yy&#+ zS^M?DG2y|~Ce%X=HZlEOB!#gF3H(=I{3FQ*sbgd`8pa?$#=Cig3P~7bTGHF1Y)&7cq0q- z!S20k3(|>rs_#ac_1Zf5J%Jnti3EVcN#hnU<+Lmt%E# ze3hAw1vTjM*e1`>3B%!I-NJ&wMO&^5=_k%v&~I!(k(;d-+cQkC?kIN^2yz|?7rRY#?&y3BpACohMbremJCU@jA6dWmB?k>b}x0LdXIbtULaDm)W4PY zML8?|<_pH8z^q7o`PQbY(ireTQM?*fy3Ko}1}~`iZVNX!A)KVTYr)ahUs*e&F5YiHzsvx30UZ%6e@*6r#|2lcBG7@OjnWtVu%bF-Bh#t(RI&u2Gt*6$ohe)*$f zVb^Y#SmQIi+wlVQ3)FebBh@Cf_+akwyG`_CRHK7;u?G#gV^|)GRf{mDJq@rZ5?CB2 zZSEzEJ`u%+o}jB>R^m7&#Mb!uO%t`pbwSMWiK0)K+*jZ=rDSi#dApyzeWG8svrqz4 z^rmK{zizwX4`;$1h1X(pheNtHpQ)OEt4LdZ@sm29BQRi7sNY+f8{Flla|;JI-EtXp z4pYB-qoF~AubS!YZVVg==PS<%47~GYrr8s(o+`qkR4c-+x{ydiE5~!?(fl(}jSfX3 z&AIyzjuCY8h9CmI`Y=;*%xN$=Yk1BSPKKfWK4qrnPKFy@KcYzGb#eQeBK^RwrLnfPwA<98S2P8i)a zA6qBN$@sQ5g65r*)T>Xk_~>mMny=wxy|#9U*OW@L^uWQ>`udKzU7m8n_`TP-bzr4# zv6$9mKnU#arLX6>ncmKEH$N8*%~I9I_KmWT43MB zU(F=Z-u&R|7Gl5duBQ4!^!Tqce3JDy+#dE~NPCphJ;pV$Rx~d!)6g|h@3HirQgJ!Q zvSQBr#`>v6gx2wb?<-oHu;(jXvr0o5bC-MS>R6-*@tIBY$MPiOCfr4|ZE6KDi55FTQFeY++T)zV2}^-@PU{C3`K&T%SunGUDg)wl>r$OYBm&k&>ImJYIy7?g2y5Ji zuy0859XU)qu|6bSJC`a%o9|4)-FJ^8=!Z$BLYPB`sVgekGuu?F_F3*5R=OU>+Qek( z@Zkc@z?bC=S}vcC3))|5GjiU2y{H?UB}%vO?9HXen2qa1Qy*uVOjnbXX!e@oj#O-? zS?7F)*vsluR{uB@Z$P_egkOF*(3?h}9iv)y_|6j6gALGM#-x;u1O_tpJKxr?;=gi} zg~_?K1%F4pW}iwBS`6JnqrnZ!uhJo+fLjATKbiJf@8uV4wjuX6{-h>W?fVm}dt1_q za8OK|q`v9@MY;6E<|hd(81)LQ$+1>R1@)2qUAe3-d<7ei*Rx_bVpB5kHx)vjr}-#j zd26I*Y*Xms7pRKj))%qS?3{N}r`t1$p1ttHqx0OQRldh^*(VgeFm%^Y&!7C(l?LWY zFyH?iyn2nCdOCNqAzug|lkz}sPg&%E;N_;wDkkZ7M-S}QpR;VW1LJLm!pV^jrd=$# zHXCnI0xfeZR(TD+^-9;2RKnbe9Z4U@sc{l^Kx-+u@WX1e7M51%@q7Lz>V#dyTTRrQ zI`2-WUFphVGE!8qmtdNi9lnm^l`#V9jyOSZx=h<6bw^5O1pC4y_rN;qOS!9b?T-G+ zxjgbS_fxtq=G>Ci>p8Q(}dlaO#z->>;p<0qE>4dx1OS>+MBoCDsLUzX=+gB z#M98aTkssKZQINQ+ zB5^$yN#YbQ9?VR%I0K(-?mepeFENwxXgs$#(m%^Tx1kA^1gw!of&Q}uO%{kjCqd)3A4Ms}YQGhaF{B;m7YsD}z>`C{fyLu;54QG&kl zS)S*Jd%+StK|OZ;{Y&+Ik4NRA-K1z1mGRQtecM@l%wMWhThofmPRUcT&TN_=Dw%Vb zxF$kF#syICw-0{1UfU6q?gYKe`YzO?-ts$V0`T$)qDBf17^bhwXC9fsf!;J;PNs{e z;fDQ6#!MLBBVw?&U>sj6mTyyn@VaC4EywZI`-NLIg)T>i>&t61*2;huIn9&2Zo)a!>Ps%OT+tK zy)(mn*nMHEnd8=L2dJgwsEyan%P3_Z$n9FVTHg`&q(A;#FfVPJHRG%BT-M~64YWJL zum?wpq?XceSA2uG$Jo8GrVC(a{YHO#!NA3OksV)x@`f|C=Uag9YC;0Ao~GT(Y1mPJ zn|kycDYlN;<1MzArF{apRK&T=R8ziQ6xqB}Yu`QcySaOJu{0;!dp2tB zCt8!GC$Hmq?b+ts^hL2wo7RWaT@e<$<2Tc#(#$60nrUFr0vcmy>I+9 zJZ6R2P{rh@oK2;9vql&&I3bLTFh2NfGaKL;oJ4a~>ckvbN%W1(LetpH8?IBGSTQ;W z-ACGO_1b&u+c@;Sso$AVFMI&cp~s3F-xwzKlPVZ7VjM5K_HATa=Lp-@S!Y*#W|?ge z6nm5yD7^mSj}P`EACx z7Jd${q8dF9nKyFH=|}JLOHi+*@ab_5o>7UE;9KY}i@zNi{6ajQli>_atIK}ldw7e% z$GaI>BiQr}#^$I@^-oS$Z3zALq< zSQ=AT^)EiNT0Yr;o_|wjBwn~v-ARH$0lXRU7}%X@7`01%3!y%V|&X* z!d3b8tyXSGv0-@Kv4dplXW0ps+=DTSwC4M|-dkM@R$%N2E);HVJ?LOlzu#2czXV(2 zIAGKrSyEyfq_#xwSQ42#CvoEZ)ICq#XS4!5g&*!yGXi6gA+~VqgNn#7*~z{IbKNQqn2&Vd2oAu6n0t75beRblus{1LhRf) z?PkT7GFgVh4~!DLcfB>I{W=Fcbc>{FGr9l)rddqV5_M**p$^i{GdBfR%@m>q^{LL; zOq@He{#iiC^W4d+9=Ux7mnIy~WCx$ssn>m3ue(dxp@b@1x~OK1*Rh=6jQu#q<{MAa z*n^J+j}sUk9>W|Kbfv4oQ0H(AT+w#a-@~K&K3$Ehy#03n4d@>`Q+-fTk(}g~Jrj|6ddy!T+-Xk)htM5Ja>j-9V*Uh-MyU>!JRsSgA8jDnz zEumT2%t3NQ%K7o@I6?f8#{M${9s@@j>)Hss+h$Kr=v&wyk=BQ<<{MuOk8MrzM#}ox zg6F&*ZFHYQ!}GS>^s4n<*BC3M+f8Z9x?MBR@|?^8hf|tcInD}6n*ShqZdKS<68y4t=y;|V7v`!F4|cEYfg-+%`@TCpqBJ!7BQF(Qkgz#DHsH5CH`a4Lo0`qiG+ErRL)7m3qc6^n zQqJdApv}Nf_$d!`5hJ-EZ#R|(J%;W-UQL(lU#@~@=1*b|Dq7q;taUu6&RUjX{;X^K z39Rr`wo0*ia*Y-&(B7#R1Qkc`B{gt4F$& zGDjR6PBVY8-D*brWBgX_^&7g3;ti}U5}%Ks2@qX!KirZZ9LZ( z!>vKAqK{&UQmiOM|d6<|O&33!YdMa7Gqr+j(7v($q z8Hc3LJY%kGqkG#PJ}vxGVi;6xmyz|x z(D`QQ+pc#14;EWGF5$B?A2&V{YnS4#G^fA7v~NdlSHlC3Hiex*K~U*fan`kPBFoJB z@_c?k(2&yrwpUHzSoTs6$>UJ)R*bsn4C3a<~Y%C`K`uy&wZD@ zmQE!4{>5kK zPBu^O?v`xbr+`zK7a6%aZfK;KRI;g+6U8W&DwprC5=n7S=rGHo( zm(IZUa-wR~eqX|ULB@e3!Bx&>s`aX+U8LnQPrIlqr|iA7)wbpfuHvwxAOE2JlAqcsG{7-vUmj%&wYaN@jET`@1oGo!&i}y z>bA^?DAY~M*DmPXl>fF_XfP*qS4E55JE^A%tSx1q!1hVmv)r)F=WgL}Swdc{)V9T) z8s%BT+3UXkSaGaAc0C>cY~AQl?mfrvgjT=9x_zn2%q?6PpWx~~`g(Pq3o9h}fU+Lf zsEY8lOgPpv!>>ew)B1_Y>_)ENq`awL%170nNIaj*`7eH$U#17D=FFeCE9RgNKdOwN>#S$ zZIH(YUmE@0_Y#jd(6KPk_jypCS{Ge8r-9oD?Fyb7?Wf6HZGqo5886Oj-0^Ug*~(%O zu}JN|NsWyWW7%Kl=2RG$u}Oo)#)M$cJL@e*wWBB`{X3r^wlmU?9K<)XWhXJ~WyIIb z#j1-Iv=0RXvklHpCs$wQ`e2ikEMUD6IVDI2Dk<93$9G4H;*?Aaid(-8wP(6CUXkf% z08ts43m-r38+=YJ;<_XlgsJ(q->Z%!{`vc~n!A-i@=eWylDri>qbF=HJY@@_s}Z15 zX4!M~Y7teq#K1}QGQNhI=Xo_fb$Xr9I4F2$e$1<`ZHFp%FFTVZyKU|s+!2~HbuQRC zzbh1H@x!yDgzcMW`$L9Bg+=bnU$-{)>y$8Cd|ZLPvDC|HtoorhkVu|)nSH(eVIa2A zB3N;3xd`|xCbd}i(FgJH7Tx<|QoEkCAByTXeCs9X^l?Cd4pT;I*Fed}{JSW&y*YzZ zU|MQGg9G?OC=XKt==Z<6zc5^v=0hwF{2{T>+aK-zJ&4YAM3PVCQxyx-H5L4`C@KC< z<3-JFn%(9YpL1HC@404JkDA`AEB{P2bIEs!gHiLa9El!sZq>bMXxDuMC*o5t+EpZB>wfi z{QCxFz7O7T9lx^g@a`^DN#y zmzh6);8lJ6c@?W!TVs8(-fsyc8GnlK-#+{D;Aw%b95u=uSL3aqltuQ*o- z55`~p^2uzQ6Z@uFz0U*g{?E3}^3nxhybsKzH8M%w^|;DA_li$9IA=6)F~1kDFEf3I z`TK0997QJ9hp|Z87`Mk1o!dRut~0ItWBJ_VyR^---|O0z4z^8JQB9QiaPuhoV^BP} zwQ}ZphQ3kj=_7O7*k<*X=O@`iLldslJ&UPZKiM}vF?7dNpBDGz1GSskF%lEWY2|GC z!iXpFM~q?MhhU}p>reFXi7r7G&whDw^5d(0iczg4ix)q#k$coMZwi5i?AK4pJ_|vl zlF}DjXZAT$r|ytmJ62zwWW+CTc{^RlF7-nAn!VjCCmqolJI~=w=RF!NOoDb6+OJ5+ zp6RpCnrkGn$7esOzi6yu{YqT^ptb=fvwZ)JD5`{Wf-?onvYnKNsgc-r=%^Y;3y)wbHprSgb}S>fdqdy6*gX03_g4=4 z8!t_WtZtm+V4by448mvxjiqUn7swqVobGzKwdQunxV#FE7V> zTVFWq&4goaySt%l>d`{AQr~W@@?jbw)r_{GlIzU09tH|Hy9VxAN$SHDq*)^vyQM|@ zQ26}A+t%!s#viUo!1>g3ZzZ52{h+-rbMM26cN4SEtu1by@>NrAsn2iFi<_CxD7-_8 za@@D%5xZC1eKFc~;6;DDZpo&U*t_$#yu+XAmK@pjZ{KS0Jj6({6~Yrlec$ORR^Jfe z9y|N+2qu9FC)BrRrg?|WqQK2MGj*N5M{Gm;`KDxP6HYDe8r`MhzO6UtnX|nEM~72h zkN5st{kF|FBS{lpQ`NKdmpv6ffB#YAEMvUH(~^qOe>N^at!S0`daP#rU?(OgDC0>< z4|SvU@y+C3~ZKR)Xm(#5QgB2CaA*-!E;w{OuMEvYZLC1$aGMw0Kb&yft( z+zspIpE#yh(-JM%MON%@)OSs>;0bLS6E8<3bvy45F~If5Z>|G7zP{Fdoptbb3x6A% zi~`4p%rE6PKldmXsm)Z2lTcMHUPf}0!!CSmek0A51@yS%I+rW=%E&=`B z^@ZnfR~PhB@B-9Hg7Fk*iz&BlZr$iTEhST(O;R1kzB7G^!{2&HZ62>*;>YSYZs2@yq;w47(wlA}GzTEx3)uyHv>rGGeg1fXhjp>np{~P1NLD zBsI&&Rv6#=vR8u7 z-bdM@gk#E7sty%h7iO}_PK~qNPWL44#=_!;fKT|&#Zv~Br6(J5b`gD8*xdJY^4EI~ zeEn>cc&aaCVPs{IwQ7MNrdLgp?YzpZ1)gj{Q!J&!;+lI$b1m-N-*A+k#cJ3+XPb%F z_gF>I$ANyLdvkzBAgliY{(ag5H;2I9d-iy%N@XHbz0k!?mf{3?0iWGlvtHYZ)omI0 z>UNx7ZpN46x_KuHmJMV7_~HIa`LW>(*LC(eQ29)2Je+RvR}2_RjD9atQO<|$qt8_w zWV=T7<9kRf2?bFlA`4eZim`iY=9eZee4HGk(f2Hwcbk&lS~v1JapBhLV|$H?_#kzR zM(9CmA5MBK-K0DZYw-49G4iO>RH+3^pC6wiSuu(DCLvL?lgw*apF1DIMN0}&Sh`5- z$XUT&?zvr#(7!yY6MxXHwP0c|we0NI{*{y8nR`r3e823m5~y;w-^cLYfaWrTVXaQe zl;^vrW5G`*->jF;q_s=kcxJ}1ZbL;m)47TZXTDOI&j%{*pLMP9aT^s!i@Vo^13J+f|8@~p&`XIB1 zi`9>$<4wC|kErrFTDI`)#a3)C_l(Y0==N`VZzz+|zkQpk)>&VW=Aau*Z1~~F z@l!{#RvP=AYC}z_$4>?>U+*kz0K1v|2+ecfzqY0woRoRDM6e#OPA~~-{K`?dG#|o` zC;N<;vNLt~>75aAmJoM!#l^9h9uU}OZF#KTxW*yUnQqihgN6LjrOqXsc+thU)-6%G zjJ9>}DIuEcl@OXN0bEz!XEkQT_x>*c$Ury0fyLKdKeYV(u{YrKpZp%0Jp!$-DObS% z?YR zTK{U>#wkxP2m<)6yWf6OvoU?#qFCgYL>e+61DMb;jLdX5GJB7RV2y>bW|4?)UMadb zl1cA09+3WY5O8QRM(VMTe+jPNhbabM+QzQ^Sl?X5LPr5b{oOPj9D3~yBaeIL|NCuB zz3N-HtN?rvFjoK?d>L@H#nt68%=!Zf;xrJ2bP~Rh`f>%3$auu-BP~dPPf|XTkk$s{ zRF|IDLBQX-`{pD6@5g`eqf@PgKWo_LqA>=>dZ-&lxiK)tMBMACyGAV-fK5dj@z;b9 zitr?zkZ8nz#z5SMnT6fh`11WD_gQ-9KaX;mO>J0izUc;_(HOdaxxRwl=O2VA%*bPj zWwl1gLIYM?eFlsMzsPFdmsCCj{-s*~xOc28-hiA$NRgga1M2u0iHuWydfpPi@lU?< z*6E3*6BDhu>y5D<%*OaXHsk{p$S&CXf`IW za_66q+-Lr^H=u`psGj6+&-AhHtv^0;{|le_C|dnO)W?>7!3P)}@mGM0j8OsWAm9s> zHHjp^uhu1(Is-1Lb|~I~sxCc#O@5pPr{|pu@H-!W$F;5I?D1wXc}&=wr?3GC!aVx_ zgy5$e-N*fq7c309WcW+uE5)UMmYBr;}!f$4dr0Dk||Kl<*O$-O5g8nefYvB8=L>unIEez5LVOgjvsM~wjBKGr{e$-^hWP8@CRd1uAD>fsjs3ua|)BrqW$RSNA9=uuAfO>K0-v8|Nb8w zxnKX8C((GO4S-;*-MRp<7OB-oq5uG@N_IKP2>dNsf3Am%OQ65;ki6hI`7 zye(q|Fg!h{1n|3m|IUSpV(#`t7`uthkTza~^THFOYy=2ngT^AOU3jM?E?G zM?pn!8aVt{lQ$-Iv|-6u6P}J0vx)v^?gsrtAF##1{Th1@qdQq*V%=MD`_e06=az5m3 zK0RkHz#rWE<2TK;c0W4Z+I5>X%^196*>6mg_5ie?=U^aUe5Y1Hz`9@{Cj+WP{F6s= za}^L$d>E$w$7Nz*5t!^Z(fR5ZhVJJGnEw8E003Nru<*7YA9<|ym+pmKCjkg6fUW$D zQUKb=&m;b6UIR{{PFs9UK+0mLz)$zDRRA@>kGqVr{;{Fj`X@~ur%L*U6TpAD_gzQ+ z_`aY0)A`8*e>TyWUrc>_mhgIr5B1S~O&B&%fZ9LC1LD3SSGl9P*r}>209*d)_yX5u zN`tkJePHCiv#|U-s5@DH%_s_9b=O)ICc`NNxf2=z+9%oNCiIlmPzdzMsBze(KPPnbw}0ttm1q zZ=!XF0S5t-Af8eKV)sor4XgL}DGmiH4MC_p2F!moLAY)g?!ws!fME*2Al*MGf&QoN z0+s!t`!+9o5z1+xc-fVp#>DXBv{%tN@j$fT;iR^z0A#`v7)S#m{tEn>Kn4LHHNd?z z>mLsn3f3-HiHFoVgc3sNifG2ElD;7^fd6*lM~}?3_8o5(v)B9KItReOkP&cFF88^^ za!^ksEYJBfGW_m`{tU<}q~On=!4I9uwKw2a&%>==0GWO)Gb|Ye1_mbq zjgCR@$uFXD=oJHZElmOJnZ^9~{Mg9jY<}Srzy*%}g==E%==O0ltx;BANBnE#Kh(K^ zAm0>3yjF?DXBoR|TY<13;1z-h`NBkAIl05s?@%;4ZYBk_jQrwm) z2_clEP7Qp-b3@uTRjIP@sywS4WXpWKF$)Y#UpE5yqX&NCy19vi@0lp(kNKP7K*qpO z=(WLW8xx23AXK0x_$ZYCvfge5a$w)vgWZGGSP#PZtU#y&h`?6hr(PHRGf#oq8wq&5 zvRCqXNHXeK0O`o#Z~xPGfo}dqzziM@m_GJq(8TOeb7j{~gPyoh0jLQt`rJygQ3Pd{ zo*_#q_*Gu)|3z84YZ`FGPwVrnJr3A~D7_B;QH=0|EMQz*C)Pf?1Qgb>m6xy~69zF?c-7!t0l$(u zZS=l&5-7_A{JL|A^>NaTMgz3qA8F%9EW4R@JVH2?Fwq zOX>le0yG>1I$ogcvmk3bzV4$fIA6SYN-3cb(z^8d08H2^ag0e|aC+7R@S%r){?_@{ z6}L|mvx@`=M=tf}gVhJrc)4sRwcUs>Z;gRVrU2`h0TAUm`PqU*GS2U(55qgW&Vq~! zghQasjTHP0T5a`R?CX3eHOBrcE?Hd-k%7j?!NhAgW`52Kz#uP9(T`~dO z@$kjL{N|Q*MC#Qp+zEhyTi3gVhT(h9nQHydDMCHHY${kHLJ~yDs`V7u3hb zJ)ezW$$a&T8U{30fj# zkmne{mL>vmydbNilGo=Q^3gQ=b=w*E3l~d7m{gsU2<2^KOCL!@$U?VkoCc=W1x#U>8`goA-oJCR>vJ8^rt;wUgK1ownPAb_t-BiOcuLupKo4%TfFAKi~6Ag+KAg#}EB2!198 z?;5bbo^A(3$D2qJ>0^F8BuEnM5=S9CttY*%CgQJ2LDqE~`oDcEimQ*Ic-eQLTsVTV zIaeRzvDWQn#}H@o)*kW4o#fK(~d@K>m*LKQN6XfR8=#?(1e7`;SjG_8w*6 zC*VX>0CDOri*xm(IKEG00XPA_pCANZP6Z7)7$}4gVg~Y{kEj7sBLJZ;RtNAMz}RR( zBH%;5;tm9I*mQ6YokjV@kD&KwABJ6M0J8;(t6zb}%f1z~|5Yfbc7t3mcFGD^qZ;Uc z;%`CMykpBZ$Nl4-=%4E*om|%!7{JG0wvbGi36xv?I>4q1APxgEK~{mU>P6i#P_h20 z?C0Z3LAiH{#aASvt$#{r@%Y-U7^i9)NC3-U`yUsk82b}4l|H!@Qe&}A92@AWs1v8}saaFK#zVq-I%(S0IcXIFO8)FSt|MdNtg{w+j z!;^cql0-d43z~qm0$7&dke8$;!+tsNYsp1j-x?C(o1lGm;pWWx3kj%w6ZZtF9Mby6 zsXo;bz^6|Bo0}F|FFrm|EG-f_I4V8F6!7DWzI0jp?^*>|GANlWh#-Etni$0EDzgKV zslOcMr$|5m^5TU*k~gF~7DWI4-IwndJ?S*-xn zwPtv0uxrxF_T_7>L|vNS($(3SDItK5o&4n^bB)W7FSK5KJy1aA?`xTlFqB7>MCGHz z&y+zHBZwAW#pn}vLO&NkieLy#Ch^#ia`Kne0hFr{)&od^o?CeZ^dwMNGh!eS|6mg9 z{PQFq%xnLXe}~qK|2uT-%+MR-?tVX9$0bA}_DusTJo~Vfe$F1|Nm)zd6bBj)VeQk}kKTi>` z6xR!IbwE&!5?JE{f>I{{z|a1ZQ3Uqe5lMnnTtJQ!EIiLFBreUoK+!dj*Mf0pNFx5# zb($ceixA5uf0gwanh(`pxa%>r9{y>x4o;!@vR9%wb{)#OE6{1~Lf`pwY*~lZyFOh7 zYo;hDD1a@y0Q@wNssll_FtF?MVG>dW_?k$n`2_fiSbVl>{Y7y=fm?4^B%*_Wkm~_e zf2?E-|G}wo&*^`4?PBv~$7dRcj~U~wek#ccVF1~KlC5|0;7%IwOGg3_%SfHk!fUs| zmk$9#M3RUBMpDOMmjFRs1k#}39`M+F2nzC^VnO6DCF=y(iHney9To9s@Jk0&`xAi8 zC)ct0#2V=DKMMNYkD#@C0(3BgFA2>dGQBk+Yp)OD>6`{}w;(8uzseTr1ZshS!Gr*AmXG;<*8 zLn8c{{|+$7_@Nao8#1O0ib%-V4|pWQUUv5unGXc)%$uoXEw2Ux($5(Ea;Ah2GmDMeNwG8 z{$;9LNczam0zNlpS;{e!JPUqDrt+9wu|DkY!shio}NCstl# z{0~lzo?C-K2bMe$Skr<*fdc-sQzbfZ)PsPXC+;wSg(z4(AOIeb9I`nBmcry9SbwVw zPY`lSJ&+IvOy&mUBLm@B!C~-cCXWJBTnE3LJo1~o7!+bX^xu>)(#|LyE(2~Pf;=x9 zEBWeh-(VsEyk1-V=qIr_F$MdQ*P%0W6djsEudKh>F$h>ww*q^DQ{lfF;L|AJXM_U2 zKEtg)=Vh~M52-dKM8MZj4yP5sm;wf+M(KJ0y57*k0H%OJ0gDDabSw;-Fos|aP`WOh z=|#Y)AR&2yFY5%5wf;YEH3C+S9f+SFRE5kPAhljSEeJjiDE_iSwZ4$3Xx2E)!+_vlS5=aC85|La${-#6 zdjbglEkQ24Er|2gZ6w<{@4%l7b0DYkL}7pO>-Ar9z8%4VzxI?X{R-d#PgY%l)v{g+3MJQ&bv9ML6_gyIbe0>*)@Sy|WiHDC|2U}R!qJXg$wy-eP zz!vs|EsAAR*kx;r<%fUo)*VL(jltkY=m}2UY==H|K(q-e2|yqOQ^2AbhVmv*pao+} zkSPJ`!l5TpUym?wOd+}fRzi8XMVI&f$*e=plPCmvF^7rjfZY@p0&#X<3IG8-pZHMl z`&Q}eq&vP?zsz!7GWhvcsQRjPUkv!wHQ?6PfuFaA^|5R`f8G!+ZcUph-B;J05C}|$cyzHO7_k=0T zvbA>E7`y!7Z~lvKdO~POpA9MY;gkq!iGm#Z)K{P}Fs6aRG+<2=Ad3Q%urvXO9>|n1 z=modI178Ay9MnS`>f>QNjvWv|1tDYJD}E1pKiSAH`}zp-PyiD}-8%s4BK~;?+!9zb z;A-*!R6c?g>N*RQBp=D6@(~a2^$aL=ulRr(n01n0TjvV+8C0Mc8-Y3&(qqZ_hkCf3 z3iWDP6M^n{HNe;HQ`apeDF5GSJApe6eJ372{OvH-q9`n^Eno@@Q&?DQ{IVwi|A16s zJu#R-2o|>)YjMrb|IUe`**HN&%huTC&;DOO_E-&uUeKu_7E3xWb9JQD2!5yzo0TC? z2%tXt(&+;spb2Z5Fs23LEE;AK4qX`Z;7lJ5T>*0H>aQq*?6ch|SaX05$_s*OR7C8=0|{3u9Q@gCzC#hi-)E%Y@!f(iWPOW%q!dtVM)c&l%0p@45- zm=H9;dD(vFb2lhqsHJCW6Uka8hrcz{D+SkW0qmevN3k~6Su$p1xE-C z0W)7xZS_gr-~i~v$|R)0Ozxx}F6#t1C3>z8LL2O>TX;ffm=>C7p`?Czt8f=3bz#vD zq`=P%5aNSI6ebYqNkEjk4hU_RAuxnau0T->em)0(p=#y7BE4woRUR2YN8|-|3arCF z+dZhhc#Z-f$>-qIz^oyKq-|BQNrA813RRy1vI2f${rMWpwv>45tbd1q5AJ;(9z1eA zFfolrp}}qf0Tc@K;aB+A&RJs;ix0yDU;+ssyhX_fDR{dS`kygJjWM^t;1&S*C;#i8 zJ!%Y=jWNrBS-$&UzwLQX2o334l@2aFKazoT95Af8NK^spCVT6}9mvT#Q5DXiBt;1D zgiu(o5SnHZ4m~(mqNFZL>cAQYXM7BShx_=w032A1F9bo{&JCA{e`s#@@J~Vzh}Q$j zwc$Jign?VlhmvB_B0%f&x{lEie^oc>d}@u8Jn4J})CBw)80F4Gor_*hfu9nK27a}E zeZA{QLD$8%<#WXxKK#;~u)6C?FY|4b`X&bX1oYN=D<9WI>&~DDFB=Vb6Gp0)8bG>E zmSK*1GBCHm0N?p*AM}JkScb7D?)+D8xew2?6z2gjNR|Fd0Gk8CXH`c}t^Tlj2IWB| z`skroZiqrCjE^4_riH>zp>$msB9ycV3WQLi>utS^5&)hY_&Wn?K9B->B5f}s;vcV> zKu`#9FjzD4P<2pJp8}E23%1kx5-??XvGgiG5d3xERA3asL|%V-3~4h|o4L2eE+PBq z{V8i>+&^BsGer2SL*I#iJbD9cVbNfj&sJY60B!Yy)Hg*0x0d+g1_-dN+76koqCaI1 zBx(TSf*R>>M*|t$0szazvKl zAs#*jd}8VA!CC<>lL}ukr2ujjA^D`fNFH1LY&S8a27pOheW|Yw{8D{dpU;PR7};?L zkxK-6UT-87sN>{h7fC|b1$=z=3Vis=??Jh6Fhutw@J(3uWDtgwZ3Mh!i>@rbv3}TD z6oAzLkAHn0aTY7P0ij(&05Xw{*F(ztoLK~LbNJ!9fA^Ow04)Pp2C#hRuYBVbLPKt) z?yL-04fa9cuLCnt27Z|Iq5uFO07*naRQ??Jy0c{4$YmWj-R96kw;w=b;0eLB(6Ccz z**WxG2hNq~Q5(*6VN4$|{)3`ElCHpC_YsewVCBoC@(Nt3a>()w8V#U$A>#GG;NiX$ ztW?P-#3a|S4wz8+!SzXAKRH?9)R>X5x z`FLG3u&Vsvi2oMyd3{0{IeFBr*XI?_p8yn-lUUrl2eh_YbBvs%(qlQ*E@>mhVNIeq zt50tuB5XRshi9(9JuiPd+{`ZT#lr!4{*-IzY;!?`#RXCTmZK4@B{%9L7w~h z3CLwXUK8h}NGU}EngG&OG-&(Roe<0y&ipP#@I!UVJmSxvrcyU6%$U&t7SLuSP|Zi+ zR!PF8;IF9z1~@KHoD24@lk1X9@nA`uKn`&sP+q!5?ao%b4c@==;uf@NU)PRj)xj4)>K< zH%QgbdAV+WhYODI!9CaDi&wuzhW7L%9vf!vvBkH>0utD>{+$OZ!VeDcDZq0XASeMK zl@Zwzh`cOcY0Q-q3(79Hd&|G zx@xTzIZ#qd3dEW`a?obP!1c=Ug>X_rk#0@O`h)z}fb!#$L#NyTbps(7pkXIa*a^_? zMYQ+q!^*4M_;T5SJ-dpTubjiu<83TGzJbZ}eQ;Ny*h<|u-Zvux0G}-8apx=FfwetXrgC4C zfR_FS$q=6bD}pSL7bo&W3tkECMF|hX);{&|dA%>|{~3487WX1{VCOJCc-uE*ObR(fb!|D3&mm9JbxTqJ_z zdZpySQxPJl#B?fvXTYrikOpq*LO|uy`uGH5o~rfnD+l?Uxc6uhlXl5QzC{C$6Wjf@ zlJKD`uE$r8z5z5bjYd(ZC-&-w^pFni*&O7lpYPGERR9Fg;@gK-8)M}Cvqp{Qg%wc+ z$g)4@_&U1`m;&Z!M*xdUf_(`8j>s-^Azdc?7d(3Hwj4;qWtE9NHkv@RxeIz0pJpSYuv;pRY|0+|+s|M4(9})ThDD z^#Qz3eSP%`FV@EuXz3n_ijS3%fL@l>im*byLxhh{T!y<|{SNe(4#|WbqyHK$`GYTC zWRZL!04q{m&)Q4<`YgB?thai7Z4C)%$YiS9P=ypKz#e4Lw?G1gF_>>yEHny~{Stj5 zIOn#J04g^*f+m&NNVkX1QWx=u?nnSY*(^Y@)`-W_WWyF)I`uH%^ROX2z`;O5cFeT> zQ3WV6$bOCp{r);OX-(oA&0-48Vj5;{0w-x5Uz=~>-j_Df=sWB=+sEZAB@Ul0abVS9 z(hPY87Y04xlYy1bs6qr;eL@(T2#~Dj`k?BAl#7HB2{^qTIpIW-$&L%Y-a>ck*gpRy zwD5sL-+{9)`)*ik(JZ{Qx60qogM0#dJ&P|HzqbPVWWtV~sFMf%z{7X`t0{@!A5!`j zYqee#irw8nUPB$%1QMuWf+50Ss<7DGY5+v&^-BT1qis|G{$_1`j=u(vQ{^;ih#V$D z-Ed!}o&@-@V@Dux8&e`8-9DGAi3n;J_UGdY9Z6XYz^ub3KcL-R12uugnMIsGe;!36 zbhbu;XAU)R_Aqdt0UABRo-kN5sB4x2AL(&x+It}k?S1Z5KLDEB?d87A+vkUM3N^$RBKk!b(yIl& z3i^S4b>lxd0RWbt6jg)(D4*U$l(Im8r-Q`cQx^Oq=#mP@14tmt4N3)oJyT6g!=Ud< zlqDs==lpi0M%n=&zyVUQ>5jvo<((oJ>+-Ra38X6^g|cXr95W90MM6mkBtK?ePh<_X z%~i`fO)&mgA&w|*6c>UBrZ58jXkem0Ns|Vr4^QIs;Yobn07chf>FEv*p6+4)=^pm1 zlxP_f+lL6;Bu_d&0IXR)R+AG!3eH@K4XzvE6YG%>xXsYVi7i*akH`8PHSyuA--7d3 zyv{$hXDw_a0^b&1!drecS5MnjLwsW4WnR8f(|9eQfXnxXRG^Ltg7F)DeSd+pLqHnH zfiSAj^MFq=UoE+npx{iyV((-FR62CaG6B5gx7P(wjF0q&rPex-1Bky9Da*CMQ~7}K z8o=@e*+RrW(uuO_VV#cnXW-9|=}1tWZHEwB;xdTxQ0pDyymMpN^^}_|eL`JmH(~J1 z<&!vb`6TWKq0x2Nb*hW~Py65Qb0r#l7Z}7sgf(F0inRr<%h%PBS!JrJFReQ~PeJk6 zmE%cWfLa3(aJnSiv3NDU^6Ga2lQU=*{8A1-ZPzAp-&Ev2jCTzrq7MsW2ng$}miY{H z6};nVyCl(mL=s38zlpg3Rv`gI$#3|24YHOHY(yjEVx(!zWTY^d^%Z$ z>$3(Z3X@oVzOC^)hLqlC1L+X`34>FYPvF$$Eu09~boWV52A7@cV*jb0w?hMgn-W(& z5eQSKdG3^|4yf~u_g!|5_c4@})zU6yLc%dRMEHcA!`;W;iT=Kq_?L79z{h#}*|Yf} zk0i*4WGp`h^$_!b%;IC~4M05`rag9c6;d!nH=Wc9z#1#pA6!?31ZwVgD4UZ&zR%)B z6N}-aE!}>ZfV^}ufF1MB3$krUG76x!7I`Ai?0St=xYv3o~ z7f_~P7d25A@#p%7kboRR1Kz3i;pZi*skith?FI7Ya$?t!LSBIkz?uC`oW87q`(HT) za=_lFJGks*5BpDbaoN+J5b7*F__`wZk=6pDRlN>7}9R^4^_7i=F z4<3F4PQLt~!c5NiXZ2-Vp8;QE`SWSJHv01t15m?vB*T1B$}_e^<|C2-5i5cC^~nUU zL?K>4vJbdi?hXtffePm96u$u`tij%iCR)~@*YDT+`W4^bZL5iWtliwsf0`z))O?hKPfkL$3z$9>zMFGEwfYcy@Aw0dWg_C=l zxbKxya75TA3ZaJsr+V>T@iobbRRk`0Lg_Jn-t9 z(BJ>k@RANImY;!dlX?3wu;;Bm;v>4++A|rb;k+2lM+Cj70EE}d`6WUK5fA`a*^Tsx zz{m?_W!s=zyJZr{eFI?kL<38$0{wo8PPg1L=(l|oK!e$X3D6OLw&a%@@vnd|CmYp? zV_8KA$$nwE67i4qx~%lz(w5uTIW61SHyk~OknCJcY zJXm#^nlhSjPIMfWU;JG-_sZ`@Q50wt#y`1N6fhRqlY2&FzWDbBo&YRF1;B()%L~}G ze2>;YBAx?v;l@zVO$kq!nQ(CwAz zmmb_iae|L?t`hNgj>HY%KDZsJ5iMtp)nxGJRzm+x@~Jh}3gO}-{=mRF{yF$FAg5M8 z9M`F`7ZdSEYW?|U`RB?Zy~@9eEWNmbb)skiOtj7lis7%Ga+i@Fmsb}2$$brc?Fat{ zOj?Vji2_p(J%HB352AedAFy%H-RPZq68&~Nv_&0Jkz6$&J3ik}1M;yJT?VnKr%S>| zW{=>Z>wXxR+l`if5ohw~t{T(NAInwKcl{^Kf(n3@x%p|34|4JI>+anw3yojZ-0&JX zKA%7bQePxX6SO)LNG%EEdS8=(8X2(0VE04=i%pAO-(j;~1$pV55B${(`K1bA8%F_v z*zr?l=g!YXYhLM9140nti6>6u;KAKB)?3x1iGmbRRsD6~hjUrIH7b}_=U#OD*>enxlOz+f}O7c-xqPid`|`u(C#~I z^d0E%)v%Xejfw9zSiH4?=_ejR^NSClyzkRkzvpAl3Mw)&9mz6Bu`K%^u;#eMiv zs++^-ss&hx9?JQ}x&0(0KqTlNNCHtEWyt`u4U7GgO%Mp1Js#KR$UZ6)eCqNX_}lgv zKqAKzKqKjZ3Gi#cFLeM|dEx+e+pF*okKTi)E<8*aq>FBF(~{41Dy_(9P9pGNzhyU}^-DU|K? zNGJ?!eIBx28wcQtKH)>VUWSw3{v$9`GiX{Hqx|~)d~)~8TfdDHfnuji!41lTezDWlfnNjfpS=1_c-<@Ch_~Vg008GMoW)n4dIa}8 z@KJp6@lRrY7h(TpyRd)%BD8xDJ6Q()aMG6=@z-_M62XXwzjKh|`|`>#1;5_k^JulT z9H3A}pl%|Vq>Lt^gwT-vOj4fvDRDiJ%P1Coy+>HHgiH48@A0tNmb;oDGB57xu-=2uSD3qWA#3}}< z$iY1?^Vv3$n79v(8kalyOB>gXfUondUvR_{U==V#sdnIV;5bU=d zMC0{0V*Xzin0ew6%zfzr;KZlV{?wi5o;r!%#+ne*Lmh_?AAKWMuYC*5)QtH0$?%?u z!+bK4N57y$1^0~20k493P~UW5OAZD;&)GNX%7s5Buz`)&%~u;g8>g=ggBFM&PgFnv zctQdyRFJ1e;pP5x0d#ua>X)Iv4JLpL_!{(jIoWr6CIZN7!NvL_1;2Dg1%3fn0{qyK zN2O+?h0FIJ#pU~t;>I_=9e`H}kDq)5pZUUFc;Jy`OwE zRbH9I+%K3|^RLjXMl_y#tN?MGi)?q~kKuub@5PCS?!u|{FW~UuCG6QVhi0=#)Jm=V zhhE1;8PsYSNY0}!;xBop&o=-)Tq?i9FGP#$*Rg`}MOfQtvW3Jqc$xSO#+vvtM91sS zZP4J4>LZ?Gby?kN-yR3_DJX>20?fC(5sg=VCw5H~cxO>yb7K={o>{|Fr_bYSPn|{A zQS!XLRd3^(MTcIBw_X230DzU%v-rYS?!!NR^*(&^!9U01t|qR! zYF}u3j3cD&g(5aeBL19r;wmU&$idG9$)v(v(8O_mg~SkyLU`e*ZWjOnI?2aL<$XI+ zJX-pM2$c1YWg&G7c_Sw{AQA+{ma1zc_f*yAh|nj(X20~Y4*<=@SzNVe0k6L1DB2qx zoIAgP(<>V|y|Rj4Ny#g@lDG1(|E>rj0^Nq|mXd((Mj`YHLU?#LSZ}!)WIYKmQh7f@ zWC{gzo-x435a)nNzywz=>&i?5)&RRF3M@?&DEkiUopOM$?`r+{TK~Rg9jR?X4%7{J!fpz7~rAUA+q{X>ic*C_f;0@Q_fLq>r8~*t3eh+8yS4o?ONBmP@G0+)A z{vuQr1G8?w?4e)wKxEOdlaS#(5TJuW0%S#yvN`~rkE^dYD-##@Nr4?|=eDWZHTaY5 zvSYp;RW(4;`h;u%l30r)pdTde8S=h(b7l%hmuB(u=0WtkU97HkaOOfAE9;x+^aE=s z0`N#H_;ZcmDY`n~$JW=V%V_}rCF#&mW5Q$A;Mscs!YAb9o(e&SV-v-B032kL11QE( z@<6P=)(_2}o#0|P$IS-e%LGSO#q2B{Gx+Mr5 zSQs>m2KMh=z>5y=g6lb~b$VFY=wM}|i~qm9FORb9D(n57TeqsZyQ;gZx;p93Kn9W! zAefFAG$@!xoWS?uywv4W`?;3$6cBhKEbT0wpyCkLdQasc(?WfyU=lz;7h(b=gcp${ zBoN48I^7w1uCDQp=X-ygv-kde-`?jARn?iiyVvcy=j?sH{Y}5$-e;e4&uMk?68PmJ zo|)>~g*~rhxluzggK##gyZr^l;$L1vhP*s|4@g$)dIWYpkN5W$!08CpF3W)3o9ozG zA48{;W2M>dyTadV%CATN975Oni#jBU!9B`;y`>J3XvN1qW| zgl!Q7*)(dWz^_Jk63qShcJ_GJJOO@7UZGE*D>*t{V5QZ8ESEEcu?#cQn{oEsG^CSb zx!uJ=vx9|33(ZbRpvb(Q;SAh6*S{HB8C{@M%^CnH7ZPj8BsZhHpY)d(8r+tX?-a<) z<-uaTXY2SF&YY;DRuil?+vRorYS2e3|B4850<{A@%AB*a0F=a_2d#}L_0)>L12kuW z20kUY`SA~rI;Xp2$mo$?2>tgUg2LIsks^X_-a)I~M7!HcfS$q6318ZmIiDf;%Z{W@ zr}D-hKZ57w>b@ZlJwl5IDqwY>5RnQ%Od#+?5c;p`!?(>v%OJUYdJvAWWDMlU(QN~( ztqzVY6?mGc)v$GZ3}XiSWN=;u?)K=#xXr!L#xxl%4#> z6aZN5bg?yjA3rPxaWJh*+!=v^}EUQcz3&QVl6=8_9?Kd zX}8yLX<4`dEEn<%{dX zPG1M8kci=OU!iBG+eWM1K)c&Qx7$r7u0CJ33C=(a`?9P&jq=mw^5bvyEfb9OftKM1 zQ{Q*s6u{`{t6MJeRh{3bZ}D{lQq|pj5$Mf=U;%!8Lz}-{eFKVmEO^y- zq+a5o0<5pUXYO3BU&(-NyOyxJ`BvQY@1MjIhfe~XX;OvzSSQ9ZOxA1I zJy}P6^Eg(zIhMP{?aj?j7buqefElO!?lwhIUJ(P(Qm)r2 zlEpD}vKfqYY>yTH2>h;1iZq}>AAz6q$mI^=_POL(=?a#cg5yi8$WX&XJ;Ubu7|xm) z$N1(+EO!N~-5iUp9F2CT$RYO&L6LGHu(&c%SebbrxO0XKz@}OaXHM2JSx3)eqtB&wl_%k1nE)?RehVFU7-;K7hINi{pxgyat+`Rdl)? zeeRzaX@uA{MGUFq7*#&)RRWVg9H?oAz@SqgHejh7eSLeDAAGIq!3_fu0HMDylPSLs zx#ACRhqfj3hYJ0j-(2n_NKFw*h((SZD^0<2qYcO^1cFWVIyTkEuxql8iLDb@=?GTx z97~-XjaE0DB+$(Td=pR&8RjNxn5ox`QGd0GPB*N^Cs+D=f!<8xQ|I++nxC5X6WIN> zZ_9+gzT$5M_EGEkNUylAh@gI?7v*H{QeK$#AW*>;ohT4&-8zZs>2Zh%mgRO#o!LaI z-9e+Xigu?}0p2QL2g2)nT%M{6r0Id57dG*)6XE*^J_#U20Fd*UAUZ|?o^=;3-wRbh zBM=%}RsXq>p>)vTX7EO%xFrwv83_Em&Y078Ns0jRCtR$1w(Awbd#|{TS?(*YRzeUQ>VCpPc%a zF;JKL%Jl_L1nh$iD0QDiQg)<9_K;8{=^PkwEAN=^iDAn0Utaj~&8p(IHt zVb)(Z_A!vX9U$N)`tjR&XzSAtVfIOng$y$|&cj@=(rja?A&|ue0AR9S!=`!-vy*k~ z-ZF_sSI`77E`rTjacASHW~+EmM+InmUh;Eo2VL2BVD>o=KggtSP%G*^d2A>FIA<%c zgbWM73%AI^wI^b5TDo(SffN*kOL2rL0bgbxN?EQw6OXYo42KBLeUQFBt>;TLT4K&KqA>j43K>o4?UAm=`)P9k7ZawsR$G=7*a3vAUP8Omh(<&e@BtU`G^+To-ZzM>(6>u^QOEj3R*6^vF48FmCkPOlBtm@;@GC-z z^#=$P9&~iPA{K6=fzI_#A8T`HPq~At_Wi-{^vp@`0CZ7x(M-X`s|2gPwx9fEo)~cV zTjZnf(P*!}^J#$Sd@j%Zi~XP(FZp2uO5OFS!Cw8??V{i6*LFkr=dpc?i)AiocN=K5 zm(c306xZv4IMM9i$~J2w3$0XdvVihWZyf;I2E05Y&VbwyLy87=X~mD*ys&s8jue^raR5 zKnXFpT}2F1+n@E7+wBqPd(n1jq6vOVulTEeh)DR{+WVJt+K^pXyvzOTw!-KiZtT$` zJYuk0Qb^S41wE*CcE1{M%^HA+-sc0i&Qj+NoQItf{-G7(!xaTL_1bPY5cDaVCZ_R= z-}lE5$+6s6z{7{Xg0CL@Jnnz|vyl1{CMJQgx<{2taG&Tp57$o?G>TTr^2tlUM$n6~ zOuNest&1GPliCIAPD4&=OP^|32p4ZRr&&$hwj7OhC z?*ENp0RLhTFk`OcwcR2^oC5fgVQSNMTz39TaoPDV1@Kz5yG+Dll}_>)PVSyh}0;nbl1cafTv|DGN@Sb46~;2yIa2ohEi0%)JFvR%_Qb`p~I_A}OJOA_#&=cOwmopmd9* zAkwWMNS7!L5(<*i9fBeupp-O7rx_$?svMRd^c4o+RM*EpG1Y44Ac?ipSm(Yiax4YJ4d2PJn76zj)! zFZLyE&x;^+1fl1~{fxIwFEMJL1zQnfNbR6mnCfsn)ZJ1LsvO~oeRU~F!`t2XZ0;59 ztJJ7w&KC`#8n}~na|-8#UjC9YSGPN~<5ExN3|FM<9*!jzYvp@YqG)bT=xIy$JfCnq z+clYdNdHsUm1llQ^X26#%B?Rk8SQKFab?l=8nW za@+8d2BU%(wz>5HA&Q+I7r(*Q&v$lUh`%=2>;axa$CDhRbXXS34#c&uZNL;c1`zrZXz9kHVm z{_GV|2_1~?MOvh)n?k6-Ob5-H^XgFLY>+oz!tm)h~hO@o| zMG~94!6$zvD{`kZ*EG^iEjZMMuU%jc^z~h-Um|V9Q*GV6xBmDMnG=pNZdvde}NPN%b8&wK#DJ zTk1pLX5PlB1cN++)=qTH&2{~^n-96d zyBXe(ElNC$(PJz`Z%a8$Xj_onh!H+|aU*y4jKM9p{&!tLUi5z5bZHNcd?=PGs_#z! ztgNima>6^mFt*#?+Eyjh=P>GAXWzN*v&cGg0Vy}{$azoV@GFEEW39Dg;yq)y`)i}T zfdd1dR=b>xh~t1YydRCSwe;hN^AUf3Gk8LPb2WsV~s$1r;u35J*rp zlhord5h$Bn5a}*FZAwUcYqMSaeG1mc?RUyUhV}aICpqrcLXx1H>prH8S@y+51txNp zJcEfoe9U@n^Hp3CYNHm>cf1S0+Q{?{!;_#rWJ>FPBK}f~%)>|rt{B&u9)0nh;17*IcrdLK zEQA8@(K!=`bM~pWYVkZH=sGZ~9$&I;{l4ikz{MXx`2Lf$m;!F?TJ2Ui*5@%=IwBi? z>(`Zz3^|NSD99ppt~_i@Fd)5w)B(pL)r?vc_D4Doc(!ueuBAa zYqmVvCd*;e7=I*bx9`{k!8t-P)K0=^%or4Ej0m0lshyB=6$WqYWYeAt(*MB zWk>ZQ&rEK4JBt;`En>XBjArfN4ED%4sKxjqWzOyK?c>$w$|Ib4ygmVosyUV`4& zI|qH@2XmYaIK5Am2EH_6EqLaS^Km-Oht=1KSGIVhF+u(ST{)Sh(3)=C?uo%mABfI`K*C%s?*fl&=_ zt9ZBCdsK4JJ>oX5b~UWha5Oqn(qVHNKXVAuW{fQyRqy(FI`-bB!X7@Kca9he6`q&d zA2pWyU+=s;eEK227=Lb9mVbXrHp*)3o;Pn2TX;`==#G{09*(HGS0_pAWwzb3Boe zdXj?`eUH;ws!iWJ^Oz=rXeU^wC{QC;#K#tEn08N@Z|kj15l3ch75F;r;A^(ssuu=0M*< zEAP!1v&XoK8!608eC2WlMscGS<0Wj<`@Ah*NOg(ZmeoI|MqOc&jCvAcO)2?pu+47& zUIQ?2Erj*+{MFhNhJ5A1OU!rz99(z)CbVLo5xCqU?xGs-{ z`caYcbvf)f6tBMtAg{ zvBaH@9#gv$`)ZT7?k~|VJP~Z@gk_UNb9B1})_CZK@d0&CxL*4|R$HA%4zZ^;y^`Fv z32n+(0_@i6)l9RzxNKZFywXQnCN0;oCud}`P<36Dl7S-qEv@{*%UzP4FKE`N&BV|S zA4QOoy$|>jjBgrnU&Uh)6VKy9k8a;%at7-MzR%^h6CZyM z3xm!b2S5ZjnWP zRDB1{`+#z>;jlMQO3OD+oO(vTaTc{pmC;wT@#$GK#kkblsqar`d_^_t71+>E3Y_gz2~|y3t~W*e3RR z;P38UE5Oq)XnpXRpSn^1lJntfT7mIty!xFWy9=&7{*Rx2@;qBcxWgJ5lZEnQo8+&G z5>qP4$FxuOyn=cmz>Z~ftL@glAGOjo$%huIk+cSs32>wpq0`Fm%#;R?p(v58okns4|qFVqg2+=r(`xLR5Aru!S$=Ys0)$Y8Yl zDxIIz*U6A?@Eyu}bMr#@LD7Si&HJwBxM_FPhndL}LKG>h z<}%%eK6;cYXxHj9HBjO37H-gEz7T!yTmAI|ZxU}n^3gl;iBAfTcNqOYiyD0RSm1fd z3$Kj89^XDgh}5A4sr(nTV>kHw{lt}>xf;~yGrOA+?a`RYVNTy7t2`5aMpf=Uj{8G_ z=NCz|@axBkFmv!maHW)UF|DuyH69s^yct4b_mL=7-sTwD;YX>Kh08FnAG^B>Ds<5*LFeA>*-#cy zQ;}(tnx_eNwx8+*oMcpPtGrm}B(cVs3fg=>U67>Si>F}rkn5Mw!|_?E(>GmbCBqd) zcP%&FTM~x;?$#;(RMXw<(@SX`H|Nd@C3Q39RAsLU zlva*1l%QS7z4evI^Ul)y2n3Br?6t?0{O9i6Tjr5(%{-w3+w!n9qR$KMq$Z+PxDxFeIRA&_X`>9fNoQHA!LfH(FXWY+; zG+Glf5gWO4--y8U!f|0%;mguFc$zu0piY3Nj(@IZImQs@mq1pcau0{l(P^56BaF&d zr|Dm5VUN=mW{9?=9$4h7AEnfdYqa$$YwRZ!yM0<13BxHQFpk#34rKUoWulxTFZpzN zPidCi}$rGkl$?PinIh(-uY?rtu@57$WJMjv zMc2HAFe9k16H6XSi49O9JUHPteaFBsa^dDNhl6^!*A z$XjSLqOe|v;cdkgJv+1#w?6o0`?TTF%@5>?+dj`Z@)JDWEXZAr)ig7TX|LYbXKR} z%(9LBozaKI8oOJU zretA=_aetesGUW$ZX1-eZp{r3WP$6?E9WrtKI?q-Yk5NR^`Y)$lpbdzqY$-9?mM=w zg%3qJ4g><&Au8%Y4}S6U*FB$IO4uk|zJuJ`LDvw>Tx-rD^CGI{RsPl1;=Rv!URci0 zbJ#pfv1;v$6Xuvj%Dj`Nvc)3zX`Tm_r#wD*L=al0bLvpQK@iZ5Lw6NcC{Op{otz-^7fQ7^PDbz$;`qf7_ub zBx>X>RiN8N{oxR{CX*VG%SY?+Zgj1Y`z?vrUmV`#kHHMG#revvM2dEE$xWL6dq7A; z$Kv=G;(NWCi=WmrDcrc}t)gmZ9F=J7kJjgiuxl699P<}h9@$p;({t|L=5m`^w)jS% zGcT~3iXXmSYbYi}p}i^a&Pz1H^8F@!YAWcfa*Xru{&3>01ig9gk!hMIa zr2WftYKgxZ0_jeXK3mxGw|I2MMI~)J~^Uut|tG9LW>oqWM@WM`4x`Y{+}}=Ti;zeNdIaP4#x|*}5QZo@%{) ze)%U)p`+T}{I`9#H`H(#;^q0dQmDi*ycIt3e!R!@z?%$+<<4(<8Z6}Kzd1d>U9gc- zc6gf3!$FaonVx%GIKnCj?_H@LrI=8%Ur?*Lj-Nwtjq*<Wsq;zg@PRPC%4@MSIp2*S@jUs)LDNM?!OnYF zRCiR^FP)MkWHGxRey7=w#HzGj#I%b48HKt3McY&R_c3ldUngerEsl-)prWBF;QB&Z zXfylbML#;8r)-fK&B@}L2T{%G`pJ3%EvS#JicH2uqTTk}#$wqXOtt&r8B_zR^$+cp z_8+8vX`HmAz7+RTOUv5O@?pUxFwy>%;2qu+{s+_~41 z`1p0`kvKKR+1kY6`>FdxuGjC|3lf|y#WRV+%H0ULpVh!D!#wNWEi2u+W9*n;bTtj{ z(nvZJ@ssr^W-iW+kLNUPFoNoc=x;J=VzPK|;p8g2z8ZLjNjG`UAWws%Xil2z>*3}_ zg5+jqZGHoivJ6L6Bdi5GC;dU~Y+j!F1-hLksqU2@d=)*-M(H6oE0g*(ycPFpc7#3g zXO{=KR7rKOm0`8M)J>MWcIbDgvB&xCIz?_~MUFskC#>%GE{ln*amhQ88D?u~&<4D~ zJtSICc;3m_^n;>A?`BaG(;1xhF>m#s^BJTpCeBnA=S*@iS0gsD3#d#Q?AP@LwjFbK zcdUuV%Dj@(f)fi~3Q)ec&h)tQC3+;y^q$wPP{t3nT=p*$*-vx4o18eK!1sPgqMs>V z@ZGJS^@XNhigjIO!nS2MEw|4{QD3r)WOT2*w2+&ht;-}IF60wC<(0J5WK`A&wMml~ zKZ;1Y*C>}X?R&eWHX9|odfCwybg)^Rf1TMt!*vyvMxKq0ii^zOa23<`i;#Zpi2-_I za%D#*6Jo})Z^!F`L_CbBRxxoeTu8{H|H|`dieDpE^k%y|cMOd%SNba= zj2hP`VPY~8-Owa?{yI(G9_}qQ%V|fA4SCv~)fj!R9R5ZA`_A}Sgt$hS7_R8nwMBR7{?}}8{6qK0BK#5(!^<(6I5@L$_I93we#yhT*648< zqL>tPbWMk?vDmKfB%ji7RGGx7SYQWG8M4eURO&O!oVpb|wHdms)WO9@j8U(X>%N^W zMC4(4h#ww*k4zN6qL|j6n|xk4Dou_dd)u^ z^$efFrvs~narot4Ax}9fr&(@LJ5|q}&w~J+jO6U$sD*6h3}TxK`_%YH)7;WZfsYB? ziN%=VsL;Z@r|9ZtpZVMNB3MIY>TNx6yj5#3yPx_2rxmXMqC8-V$fH)XOf}BJofk&YpKYsFHXFN(ru?k; zXE_?}yy>D5W{-%eS(>$)-7RdwE>zE7P28)V< zP*>n_7sg$S^gm`et9v(>GKn!il3j2nx4J{;`(xz6!z=-E3p<7j>CJa{f~hQtmjcFr zq!^IdsZU?qor}kgAB@BAra$B}CvwxFOSCq6k}@-MeYzJ-VNZ^Uz_(L?4)@mdB@sP4 z4{RO#E+ttTw4lY^0CH;QwcQJv_pJN*y!}YK@qQXiXQ<=dDaq;Q`hHMf=rg*(=n~pr zJDE25I*A+AoL&~5?C3@MzPMCy*s9h-3_n6%ic~L@`8DI^#JU5s-Lau;ZxyG9%2gBv zQsg*6PrJzVZ3BJGG~aJT8HSjooZhRUu3NSw8DycF}a*rrIz_QgmW%bPSmFKFmg z&xd6Pq6fK_qS<=2)?u}7b2s>FDIEmM3}5q))p7!Pp2dO+~Qr zw*J^5|9q^9f#$_pg*40pm*KLksR~c`v{0ghW?rN2ICNphBA>C{adL)4cC_d9xEOb_ z`W&n1FWFpTTH7o2TUmWxaerw*C) z+@$=Rjqb($QqzxM-Tf*Gb;d|uO8k~Sro|1x-OoNQIvz*cKBNnc@$%K{b?z9WdtW#( zmP0qf7F&Hq=ZuCK=9*C^$=m3n5;TKdn=MNi=ffOpmS4%(tZGK#Hk=N6yWe@Sj_z<| z)UG;iexZnpcx`wK|Lk~Id8^*~!mrw_Ls8d#UDOKt+G&(?ufEq$TC{C^ll&A$e?2jQ zQ~ArZ+f1}ET>XmYTJ(2hRtAudac^QP9r1Nvkw`oGPKbB}?54#YMQ_U8T1xY%R>~MR z$GwJUI~0sN`tw(S{nv^(WB2;UF7EC8z0OlN;-+>)w@n?Xu5L)nd2$N4ULF)+##m81;&PG{wFib&>EYxI$3?OWOG*(u7EAI?#1X8PS@IX>uhgu7>VN0#U% zjm!$(N&E9wuYM9%|4g|3!-b4N4(@wy3S^3;(Aa(9Smo_d`Sd1SzKFS%*Vx8}RdYBC zyZ6!~OH$$V@T~|J)PCLOPQcg~4($9r(PaDhH{PkG9x}`e6bA{<31v)nx7#}#^Q)(q zQa7fYueUz1-Q`lI(G?pFF;_gtmow;t+@q@K5@#H#)NIn9Zd=#!22;#2@zTqpg!(I7(e^&0_F_c8 z@7P*CU3>YshEK$l0#iq?7)@r9LGxfsm310@C?Lqwb%}M=vaeq9)z%T=eJ0)>YCm2hT8fkG zY)89V`)kpHEjRo?WPHc7aJl4YRI!KBXww{3<;0(Te!u;_Lv4txmt2CrA+_r(#=-1) zo=G>OhwB?2-#_h5)^n0}&_BM8#%o@jg{#-wyH`7_e>iq$DZ+@ekltU1{W6OCbKvK( z-ir^54(o%1-;FJ7SA4NGazEp=@;ylAL7^=iSmrn;if$X?zB`;86Y=c5hI~Vhpe>1T z&qY)`S_&%CM#D;7(1JL`I=)%|pxgAtJECMBelGWUEex-t!2r9-UDCVlr>18lQn5w0 zctxIeT<>iY)Mca9?Pg33G(>T6J~Ce!2pSx``i#>1r~KEsr0hEwaoE>5HU{f#rUS~y zV!Lrwwp_8p(0_r<}JETVhu9EmwZOis8xhmOMg?1n*OAlg??B(wRIOeH~Krdymn}x5tiFr^<4> zipj=h2i_sAYQJxCR+aMTvr9kLhrC=-UI!!OhFsLUmQh!X+w9kGJq^w(MGg2S*BP0= z)^#Zh&UKemBij{s`X%T5%4p4vTA00>TvDiGed*%B(c${!7N&&FFKaR)Y%d`{xuMy~ zrI-ea%|^OU%T(y&y1L|IZx41iGDy9zuDTow2N8QN?C3eymiJ;s)KpyZM-y|W`gj4I zKfIi(p@KA$nK5j@X@7P1RcmD2_R}kbxTDPKxF414Wxk8N#0$i9voI0bp2MqN(V#pU zk0Cd3V2-#+ty^?AWQkB@hSlu*HPI#|v(@#w2@!|wpQUuY&ZXnlsxRzx=MzrTxwv7GUnA!*216IR(=VG=b~QPd z^fcS;)~i(?Md?- z^$hG%Z7ad?w)EEZ4`DoOQ5;_EdM-cwz51tQ$e~-!){HQGuCHEv$oo!9l*b)YTd6Zt zovzBnW-n>HlxXlGNw3C~1@mzl`VUudk8M}|U0*xt8>T^fz}ViF2Fm7& zX!(yVa$E8SJr z(U(W3mn@_4ON+gF(h~}%bEmo^Tf48x|qn8>jSlK6TqYq2O9E34QGuJG6RZ ztwme3r1_aM{h}GT6M|m9e2t#g`-?p@LQDEtTl<(~NNtQ$NN4FCP63qeoTu9B8C8`I zu~hv?aGC4MZ~*4!l#69_M&%~=tEl(k6%QZc>F<8`DBn+F(Q%%L-z{wKYL-63|6yaR z1OGdf7-r|VFQ(liq;%alm5sgNC7egDt1=x)b0{B&8lsP%`_$}j>MvE)JZo~{fU4jb z`mW^cP;n5->Q`T~a4zn|Lb6~NNh2Al8NQlWLoUH(85|sHL$AO@A*7nfTNN(pRB-bn zyK$jny0Bh`9%lmcLKTMUr|oBqcRp&B5sL=gelkFM1m?U?h=k8zj+@uYt z-QtPu&5s_T0@S>{l-TxjnNjy_T>396;`XD}mmPiPzY||1VZ{gu@_Ki=G6T)q{NzWcqyHxDUf zU1xc89KZ4Cl)ABY#?!V|O!;!+mi9deSH_uh3Rkj`NPm@nmGboa>)dy!lxG41qdTSC zjh4*9=OZ-FU=UAdlqR?f(_4`CWl$&$H}O2VNHgcExVY-TvC59VD-d)akoVPGL6?ge z9);{Zyv-O<`&b75aAy(hm3VIk=EoPBYP*hR8ctP|@ZL&QllK{0E*v+%Ac=pO-mct9 zb-9O8{8=!!4;u~t6O90siEd%vdpBL~nPsZdEIEdb@PpptS?O?r&ILw z&u_SPv5e8~8nd+zT-Oe-CTj%^YwM;(kQp*=wiPuSm^~0`+&d!O&L+`MK35@}iWQY; zepC3ahQ9b}3PCxC8Q}#?j`U#{n&sEYF>a%W9}RzIym^kBqEfLa89>IMvwdi;qnjUA zSEPx2Z?(A1}$$jv(UJ1N}F#365#X|su# ziLrB5?=9Z0C@|l{^H|s16=hBh`Z5;&^scB~Et(B>kw+rO!O?xu8XA)N>(z78!WL@| z>P_fkVUbOV!LK&6)Oamrn{V9+`jAB-Y(}Xj*zA1+b58EkeL-KLZp{>iJe5z6N#@rH zeb;Cw87t%L4urfP442g{A4*j(sX-oS2@G+iKxy=znW{;ar$qdUAie4YdoY|7alL{!m%XmV9-9 zi4>~qz$hx&J9AMoGT^>Q8m3YtW+0<$epkD7Zhn(tzmv;|l?;q93RTA!|%&NHzo%?i^6E7~o>^kaV z3jG&$EI#x+w4hCkS;OXxm&ZIqjcr7G(@}JwlGdNdns!GQTSl|J%sO+=?+O|p_L~RhxR}C2IT7WvY#8F^ z3;KSiY9nYPsB>*U>UF(u`(h(Qgv|MtvRB344%mkU?#dQQ z2FVrFBz9jE|ejg%z7~-u{b--ckc{gNFM+S2( zxw?8pp7Sz)j36>2P`+B`x~Yxj`J!wKpMIkNwSNxg%aS$}+?t|E(4K!% zoq1Asgrqj(aK_eo zgAS`q+*AscUX(hMmh9HGeN7Pey8jL2adjt*a49-2``*LR>)y^EQe{Br2ij6H>@Ft9 zB5eZ;BGWOwciDGWSBKd5K4jE5apseHn{}Yn1d}Fr_*gdKT;X~3uBaC=ix`uy1RXiMtDWEtiSLN9#rOwC3}AER5CO(qatAK5SIlP zwC%)$Ia`rsB=?&!H8hQT=(2Pe@B597fnv_%bDxmb^_t$jRs~|#e$gy9!^xjtR46g zBw}mVF!w{frf+m*|7B)x?Na#wjT`TM+2wo)}L3Wn&+KdK9}{C-n!;X z1_O?p65|T)me{BYN?#9InBYw!`*TH)oVtUnZuG8CuL%lt1H@hP2eKp~5~w6fGY z2?O8D$o4g47$5@;`HP(6#hdA5^?%%tXEFqIPbF~m{e&@-)2w)3L~4)`{U2xK-wcY^ zJx>>_O{E`s>s)e}3hCJMo2MaA8XumBVMCHB8$_y;jUmTX{`Oz~f8ES>mfU&TVH{c`^=DNasgiIA7h|KFpxvI+F1xuB!)C=u0Obrj|D3b!&7NO@D|0ba~H&> z`|m(Op~=ZXt;cUmXDET|U@*P_ z@p`lndzbozGvNqlGVHj2AJe7+Bt8&sks>}g4So{%;4Xx{X2XRM`2Q5c#fJ(U3|2C> zw5DSDH2MOol-k@tV;}&W4r*)Xjf8Cf*Xz|r%Rp7Q6qG-gfcJr-P;`vJix2WWd7vfJ z7QVH_zv3hM4#kOI{_=sBC@whjVS*7Gx*+m9k*_EE zua~PKrW~w+2DU6d+WE|Gw^kV+DZ>Rjd7!tH0$LLhK<55r&E=#4=aGE`Qyta48-ILT zA14PjQ8G{&E{R|ehxY*@Q1?<7zJG0k@zK$LjPE^F@XCk*Ug(}b;eECv-yd$pYEvUV zAV+*~1_I?zgD>I(Hz91W;KUF>ET={W-Z29Hx89;W87{WmiH`Z4aW<+1V3h`PsZ4{7U(VyhtASaXwP>CM3er^`J2arkZi_;>?4?T&ON25UfZ7Ph<$;*ayeIROMejki`Fo{o?O~ zL8veQ_;V2qneK2S^)c|KIDy*RKwu9uM=%(|6@MML?5PP%&T0q-MPRU&feZFGfyYZ1 z!Grh&@x^%~VW8IG1&Z68&{ysZUy2=|J;w~1Q}keMb@gP7biDtqNj`sR5~8b!CS}>5PKhlV0a3WCC{OLup9aY z`=RDr2V9GO0PLY=!1l}#Sp4q6B@YC{V^u)n8IEIu*H8aso8C$q@qyTJ9KdmG_gA?? zZ`ot`Qse;L$h9|r{yZ5UG9CVCQo-Lf>EXXMDc?&RUfZ%ks^w)QPawQ9pg+;msM{2X z56&WSK=cp$Wve1VvOE+-OM*bAEDR>b$4*AwKo@Yu+rhOcOF-fokaz|b-@9yrL#0P_Q-teu`4SGwRV4xudc6WA8#>X6IsC%IaPzce-m(bI4Z#0N-D`-}U>YlXU4IKfaB0NoN`kAx4t6UH2NU`h7k+{HJ;F#`4A2c4o=3_`Z9cY?FMDf zm7p=v2Jy`snCNama)dmRAB2${&5fK>fjl=p1P>QvAUsMz>W#l*IR??wV;}r|{#|;C z;eLA#s5iVs><|7EYk$SuU+dVGA~@Dnw&35IbnFYENbBGJIMG)`o2(ISLiBX}Q!tFS zJcZ!~-xCfkjP}6M(b0c7#=GjFB2483LxGP3)Wm2b91=tFHG;vB6Uo)ANGu~fI?fS@ zo{=0i}F?YNknVkgBid483V+HKt$C_kx;tP=&```NN zdFOXrBbr2SDGieyp)me22u2VLL-jt;{>Ba_dz#^YT_(RYA-Ta8s-tfsF`<0IoyG(c zq;`@=Fo;90D?d{AaUptyU_f%hOC*+$een1B_Z_H)V+@bK6oFCIOW4`oJ{e>4)8Jf@ z1ZsKD5M6bH8;GWIy|hO#Aet0rc9I{&;vIi$6B5_QF>#Wo5j~ymj)ck2p)hugq4~)P zhGR`?f9njjaSsqpb%6SK187Y*fuyT!}hUgI!Qftut!~THbdhi{n0{6aBFs*zEd%u32jP{W}(5ZU`>J`x-Q}7G~ko?A- z{OGqPA-c*LZ3PmE&cJ@GO~*0er3FkbNPa-H^+#_kf*}II5CUUu&)|EL|35UTyYL}& zZsUh>11`9&LiB zL$z=png7GX!~b%89PR<-q7aZrFi5`k0+EErAeV&jDiZMp;)AQl+UkvHE0Px$1``kr zFJPu88oo70LqbFhBn4VQb(|qo#oR?|Ic;c4z6&jBci}^t7JNuOwp|^X5|p7K9;uyT z<)Ai39zLWa`8(AH%0q8M(Gvxv?huD8q`vz*|6``xA$+10f<_wQLB*^8n?|htT!+^F zPVjr53>xK8pj7xAfm|PHC=R*_oZ}@9SL1n1qNgs4v zgV)4pAUVSlN&?mYVSmzm55)iIgy^YH5IXh&o_%iszrlZNl6^-WSbod~la@5lt51YG zrHP=G@D5}jW&(@!YhdQBMaD=^c)3Ki)12LiR&s zki4z)5Bt-WzCz;s7kKfb1EMBBLdaMP1b+XwCb{*#2bZ1_@a!vt+mDCfrp^TL$PdGH z>3-ml=mR#9ZeS7U0HzZREkMiB2-GJSDo^H{guWQTkPo7YAAy49H!c!}A_PMokWl)9 z8V@go>o7yS1yZxwBR#SU59E6CBXtFW<*5jQMf9YXM*8Vhq#w;k#y|SKtknTXTkM0x zxo(J?`3&*XpW($+JG`3u4ADqz2OwD72P(j#I}b$fO@ffdIPfcu0*~Afa2;b1?*&%j zF1Rf48JKw5fPw26LjzE;*1(P1pW*DqQXr-;0x^|OC&$PyAwIbH9*F4*fRO49;F2W( z4V4bqNHRd!Jw|w8#tJF6*C6xpjj?y$0{Mmh!nP#=q8xvZ*}rcWZ;V0d`WO_geut8k z?~u9B2k|o<@N}#ZJcg>k@#}lA`jQVO9XTLwI1M7&2nO|W;8Prd8^;)=z5%;fFK{aK z0=sNCFy3ebkE9X!{B;DXyT{?Ty`>I zL!d6`Hobn7;bpqN+xWlRuHBp-tJ;`=qLpDtL3nhmN5SLG;Pbr}T>8twuD1{@kOz7U zKV^cH`412`Krm=c!mWpsz^^(AH)IFlP2TY#`S0f(W*UidmixnABlG?WiQ{7}eL7MP z9)p$O*jEBp-S5EUb2jLi8)&S~(&o+q#0sn!tCc2Asc@ zg6)?Au;_RL1|MI8_J>$3>bcGG`qY!QPoc$PZ(XaBgzHF6t*sNC_BV-4Lu{-sHE zAgkE}7rB3H)W75VJ&+)AaEdwy@j>eUC;R`x;WJ$0<eyQh54v)}q&*9CTGK$Y zDG?sUuY%r_AAf4>WBWV4Eu8HCwa%%WhKQ{3lVc5?vxu&?03D)9@>+d=;`=;165m&9 z5MGrddV1`GTts7E|F_tGjKjP;m(IHDU7h*o9586h0PSWZ_agm)OY%Ax1^)O`V=LKx z|FeC^e&}R-c3}+?5dIw3-Hjb{e{5E``}HI?E^@a5{k5hOUY+PEV=0i(BR-(X`ERrT zFJB;>x7BG$S-stu04jBHU7oKuz$9oIc6N{&8yT*VLnrIf##xZjnS}0d%P0HaCs$9_ zji2Y?GJpGtCMoF*0E^(?^TcnBCBO79AJFFhx7&Y=1L=>6kpA-IKBsT)CYU1j@9iNq zHZn?TXHM2W(W9XA@CSH=e*bgs_KW`HyprALo($&#ViXbH@G7Qn!H4oYigVP$m} znm;arqt926P#*vRg+9=?LUNw`R|LZs1VaZ>Gact@L{Hi4kyx&Rb4;cGar;AScflfT z@*g#JNWv)CMXZ2j;1Za6FTgFt&fE7V;FjqWDCv!X?49o*t~LM`PE#PRJ_rIg`+!Tb z8{twrF!TK8(eIkC;y-SGWYaIOjF^HC?Thf@^*Fdije%R@GC00i1>3M?utc7RG4Y%S zy+^a4Z8ZZL#*?6^I|eeE!(i?>4T?G=Aa!d11QmPXx>Of};S=JA4~QN$05xm%f874q zwmpbw`~@LZI}lj14Sso>;F-ArE=gzXP$=+Yny14W4f|!8&mrOd?l7 zH)sVkk$OYTbpaF++vSkjN!ok{l&yY%fx{G-J{$ujoqmv1??Gas131Oo{^Rz)9z24Q z?}v~-Z~%E<_aUWy4?=2o!KY{&oO3q84$05vv1?!uvI5%vOQ7Mt0LqW%;ikEv7Sd(nAPqPJlGh51Boh z0u%dTxT)RqAGiNF7bJG=tE6`9jm0(pjsf3-ZE(r_33do?ETUGyAZQu1eUZ6Gc%$qv z2l7@3Z_Ivx1k(G6>Hgbxp<5FteUP9kQiB^zf`#MA|A_s^7+$sR5k=PSh8=5==Nn}1 zQ#QaV4w-vIe{`QLfhNKm)kkxnU^5G{$GkB@c%z5#=I$hj+?fF3zu7LJG6n)lV<2&B zyzcng|C7t(tW8mujGtY0uaLP%a+m>^!}tl#6EGw*xma?p$!<1S_}W{k-Qp-3(zH0k1!bkl{#B}z%?iLP`NF_k1l7o$Xs zlqaHOQsgdOB+50KVR9M2^II!V?b*XYalKzu)(7S(DD+ zyh7Lf>c4!vuR4cmirbo&KDU(So}7Evx&|Yc8|h;muDg!1t^HrMgIBflrpJUP@5eib zRnxyU*S=~B@wrJ&jWZ={uAb4v&vd-u>m`69wI;O=vMXP;cYbriRgU<-s^)mb_7=}W zY$~N))OjyH-+xi_Y~(_38oN%lc_)Plv@?Kgq!TW`ssknKYR*{dMKHoQZdw^8)hhm&y_ME_VT zei5AMZ3(tP__%%Oe+u79cyHsv=1Rt-o1>m&VzzIdjWfA1ctl^aG%Es$!XpnHE_o!u zkO-bCk^G48Mg(^u*tQt!Zse&t$T?8;ZQTsxnmE?tk=ZjHQQGvnq(Zm=8r}NqJ z$ z9pBSm%!_wyiTTgxCF4rbKKXfXh!yV(eU3p3*=wfBS}=YI&OH%h%Q<+-tM;V7Yg4cV zBQgSTxcn3v8t$RsN|~%JxB8)9h=aTq-)!`5z8>UD#P1;^!wYBj&PNWtUe)%?PYo#9 z8%ytJ92qN9&b=XhTW7L*BgifYul5Vcwyn47(sKU3^f!gaY&&L4mi=b(`Ni|aWtH4tOAErWjSB7C;W7GKk! zH{b5hxtJ;M3-O!G%3g_UC6TBWJ}{XG;bWKW^}xC805Uc!x$lG0-JaxymeSw$rblFo zj1{9f25pX^Z_D|I)1M10KZH4C9mIJHpY&|?C0WPi?}pAs59n@MjA<#s7$DfIKwJ8` z^u(1x^rPwJo{T_ENj%OS48fTn0@>Fb6sJ0HPUwGbHluGlk^ZMS{pV%$JQvbCH{=+G zw)8K21>sFCq>ocbHm9NSKJv~KAs{0TMzLPfTQ*Gch1K>TXs?-r0jnMHxu-Kc12)3m zF%7-?r%|U)#E3!G*yPUVF`178QO5Kxjiq;~TyG?qyPPOP6s5SKFljLkedp5B|3>;t z!sA;^PrxlV0ZxTmVSP9rZUsBw_2VuqIJzB%ajWsQix>J%j=`t8ne_5K53-!@OW}xuvM$v1e-$-^;aEsZI<2g2)mi~oL7gt}3ZFObX zR8x%gl?7OLC0}}TGdAZzkFm7yQu@=A6Pl&)6oudL(I>m{VUOKt)nS|TMMq31kp5Mh zE<5n~Cl*kc_o0XQmGG-Tzv_kOHd1(Q(r;7!{`NOk;{biC9kJ@a>+AqDQk>3=dJ$+2!0k-Otf;Jzo z#F&0s2%r9G)uwrx2`S#%wizpRG*tbn>Y_*WRfigD=rNUH4f|e6f60U1mNs5lL5YwWA?RG6seao#g5i#@_S5+RtQhJK>Pp4xh{RxXr$6%|d!cD&4Y?o9btKhoa zPd<1Jk=rWeez(B~rDkkkQ6k47Jgl~24(Ofy$4BwM+rMf&{*`%pD=+7i3V%d+JJ$3@ zoYM>78eT)s;~o~hsKtJ5-5o@3t&+#P4#=SnUx;z^afEg){LOaMOy3uC&|&Lq_pchq zw%#ymTeieFq?A?iZJnfY(Dm> zmQw21lO4;co0iCHA-wXA^bf=wsM`7?b%e4>5wP_}i!MD&aT)fCn=oNtlO0b(pE}{- zv1QZ@%V9_DaIj$+$54!(1CL1`sXgc5kMyhh4mC|hz|OnyU0aU1t1Ibq-G|%SdvKwi zEPRP+)RIl?sWOJ3 zPq6Jcby4Lna8GQ6!L2LA%(>|5gpW7b0 z{wwxU*Ni&c1V7G!$F`r~LQULe)m>ODrv~fRtYHcrO>hwU-S) zt~P9y&N$fh&o;~aAx*|ut|6A)mElvmG_E#o2fwlESTU+KlH7)iyeDOlLF?DP7K|VM zT{R{V*Nfbe4Gz5+OMc1tdJ^_WS|Z-54}y%^Ld5eTw~AVVh@%y%@4Z};H`QZ|qF}t9 z&fJ3r#%v8;>~-XME29nB{s?Sw)54Y02T@a9Ez7>}Y19nbW9Kph$>oO`x5Wzmcf~c8 z8yQ^%V$*OTsRB=cf~9udU6DpmUfVi%wX8OIUfbJ4|RjLb=p{5#|SapChISbhdxk$Eu`#NgQ96>y}&bWEHWXbxF^VF2_tjGZn%&^cCRP77? zarX8Q@-4UFyd@Z4driS-%&idYuW_8W%ykhQc6z!exmai9#<;hu?ku7jbi{hH+XB03_8?ww=GSY%=owU_g<`9U!GWYpNHa9PZOr*at zcKS^s$H&}}1p{$#jRj+%xzq^sIA5B~0r<3~{l&$*;JzagPoF-8Q(_>@Bi!+cn=!s* zp3CIqK*`dIzV_t?kU`x-hBg+J$JdgZ4abGtVDjWXjP(}dRHhI4(uKUvquIxRmiGP6 zXJcu~I(g3be|~`S`m6Y2*;MF}^|ne1!oYCGcK+5#NDD`FL;|d)M`7748|;r@zD$e- z^5aD;$CxU59Ex`?VqEKq-1SZ^?XRgk2KVeun3=p9Cf^6aAaNPA!)9ab+933sIUk>! zN2B}D47oNG?BF|Go9m1_g#LYP5b7`pX^fZl1`C$4na#|OFl78@-O_%1T`9sV@)3Og zAd)IiU{ggQjF+CmP~!@+5Er=KC`31Ma36F{fu%<&+H~9oJ>%mtCi<{*0LBj-h7C?% zP#YPD^c6aSrBsqRRH&|R4q3N?##&_Exr}YsOEJGJ14Cw?z<6iIKZa)!yrmlPd-z^+ zDV920+peh?HK|y}fr1%)=i@kh+1*STW!qa%)#l4}(w*}ru{TaOh#13@@r^SXqLH4( zxOKB0Ha=HSRZ}nbO&6Y&bvwp)29rxAb19fO!D7AK-mCT_uNJ5)E@nrNQGyNQ2g|5a zXl#6drRy)r`f}A>WF5UO_dAl28b9rfWR3)H^g;LZ-)&#jr_+fYdQ*y^N3$t!pEsU(swQw+f z$+|3Dew8tCvE)TMk`;N~c2)mp5pevLD*s}P!$nvuxB)Xq#uB#Y$rYc)EDy4bU*zSd!$vL_l~;;yYQPZ6xZx%nREdDDOUri{G8o39n= zdn@;qKf|umPmoy9gs2}L!FgQ+rgCj2)`enCx**^t*Tpp$V}AOL=jY_D=O)Q#o;F0$ zkM^M#EqrL3sCYuH}Wl zD!f&Cd#d$hDgDTZ*PZuQ{omZ^20i+SRi^Y1=^vKrTVIQy?dvBChmHIjKm5UsIpA%=#;aR} zO;itH-ui2uW5Jryx*02mB(JyY-WX%kiR+vXnYTQIxu)$9IHr~F%R^z#Vvink#?I|z zrJc<@%bfRT4EeGo#=vWpv;Qp4{}ow6tOGF#?k>dAapztQiV(Aw%%+-cb) z)ctf1ZS97l^GrQhZ&aWo*DT(lmiXyfA&&2G!xrYLCAklx*5@R16TYMN9%9_K$WfV4 z)H}>hO2+#y6hX_w0`^J4=*oOQ-xw!UQqTSM*<(DsSBtHSbWuWGdf#dj=DYR6+Nm9i zmX+=)8kg#i#>c;4MxsB)t)7D^(VqC!Z#q_|EWm?P3Y4W;GVj94KO-H2nUe#noRz`T)h zIGQk(TCP9Wa<1nSZkGM%R+5hS1>3Qb{I$uvV=$RP-KUolL&qG)yPfu--Dib(uYEWy zhW0_K#}~}?))Bb^jd^i4!3B@%b{^w=5@?lLJoPx$#^Ud`x%$2p5XBl;MBQ?7&IymGh8lG zmpROL3iXfoQN9c#|d8B(L4=t0R2xWc}0cuRiiCY@!*{hTcJR`p*bR zXh29}1IIvyeaw&4lk)!Q_j9j3e|(V7;2nicn91jtiRVq|&!i3zavK4WH(+31;{U2~ zzkF;})}yHT-`&Sl#`A(J89|<8m}zx`u4#3$h12yBFaIy{)zqHTr_X!&;6Lv3@#IUd NK9A-8OQnhX{{?OVn?wKr diff --git a/vs2010mfc/src/res/bgslibrary_vs2010_mfc.rc2 b/vs2010mfc/src/res/bgslibrary_vs2010_mfc.rc2 deleted file mode 100644 index e327fa85913f2210efffc3a01eb0194aadda29e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 826 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 - - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDemo2 - Win32 - - - ReleaseDemo2 - x64 - - - ReleaseDemo - Win32 - - - ReleaseDemo - x64 - - - Release - Win32 - - - Release - x64 - - - - {3B6BF763-9CDE-4859-ADD9-8EB7B282659F} - Win32Proj - bgslibrary - - - - Application - true - Unicode - v120 - - - Application - true - Unicode - v120 - - - Application - false - true - Unicode - v120 - - - Application - false - true - Unicode - v120 - - - Application - false - true - Unicode - v120 - - - Application - false - true - Unicode - v120 - - - Application - false - true - Unicode - v120 - - - Application - false - true - Unicode - v120 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - true - - - false - ..\ - $(Platform)\$(Configuration)\ - - - false - ../ - - - false - ..\ - Demo - $(Platform)\$(Configuration)\ - - - Demo - false - ../ - - - false - ..\ - Demo2 - $(Platform)\$(Configuration)\ - - - Demo2 - false - ../ - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - C:\OpenCV2.4.10\build\include;C:\OpenCV2.4.10\build\include\opencv;%(AdditionalIncludeDirectories) - MultiThreaded - - - Console - true - true - true - C:\OpenCV2.4.10\build\x86\vc12\staticlib\*.lib;comctl32.lib;VFW32.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - C:\OpenCV2.4.10\build\include;C:\OpenCV2.4.10\build\include\opencv;%(AdditionalIncludeDirectories) - MultiThreaded - - - Console - true - true - true - C:\OpenCV2.4.10\build\x64\vc12\staticlib\*.lib;comctl32.lib;VFW32.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - C:\OpenCV2.4.10\build\include;C:\OpenCV2.4.10\build\include\opencv;%(AdditionalIncludeDirectories) - MultiThreaded - - - Console - true - true - true - C:\OpenCV2.4.10\build\x86\vc12\staticlib\*.lib;comctl32.lib;VFW32.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - C:\OpenCV2.4.10\build\include;C:\OpenCV2.4.10\build\include\opencv;%(AdditionalIncludeDirectories) - MultiThreaded - - - Console - true - true - true - C:\OpenCV2.4.10\build\x64\vc12\staticlib\*.lib;comctl32.lib;VFW32.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - C:\OpenCV2.4.10\build\include;C:\OpenCV2.4.10\build\include\opencv;%(AdditionalIncludeDirectories) - MultiThreaded - - - Console - true - true - true - C:\OpenCV2.4.10\build\x86\vc12\staticlib\*.lib;comctl32.lib;VFW32.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - C:\OpenCV2.4.10\build\include;C:\OpenCV2.4.10\build\include\opencv;%(AdditionalIncludeDirectories) - MultiThreaded - - - Console - true - true - true - C:\OpenCV2.4.10\build\x64\vc12\staticlib\*.lib;comctl32.lib;VFW32.lib;%(AdditionalDependencies) - - - - - true - true - false - false - true - true - - - true - true - true - true - false - false - - - false - false - false - false - false - false - - - false - false - true - true - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - false - false - false - false - false - false - - - false - false - false - false - false - false - - - false - false - false - false - false - false - - - - - false - false - false - false - false - false - - - false - false - false - false - false - false - - - false - false - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - false - false - false - false - false - false - - - false - false - false - false - false - false - - - false - false - false - false - false - false - - - - - - - - - - - - \ No newline at end of file diff --git a/vs2013/bgslibrary.vcxproj.filters b/vs2013/bgslibrary.vcxproj.filters deleted file mode 100644 index ab0fbf0..0000000 --- a/vs2013/bgslibrary.vcxproj.filters +++ /dev/null @@ -1,644 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {8cb396e6-81b6-4db9-a1b0-5c2b7c122bd9} - - - {e1ab6d45-3486-42fa-8f51-69a300c0c173} - - - {7992fa8c-e616-4e72-b249-6ede4f4291b4} - - - {667f4048-d125-4453-9f0c-42f9abd4ed3a} - - - {89c4b817-936b-483c-abed-3e7e7c1fc427} - - - {c5e0f44c-6120-4906-917d-c8c8af3eafec} - - - {728fbe82-1489-4878-89ea-a62ba0932204} - - - {6b017402-c47a-49a4-8f57-b5db863e1bde} - - - {e25c1e03-530d-4c7a-b776-26bf17595213} - - - {53f2c4fb-9468-44ce-b76e-e25ea018c084} - - - {23f1cd4a-e9b2-4338-a5e7-128f451d3c89} - - - {52a9f254-d817-4577-96c2-0b3b0a9527b7} - - - {0494c5d4-b4bb-421c-b032-176903ba8e1b} - - - {87961eee-b843-45bd-b642-9dcd9d78b661} - - - {cd33a41f-6151-46a5-95b6-b79022786144} - - - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\db - - - Header Files\package_bgs\db - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\sjn - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files\demo - - - Header Files\package_analysis - - - Source Files\demo - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\ae - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\av - - - Header Files\package_bgs\db - - - Header Files\package_bgs\db - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\dp - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\jmo - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\lb - - - Header Files\package_bgs\sjn - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Header Files\package_bgs\tb - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Header Files\package_analysis - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\bl - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\ck - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - Header Files\package_bgs\pl - - - \ No newline at end of file diff --git a/vs2013/bgslibrary.vcxproj.user b/vs2013/bgslibrary.vcxproj.user deleted file mode 100644 index 02146ba..0000000 --- a/vs2013/bgslibrary.vcxproj.user +++ /dev/null @@ -1,34 +0,0 @@ - - - - ../ - WindowsLocalDebugger - -uf=true -fn=dataset/video.avi - - - ../ - WindowsLocalDebugger - -uf=true -fn=dataset/video.avi - - - ../ - WindowsLocalDebugger - - - - - ../ - WindowsLocalDebugger - - - - ../ - WindowsLocalDebugger - - - - ../ - WindowsLocalDebugger - - - \ No newline at end of file diff --git a/vs2013mfc/.gitignore b/vs2013mfc/.gitignore deleted file mode 100644 index fc99052..0000000 --- a/vs2013mfc/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.exe -*.pdb -*.dll \ No newline at end of file diff --git a/vs2013mfc/config/AdaptiveBackgroundLearning.xml b/vs2013mfc/config/AdaptiveBackgroundLearning.xml deleted file mode 100644 index 313e1d4..0000000 --- a/vs2013mfc/config/AdaptiveBackgroundLearning.xml +++ /dev/null @@ -1,9 +0,0 @@ - - -5.0000000000000003e-002 --1 -1 -15 -0 -0 - diff --git a/vs2013mfc/config/AdaptiveSelectiveBackgroundLearning.xml b/vs2013mfc/config/AdaptiveSelectiveBackgroundLearning.xml deleted file mode 100644 index f175558..0000000 --- a/vs2013mfc/config/AdaptiveSelectiveBackgroundLearning.xml +++ /dev/null @@ -1,8 +0,0 @@ - - -90 -5.0000000000000003e-002 -5.0000000000000003e-002 -25 -0 - diff --git a/vs2013mfc/config/DPAdaptiveMedianBGS.xml b/vs2013mfc/config/DPAdaptiveMedianBGS.xml deleted file mode 100644 index 8d09e85..0000000 --- a/vs2013mfc/config/DPAdaptiveMedianBGS.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -40 -7 -30 -0 - diff --git a/vs2013mfc/config/DPEigenbackgroundBGS.xml b/vs2013mfc/config/DPEigenbackgroundBGS.xml deleted file mode 100644 index d610426..0000000 --- a/vs2013mfc/config/DPEigenbackgroundBGS.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -225 -20 -10 -0 - diff --git a/vs2013mfc/config/DPGrimsonGMMBGS.xml b/vs2013mfc/config/DPGrimsonGMMBGS.xml deleted file mode 100644 index 8ade44f..0000000 --- a/vs2013mfc/config/DPGrimsonGMMBGS.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -9. -1.0000000000000000e-002 -3 -0 - diff --git a/vs2013mfc/config/DPMeanBGS.xml b/vs2013mfc/config/DPMeanBGS.xml deleted file mode 100644 index 9bd59a8..0000000 --- a/vs2013mfc/config/DPMeanBGS.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -2700 -9.9999999747524271e-007 -30 -0 - diff --git a/vs2013mfc/config/DPPratiMediodBGS.xml b/vs2013mfc/config/DPPratiMediodBGS.xml deleted file mode 100644 index fdf20b4..0000000 --- a/vs2013mfc/config/DPPratiMediodBGS.xml +++ /dev/null @@ -1,8 +0,0 @@ - - -30 -5 -16 -5 -0 - diff --git a/vs2013mfc/config/DPTextureBGS.xml b/vs2013mfc/config/DPTextureBGS.xml deleted file mode 100644 index 0d63e78..0000000 --- a/vs2013mfc/config/DPTextureBGS.xml +++ /dev/null @@ -1,4 +0,0 @@ - - -0 - diff --git a/vs2013mfc/config/DPWrenGABGS.xml b/vs2013mfc/config/DPWrenGABGS.xml deleted file mode 100644 index 7eaa78b..0000000 --- a/vs2013mfc/config/DPWrenGABGS.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -1.2250000000000000e+001 -4.9999998882412910e-003 -30 -0 - diff --git a/vs2013mfc/config/DPZivkovicAGMMBGS.xml b/vs2013mfc/config/DPZivkovicAGMMBGS.xml deleted file mode 100644 index 98ee82a..0000000 --- a/vs2013mfc/config/DPZivkovicAGMMBGS.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -25. -1.0000000474974513e-003 -3 -0 - diff --git a/vs2013mfc/config/FrameDifferenceBGS.xml b/vs2013mfc/config/FrameDifferenceBGS.xml deleted file mode 100644 index 943a6c9..0000000 --- a/vs2013mfc/config/FrameDifferenceBGS.xml +++ /dev/null @@ -1,6 +0,0 @@ - - -1 -15 -0 - diff --git a/vs2013mfc/config/FuzzyChoquetIntegral.xml b/vs2013mfc/config/FuzzyChoquetIntegral.xml deleted file mode 100644 index 22074aa..0000000 --- a/vs2013mfc/config/FuzzyChoquetIntegral.xml +++ /dev/null @@ -1,11 +0,0 @@ - - -0 -10 -1.0000000000000001e-001 -1.0000000000000000e-002 -1 - -1 -6.7000000000000004e-001 - diff --git a/vs2013mfc/config/FuzzySugenoIntegral.xml b/vs2013mfc/config/FuzzySugenoIntegral.xml deleted file mode 100644 index 22074aa..0000000 --- a/vs2013mfc/config/FuzzySugenoIntegral.xml +++ /dev/null @@ -1,11 +0,0 @@ - - -0 -10 -1.0000000000000001e-001 -1.0000000000000000e-002 -1 - -1 -6.7000000000000004e-001 - diff --git a/vs2013mfc/config/GMG.xml b/vs2013mfc/config/GMG.xml deleted file mode 100644 index e3ebdc7..0000000 --- a/vs2013mfc/config/GMG.xml +++ /dev/null @@ -1,6 +0,0 @@ - - -20 -6.9999999999999996e-001 -0 - diff --git a/vs2013mfc/config/IndependentMultimodalBGS.xml b/vs2013mfc/config/IndependentMultimodalBGS.xml deleted file mode 100644 index 0d63e78..0000000 --- a/vs2013mfc/config/IndependentMultimodalBGS.xml +++ /dev/null @@ -1,4 +0,0 @@ - - -0 - diff --git a/vs2013mfc/config/KDE.xml b/vs2013mfc/config/KDE.xml deleted file mode 100644 index c9b7402..0000000 --- a/vs2013mfc/config/KDE.xml +++ /dev/null @@ -1,11 +0,0 @@ - - -10 -50 -100 -1 -1 -9.9999999999999995e-008 -2.9999999999999999e-001 -0 - diff --git a/vs2013mfc/config/LBAdaptiveSOM.xml b/vs2013mfc/config/LBAdaptiveSOM.xml deleted file mode 100644 index 94b2570..0000000 --- a/vs2013mfc/config/LBAdaptiveSOM.xml +++ /dev/null @@ -1,9 +0,0 @@ - - -75 -245 -62 -255 -55 -0 - diff --git a/vs2013mfc/config/LBFuzzyAdaptiveSOM.xml b/vs2013mfc/config/LBFuzzyAdaptiveSOM.xml deleted file mode 100644 index 7168563..0000000 --- a/vs2013mfc/config/LBFuzzyAdaptiveSOM.xml +++ /dev/null @@ -1,9 +0,0 @@ - - -90 -240 -38 -255 -81 -0 - diff --git a/vs2013mfc/config/LBFuzzyGaussian.xml b/vs2013mfc/config/LBFuzzyGaussian.xml deleted file mode 100644 index 18635a1..0000000 --- a/vs2013mfc/config/LBFuzzyGaussian.xml +++ /dev/null @@ -1,8 +0,0 @@ - - -72 -162 -49 -195 -0 - diff --git a/vs2013mfc/config/LBMixtureOfGaussians.xml b/vs2013mfc/config/LBMixtureOfGaussians.xml deleted file mode 100644 index 2273720..0000000 --- a/vs2013mfc/config/LBMixtureOfGaussians.xml +++ /dev/null @@ -1,8 +0,0 @@ - - -81 -83 -59 -206 -0 - diff --git a/vs2013mfc/config/LBSimpleGaussian.xml b/vs2013mfc/config/LBSimpleGaussian.xml deleted file mode 100644 index a803f3f..0000000 --- a/vs2013mfc/config/LBSimpleGaussian.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -66 -162 -18 -0 - diff --git a/vs2013mfc/config/LOBSTERBGS.xml b/vs2013mfc/config/LOBSTERBGS.xml deleted file mode 100644 index c17b551..0000000 --- a/vs2013mfc/config/LOBSTERBGS.xml +++ /dev/null @@ -1,10 +0,0 @@ - - -3.6500000953674316e-001 -0 -4 -30 -35 -2 -0 - diff --git a/vs2013mfc/config/MixtureOfGaussianV1BGS.xml b/vs2013mfc/config/MixtureOfGaussianV1BGS.xml deleted file mode 100644 index 1e09ceb..0000000 --- a/vs2013mfc/config/MixtureOfGaussianV1BGS.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -5.0000000000000003e-002 -1 -15 -0 - diff --git a/vs2013mfc/config/MixtureOfGaussianV2BGS.xml b/vs2013mfc/config/MixtureOfGaussianV2BGS.xml deleted file mode 100644 index 1e09ceb..0000000 --- a/vs2013mfc/config/MixtureOfGaussianV2BGS.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -5.0000000000000003e-002 -1 -15 -0 - diff --git a/vs2013mfc/config/MultiCueBGS.xml b/vs2013mfc/config/MultiCueBGS.xml deleted file mode 100644 index 0d63e78..0000000 --- a/vs2013mfc/config/MultiCueBGS.xml +++ /dev/null @@ -1,4 +0,0 @@ - - -0 - diff --git a/vs2013mfc/config/MultiLayerBGS.xml b/vs2013mfc/config/MultiLayerBGS.xml deleted file mode 100644 index 9b803db..0000000 --- a/vs2013mfc/config/MultiLayerBGS.xml +++ /dev/null @@ -1,31 +0,0 @@ - - -"./models/MultiLayerBGSModel.yml" -0 -0 -1 -0 -1 -5 -5. -5.0000000000000000e-001 -6.0000002384185791e-001 -4 -3. -2.0000000298023224e-001 -2.0000000298023224e-001 -3 -1.7453293502330780e-001 -6.0000002384185791e-001 -1.2000000476837158e+000 -3. -1.0000000149011612e-001 -1.0000000149011612e-001 -5.0000000000000000e-001 -5.0000000000000000e-001 -5.0000000745058060e-002 -9.9999997764825821e-003 -9.9999997764825821e-003 -1.0000000474974513e-003 -0 - diff --git a/vs2013mfc/config/SigmaDeltaBGS.xml b/vs2013mfc/config/SigmaDeltaBGS.xml deleted file mode 100644 index f1b2b2f..0000000 --- a/vs2013mfc/config/SigmaDeltaBGS.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -1 -15 -255 -0 - diff --git a/vs2013mfc/config/StaticFrameDifferenceBGS.xml b/vs2013mfc/config/StaticFrameDifferenceBGS.xml deleted file mode 100644 index 943a6c9..0000000 --- a/vs2013mfc/config/StaticFrameDifferenceBGS.xml +++ /dev/null @@ -1,6 +0,0 @@ - - -1 -15 -0 - diff --git a/vs2013mfc/config/SuBSENSEBGS.xml b/vs2013mfc/config/SuBSENSEBGS.xml deleted file mode 100644 index 05509c1..0000000 --- a/vs2013mfc/config/SuBSENSEBGS.xml +++ /dev/null @@ -1,10 +0,0 @@ - - -3.3300000429153442e-001 -3 -30 -50 -2 -100 -0 - diff --git a/vs2013mfc/config/T2FGMM_UM.xml b/vs2013mfc/config/T2FGMM_UM.xml deleted file mode 100644 index d2f1054..0000000 --- a/vs2013mfc/config/T2FGMM_UM.xml +++ /dev/null @@ -1,9 +0,0 @@ - - -9. -1.0000000000000000e-002 -1.5000000000000000e+000 -6.0000002384185791e-001 -3 -0 - diff --git a/vs2013mfc/config/T2FGMM_UV.xml b/vs2013mfc/config/T2FGMM_UV.xml deleted file mode 100644 index d2f1054..0000000 --- a/vs2013mfc/config/T2FGMM_UV.xml +++ /dev/null @@ -1,9 +0,0 @@ - - -9. -1.0000000000000000e-002 -1.5000000000000000e+000 -6.0000002384185791e-001 -3 -0 - diff --git a/vs2013mfc/config/T2FMRF_UM.xml b/vs2013mfc/config/T2FMRF_UM.xml deleted file mode 100644 index 9a65c57..0000000 --- a/vs2013mfc/config/T2FMRF_UM.xml +++ /dev/null @@ -1,9 +0,0 @@ - - -9. -1.0000000000000000e-002 -2. -8.9999997615814209e-001 -3 -0 - diff --git a/vs2013mfc/config/T2FMRF_UV.xml b/vs2013mfc/config/T2FMRF_UV.xml deleted file mode 100644 index 9a65c57..0000000 --- a/vs2013mfc/config/T2FMRF_UV.xml +++ /dev/null @@ -1,9 +0,0 @@ - - -9. -1.0000000000000000e-002 -2. -8.9999997615814209e-001 -3 -0 - diff --git a/vs2013mfc/config/VuMeter.xml b/vs2013mfc/config/VuMeter.xml deleted file mode 100644 index d28fda7..0000000 --- a/vs2013mfc/config/VuMeter.xml +++ /dev/null @@ -1,8 +0,0 @@ - - -1 -8 -9.9500000000000000e-001 -2.9999999999999999e-002 -0 - diff --git a/vs2013mfc/config/WeightedMovingMeanBGS.xml b/vs2013mfc/config/WeightedMovingMeanBGS.xml deleted file mode 100644 index 008ad74..0000000 --- a/vs2013mfc/config/WeightedMovingMeanBGS.xml +++ /dev/null @@ -1,8 +0,0 @@ - - -1 -1 -15 -0 -0 - diff --git a/vs2013mfc/config/WeightedMovingVarianceBGS.xml b/vs2013mfc/config/WeightedMovingVarianceBGS.xml deleted file mode 100644 index d9de1d4..0000000 --- a/vs2013mfc/config/WeightedMovingVarianceBGS.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -1 -1 -15 -0 - diff --git a/vs2013mfc/dataset/video.avi b/vs2013mfc/dataset/video.avi deleted file mode 100644 index a29f00658be7d05b10344859e6bee9a501a7a2b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1049784 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�>ripFnM&(^VoG($ z$r8j%TYAh$6oQ;{?WNND50TV>W~-L%`^ zP7$i>hqm9)*YMA4`O2c~VZ-W3IIt?CCxmrM?#*;qm|N{IuBYSbby500c8rvzrllQ- zuVE#_CVMuY+=OX@4{vL~ax7muei{|p$bDV^WteZyJ(U7J?awDRGRpOc)%-Pli|4i0 zZ2$Z2(^?sCec2RRkVO9Sg0AU6&&+W&SB*m^n>f2yh!C7|Jr3a2{`7zjPc z5bs;6ZJnynzuc|q4Yu@JxZh>{zDwKjJp2vt$pTQi^xHrYfdsxJ*9%vED0eL^WxVdi z5%-yG^d{2Dl zLRF~R#NrvBs3Fz8L~DmDdU?zEV85HCOCE#nuQ$qaHVw4_d!7tn8;S)k|BJ3$fr4?5 z_i2xrzi0QQK(bX(xT4oEqN2{iDdx8w&{>e-uDX6&I0@MpOT%b0y_T9CPfZFnjycI~ z(oe0_o6r0uMlC~bs$zOEG;|4qiW4tMa|~|?J~LIlocr)dxM{)haTu8n1!yIN9;@*- z(ebzpQI{jr!NzZBUeL#JvoMd{Eg6S?7Of2e9c#?uh7C~ z#=I;X#94U{Ip34yEd{h|F|;}2lnXVuH2vIKK+%|3?HLZ{jKFu1w*i*t6)<-lDrNw6 z(sg%&nrQKmX+xe{B%IF(uTjuYCe-0I{ZzSWHJ4Acjfe?EfrlnE%-MpPrks7vrMcsT z-ErfV@n6r&v)g^WbOPB#`AI%c55tf@rz`3&nLQpD4Tr$t(>6v`g40d}j&id|fGf|# zkx}kN7$|zqcK0GO6aC$P_lU!AFw1^Rc9^x^<;Q!Jj#wc^nm!V=Xf5*f_ZAOfv7M74 zD*EM?4XD9C{ayRwR+M?td?Rf6$m&_~VZ|F+fpBjTD&8yYLFC7-1{2cxAR%oR7ibx4oPbG~TqeC05%n!IDWNcs2Np8GZ*q{Z>U%zYVca>imQ zZskWJy=$H&%*StDopVXB!)0_Ajj5y2K0Z?N|8talq5S_7$z|@NYcs;ZQ}>hmUx45u zLR$xXGw4;b_GN%!kl#|UzrR1B?eeqV;!^N$0->T%tD5EwG%(`CtxI63wB>o?_221v ztjeXtzR04E^Lk4L?=5Du2q4l;%K!+k_WoI-qT$gSGiK3$aZl8hn~Fod1%uqGdjW-* z)jBH)G9##**cfwLG4^Nq!IZ>iBof1#?_ncWV32CVTdu91ch(q4dHA>xhnb=Xux7t_ zk>3I=28mpXF|<&(>27w#7yj*^C$Ae=74I*#o8Oet5YGV0slGL*M;Ts4;!@|s5Tmmib^xM zTwSOsZB(sD22G~fa#XcP!U8+vcB9r6JbJo3bk{VWRWPr-Zwal*&28V(wojZ%979yI z^fwYCe{eDCJQrwU<08Iaki{|1VOpu_Ta+)07Mcq^#aAHLwXU~I87GmJKSMMxYDKZZ zCyzd;|J&Xj0}Vj_O%!RR;q}T*xkVjQp<7~KU94KN&u-?o5@&*Ah`jp^PdE^^OHzR* z>&Nz87M_yDrg!CJKd&0F8Jg%A^Hna~*T>QbRKA|n;&zlSGaxKIvrPl}Z9L};3Y+NW z#ihfC*R{4g`{i?zXao$#XwHO?=6!em@KD&@WMSb1o=NXjqMiDWxx$fGPtYm_Jm7C@ zCtBUQCITnNZ=0&UspmcqcPsOJN5lAyWeUqi-Yex9EtP${HaqpYg5!==IcTmMyFzP( zzKn5*;1cl<1E2Uxv~ttAN`UUv zo6R<7*b)p6OFIh}E$2h<+2phW8}Fcd7Gq-1&2_y(Hw_&s8=eVZ>zJHRH^r>?$9#Y@ zGC&K6(Ky2KXx4DjF4QSV^4M}8 z1x(J1WKafjV1FWHYU>0lJKdV#UPse@$b7;r2H17$)6x;ct4n5kR<1MN_i?x9&)UlHD$a5@=;!PR<%(${~NGS}E z#JQnjwXS4y5&Nr!e<|A+CnhV-j9y)Q7|$PP{HkHfqNI_r&Yr$L6rVF_Q@iYX|OyS)3*b$ZXt`J9VC((*T$vV(1MBPkkjU$rFr8x+Rg{a{=~a4_^>me!o|s- zVx0m?+g7Fed>>%MAb{lqfB|1`r2;(%PAV$RX_<{T3Kk_FJkAJXxRWTzpn?Vlw)+bR zb6K~bL#_B_0Lh}b%=rS?uYbA)CWeZo5CLm300R{6sz`Knf>0q5-v=Qp$yM4@eylx# zL6^E=+B6`Yd-tft{6Kc3K>1GmT1f=x*;XSUX+Wi>Jx&dCNcGg;4a`DM4V>(- zu7VtHv(mpPm>E1YBb;kliyfhtG304(R~B5m@Ym?;{j9r&N7zHI#LVx1Ay7a zx!n+|R0&uy?y>--#W)Mya5WIE)E$c=H)@6aL$^~ETZG1~R+!^tAsFCwW)}(pP_}PF zX*8WSRJ_QuTT4--$WmA5O3Rj@kWi0IC8pi~gUaj?fXWBTi9cq11*BG7x5z?H)|L&x zM6QZw|3!Ty@1&JWS>{q`s@AUT6?K^=hAop2^g#NKcks6PL9-LfPFhWqe}3shkcC)R zo@oa|pMs;r#Lzovbg(;`{~Z~L+i&mp2SGxAS|~CLyQgy0nk{PfI`2@jTbf zYiL>6GS_q%!rmtl%@JnZM5cVj5oi1U4H-)u?-IKmOZy!PJ6WQ9EFBU!Uk^28J6!A* z7&uw;IQZMYf0bxaL>ia0EMx=t4L#s}*8*4$#7~HpX+=2z9}&WK5i~aou$dwN&H*IJ z$w5Pm3}_nsfOYRJH1?R06NZy*-cCa;>7Q6uKgnK%0(hfqz~A&=)X2VqH1MU|qQx)` zR!|Uu@?r`_&%KopBHg!7^iIEy37!$QCu%$n17OcXUQvh?&rYE+gUVBsX@!~ zy(B=tYS}XL0MMEg0Y3vyfqKBmc^m5$$1zKXK=|GdDXR76j1mj4;Ie`&zwmHsN;$&L z$2op0Z#0|#tj2eO?54=*=fxDIJEa> zA1SOhzVfem^@Q-U>Dl0sfxvMRnE3A?kHwe5@~>Xu4IZ|Ex#@XOCEM8g#ccFPJnAnl z#{jE&wj+2aF063r&lORl4bidz8lXi{v{d)vsueR&l~!@3Z2*1YU#lD1TJD*$jqjlJE`9*@)_--s?TcL){2S3&h^tgOz$lDHnJBa7e74!otO?mzU5t%!mrNO zmzZe?Sb1^Oi4ng^`89#>nuu@Fa6ix_qV8Z?_G<+}`0Xo|;j9cP;S@`sHX?or;=CDj zM4TjKxIl>e~xrHP)i!c)OdvS{I$kk2LWjG-6<%}{O}$^5ma{L*h} z#!UlvKuUhDp>!V7&%M8X5iMAM+@TI}ItmDuEwL=`Kh<9u1>S-;x0kz#Sip+Q$T@%w ze-AACguom^`)=wY!*y3v3ceFbcqSi*VqV(Xxg?vFKs#s2X8f?caf4b5)TOZnSI(zTFV z0gT-!E)McHx4`7DZiTy^8_2@&@YAhSY8<2@?<7FWqoD78F4z23P|E zX!m-Ezf=$Pq1Gp6`SHBZt(2I<&nnk=*9spB#W8-7%MsvvdH zm`JGZxKnUcth{dga_G}p$?2bj-(|L!OjgWX44dEPIRAemXbt;yAB?ZDouCmW~a$bx0<-*YP<1gzL>ay?tMJuT<7Nr0J5v0#fNn;^ByOt>LYdb1%&GQQ?lq7jD6n zA1njUZ1YP3fYLN-FKHGOv_bH+Q-~-!I9+k)awOL zG&#d!(%sA&rr`3Sd((Skr~hdRZn6Gf1#j%lNQ$hNJnm1<+0|_!mMdzvFA(Fqv|;k2 z!L&i{&I?gT{`1(%+NcvG&igEXHyxY+op+zG5f6@_xbbX_dh3robF^H1IDI_T!{5;n zLpjq42|0=&@2Tx0;w_;aSmm26z4uA%5iHgZR>H5`XlSFs$A35tKIYUmyKmH(KU9)< zpApTV-}jrP|F80Ai`as?#k|@Tk`JtVD(t^iKMHERW}|pjice4|D;Y^j)RFJ-Xaaqw zP@CVJ<%rm-c?N_C9y@=ge713WXU97xGWWsy44L}o!_M5HVAl~dSKNJpsqITcrd=btc6!!8> z3FU=7(+n$+?B(#|F!qVFu;^@x?E z`_$j-tJ>S&o*~7*Znmt7u{S)LG-RrLOt^_2cMDt)ap`uX%i!~)wsoBG=y0o8irMclZQJCQvQe=SWADxYo~0n%{(*S-FxDp=<4**z_Y)?wk>u zsj1{xC;969csMX2%zt-dq|)0lFkbTXsJnck;@|C$izOzOToevs-W~32&???JdHYhD z+oS`28q5CR_dy|r1LiZ&7#@D1(ete(-wN=d>@-fen1#~nOMN0y41S~`dV`MQMT_Xi zaoa8uX20ebmQy|M+hm^;#WG(z|DrPLl~cmkNYW)`xu^2aC%j6x{o0aLQTk4CLH8BI z&rctIJI=l>R#?e>=YnkQVmSB!pw#Oxb zasj2GL)OdNOyu???Q|fJ4GA4Jl-Oi}udgP;8h&CHx$vOF`gGI8s_E59j+>RK$2wlb z3?IDI*q%HQZum?aG>}cFShsHWSFqwFFYn}Rb7S~mYd_3?<|XzgVP|L7dG6e2_h5jSJa->|Xe zeJX97jEpR5xMwF%8qh)ugnh_YDy}U*gCr8zU~uA^we>BLe@~)fR~}Y)to|l#Qspst zMctAddJCjI!7a&U@k;E*+Ni1)$h{~SuUhLkSGbk}z+_g9N;UMh96RuMG z!=5|_vOKOQ>^1xT!J-w38sTexJsjl(TV*86wITx>hx{_k1cmwv@?c_LtJ6*->@4iKLZzaqBBL?5| z6?m!DBvzKFx%YNx$j`C=Q!_86-7T|`r4c|~vB<5e*h@_O?F}E|XsL9tRxeO(J;UK= z!l@>GpBNUlhsL4uE_K{<^5#s2C7eo^@1FRqNsWkGxXhYnEoHo{Y&@TvJ4iD&)vbRi zC9=_KKW{+uW`|gr$Xl)KidXRXyYo}?G$B9ppZiO5At^rzXoIu!K zg`UT5-!v5-@+*vcL*Av7>J7!b^8M5NuriBBC@bYvx$vL#fuKZ0pnBDeqpHNUC89Zd z>uy!;L2%q*$FRe@Bb(}9yHT~oXR+b)ku@?P%u8*^`u0aN4>sCP8yUNx?<8soS^9Bj zXX*NC37*5r0Mp95c}WN{Odc6 zyHj_I4B##7V4Y7Ko8OQ+!}oX({SryC!~5TUo;Why*SqY%`zkBZASE|~{md%LYN+kg zqdZJUG;&g-2OnrUBJ*@)UEtDQevK)=h8A^kLSZvr2OmEc^Z^AWij0&NIu{~B#`xV%1%8bgC5A67jFtWKdGm}eU ziSA!Fv&Kw6mQY0!vxsqlnSsiE^)V0{6oaOZ?VZ+ruB3d&O6TxSi05XJCS{)_d@^d` zgWm@K)APr>)ofs(LxkIsK^ZjwVSvI0l{NH^jGs2*0o5#jSW*C#bYzalUC}Fq$8&4} zg^S%CzLV!A6iEd1qW>xHVV#lCJrvd+$G;_EH$T)d3)flTT`PH5#zQ;&qwFX!H)SGf zp)7&rsl?ghh~Gf^Lg;Wnmy>a6Mt07^9SQY``#}v@s>@r?1?ToWhXY5NyiV^Z_X2YD z)d+eEdQ_=0pzQ6@NCvGfQ#15HITL$W&P`zCU>uYe5AHD`?Q4%1F_?U%~HlZ-93lU1h$wiCAdWD3b2jm$9i|vUK zMgf6MhKH}RO{~!liHcKM@24%R%Zd(uT~u#wyzVSqxPVxB&NaPNxH7)H%vyM0O)91? zYNzV6EL%7{c(DDhcmu0u80zLJhbOsnelQjk(B2j%Tl7WNpwN0GDb#29W!)enEUJE5 zzt{!_<-7%tFi5X7Y4_v_ggkezyc@DYF10&RpeeE#tv9QsD2}8V`}A0C(C}j@i$D{q z&hLY^)_ozW8*%Wydj{&8Y#aTn*`;jR1l6+SspSm~O{BA+nn$F!ys-ZR_HP9lBR1NL zx5r45v-}@2%d~zTNS2*>z!W)SPT=O9yJXakpOmol8pTPix7Lyk->drY)at4mSv4S< zo_%!TCE*5j+FKnAy=k&W)Z{PeLGxz~74GY@s z?q_L@wK2wx&k;*5duT>b#2&p?F4%BtI~b`bW@m2B zJ@q-=uR;o6li23(^YaLOAe|Z3{jn{(Ir;f&f8=>*j_T4MWy6ssPA)L6owZoG#{Rat zLQEy+HUADow^VN+&0G(D?*HxI-z!@SH~T>Uw3%sf%%$_!*_nkmo|$aGC;k*vKm|0b z;{mk46oG_SGL1^HlU6_d&)6uaglENPKv?>;0DCQey5Kic?(W&VDC*XJE; zB!~m1x<5txh<}fbsx_!ZXkEx%dHrLw5gr*7PFAvPvYFKTnJV|x7hy0{wpZtrMiXYS;9)iRxgUFlzE^~>Q5=T8qWhQsG0YCQJJ!@DZ_)7?dfe0gIv`Kfz(TUw600t%U zeh>~*H)SR#?e8%WaZ;)Q%zeWna3PK%#{%O;ca{8`)xQLnVt}a{3-; zy<^5R`}PNyBpY_`lR9w{WpwvF?dWZ{Iy~a(Fw(7+X>7>W|3nN+t0uvul`=c@QR#FDJMr*Z_`2v-oMj_~tA0x3jf=>52gPr_7IjfD_YT*tL0uC1du^)*SK24J?+uZEx|vIk z{RiK|MC(rX|8(0i;W_WLJQRi50(Q2mak)!J_rP_h$@x6Q<^3SNa&>}e7Y4PtKKYY2v&W`i&`>K%kV?{X^vK%O%CCd0pvMz>cP>lDr z^1&V=q}kmV~H0VR#Qh~8L1*ekSYxYL@Z2p zzVh=JRmb3KJy4=5q3q=$%0;`pzH+3K!+Wv!OaAt)zTH*Y$?=CPKXcG<`2 zmlhu8>uqxTm$U08uS4n{_D;XNE(ejv2SCqcgml2u)y|;IqQ^qwnzQZuR`<3C+8i6{ zp|Be19tAi?EVZX)($*?VLNR=EwejX~tt)7^oRHvCHY|>=^~kPbilkLnSk(BT8UYIW z^6uA@Fh=@WuG;WrTC%)*8t=Ka0V0~SEW@{)`ts96?JK$GvW>OsMKIxV-*Il#ao2Zy zAAP~G>$(Ml$#=8w;&rss>M}J=$uoofXiV^fIc>Sh4q}r&g$X}Vr312%rsh3fU>RTt z-Xj`a(zt_My=Cp?xXH{GT%PYOO%BEc7gBHZB^iOq6@06;Hy{xfFFe#NOT#AHlD(z% z7n5`RI9i?Bs4XT?6!p^_)Y78fmlncVGRuV~92U=GEtQWB{6w4NS{ZsHPi)xxW#8rz z=eRD(I~_-@fbrvs=zAGn2kv0w)5G@@-B*>SoAu?3?r*K~(6?0QI6zZc49i#AP(dEV zR@JP$XoSI`3H@~9yeVBfq{~%|os^6)`x*51BFHtdX@5Yi{JqUFRX~0cMX;Z$A|>HO zBz}FGoAB$6SOoqVe{%W1Ie5vm|A(E!?T<2pe?4>xt<1e2KuL%2ya61P_(Nc(DoIX( zLQ#DI)AB0=IzIr^C>51Q&b=HikIg`wn{(hY;6+2r$}qIyG6e}ObMVr3wd=k>?%Pl` zG{PLa?VjB#{L%mRuRj?xbmgK~DSSx&y>5M&AmDhy8hXS zo`)oKG0^$EX!bd4;BFmLGBwO0@t|kDoi%hIO&1hKA4?>kCMF##O~c1>*x|^~C$dRG z!p&1eT=dN6eG7NC0I$kDq3fgNeD}TdjiwfW2cXY+VCwl9w7hXu;Hx@`iFKo>2)@wQ$mamv%Hc71Os|Y#oeePPP zy{)0#h$|Nk`(rFulap5SyMN6}aE3x*fsAMc_IHxF*~ueXuv7a~YBwZ3?V?{&ccOF9 z;~hP~ik_k3Qfn^-s}$>q!JnDxqfOw5eXj)<%7ysr6m3yF4-yg9gTVoTE+)s_%Z4;q zCHlE%X?_#b$y0On|9&9ux;;1c_a}jkTndUee*D?=HTAryzmU$(@RJ>#qQ+SBM%u(w z*=lhZow?aM^Pss0V(BS&>ik&n?TJ3mOTw_&$xdjx7mL~Y>cjgtPeo0y+IRWYHvaj3 zI=^avCVz8p^(?#gMf3XDqJK^-(X2C>4xQx$5H3>hj|en4dGMY|r>dda&$0_e7dHAk z`6dnHf|>U+2QdSkT};+(sNF=miKKkEdz-M;#T2b0^@iVoE6$XmyB(7J5Tay|^x;#S zIjqD_bOi zM}$-Q6Z&xh6rIUEP4Z#_8u+Gi{cF_dHcu*_M(eHt)hhO~E;H-elD%%ae9Ie6%aIXo zr^pa4$qZoR;@nnV1ilDJg~Ny(&pM0nrcX$c>|y{gA05{(qXNqU!Al=&AP6PDN0$sp zl!lPoag>5VY0TtLF*LBmv?y3O@>~D zb?f`b*V-LRHs<5I=8*0&xQpCL8|Xo%7$@(<31_KiAHYvx zm3!$7|4JYCt}Cvit4w!G8IQ*Lzc8X|6(oQz)j!;Iw}n+RsC8vS(@O6H-+~U>_9#8^ z+ZP^M)?|yi*P4BjXZ82r&fg@>x043SJ`FaLZzw%@ZF|pBF~>UlpDwq-H(m_nRCCwE zh!71}W2U8LUj6u>v!cllu1l>t$pTgU{6a8aeZL2Of6s6?eXnVtRgRD2;O0A9$YO$v zm8$QHHwte)8q&_KbDX-+BQ7auK($a{C8&6lLXp!qvTr?l*X3Z<9uV=TA549xeMC}h zIvVa6A}it|X?uC}<8Q$=kE6U-y{`Cif#5pK_i|0UM z0|NvTP6wDW$&C~N=Okt9PzTGQPigrCPf~V!S67>bG*3pxSr!2K49#tLBS$-SZDZY$ zKIo;hwBWJO^uys{mHrKJO1m@uaxlq?mOh029tG}=JUOQcY4rqbR4bP{+`-R9hO*$h zu_=odrGF<=2Qwd+j_M6bHt(U9@#%U>)<_~P6_sFYpKKQQvvIbNo)Br$s+=)tcRk2f zhiZ7EH4EiN)T>I>bZ%MnC9&Y52NQC%q(a~h_+0(-IpLD0t?QJlHQDk3h692S=Msa4 zM+}^ZJEK~!auz~z?|Ll}K6hwK3N-59LF)cK&Wjj5DX5=&QSOWgoBRHz?U%qR#eItn zP^JF1L{7Z(>aF-!9)TX|tLN?kxA`Z+LT!hqiEp?|m;rL*S(Be0Xy1|>lJaiNO$%Lb zo$3yd)47~Po*eKj?4!veeaZNw)j! z)^*9iyy36Kdr@(UZRPwa(r$i*yjy0Vw$`I1myWqHhcr}-lvhv`?sh{s8=PJ0&34`4 zW!UY|bZtKA$&0cF_II!Ko4xvIP&vnI*8hssJ?@7U&57*I7i#{V-fQ3VD4+MLzNmh7 zGTjtY#O+1cJN4Kt&!?IR@vx~gYyJ|I&rh5?c&gjxk@NA=bN4NKolOM+a2vn$sB=wt z5k3FO$aBu+j=xi7^Ncm)(FynW;hdUrNTNeaE+6Up0FGaxO&0PGs4h@V9~{IMYpv-J zA%Tu_DON{ueVP)avZsqS?m0IFM$3-iEuL4_U)gcm|>isNY-qCdOjgX+VSI7JVYU zihqd^I`So<@^3#JmtZ~}tLO}tv}p4Ap2P=@)H`K#$lT`+8m&q8aGL&v{cVlyRePPF zYTmj}r4%#wDVOL2`Fbjj`L9avztRz|t5=k&TWZRui{|TY*7lijQeEZn4)^2Z%0)5a z3F7a;wMA&ZR>uyaylF%5n4QqhuH}m{!QQ%!<028?K~aUsGw`7kRBEwTZS5}Gg{M*< zOOY#Vt=3ctolwq6GlI*=D6ej=@s@0E z`f{SvMeMo8&4xIm+`aTqfF+lP)vR75OjP|DwmMEPfDwGcaV;Vmz0{<5jll8Jw9DZW zv9zij>{os=>6QzzOAHF|2r3{(=Ts0cfPw}yAs7UWpjw@k=0_TEEDS9N(JG#%fsxSj za}ha-!pNWY5Ux7Q&V8GNBHJ&z60cWJkj8xS8`bWeoxW!-PTKe6oK8+=gl?Kh=tutt z>OCqp1>tPBpLhlz;rtzcA$AD1l|I(^i&OLEaJp_PEvOAtjEgw~*GcFfssNB8rDV>4 z=X<-|nkxO=O1Ww$f>Csk=ooC``!_kTGuZ#1-$8IW8yP|yb4EBJiYl%#Ag2rVO@e>w z!kimG-gStamK5e222d8P*pHWh?89Y1Sb2>7a{3eP4uV6i@DmZy4sKnh&4?~1F#Mv?^hz0SGaNSKz zNL#wV-5prr>(lPy_gcPGKg-HKi!t_ePePP{W`v*u6{2s<51Xc zoV|T%_;9nh=)2&rjk%SX>L{lnL?VI!QoXGG__(XM9{QY``b|t&Nma^^>cecRuFdb4 zgkE@nF=@B_M`+mjhJEct;m+ywxrt{v{?Gf#AKgjD&XA}^is8+c$E2h!k2e!a)63|R z!S%Q2l!$<9BPLNr-GYOl`Dqh_=#k>$o-gCixmo>f&`Z% z>xK|GR7=jQ+9L~<1OBm;t5dBIc{U_i&1crg*GXlxEb#T)G6x_cs$>Y4Gv|zm0`NN# zbR1^evU5!26e{mUoFXxvAUk(50CP)MJI8&mE~Q-#x@fU)tie-(Sq6f4QVR5A3$d4ZKhyt%N2A8rNgHz zW43eW-fkT{zEw5NtX6nts?pg0CW~sQ{P4QXY%BEs;J7i&Te!*2nhkLuVKQUu$qNcSFYKp5Ukl% z@j@pKqN?_K3INZR*qn@+$TgXYhXiyF8DcF@B1#e%MLo>h!ZW7j2JsVLy;ZZvnh}j{})<2@gx}vi%xBqzX(>8 z9&!Hj9=aqtNH-Bn3kxvboUP!>2s9l9=ST4-CQQGmjPI0R^b`jYkAN93lwRS!IiQ}y z^NZKwy*0_ACvFv7uTm`A(U64${0&|bx`+y{0IrBzwf9;y8JO&D9R^%!5emC%WC&?b zAA{Zr0-~!YTRe$)3PbF$drW~0e2^eAOd{C z*noQmOXJ7^!PD7el~pPL9akJ<1F=vyNf@=A5)nqxh^%Wly38&at-?VVkhS5>p!IUx zla};P9ayOq46L(Lys4Sau5<5f{n+@u$cRx-`RQl;&xR#El#b(IFNeX?{9B-HZ!Ii8 z9>TcUUioB{{fZ`Nvu@a9AeiZLvYY|Yeb!zS20_NVvWOs#PF~8$rA+FE)S9QQaB39~ z4VPXnnN%WRaWDiVUVZTh#bv;O&k&lYTM0NZzjak-$$I>!Hr10NPiub_{TIaoQ_x?!UEEb<(l0NwUZ(HalA3RVH)zo4MEJ@_BEixF zw21HF_ej{`@@H}pJP@va7OOnGa=k3!g93CcI`iXP_F`gJT1y$^4oYPvae_5%BIdjq z25=si`Qsvj;89yDqpjJ!x}UVEhhDp#m+-p%o^p|4;fzG0aVi*)Xme#PFKR+f`EQ%4 zoMVEsLxrOZfGWlfGl5nQ=hJLEi}9(%mriSzGUTDH4?nydlxPxmu}HQ zD0aeu3z^D6!N8)4DY0s((!v{$*P!qy#)RshsEqOu9VCKcyTCu!3V<;?8e`bA{i$kOo5*4N5!7 zow}i2YlquuZn0CXHUxaFIV}ELK5__1@M1^YwEnsAacl)E5Y7n)M<5J#J4;25YWFsQ znOmC|WA{|@uqfiZ^>!d~3vljC8(@(;O;@ZH&wJ|#9%*eTke;*k=dBCWL6};X|MkXx zYU%^;k-fqeM+&#+pl;3>3fcy_Yr{PEwuL|wLBv(E)P~8~*%60@(RQOc{;`>)Gp*}h z70*hsWkBck=xRJ)$A}o|{d^YMwS$4`P{&R$mR9|6Wc?!hBP$a0)NIRii|XC){B{@T zL0x&sFTwk|b8>IYHZR(>s$8)nD5Wjd46P7wHt7AIRWWJGAdb%XuKuvR1dDs?GnxuA zLb5#iIRVB*;X&3#bJ6{a2HgCJtwOcWpV&14!pC}3vqwO(T#ipMU(Jr^KtM%IPVv93 z^e+PrjaB+rRF%gQq}!uHITX;a)7g*!bN@mcg8Kc1SJEB3Wlc{!|l)we@dXEQ4pZoA3p(1_o8 z_HGgUy1Vy%mMeY4l*g#x+7IG!q4j)zS#sW+TXqk2IQKCn31aR#H-k#Po%z1k+gll` zCF+dfG<043TXFQFP0Z%kM&^{F8;kz4hNvQ~dWp1d$Z4@Xee?bO2KvpDKP4E)odvx`Ok*`AA z14_XRKC(jgy`|FA%IbGYnr4dbr`(<=@2@Hts9?fmGLM%~Zq+EQ={^$T!7&43C{>Ji zOvuY3tqIpM@l8Rl~&_M-~v`exJ~k>V$TaRrL9m7&IoVro3GWg1jUxdjw`*_!x*Oj zK00lTd4GNwobY$=IUDzJp)U8UmLEagqPO8a)<0)R`;U(*E3#z7pT4x3*^(BuqDIZ0 zyzF&W9HuMyYm>>(ttDYa@x6$pK|+g$c8qHUBl0fgS`uoM<)vkL`)wR+H|DKztF$!z zLO}Jzg~R_5!t`6qdd-mT~L zEj5p0FXIAhlx4o&AuWxqgGD$0KuW!>OM6dVP+{g!xiHW$F{I<3qKYxTZY+Q@OOy>> z4yp$Rw_;yTuXbgGbTt~jIz|%+iE``6?;dpnas;_%`2a69+otF2e_U~l*SCHOvKQ1j zC+K~C6H~A=jCnwt5x_~#MOJQF3UvGJoU~`FT7^dkg5YiYAas>xs2xWWM!73j#Q2g8 zga^KQ6`?IacoAqF+ty0}1BUZ%ApaqHecFJ4RU?BXhEzKfGc?-3-c2L%XI*RS|At&l zG+D0?HS+*-Pdy|n3sQ85O%tPtLy8!TobWa)Qk2~Hq3~kFMT%M@l8Xq z(MpFGdC!$~>b|a&`)6u?=+U1KfDRjvxim4&3;y_jES-g0lW*9DfzeVr&-H?|9P-@|47&C ziKms0QAf7~6nql8S@&57uowRv|9LjBhhLXgtglPP6;09crVc)qwZ3rtGHvl^!iM$F zgTMHXoo3}1yi11*5yK$9O3Y-?OO(YAv&3A9yTP}HF1b}MuGh^h&g`t@oHzev!MTBnOaePl0?s;{_ z%Vw4>9X(@g3a=^qI-EoupI24^!23s}DBj*WFFmMWRg(ZTQOx~rY5XChdF8_O&qL+= z$K<{S=1-KUS{Q={TxTfpOT3`ams*PjC<9}#1C;f6$d zuAf}R$s}2#;@|8lKTx`MQFct`tR=U1~086t=32IM6RaHwv9-1Ajy#S5zi zUv0RKHuDK8oN&Q8u|B1-W|kT3Op^3#@-1-RTYvT0L32t}q2T=SrSpwE{Ns zx8XgYHEb$>7(oF3b49nyorqvuc8n7?)B7~jV0@|oBD`5M*6d_bhKrj4_GKvG0fMf8 zw~B5hn9l%5GfZiHYgQ7%jpC(%7-W4LQ`Q7j$ypL&G~73JhyTnzh#FIvObRgne3uPb zf7mXs7Z|?zZmdn}R|waxopuC0q;Wf}k&*XMOHqCKhKu3CKp?H*^*~AR`qALPWJe&1 z1VW&#%mePI0)XjLoPL<8j0A51VBgd#D`3Hj=@Lg>>)sKk0x>EwUL0&jW-{zr0uT5d zSh=?Ibj$Jtb@$z;a@Edsa2a1BIs%;~zgoo8CZZJ&ZrNAvJkBTw&;HbyKSV@lTahQJ z*`xrz7`+DqEbWxO6#|JBw#Q*nM|3;@OE}Yxvz1-32SZPEYt&LXDkPJGtbHV`#Iy@S z+9e+S>*0!4Ch4ZO@(x^b#d~CcIF1RcaqNhG;VMqD_&swMk8&c)Q2Bygy#_{odw6y% zqc0y|qqkq=`!bYu{M2=9l&wTU|7=JW7K$Un$6xMt5q995;-keI#+r?FrR_QVW@rcj zD50ZX#qiOP0ltgLPh~W;_-x@lrte6^vMfL3s0N!OlvSVO(P2N$WeO@HNhc%E4aq-T z)(atMn||rZXcyZ!`M9i=aewp02ytF=ue_T!4(}UmhSq{?FwEt{`j+?4=77=hZ9Crm z@rQH9;D&$X8{O9!=J;Mr8602490Kp|?3n0ppCRByoSRF|R$5udwgQO5%i8<&yKlV( zZr_2^=9-lr8)`Y%yy4w3B1$L7eBHH`;_5AtOwz7xq(%K-j5~!`XForoLqrDjpUXm7Z@(#o5O>{F$=Iq^B=|< zoV(|o;$4)J@#lC;j2hN1Qw}u@mOR}h8XP|i-xOsey)QUJ-M^u}i}mKI9_FUWay>n} zf)odJ7yf5sA1b5AJSLC^*ZK~i6qOj7KISf|U3C3y?sZmJDSzYoVPjImgcSqfB1p-DA8&;a1wfOSe@AmH!DV zwVpI?Be0mlz4AwiLo+lO{A&E#f3?rQ(I%8-T4?!mq0+juv%@E=Y2($Zo5p??D=RxQ z(kWE0<^nMD!$|KyLE;IvWf3C?B+AS5P9|J`h%l?N?Md*LVC2zKC!$O;3{QRmee8K# z-0oOeWRlHzK+t|J;<&1%qoWi!M|7{VAlme1)bdkJ$-qi9Lf>J%`s~zSZ2!+(&3R~n zmRoxT@$)7urB<$d`oQUdvb_LiIw>;-0_%MWg>mqmagw`z1gc;+)s&a2X>GYL|2tax7e9D%5Z&znX#+Y4Psc*R?+*v-hC0dsYD|eMunyfO?1&hcH0Is(;uvkHn&}- z@A`}My1TcdJZq#VJ{8dU@}hR+~`MlK{Q4*D|u<6oZ^66h}yWAap>p1mUUHe|?I zlzuwQ$77j%mhM2i(Op!;&sTU`w#RtCm+rGRVfg84Rp~IQ@ue5}#cuM##jD?fAL&D% zji;oZb=szMaxk`YH7_fw%{5nZ^x5?%Gl_K1F}gXYeti7nRpqc_w_s1oB|>p z`_NWhX5ZJz4jKd3%Np*l2dV;dvbkhP$b+vk`>(Emfotc>r~pCqswee@kVGn$v*hP&o_6+*e^?bz z2V`xajj78=X_1lz_=?GtnirYF9_bCSFOMr*8{w}E%c1HB!9g5gEXoL62~*6dlyI#; z)O4%6pqY3y6H)x2dRwFefuj5Xn%h?S-~1$PQOwnhzcX^~&DeFg)6O9-=|_Rlz}G#^ zNNq*oMoNbGv{a%}v8lCRMCVN?d^_5YjY108vY&e8zqMjaoh3 z$O!MDa+w>j1zR4oC!@8ap6f}$9Fyb=k`ULm7@l{4hQL z$S@meK%}Ep*?tn*rjKH};5U_dB`AN_6;oIMoh@tv2DbJR3J$@I!st5zVzNiKW>ZGv zT{j4TdukH{KlELIKM3Ul{4T zVUJn9`8x8mbyZRSI=yrw@goqbYk2JDn=}I4F}C}_-nB!qm6fy~F{K|ky9giMH6CV{ zt&3*Uvd`N*`gYj!XFm^V#fzhi6Q*5Q-NKylfoxv7(6lpfR?@XH`sgN&Ld#ycll2}F zQ(H3ZfH=AT;pEJCcPZnreZH#Ynv-vqVcW`^O6EuQP$}UgyC?|(S4MY0qm&+?7+#4L zrd{F_q%fazJt3IC?!E0j%yW*kzr=HTR`dQXybzs1sTKtR%S6`@KnFI^3dC@6BTE$VZk%f zR6^928$G+}IbI^%swPM?yU&J~GbNVVb6F{$44x|71$xXV6b!*Iepx)V<`R--{J5Jy zllSAbV{W^~&%#72Ir1Lf@H51eX2`Lr+9mAHr*_ZDcNr@>jXf)y=kgdMTv;|M*)XZ$ zvf7sGC3(##&l5-DzKz@I{?$V$)fFA+%k$~%pv^Fm7R>%5P_zuF+8P%*^Bm_^^l2xl zB)^`06qvfja5U_nSq>%gyx!u)(D31$XXzJC7IAWM%gr$2leC4U_vB%?@7^bg`d3z(P<-oNsx)}o0f82W{p|xX?%Swg&WbbF`1#Rz?pE6Aln)70 zha4;2i&?vfKu87@T|1vQef$v0`Y@hB-O6`Bc>Rcj3r0!l_}Tf(Bbq>RtMU^&T|KiW zSwcMZh7&v9y3}tK?2kcKVIQf!yW#bVNZ5VWEM74?-rG>|3rO^6RHlR8AJ03RkSF17OcM!fD%!mU-|V?M!zx z5_-0!^lJ`%c!-E6D(E4iV&whr4NCA1)XytvUU+g4_|Qnmpg}TAY4b1cEC92rOeM8> z)LI34l=6mc;~iEI4Z*tv3OHG2|GKvF6aMH=L1lC2My&@lT}`=p7FUw6oB@*${7hj) zQDW(2OBFAjeg0m?Tqq&KA=1EV>Xno<*4jK`B9Ar0T(#-)PCb{MV1wq%MoJmXoBz=Dlb)P@kbufsGvq z599}f{2WOa20(q_(DE{RjajB=#>45ke#tzw^55}hmlXMYG{TyGxbLe9!|xS%>3nCw z#&d(FPw3{52lN~FPWU53&Z;mOBI%Ub-_2>CdmrlQLtek> z2Zc+nWj;kH_mvee&^b3g*&t2{cM?9mQ0-A&isO?fR`^pEnxW+0P;RI9EU?bIe4s$b zYAPvLRnX;;{qCFN4Vv1A79cP^ukBjMCCy!kz&6V%>)>z2}$5^#64hyy%l|_tiH$fzS(&Qf>3G{^yuW zz8&+|A6=`?nCn{vD8V%gb}Z-@axKY2M}NP`kl!jFt$pUMA@Q?-aa`gFetgjsHqN!| zsYJEf`+;^TjY=!Zi2U60PYUwmH+j|1&#)l@iigcEa*fg%nj$^RRw>p>eK7ZoZEzTw z*e!)4z3Sq?2rgGj<2Oqrb(~XZG*Odobrzu=e&9*q7CqVlZWiT}D9mfhJ{2(hQuwf` zlNn#QvZxlxJu9GQsp!P(Mdo8l+nM-6xmqX{yiT8m^~J-X!~2b6T2(IBGdf7{o>Rs4 z`?yCh+V>Z792VPMm5Zm3iwq&rjytO1{>I6l9%r>iu3Rpze*JhSoL#C>OoXXrV_&8z zF5q1(W?d94lU3}B=zgNWIkyo8TV|n|PqcAe^kXZtesPjTblTE*DO5a0XOZAIZ9Y}A z^Xi?*pD&qoPS3>t%bKA6pYW}{3J|`lQ@1lz{hrbD`bm#6z8r?=aK3o-h8R*FqBJyB z_x<&SS0vIZ)H!A&jMpIQyyyLV?rW+Uu6I{lT-6<1PCa$xlP-ohap!BV-_Mz3WVp9) z{&qVN#K-I@HCX2}b7cfVziJDrqAI|=RZcJHS^e9i{bCK3V8gQ+)a|mywEvfP1f^;9 zrG$u<`03(1@k)K6+{WH~(=djDSdIfcd&x)D@t8}_LC$8d|zi7&0xmV%F zO_B5@(l=gOyTWFKsI0FF`qQBB2F&5Ue*;o9tLy4){^y_9V(bPn;yt7Z9#5r*hV;lW zJf(@6k(}g#{5|iZzZ4;sNVD@E{%`~oHJX2Mt^VNc zs27|%jL91PCG2O9t+pJ5;JnuvF>|K9sscu&=XdH4$ z^(ne4d+4QHs-v$bpEK(yq}l8VhHl= zkhlZO_uW7Py!Bm@uE_e+7(PXZkR{^vC*Aj+n|^E6?9i@VDXN6PFy5!FUx}wX!taQf zC~pT=PRncYHqNb=!FVh6$DJoP`OCCpXj}KH>cokF02fa#4W5=;6!bWKBdF|Xf7SCw>RpkEmuPqvwoBt+oF3^ioXe+G0wxjyvylIoB)oVS_Fq{6E)a%`6gXmC%H@vA zM;vkDlEmf{``x65vT``cha|^cNx#sk>fq!8vr6tAF(WDB&yvZnzcL7Yvwi!WE$Jb77 z&j@YgRiL^$?VMH;b}*HZraem$WZ;OtQ4lK)SSk%f;3!Ba1uad6|2V0NtFsJntR3Gf zha=OhOL9F2%dc~+qda~+)&6Mix;b#9`G&@_WY52P_uDRA$c2x?h7b4O&^Utc%XOIp zIEC81-cOin^a8ADIw%Z}_7;ji`K&VWoFHt`kE>@}N^iLgYF`^Gee@|E^$Gb3#o3rk zSOX<_9XETH_e{RS_E`3~;8uz#^xqazcyiemEiJ52S$-8qro3sUh4B$X*tX}ZM}}8N zN2gJAn&bUw-i3X(rB+(A_ZuT|9Bl08r&lIoV|TEwUTWVc+QLaT_!V?lJ zxtoYN(2bJ#vu=KsQ3$(zyT-E&pJ`5Ysa+}A%lZO5_nZ4Tg@h||-i!07z$IA)K^SIi zGVYQnNGb5e*e7CQ2G@z0&K2BCq+tsvdJcCsz{a_Cppnb79$Ok#KdMlRKfhN`K;Qko zWAJ$!a5LrWwD4!^RYX}hXfv;O_)vS!vx9Scp}p17v>tXS>($VGa}UAan3_NH-*OD0 zdTaX$>j{q6YfE;=D&~J56b=7r0qkEWx_PrF62saLz*sNBdf;iVD1dJx?3zDXkB z=S3{4^rny)b>|&b|5we@@phRrulZrjuHfGbFFg(%GKMJ0UqH%2WA*SYIJf{0eqnUt z{fTM9-w>4TAxrD{b3t3LY{vDkKF8-E6oe-vhzg{I|8{Ei8rb}ULTCxjJS$wp@4B6Q z*e6yh>OLGWm&bjdVpetv7^eNDHK)(4<*OkbB5z@6c`+ZJ7k9LI7-Ny!j2!Nliwr6S zYSW2@Y1%wjtXO18YnN%1*bKt(A#rWuJlNVKqpWOSja7brepa)Rn?EKNHUzytni?f| z)59`f`+!Ek77Gvqm+1SpQhyzd#W)cl2Eq{ZRlBi+DiAixlSaTktHwf~FdAI`urI~& zP}R4!>fc1pkU^5FK!k7Rue*Rt2iJxYU<4dEI_$8md=uFgAY}Gpy(&r<67ulp2c?f; zzQg&9x&Djnc`c=(RB8fn8p(6xi-RNnwI~On7^Ilc6AuBeb$3`r%N@CT_#nc-eo}59K)#AvBfT&K|@4 zh3&8BZ0OOM+uupUli2@jUT=4&z2va>-`4IunF+8`RG8J(#B38&jB7e~WR8oZ=65n| z5IYbx;|(PcVFq>k`w%j+Ww+WM4ZnXbaO_a;m|MkSE%sCTCz6(|6UD()tU!k^tN+T4 zSzs^8noHoqS6%{nc`h*`@f1ByK*)z;ub)i}5^FW@4{YM4YnqN0zyOk6Q zBI1~#YaG3Lym%ZX4=4z}8HB>$#Pb3CD2=15%KADG!6&q@K!h1SGT4Jp#X#WJHHwzv zRD}6&qu1C$-ql65-Qg6GS=yuZ^g%(UYn;HMl~?i%bx13dj7IJf!lZRDU(PYS>AY-Q z;<>+z=Ad6JsJ}2S443*ju%KbJZg&U36YuvnfSGZDMFZbnX07u?U&9Jx4j*|`1_QnfzQAMnE z0RwR{alr`qzd87vd=)=BBv>~>#VStd(2N1({f22``iJNchrTLwU6ykxyy))09)bEU z5fbTsR``a0frZ_xjcwSgWA|4#FPr=!0meAgazE|9aa|xfW+7$1g7rF>!7D9x%u;;m z#`)lhK9UKHkJIpllLDxJfQhm<>LBCjC37v|sz9YvRhaN}p2^?C+|#AA<@rWm4%C)G zCAOMs>csxbcrDCvL&EsVUW*&)ukc&5G5yv*1Nv=@_Glfp8- zyY`Gz`j-9L6XYB{qZrRY6Wo{Xtn96wzmTwR#P5u+j3H zhIz;wuC9OfG%e!)7=BR;-!UX)-xA1=sZ_J;Wbs>@jb?l^-gA}+bhWK~sbb;dui*YW z9GLDh0MlJ>ZnEH*Y5x}_%}~d}m6{?0Z4*(|QA5sK%}f6VA#R5i`?%p(y>&F`z;jm~ z-C>R8SmF0}>sG^y%_aYo4Gaa-P9HcMW4I$D2_1j3<6>h-0%A{GY>1yS3=7j=yTX`9 z*EXi}c^edY_!zy7Fl^oO7F{)G0DdasS9qj6-RBSvZXAkg<#m*VwTijRW9xx|kzO+C zVB8GSUTQDiXOM@40?Dg8(%Tud4jKE`6PLRV1>MsV)Rnh#O#c_=jNr91bXiY)C^nC8 zHx)FJ=Yi|)$TV<8DV0z_+e9?D4kp38>xUULRfyj=@PR?INqQ=^xX z!^aZF4PM(OU^>(h*I|ydT;B#%gO0E-)u#37qyC&nS+}z0?g00gwtybEtzUM#uExWm zcXr>xW*CWItn-yvXerJrLX#_O8A~F+&vMk8m@sq*NL%!%RBwN@E;%}k`N}GSLxvNI z1Mk81Ki$%=Uy57(&d46!pIfNeqyx{-=hl|Mm)7k@cZijYgy7$ijaIjnQ`fQjuZmQ{ zR&B}^0Ir|>e#E+t>AgB5h8HmN{mQe#-NnPr!vm)1xkNL1d(N_hrKu8)#cL)wbkE+t;<)$sxpqPC zO`jS%x7Xmfpr{MfF;^d6(6J|ah@11qPX_&9e|%urPLZJX>UXA6zHiF@><+pp7Vf)v zfd>oq@UDEnR`o#gwAFU-(PG^DIYp7L;F)-oUy4v$>3mp_3BKdgzv>r?x?2P5&$HA; z3C)aHi9ffa7%<+8lHOS!hX%TRrwJn?#l4c8G*i#u0Cedxh%n)DMQsTk@zB$A$>g0inDZ0h85lO z4FA7pxpJ_IhDuIZgBAP^yoK_QI+7D`mf42WucBjryBAiPSLT0c2{J%cZbqE{Aja-S zL362BEi0n{IrZjGiRpNXk%AYnjDIy_xyIs`oNnjDvCQsRLf2H{xYIEYCxJ;M!4C{6Xd$q|0(bH>!b(8WhcnjeH@6k9Y#plw~LnbPG#~+0V~LUS`1N!D`oJjwwC6X zQYzI=clP7eHX)f_Ohbcp)&C&X%yBOd?bNO;>hxX9g@LuM3R^pk<-%dMBhqS^xy2Fb z6~8FWbCr<2^^}W@hb;P0@AXS5TNNeHz$VV>aU>Vp4qHia(UVmzjBol3L^$2Nln`kY z2{!%922p)n1PxGSRT=W75=xqr4xYn2I(b4;*d!Dw+iv`2|IAwVwmG6oHWmA&pstZmF z9Tuk!L1uEc04@|O*o7D;SEl*Y(Vk!LgHIn>Z}sB%h^4%SN?koK>BVL}*Z4ZIle^Oz z#f03(<0Plq9P;kTo-R}9W{Zsgn?Al2wgCNvTO=r;#wxbKYuVPO_W35_>i9MpvZ5;V z#6al9O?C!1d9wmJ;P69ROv*=VyV|_;LAdpKJm2-=+OqvJuFr3f23Ivjt7*(GWH&Qz zPe-WAj9-H4ZE;8geY}Jzg?>DV3(=Un8jJZxiIp((f$?-zLWp-5nafDyZ&D~;bkuA3 zlqRk7Z&Cx(BS$hdpUn4DLlm8h!VBA9KKXz#2eF1Yv6ZcrkNeKDEH|InTQ@}fg~%kP znhHF=wS|UC=pnnW9gWWIcW)?QE%y8}zk03(71|he=U;%1@6w<9^6q~YLYekx2K{0n zF#C+J8RaM-_(7$A9GRW3RQZp67CdLjQS?}TuR+$9s|}bZv+Tzd4_o!*{BchDVma;{ z)Sv!tHGx8?dV4>5}B;WAIHuWj!8E4IH7wOMoP3$x&(7G-f>mX0LM=#!^v_|iY zt&x8{n;PrMW3jVuU)X$G_+Vr-iF>mEby_P3sW$!~;x2DLZ#is&i}W!hlS&j3K}V{s zOqsJSE`2pZRLAWIQ$<4~6ELOSe;-C~wSFX6rn4?->XC8!$?IS5E5aM*njB127)81F z>YhLbSZr9e6lWL7mhJ7J0@T^K4dTt`a zxx;jin+=|Julvh@$!Ox|tr28L4nyl)b<16y9inj)g0A2$SlHr2_v*VgeF}0-5G_@) zR%%jgrqij=OOt^^r+D%a+Q!EOpmVXuhzEjzVb4~-8P!%T8K>0%793vgZ!cabxh5NI zNvmh9-PGP|c)O$*lDN&{eR2ZC$2lSH@}=zH~4Bg}PagkU*ChBxeHV}NjWH1e|q zy0%8bv-9>(z^Nz&)eE|o!CY?fOi;pjtIQrtZ2YM~{Rd?hI}GyfXRKI#EW~R&7!MAA z9-pd?o8G1`@cp!XsI=@QV;w9y;@Phua0O6shUbQ>8i89||L*g94o_oSt}tI;=XGnb zYlR5t6tdNGRrAL!iC~7WYqq1zr`$_62HcI(gIA`{*^@~Mt4w6y%p}Pcx$;;VDHmvd z%lYpF`^xJ3=mAPPng-HDNozzOuLYL<3W1~r&$5>^o3h^Pl#P5jy0VeD_NlvRlHJs898utdMUFbHM0YmnrJ9~IRqBi!aAm4ayU^h9k`iIP zef42dxnbQ{r?ybv3&Mj#tYcz)?w%_?s(W5H>?=l(3nbIetzBX}NSAF4Mv`_#tuWaKSAvG@E&E8BL~(d`MMjKa zw#C>YqFa|8)fyfzkG*I`D~w_!2_OpMHXQJmav6TgJdK2v1rZ;Z>R)FBxlt&!g~McR zG&QfpKmr8c(L;WOHhEEiR)^rJpI`0%;Kmx4W&-f&5rd{u3q44r%M!!feDQQg$gi+^ zVuGSuZ5iq^u-Leib+t?aV#&rKaDg?%DSP_{~0 z9p&JL{K$rIoCIcX1dzq0gwVVwswFTPDr()ko%=UAB#BR9P=77akTZ>@99y{v9J3yH zs=UYR;Y3<+iSZ}Z4Ns)za9Hd$VlK?F>T1Cq8!CH?8KLE%A>*#mAbd7|eJvQI|D_C(9_ zh6`=yhe##~FkEZDHbVY*PJHTk#I=Jf@31<_Hf-3_1(2D|S8+q=?oR7axWRV~*2s3UlABxv>C6oEzUhGs9(?JF;H&*v=c2!Mk~!ZH!1#4r>&);pW> z{ufxe9N=@Mx>Z_ukL)mzN)S?5C$N zyBpT!^0ob9-&lr8KdUf}DB23Zk*G}^Oh_1D2`UXv((E~L=cX6xcz}sF_~s6{))~|% zPoJ>q2ABP+L|j484^^Ei-%Ul0;F{=2RC^C-AMGo4!on3n5v6J4bmw`0(^k86Ydc$c zm_W2j**r&^>P*E+UuHrGfmZi-yi!>YHbk1Ee=cfuOam{u`-CEoq zAo}~Y6!EjBv(jSA9tmPV9)TlGw>q&yK`Xm8;j)v0J#B zVs1eXCZ@l4Bx38E1^nzadUX2YX>EtgAhku zZ!zkcwu+ykM%xVsJ6(m!CvxOE9LGLT0F{!lxkjAa2DC%<9pRrCh@q_e*Yzz{#Y!pr{36s%kA!@Rs=&G z2hOOqsF(hUi*C5v`|i#H)kh*5W*@H-f;O{yay#^eg@=SC9`!!F%DI-}y-$+lx}SAt z`7jsnR~cy6(4m!Ey!c>xM(H`_7Z9|n(>&HLWXIb!JXqb4d;g}`Q0R&KsOg*K*QBZ* zf6TYqy5Kd)7y_z4bLWi&J%b!(NAH4zQ|M$EB2+;pAw;yPa86upswO0K^& zi2;W+{`-;jmrPJf!@a;IZygp)#-;VWk1JOF6mLB^e{H!g7bh_x@d;G<$)+S-%q*KW zuJ^J?0Y?DiwqQ1s6`=ZP-4Pxk9T>_6(HybkPMbIawiS1J3XcVvm9 z%P+i8z;@gAxE<3$OR%Lt>#$I=Yw*rLMBjx~8D0xdRz|&ry+LFxG9eFkE5k2Nl|R(( z#B@o7=p~b3VvvO)u%Y&Vd}#F=7s-nLkuwJDh5(i^F_Cd&g0&J9(=c}lIueYNM4%P2B+^lZ{`4+S_C}h-$bcnb? zcWu3`+Vf5_*w>%Snh+Ppb&#FT9F63~qISp5O4Wtrw?Dd9*#LMgX`~sIaf9uf%y~Qz z9~qz=9?)8F+6{YiCAm^Ik$&{+%*8m?IaQ4cn5Ro!0NJAWD9K(?I$lCtDIx}v`7uzT zL9OqwJ3Lcak;4L{Qu=P#dBM0c|DX5K1hQ;aH~Wt~G$xZ7gsHZ#!=OCs3QIis@Uzg1 zwy{uJMYSBEcTuLedB|?leO{Uz&yKM9;#6wZ%*e^RR!$;Fg(HR`-0|zKWIIMu#nSBf=G9_2R!8PaUIiE)XF}ZnbquIJ(dm9oXar#{{*2Dpg zH5vCOMo4T(_Oy}1@feF8+&#tPGbX@#j%Ot~X)h~8=eU%r9P|0}%PXHH zBWLJUW4~Vgj`56qe)&04asf9u>?>-Q{9Vv{R{8F}{9R!hQz$JI*Z zO~8xQV#<+PNL~4ICNWV668!faNX6$_ zncFR;@(h`_QNvvWz$p$?a{gA~b$9#J?UC;uGB`w*{=iO+eu!S5Ds6FW$$~cPbHx5P zP$L@JW99pJSCxbDo$=5T5u-g69IM#E za&*L;BmJnyCA>W<^=4T94Rbj^^HHVAGIGd^(2SXvfLICY$b*li{tB?J_EG`GWN^el z_Xu_}iWK`X%f^_Fk*S%K4s@~dT=E9wLjH6mu!mS}76J)lP-pMr=7v;8mqn!d8CC21 zYs2yLfH`mY@63vDb^#-FPka!-*TMvjg|}xA0xWerkl9=Cdr25MfrezF814jnve``( zQ!V>2UMdzaS$u#6Xfb>T9mSU&##XCADruK@B5a5$1dlVee{Y-o%3i#<@c%rB7@G&NpFh~uhfY5?%W%DIPg-(UOD`FOKIgw;=Ou*4@9_7R~7w@(N99Q=ha?~E?PS@R^18w1N z-J-lg;2r|qn3&>O9V1{e+XXJT#4{=AYd70h_45+Blt@~}X9&XVLx~!jrO3pH8%e-q z5S7ua2CUcM4|@Xl)=quO|N6H{nP%$6WAkk!rDY!)0H%x}4NUB+=YO;xnd zwRIlhU8Plg{*j7G9ssIUQ8zATmAvDxijuHq?;!L0`NF;!Y-p=9hptGAuYCOe^=S-0 zQub>mJ=P;XD+bW}e}UvK0lirx&2p!Kuje~YGd+Q?m?j~H1{9z{$7kuKE;iW#8avR1 zJ5L)DXPw6Ry4K9MOA)%?F`RF6;+It0iQ&u+k{qk#Jf>1gbFqk<9noP3{S_JuPT6s= zH5{1o9A|ebh&eHVJc_U!ngqyfS0+H;A}6hsLTE_&rHT8q=N!hb*6*mRJF6da+UCDs1G8SUxdgGnUemDi#T!!*4${qq_Zu=I;_hQe6bI|q+KQEq(l)&Da zeS8X!Ei+gbesTqg2f2Qw0%f>jY1Zu*)?T@5UBj0hddL6F)n%OwE~bx<+0^BCWpicB z+>7Rnh*#q3gg8V=Xyex7zdjumrJI-tyw0vBF+jicw-)myws7j zA3^~J4mb4g(;;TkL3BRd4qoV-!=8T zAo#U=)ty1zk~T!levH^Jc{ZkxF;PTd<$^s#==(%dLBQbSf2}z~8z{UicpGPH*q|76 zL6Vcz0U|n0J}~~1{=A`~K6&)#;I`rIJEvQuW3qMm8`I#=;|r9mxdd;XrtSY0LwEm} z>DIzpQxu+VQi=0r&IbQ^fdeTu9*YwuGfS^Tk2*y2ID8P&TsBcHm3X1L^jzx=ZzSp9 zpgj@@V&OXwSdXN{SOhL~w&%gbbr%^NOJnxdoRC-5N>*qj-#5dT zpY5~AXL>Q8ktK&>1yOzXukH@NC_JUCId>+|Jr`#+l|i+*iB-=q-NZu@WF6&XEw*38(fMDFDZ zA&0E47Sj=NX0bBGk#(!cJr#)PnXghVeaiW^*rAM;rN(w{n4ZTAzAZ-M$*lZj3!1-I zQ9ST|##%uoV(8JcCys9}kobOryY+~}S5oKo$voA4?qs#y%a)cps*L;Hxr$*3Qd)3W zE0TgEPd5cxU}w~|a#TMYE6Bj%?8t73EH6T5rQ{y1=KNr>YsMqqTCx9gl+Lp}n(k2l zb9^+LhqJy9U6tl01B>H)uq(EzBclFJXhu(r|4T^+>J?yQBF>ZQC84op_EF(P{>*nz zDhq zX7^*d`QE3R7#W$KFe}tw`C|rM&QeANg8gshgYnj-Ybw$G^(gfy``wJwlY3AyI|`>< zW4y!ivvXMuzjv6VmSmHcBxt{j$TYv_6j6Bw_D2a9An*omYJ@P3kJDNf?wb$Nq9mUY zK@&!Ic~QVk6^**x#{-s|gR9Q7GELg82Ms{C=O5@ep>ur~N;y&mlEU!ch4%2E@vZk~ zDG3M*E+_q}{7=!Ltl+AWX-f9YD#cW?+2GYjbRf0~4scn-B&|3zWhejv>Yz5(E)OvM z3V^-LMuGrA;T2yLC-?NZfC(IVEOmu*gRFM_>)4w@XnU%dyd!ippILrI-ZSaO2DMx@ z-+936AO?(uXr3;5(y(ui5VU6(7&}q|to+Met^ez3hnfRMzF$BE3~W2N`b->G9UrE5 z7GPxRzs7?4-fBA7xAk1_%0~hnF%sOQeiMjxQoLDX(|1S5ban@sRGxJje6u*hg{{2^ zMpp)X#g`(`<2_|P?*fG{qkp0Ik8}!yG{a5)dbc}oK$?to19hA9=#cHVfxaL5Tep;; zzj zka=d&dH2zXE;nGr2q7>q5{Jy1Y7&x0VG`)!*_)B@gLOX>Yo!f^&oZe2}=S#!jc zmAb$lx-t$|BBoJhvKKZIEb8jBQm|2&_sEV%;K`4yUq8$r3syG2&~<=mq0R%tOraJC z5kBVZ2_FrgH?QqP5y#dYyxFGXl4gz5F{xjG`pAL}da*^W34T?#*jagI6@Y92=*Glz zC3T>!!t;{O)T6*16T#46Q{F$X)iB>3mPE;}fbv@E9dqJp()IgyWJ+ZFjwNki@QH3w z6X4Hy>R9r$O>`bGcS>g+!it7L(6`8Ahr_V1k5;snMm;08E)O(Yenua*E_`&TAv9xn zri`nF&cNJ@PE*4FsDl-y;dM}_J)uI+{%b!C^u!S+ufpeTir`Zgw4p|mBhyDaQ`isi zX{MSWn=Q^v7Wvj`9oZ$!7+P4t3<^Al|DGC*M(n5E>ct7k033_X@u&)&;sb&x`Hd~Z z5AL0E1`yP+UuUzi^;bR*mmId$m6Ypm-Y@F;JKfliVF=8@kbfiwp?BbuUmbKYqzJBkMrdBju!Njm0?G~2B~H8 zXC`Yd!R45Ak7rQ=k>pwjd2}7my+V&lYq9@gElKpv*yQbnS=D|lRYd(ZP*Y=K5~-|e zDZZXre|0}(qqJ-@?SIiiy5ghKPSe8HdAQMCR_f!VJXNgGxjH@iv&n5osQTxroQ_=Y zlV;^7^Gc|)AeTG&=B4+$ys3mZ5^=_dSyPc|=UQkw2Q9dx?TCPr$t;qWOw`Df_0*+J z8F`)mpn4c&sex3Rnw84L;;K-<%EOEfp;)rur%`zQ?7JV_^plWCnet1g`fJfbg=J6} z|CqoV@1>G2S~IVcLO3^QNL~;I(j0%Z_)3(KV}Lh9yp-iVG6RjbY)5of5GVn3p)%roKh&tLvHZE63|LXB2u?srVBJT5}( zq|r=FS7w%opWIK65ntH3+(b3I8Rg$+A@=Z*pvbH?aMx3?IIb>{h`my|68R_PWjMwL(koJyPEi zD7XEmRuQ+KbGmH<`TD;ZatAlqTDE0I-km*k9NtWxy#7OG;4Up-aC|tAvNHLgU##hQoiFjm*|La)ZACS*)oV=(`)-psdI zk*#tEachQmoisV4!k1YcPjR!rXH8NsbCzCULJfPT>(2HeW|lH|@8JzZZc^rgYbSTz zgO`4p#$yLorAAFRXpinUOZa1rHg%Nh^-;l5iVPhXG7YjdsgOkPmtEc?f zPUcYP96+vXS_4K-x)Ys@Eqba~f$X=0E6VKF%bzoEV%=SWMCYPSVECsKdcw6zuy7bT z7kY(-Cn6?b$^a~L0`~N^sGtcCs|x#l5aI{61w=j+fnyqBTxife1i`BH7AvczWOoypdD3tC8mdl$m+~wozn*?*KpUvx`pB023KO}#y!Z4-cdz7GZ?Z!w0-VX%va_SX2B9}tkqmzPF%N#pZ8`; zlBC7zLXo_253V8^0+4_6g_6$o9mA^F6MK)(w*IX|)vvD|CehIw$Xv>jh^zuZ5%qOE zuxg|n3mt*9%K0@N;9OSlEWYCWQ3Lv%+!t6eEO(w_VZLfTnciA+7$}*-BU#nW`!*g< zsuy8>I({gUe8b9rZ9q+SFP5?^R{mWx>yO%iz2QY-NbiMJIrbX`M-9Kw37PhWvmo{E zmt6-h!-#c77uz#Tr4EKxzk{5-lml-ebCrGJODAi41<%8-2JGq)O!iUHri3+31!A#OD_`w|yD>N+T4{{x2 zt5h5Hq?&}2Tt6n3R z?3uidECiA9?H61NZhuGU`ZGeg+A~PTtxB!@cDQRlUfS??VCwpTRV7&f zIq4cL5EXJeY<}@~7{}Spa<9X=~lG~5YKS4=2ePao~LiqA<;J7ACcNU7-#aaZOt)FUehL*2mYiFn5RdODPJOJ>lgEr z+M`8V@F(I66|kN;_FOjVXbI%&YhfWH7*W`$oDbdJ;tGcPs{{{lE%Irg(2pzKH=Y5SK zZ%saAv{HuckdqFrzfP2`*{>{M6-AMpa5#K~ts4Nxzx$F3cD z*8(B3{5R^ZsSl%kiMa(jq`jXvVdUHB$lI7(5vSZy+(|1qnnl=vd1I28 zXi10+?6RBpo!wemKFl7p1iP8t(k0$9bSr8|bu2dUo5|I+A7A(w)-jID{ka|JOYUQR zj{2RD<)~mmSn`!!N zb)V4oqiK1zk>5gHUb>9=;cj`mbAyDHi90jzF9a!kB!N5AGrHZ;tm64`!`-^Rc*Zwq zms0yrE~fS{}F}o;9)x0;X5S>>t{Pz8#3(;zM{pdN1SZu@eY?fy6+%P)H@Kalm z>FDI#e#)0S4nb8Jk&Tn|U%In>yjxzy8Iu@#GAHcO&Jc5U& z2YG0lTrkh=Fj}&5xsFUKlmV7n_1d`~)&#Ch838Y-7ovo#I2nE~ufI8|QhoUN6r)PQ z9&Kd*WP8WzzD&p3bs}@M*l9}!GFW-ziH-1X%GXBwo^M|up8Pj&t#f8;A81fTPViAb zp`}u5EO0eSFZ&2CJ^AH9RIQ#cA34sAW$7nl*cKMpMN<)HbHwiN^cQC_Moczdr#kO3 zHE?0%Lyh?6hzQDS{!IJ1OWolhP4}Q!w9NhWj$Q`%J%+KSRW&PL?s3}F*IzApIF+ zb8Gvh$Di!;*Dac3HzJ8{lHQ~g+SiH$2|}vnCJ)k+O3gTa#QQLE5fvcJKc)IVpK?(( zcGL_to%`Xdb3Pf(vrENDRM7l$zU^xZSzp`f4!ZsxEe)1iE-9gO4Ae*cY&F+mD;V;y zucl(!tDD(Xf^<&*at>V&`Sc>fv|PH~p?>=hDQVkdj37mh85441X*@3rU;hw-)}elb)T6%SQP-+ zGFsH8zu8_E^I%{o#@-Deq~kU(f!zNbO7N0=HsM($`?bxVVdvXEnkQv_iv9=21MjN2 z%?;Tlo@1Xj2|rC?W6N=PBQ(7LQ+&-X&2MSYw%TGOh{0Y8|@zlA`x5DrH_O#{+cRw3U(a z8OQOjp)*iI>Tb$XSiRu3gJRw2&v~HsUlD(2UzH3oQ^K%_Jns)0=4vuYC z%MZv_YgflZgx)abQIWrQ9sN}J(7>y`56(Jxv26Y7krt@=ZPeSrg)t}ZCrrsNqy+7p z|Js-HOU$$BpHK1=nm8Dv+EZ)24V2M#=;c1lk?gYt16Jur{YLUOJjwWsWz?Ekiq9d_BR-4i}%1KHdjt zU9t!!t|*`qUM3GFA+Vyx3j{~|(j$pV-(U~%bdKJE)wf)cz_IxF=q4K@7Qy4uDm6iJ z1(P~XShY#fMLlMI1 zt?M4*h3LV+=u*4YK0qHiBR+-grbe#z>pTmO?~2hC?8UD4%Zo^X_Z|1>@q0cxyH9O(h^C6`@@ld zY4CPyFSeU^UefjO7P6}YO9hh}*_&UzQ^EnU?g>k=05(k{nz=#H$V-YG^2 zdcHxz1@84_ygDX^N3+?_f8CTG)nyB=P|(id+KMFXSdFY@3M zulHl284SUp&GXbDPRHIq<<#wrsFrut4D5=hqGN7fg|}At0vxvua9DBI@nv5t!oq>x zcU#4)R}gioZ|Kp^&+8&R3pMOM;DB?4>*CXQeXDZgtPz3d-Ib7vP{8ReZjGD`-{ZQS z{f2+n{ST5}x^fBMotp8|43q|(uP(E~SDno$@9?OW4qTl;jZ(e@(%N0-5gctP3Z0)p zM^-(~J8;dRhU@SDHg&&&-HY(96WVF8uWWlee_Ov)tfgH9j}o_DHj=Gfz4sT4iyWMo`%2U6e8+ zgAZ={!9s``7+C{k&C`2GwBuTB!R(U<4JcBEYmmKl-s_kS>3s`$w*rm1y%xH?rnl=i z_U+^2hm-`J*bN<-7YLr+$XuG^5Y)Kv^);SHGEES20v1ac^&lz1<=IYlTliRmKpuAw zISYPOu<)(S&>Q?R7PfxB1wYHA9E^E0&TI*_v3yrcQ#rBq?^`5(`2jDrAbhG<^aT8t z69M5=z@Q+0ZsfkSmX$55EQI~9PLddk*SPqLU?b1nGI)~{9w0zy-SPSD!Ik4uNCc&o zs4$R5wSt81E$4sRiiM-s6^saSVRcqwAk`S^`XM!OUoVSn=FpU}pW$IN7vYH)FIGa1wQbiCYAj zAWChEr2sSrf7eId+6`Zz<{$|2%)B>-C(f1rdfqI7$B3WUtv;k%&!!=a5D7Iw$QT~J z#V@`SWD250)*?qRoX?hi6mg&&fK{kgxLV?2-=cR3#+ zxBDnBZ>x&Zy2zGBjGQu(E*bD|-Po7xnpKr5zW-qo4Td7%PjiJZ57oE0mo`mC<@9-i zY$;cq*zdm#EO_s7^YVTm{SLPhH75lZmIoApEwv;MKV%Qblke=N;igwfhsgM?ST3T^?L{KPpX6@?EH{I^;FkFh8v<<_9(Coe7|2| z9UXT4cNfC+Aq|F<6LkA}`t}^0AVyZdppiS%KISv#?|ZI)6iH)yJ)EbaYV_@%YNtqCDXmYJiZj4dr z-Z`W8%#2-E+z7sAo_Z%#9&&nUeDV*d*)c;R30bj0Y5bx1IN7w_JiVLC%yz)sX(L}M zK{j2jdaU#8afYzWpqXsmDF>d5b?=8kw~~QLYhFQo(FhaXEXKJkez86)S((uG1oa{@Te>eg#)g1L zJ#+Ju%J62zi|1}oJ^CT%{rC226bhb!z#;i)I1giF5*yJY*%LQ7)>GHz>E&~@j%wWD zYs38MWs%+t%j0Fb=i>73l9j_2tj^EUQ(lOdFsjV;Q(J01zR6w z6tE8uf8f%?1$G(9lZfnh3ihy)kXM?ykF)S+KKh;@&7e>iNr>M*M^6UwP$=J$gJ+{? zqkCfS=_rs=1k^I}8q_?-rzaqfbkkJyYBa*!fPuhMyq_GmMnpk*u=)a|enY5XCI^u{ zj$MZz1)yD;8-RG0&tkK8>QFeMi!fy%LO(@ClNbn(t|pf!Cnh9NO&#q$`&Lk(FH1=& zo82nSee0qae!TMiuTjB=UTeznyg!?L@x2l-@&_oBsv}r+-x8i&^`wuEK?xOc%|&)D zo{p#6-yjZ9YK}XLAO6i8+aj&~I>xyPPGp6ydm4P|di7QBwaN?C<90S6h_Jomjo9}3 zfD6lS1_m3|ji{-J)69v?SpD12xZr7w<)tOc0O;+UT^zuT$Z;rJ1V%rK=`Ai@hZ-u! zt+EpDbXJF}RI~%X4ej{02ZqUB`03)NuXeW>P7=_L`?m%c)py_;yKJJu)T|`J)OZyF zU=8*eWi>h$U3bzBXnYt*7&B|Oo(QbSusmA6;<_G2JC|aS1lT4^#I>ZM07`$6>PAim z7i5wn^?9yRuWb~AxQXf4^*zDg>I?C<0*~|yC~;w+3OQxV{5>~TOMH(8g>3X?;P0Hh zJX-TTJa2>K;a8pmOP-f5AEu)zvwIA|iB7BX3#H9X6)}!)y%6In-9$XIAadAW(E~`4 z4~KOxXE{$qHvQr1-4cLYY2il!x;Z+f%VfHjFphq3Q_G+Rw-(V#T73dh0(99m<+ZoQ zmSNz?$@k@lElR7OSKN1Lm>LrH$dLiBzB`abF}(N`5*+3vLnSY_zY((X;n0ptWBSdI zl4&E>rz7?_$A2c=-RVz^RDq4IzsE>y<-O95R}{m~f1W!xtsBpt=d;kHwLGR!1BO^% z&Hm)r;kc$5N3tHS{KAFVz5k)_jB(uuN9(S4LqvK1>&%p?BopqFxw|8#enKiu>Nd~0 zhIGy2A};H9aeOSOJ0^*ksshh6n%eN~!)QzC8aF`@DiBr`D96g*NQVkkhsd!KJvkeBXSZ}Z1Y$Y1zAM0@ zY^f@O3DpX-&@{|KeRez)E5vgF=~8G`t*MpWcy{UmpaxjJIMsa0gVH*=_;-QHZ*vAm zK&6$k16~E-7nL)j5;r2x0dV*S1_^1ra~Mw)ZKk%M1V@^2ilhtH{e?DFmpF{?54}k~ z)!s~TH)eMq{7#t2}vQ%Cv;0EFywDa1zEc zk-Z%+qCeD+dIXite=5M0B(%tcchi{??d{Ft5XMW3J-Gdjn;O1Sr_TJ}`p@pg3qDVK z^5p$algHl{8r6}h>}PN~`hNA2!AKmi+Y&<*p$3I!2C71(@yb`4sB^O_6`=^0FNyv4 zf!{{AgNyhm*cN4W0e{xf;SlOj6Xg2M3LhB@;rj0S+BHPB=bY#7qynq_bj@|gYN4UK zRfgr7(CC_{-Bw}8D0j(m3wPJ^%H#rURqN8W(6^S+zE?Sqrzln`bZ_R!xF0I0kP0`F zMfN6Ha6Qn`F#!|>mp-Su5&9O_*Iw1r;5K&RjEnO@2u=k4=VK{04(%@H`A4C^Sd5t; zBepH3z#t8;axPPYDpLgn^;>SD+D5d;I)oOxW{R}F_23qMoHcqe@~gT3^Tv8b77;jv zjBoP3akb+Ukj?w$;@7g+)mhc4pGFh;E_d(PW8_?%z0L$t58Ea8Jdt9O z`KuwDaMvxxU|al+CSCbg#&6B`53O6(pb79>PI zlJWHczTlejasCuLI$Mi>cXquz9W*e$Bg4qzyy!juoQ3q&Bw{ z-&L7;bXvBtqMkL)*Ve@6M+ zb>r~tYF8zAl-+9ZmsNS(`*69kFv)oCP|ZgeB>>@-?T`F?BaY+PMbeklXV3E_pPAUm z1({vUl{{#NMtJ+pFk~!fAEcbjw$d*x9C`VP+fv())|JJ)c@A;c=J{;&&7HM` zU1jzS_vxPP&VnZO)-bP$3xnLWY&987X8!Ga+039xUQuNE#5SVu^u1}=rWAGgH0(o*6GfX)(sia~jTr<=~kQufSCgoahRZF@W9;Me_lQa(x@?Nf(RC^zT6tEaC3rm2sC=63Bh=QL^1P z0gB%8t61pkh2mGo%&{N%j&**V))q2H(NW*o|GJ=QBllXL1%eE!(lXyYcx`M7c5v1( zWAf!~FMYb1{&$L%4SnzB!k1UFD_RrAZHD%a4_wni!^KN(3m7$S9PY2Fu5KOb`<09c zpJAUl&Mc@(_5jaH4Aj0ez;Ohcyai*g(`iWZQe{7L7*vfcyf>gfx)IWt5?ae?e%cR0 zo8J6oI(=+@ZQkoquwz!fST22eWXpM(nNdugKz}DjG+o=yb;7D(kR|bS)b(&;HsR7f zC;M3dQ$EJ+i7rQo^P4MfEm8(K?G$|T>TTGxVwPz&D?`%_wbO&d{KdEZsSVW4b-DY} zK09Wl9~WIIj^B7DCWzY#7vBdwF{hLXWOb)0l?~Ir1f~)&y$Yj zz4c2K2^U{QS+;Z5`=!(`N%Nji%2;`NhCc3Xb$Da4FP+9*6liq$jmEQ{LFu*FR;^dN zI7zMg?o6AIp(l^|O~T!e29Y)cV4V~X50K1EWi^-g0?1#qPd-F5MNvz?cI`>#nh+p_ z;ef@w3H~*Qv{uiRmQry`Xn)x4l-`%gj$9&*-t=OvS0ekO(^gDjkQ+ez+A+iZC`HQt zaqw0?X@Zo2hT?N>L$kv(NvYNGeGjGp33=CzAW+8-5v>n*7Z;wAMp)uGhn2cX4bw9R zU3Sgz$0_}p8j~pEjP>XBIyZH7go+C%ocDph+==6cR`pjVzo@iU$xLeOP%0!yDEAR? zgY1H9so#{i(SSdj_#K*Ng#B2PnNS@o(KiZ}CSSf*6Y+Q_A3kknQo}0s4&=J@lOR9I zoNQGxUF)dcY|6G}za9tclFK|@p$eM{^gLYU@c`rh)8dx%>!lq5!k^dzpR0K_4BlqY z0%P+@<;<`K)J2lhB-St|&po}DcMX>{a#YR?hBo%OZI>*B6_OLY#G(#b{Du`c_T6X_REgUp*_S zm}lQtDR6PR+qj%04*F3)`O=4dnEU+=U4LB@(^OsI#{JKqBGQuurFmte-fG@&jtBcA zQ`6%Ng5L{1(vQ#&?R9i4duEt57Rvkc$@4maIa;l$LNIG=z*6#;1(j00PG;k=;G+7% z;_$rjRGN@8v9ovyN;x9-l(7a|q`Q$0FNm)Ycv&Z)0)nTL(9w{U_6BEC(5VvifF~_I4P`tvPV9|D<6l{&@A{qBJo(KCE7+Og(e0)IstpA&Pu^H3a`ZDk;xyn<7rPw zK|#=#JQ?x2iad*s)X`%CD>4Lx+^iNRM+&$(y`Ak@zWh}Cx@z*!ktVP`n`{!r0 zh@o;9F&Jz-ZRJ9UUutn)uF5Nf|G<) zK}R0cjhy4kBza+@lTj6JS5Su+5*PvyzF~!rBIxM=Yb0u*h!;ix{=!M(=#&e33RW`E zSRb4^Y?-(Z%twRg|HCojC!_;IkXKOlTQqT}v63DX?0VL7;d*+hS&(DHAS*(YPd0q- zsb2kw#T^`4-!_d}80QB#{TpBHoQWH)uIwjA|q>dCO&BFDBb zsb!ABqtl(ekuA#86uy1{J6!!now=ACEBb6O3%T>;)^t_Y42i9DhM!pA9w#DL9t=qd z4xg5GxZmM9d+xvMGiOx3h9|dg6Jn3nJa;lTH%m|ZPhO!%|6S2n2&HU%Eo8P2Qo5mz z-|5^G6KBY)EiNn>IO2JzT&I&zczBQ)!sg9cvhAk~qKHYki-#`C9o4 z+4*@ZG5j6AYp!@%=mO{#!h=+V&id~Yibqy0L;dx0W~?=i)R8 z%3JbRL+nZCt(|nJ%+q<2p0NPeN!{j8YO%&CdO=cL+y)ki%~0QQ?g?bk3@x5)*ichuNep69@RfjPb`)8vC-Bq$v-oD3YtW%2Cqf8P1q1Yucw}np zKEdI)V2$a`u6lNgVomCPpB6uR$ni%$EpV4QKh zPmBi|lG9MIc@?@xMZ9>M(sXnB`{#D0{b-xp80N^=kl!s#t;fuhjX3aO&)wgy)JTl~ zi-!ZcR0%KKI*@Yor#X;brm%bjJ8d!)jWY1CCfN#s^A|Hpz1`~GB{hFUib#+*dmwu-#yhGo@v8rLhn zOg%MQ6j6Wn@{qSf(?&<6T@G&py}bxC;e@H+u4P$)@4Bx~6QLBO0~mhX&tR1ECTiuF zNFLup|F*6hn0zR61&we$E&zSQu{g2;`?5i4=P+cyu_u@nh(gAtU-#S+cf}Q8FQ~AO z1BC%3Y`|lDNXX9`vU+H_m&pgQwA$B&;eV)Oc0Du*c>U#-rz$8Ehf+NHr+*uV*Ef=e z2pu#^UO7oTcX#%Cb*4XLFenko`yEJh$CrigB+XB$XOUnl6Po#X^_;G>jYD&0UF+}P z9Q}iJp^1loMv7?PGAmN1L9=Nzc@TC5;Xj1l|* z@C&Aut{sbE`yin4{EjR-i*ouMlZptxL?+WiUsnhBiFBvZe5Iqt!S>0)Be`;Ci6x90 zc@e`Sw=ixQ@iGUa2P*jzV(C^gV2TN-MfJeQip}$hkB8!22VS5b0)zwmuEBs$nUnVv zYi~67T1`GlDpWr`9&eJ27r6etxE<&3(mpuK?_dIAQ2S}<_)m{~oyf5?t(AkzF#wCP z#dWA(=Rm#pdXH=B91v=hD}JEqw8z1jMyyvzEo_>xl@=Ka5OMvRp7;=d&P9jATV z?#!{oQYd0X$3nWDpQqbd*tK!qj`;|rd?~vpLl8*LJu^klLVW7!Mj}+UB32bzb$}Df zA+^=VgfFK4b24_FpKrj6&ILdW5P*Ct#bdpc6)w%8)=n&Kp=rZ=7A1PCUQgir-b2>- zSR$h%zT!QWuc_QlJs#mPF<~8iA8!|h_Upaw?kg}xC2m)bU89N)#a;p;qG5Ddwk*U) zNrAxMz3bhQl z)>M3SGK*y39Aj|an|F-A&aIj!%cer%-?&s4%zIlXlwM&*w`3O zeX#!0;>%y$Def%36F;Flbp9`+UROz>gA=rNsumHEJQL`-W#vDsI9Iz)>r}gJQo~cx z_u9D>u21xRzJ+mCLgU!If+G~q8r47HGVvdk4;3ngvR(%3o~_q};BLAN!&4F~9JV_@ zy=>t!(n~W^b)@>+rg(VQQAY);5UlY5QL+9Z=$(GXqHCdU;z%8K{J#eY zxfEKchm3f3g%1kxD81>VhJyiP>E~UL6QQ%s&Fa4u6TOPpEJ#5X%!W`VUd00oSfvtV z1$I_+j5bIkRG+-jd;O?IY}bDuHm~=`M6FV@+7C|#s(5S_d!&=R#;3P^{sn&Gd|UGi zR)f1NSYmnLdJqCtE7nXd_d+!#N7`$7J)6Lt;dhvKTw934)!>@ZM7wgo~EsftfcNWzmEpPiEU%|bPacNC! zgrFL2OGC@FJy@1hz)I7izj9z+4yr$wvn(F@UEkD?E8U&@n-dQ2sU8UoPmJF+6*Os~ zD@Kwjc=U9U|I@&{&t`H8?1(r&Rv~?uM9m&$6`6Xk`L~DL;U&DM)Vh+d?AhC#rz0l+ zRG(ag=8q19(}7it)qsSgUChLe!biz8C6~sCMXWUwu+|(SvXcE(9On+S_P+{{5m{_& z3gX=QJ*}9{ZsyJHLT~D*3-OGVr>J+V*fl)o!)gV*-~AQ3hvy?B#ICrVx&t!W zWM@TtFniBr<#gyJ#|trt}fHi%;rThKT4{BuQs~GR1b$|O@lw~?53%Gcu(nE z7$Lz%OE*||p23M9F)qkqOWC3;!YD=7_)v=L`=EOd5@B3W$}fay2D#8Z!6WZN%9iSt z-O%dt@crB+4O$ZhvRlL*89(KYtO>UYmnZPF0t(^L zId|rBD08wVvjZenlSCx1=bX}9uOn_FqEWf&s9ejTQyc!(GTuc`SoO8)F)oRLxhvA z-(dQ;bgGN3ytXOT144@I!gY8W0PwnP+^C74RAH$upE#;P1m)=khRDs&8+Ybc zhIHClS0C&10}_K<=AuCaw4iRKIpf~4 zXKhs)O!oC@aG5o$r!Dc#_3~rH1iTmoa)&HD<3Np8&0g=yR`bUsUA=RGPE~gvtI6l; z0@^LAVG|8{>>B3ha8VZOen?1WCU}C%(@M4)>vY72m|c`p*6nD~3{4oXAiaM5bsj9lGa$nc!Q@~@Y8jSEX|C|m^k7)HY>~wS9a+m9VfvsYNv=qW zfW8P}x_czTfxv(2@Ip42Dsy5dS5){;4{V_znvcf0?fzv?5Y)zKH|?%;`6$Fy-{fsX zIlP|O=3jMyl;=*JIcUl|vRoLbVdQX4JPo7~U<0hRr?#yu4c2 z24jY&ynAQ;CM8eD>`IpJ_<6pZd2(S(M_Q;)Yzr9f*6RCp8Ovwsqh6N^Tj6w-P7s_2_C_5eDdT^|SXif9?>})eSS3A7!xgn8L&8UcTSdYI~+57y* zK(KNw3+Y4lr_;EX^UKAgNg}n;GH2Ho$ek)(bTIPPP^g37vW>6F;F(tx;@junb-ABS zPP63v0?x`+sx5NFEKV1ZNT=f+HkQVlkFt~=I9vCMg3*mRI19j6=E)Z!x|{tF6Wye zch)PnmlrQq^`8c^95bWtR{zi@1lkJpgLROXU6(yK=KCv)7F=k^^5fka?7+9vT%ugf zdJT1QdD?3>3Rc|a?U&fF&GO<3nIIN<8i(3S4+|f{o$8M6&h{A9jZ? zo_YqGdnFLy*$Hz)-1*MEHoClqHAtS>Z<0Mt#|QuL7~|kkSWQp-{p7}jQ0`D|%REo| zL=LXAZx(Ya%|AA6&7;cz=+V`GVWXvPV+1QmSX2J@nta`L+=L}ct!L<2TPi_f)h#G> z*II?Wvy$r`R0xz7rX9wOaS6c))-{EbvetEn+t?44PC=q@;cPo|CMBGu3+8CcX5=S1 zZ{H#OK(&Oyi~m>U-E!|bO!+zy=!lIL8Fy_)XUE(`L}qMv_BV>~XxD_gkElb;BmL@j zj)T__eIKRR6GapkyjKg9zZQ!A-hP=+8E~aNk%;0myeC$ZDf9cJ(9Mr}aeo+WVTsl? zsYC0T3vKrJ^PX;2$4M_#X3qK;*Am$Sy`VXJx(fVu_`{%>rNvZFye;cG%z+1X}t6kZQo7wjP zjOu?s$$Gr~eJ2mcEh8hIy^s!zsu~7-F*o~DwQ5dFDlG6oZdLB{)XF=(AWQrX4R|x6 zMcEZur`8eXXW206Cmf55dQzAGd|^S~N7t7xS97lSFHe@|05sY1=G{^=XJ|DB+K}kc z)%F_!ci7o}GeabAqddTV{En&$mP(<$*`9a}NJ;CT5vG_Zs*&@oN6(PRF006HY_f+kbibgTyOLE;ELKaV<(>$TN4&S!8LWtn^Q63Ox7D)#qG8h$ ze~)OOIgiqu@T*_*5BH*h>$*!|?ET=kb*x?8-w)itC1a;k;naaI7ANGM0}pJg?rnCS@9W)Puv*FY}G(sHND8c156W>xRz#*7sl#lL3hf<;x%X)x5+wrf*d7mQhp|#)Nuq(Ss(REL_ZaLp6=Snbkv6J zHKYAS`L(W9LWzoixBXg4Q>>|}*{uGqx&`B>Qau57$%XXN{@(IiaDUeF94gtTi-~#s z%WK!7a4p+A;Sj5yZR36;>k^D$7t?ag?y9r6H}Jj|c62yW;E3 zN_<1uPZ2-ERHb&Pc;e~JksvzS+b}!SP*~00i4+s1V)W%ct`iod907N=eEHz=7#sKZ zhxGk$NymVMr16eV{r)qb9xyh|d_a@gLb)|cJjQ3_)Lyvc<_HdskaK1r53ye9Zu!g{ zJub0# z@9bY@lADD#3j&BxFh&G9j4Y^)F9+Hqc@j*Z=QM?u=mpoM{LO2;3ef?G`2 zm5D&k3Q8y3Es2>W&5m5@eoJS0s1%@jBa!kA>`IntDnh(k;)O{v34gWrNk4Le8;JC z#l(zcto&r4zAiMRGYqdz-#jtDOp2)LUdq>#0V$sTqNec^TM&ohV@a;F79pI;5@nn_nGZQKcCgbc^M*_yyRi5nV}lT1(!&) zD<(Y?)}=8u9bb?+`#Wls0UrgY60XYCk&zS6A^6E|q82em)Lg{;ps{!DC~_Nc8etLT zuCo=6K@QV@PENp={A2=oR=DZvFE2ALQFO8?FDE9u%_hv$z0CHjtF_$rGuaq5n&Or%~7KdxKyGM#G)cS>7{U%%@* zvC%m46o={_Dz?dmuN|^es14Tb^)Sze$;Rch=KR3MkAAg+$VPp6<=@gEot4aw;eg~* zDTl;-K%9=aN z5GLulnQwg0VIUo>%X<5(R`UbZ)D6~u`Im(HH#+x?(Z22mGAHR57@UNy8m{`L{yPWF zPba?#Yhps5JEcjiK2sE|?^bNCC25gq7GLgyc2Pvl2NmBk^Lq59(}_vGVo8;ZU8*Omu)4g4=i5D zB-b+Y2W3qZ`2b6ZJ(N@L1D6YZ`?G{-xjf=gKPl{u!Qg$O_)LCk*Ef(eHhfalT)Lc_ zy9C2NKdi`xv2XC6rsM4iICNTHI~SG*5x#4|>K#VN8q`d_CI=Tye7%&!cAtLr>iwxw zYy^k~tzl~;Id6@xE=ZEOS9GLrMhI!`{!s^?No->#2w-tNh{5E;OinT)c`_UG1_oKK zKs6ye`r7Eav$coqyrbo(FU>TjinQS1py`W~Qi|#m+uXsAL|M+zNP>(n2m?!bYE_}n zCjn>uD)OMgbf_k&a7qn&S%*?1CD`^IfoPja5pFwBUQf=@?Z!avoV9s&3*$c<<_e>> z6pBvf)k}a>Tc+Wpwz@qUojEfzgES)zaVPruJ=ny#e2*iljdP}S-V?U*+q$6W2z}A_ z=)UvoP}wiEC6JoB#1m!E0Kvc&j+pRDd1$%Y068=>y}{z-t51K6BRCYdYers=FIukM z)WWKD7|=Qk{_z%c&<+<82q?bNHzl~D7mkLVR`z5$k96Op zmp$u&zB+0AwH#~lB}gw$ig|v*sI$ZFP50UJ=CW@k`Y74PGa@;yfBG_IEP<3yxx#!6 z-?<17;kxb>CO8!;8s7S5n7q=jvsr6$&9Pyedl@}pThXh(z`u|y>pNyQlH%HO47RQ9 z|0CL2PN^@(evjE`n-$^uqw4pIY5i4bna)nDSARzGbWEmlfrn2s^l(z~M5oH>18 zyjsI@HP3wnO{Kg#wUh%|t!rLQl1-4PMt5NHLit;;cfxpj?W=NoL%`u&WK-Hu^- zZ#iE{_m|*cVC21sn6+bwY(MV7dg&vyr2#^l5W7!2aGgXSW*~XOEE!2zv|hngnI%gC z!i|MEBf-}m+t4|(uyQBt+Q}vI1YhgKJU|=^Ld;}Gl%p;H>1s`W=5b?#Jr{cEc1O^j z>tBg*lJ-ozy!Kun9@Mddt%?+#S~Mya4PzsYd1{ns(&{yiRP@B=n$K%`Q;cMseg5-pR=K8QI#zeffe%(m; zxU);;^ugp?YB}=9;A{0_$qvHk3iF$|5JgEdL?)tTN-ao>!Z|)3HN-VqgYq}&H+Otb z>8IQ)iXU|cvW4*)NMlR=O)paKR%QGJ5r-Ov%2vSSK&Z@2UO`aTR!LZ8mmn!+JqCCa z)lRO4LfyrEKiiF@QxqZtp%Mp^?AW3r7PqD4$VqXCZ=+l*$R2zfmW@PM(STe{0aR;) z30cOrDb*kC*9fsbfOT{uP%o6#o?Hj1SB63=0xN*Hm1_jnM8;qt6QK(pMZTyclfr0> zA$N7MDE(FgUk0)hd)(lsdtlFQJX0FQr(=dUHt8fn`S4%xL1v{vl^V8a^<0&e-w;n> zIhqP2;Ws=89=oz*MzY?*x{NTVK;?W0QrYc1bl2NG-=M0S|2nzo!Re4Frc&Xx=FBN3xBm6%eL@55J=XiWqq8T=r7Z1w{I<>TKP&Wg5a)W zH^{ldSt!G&!k8KkC5d8B#J~E9e=q;}Z~R_grRq9dtzW9V9;xz`Hg{yZ91!L8;=3=s z?-zO~neRrGDw5=qH6y()Gqbu2AIl=sqkbkF)1Tkmmg@LW6d6i`h?gxynTFQ<`jM{P0mtAiEnA9ex3HC zB}LI~lWT82g-7M~vtx-Wx7YtpCb?3aw(1w%r=?=>=@)$8I^BF|=P+uKeXGRB5FTx0 zXT?|Q>Uc-k?VTc_Yc`BBr#=Narjm!CHijMobLk?!yr>% z#9lShU}Zz{?%-bvItSKcj$?l%DICTe z6!3(qZ@dR)-%D*s1!s1RiPUmtF*|E?peink^5k#1o|0%6tqsk^R@r6qZ0S_N$kJBw zMERk+WlqeA423?~Vr`t>2UJs%&A)C+%Gi|-6Bj98C1~B+%OrDoY`V=o`|fNk1du82 z-Vo>frpcA9tg_N#)4>j_O?i_~@ne&5gZ{Q-S;jP0r?V$Jm99x095dmF=#msUCjJ*Miy4XvC~b9V?IB9=x& z8c`kV2bXR5lxL})7pXe$cL+u+V+`H|T~W6*_c@~Vld>OAKQS})vkDcdxrE@Urv3aa zJfpmMz$vwEo{cd9B+I!BN?VcvIs$btDb&FS~mpJJw_l=XoSER@);SXmrCRTt~0vvR0b6 zy-v^JQpWOera?Or7J@j2@G1NK$JmJLqH$;?DCpV>vBOhTpV`qZj94=I8B zv$g*GH+!w^CHNf1Q9KRNV#nR=Afx)XxJrLGtub#_7q|)gdD4H_YE#Z;3cvc@hxpzu zD*~1Gsf2#$f+`>T+wG^-cZZ`_FB}Hf5Bf-bsTHW#`hg7KsxT*{MJ)&eG|GQXJw`Nu zQKy-Cn}?Zo%`?Y5M=0A5IY9D{N^f#Wl8tpvoC+hJ%dBDOPkLq?8Qbm444;jhxr{t3 z`9-X2=z;q__hCgr!5^4F(^BD*(|UP(dG;Y##&x zE5+~*7b%7RABlE|v+-A^a(_?#Kx5+iJXhlDzQ1WqCIv7yKy0G0|0D_LEvCB1|Qir^aBD4uSNR=g?u7a^?z0C^8XKn zg$$3u47j=j0WgLoDi<6h1LO!&>TTS~_7wO_{vuWIh3nM+;X1$gynSR=%Rc-sk|(}q zGwCgjHgLH|l{7r-RJeB4&iuI>Z}PQx-sVLYd%DXvLSR<}%b-Zh$4WH?a;PP#FQe_Z z{#E5_^?~hI=%Fwunp}_nnER#oN!`+Rm2zTdO?Pl)j0~fONPwr#+a~#NA>AK;*ssI! z`1wMaz)`D_tZlU0mvH%i=1s=3=H{kgpo}2@+y6x>RF?@uwdcp)7R@^hrprLrv<$M8 zU?r-cck27X<#)+^UGd;5&nb~tGa&b4QM)mz5|FD^FXF!g!$2Ob3{>zb(HEDGq~Ep0 zU#%ye1{aV1Bi)3;So_^C&|omUpruqw0kX+g;Su`arn~Rja2ON$H?*j7XkU0R$o5 z#dLfnXc$oo!X8w%>TWU9;h)RZ0*nuczmwPN&tKmr5pEHgm%shedvk3p*DBf5NtEf= zIW5ZQqLZCSk?--&1UiaweE-CyMgE&}di*ItVk@6c^9=w&fjCz|K#qSMlFaX8Phr z_fis117f@4^>^y;t-w%G9{E@Q{8!~Ziqj8N)}){k1v*Q_kt%Qc&#cxO*jpjshnD{( z9fFy)c57+`d`~?|OTj=4s(cOvBIPsG6l%aYArJ1Yf7RFA3eC0&RVq~#c|?=aH6+M; zyY_Q$bGavXB1tEB`IgHLxEyJqr?(AL+$IDnfq{X*_bBeGx8Bui2cUD>twd22+#Te& zJJ-wZpH%}V2!M!X;C{HhMI;ja@&l;#eTK*Hyne!+_q(*@Z5ngi&W5j>Ns?+*Eox$- zBIN=WX~kBCCHhGUcOK#K6Ab^3y61&uUFJTb7=8daUH_X9>St zr@>`HwCyLez-X*;a6iaZejAyn+iR4fyZu>CTRc< zy41@W5W|$_&xL*SQZ-3<(*Y|-z^1SL7&i#Rz+O>g-DRyx9|by_MxatUem7y+HwniA zez1V^Y^9{&rKR*InPh6wDPf>1zrLkd!sH!WY7leWN2cjs45{L?h>b97q~%?zZBc7f zc{Rhl7ypN-MBdmqm}SyLfbJS>S|7Q!h}FuC78+xam@8+i2Qj={ObZ^}$I?l9dSdM2 z-lvg_K9>;8q!%-_md=aIa3Tvih;%3&Q?}HKLsa zfGIch#e%MJppS{cZi$>1#QYj0SuL`aADz<(#!PG@Jl2b#;$Tt%&1{*9G;@ohf5HRu+ptY^h#2h9s)27wU=Wd%vIWN=g_t!M(3)=XQ z7sXWT44xEhv^8f)RLX_?OqK%Oe+1BKJ6J;Dd&1AxvNe4ow}Xm`G?ZMN)m4S1*{4#E zX=ZORXUaCd1+G?)35)yN+_oUL4oF~=(&ECs9Z7E`=G!3PZP$TCy7{9zsCk>)d$KEt zAnWWT?zU-Jf3LfqnUcjlX}sku#N_8V^)Td=a#AT*C^JqkxS}i}0I*j5qpTzc8XW1E z2NPul!a)cY4fO;*C;BjR>|5BdbOnf)>ol2+t43BCn9 z3y>QvSA?zffMHvUSoRE)nL(B zls_Ne*=ndHmyfT#f9vWHK3-g22NkK6T~-Kx1OBpndK-lMJ^F5k{o+mviY3!)ZR2D>W0qrUp|ET_#s8C)0DZ=Rod(C(^1exV^RRjUn{(_BA zm#fj_`FB&vZtZW**?YZ9VPV;9`$O51a@T=O50D=%L6s{AVFb8*u9t)Y;rRRy`VYV0 zYCi-hT&BzZ0Aw#7`@nP%lzxBlL?8R#&mx$K|)>NK?c}d z2g|5KR$Kv!%c8H32hhF(5GZ^<{T-^G{qXs~93=w*@%pu5RWB|dEBAqfariQzVJ3+Q zr-$S332{wo4@=Ze%JoC)CHQx31q@d|eFuZ(7aLUzm5=3FO1P(Opn6z*yb$0im2Wv5 z0ib`y;J~C3l`|wBgA}V*@L`w~!5Zk0L-j8&5>)_o0L5j(TEF$};@!{!UulaQqM;l0Pqi%EYyBd+>q?N{i}%K0Vk!E*(tSDzE!T zf+P`bulle;FNMYB-cYOl^1pgsO`DN4L`9T(JB?h#aiqMXPpgVNs?E3Kt4uKGox1qr zFcW!DCk~SeL>B`dhAe3^`$V!Y)Iho`{V@#rM%QUhwyUk0pF-B#E5b)q-(RbZWA8I7 zCz0|~>~Q1A-Ks6v;A1>HnUfEO{PuRN?HZIsbf`jmPiowEhv9Z40l=FX#E(X-H@sXuzAu2*f8+R}STke! zEw@L!SM2<`{n=}a%l+>wR~1`TMSoFk5g67QMXek!AJ2G866dHeDM$8Qf-oQcVrvk% z3UyyZr5*?&4q3Os2q37)3Q%BB34V(C7q-j660qQg5C_0;Aj0V?ye|E#2|(Q*=Cgkl zV?2M$x&h)}rJnhC1_BNBS}F$clCJCqMMwe=2gs-(d0L--g93yDvlNKfB$syB220fp z_U>UV-u1lcC{rW|f59j6&{E6oqW>3y9@P=H#Q!f+TqLWqdw5lO!b#rlX5e3<#a(^wkC2{^Z)Rq!61#P@&!%aUt2yWEwt- z{rGP}zYWi0vS!qNAY6jO{1veOA}ap^o7AYjeh=hR>EKa#yWUc({PLmX{R7s`FZA(# zT>PH{uzCR?8SeuEflxq`KS#>T)k;5?aab~jL|(xbK^o6fW6OQ{3!frzC zHY&cr&(rg=H&=dDO>EV^@F~(+LI@xikAOHBAeVegE>a{9{9dVCRX^}<59i0q>`(Py zAE=FgFJ?S`5CH$Fd3UGF#IgUVmGwo~KcYVGlwbRv^}0RxGVDHHUHGf@U5bn4^z^k* zD97DWJ@xvpDZT|BDueRxO0L;rn;#VZ4;2CRaYC>74p2OyPnAra9`JZN9{peZr}8U1 z8oA!HW{JGt2bRlQg3u|NG|(On3?%%x8mLw!d`JjG<$nD7uLLpvd*#Y)F9YD(UJVD7 z6c{9`RuG5a8}InMeFuPiem*V$zlkNnSAq`TkAVb89~aO=5VTqFQ^`;@#PRd6AK zg;V34cD!_oS`mx+IhxT=?{SHD(YU>p6tttgotiBHnp zb-(?+x|Rn^3I45L%l~S-w%efNB9-e+AO&Mkod7s+u`6rTfRfg@;1Yt)4oDIn!X?^S z;8CY&nuc1|>z`@*H5xQfXQ7S|f>-u^=OS2QeVmpPQsWS&;>vG+?R*W^Ve_ z`_x!w7gcI3{mCXtOoLB)b*R=*b3~q%PPbSMn<+OZqki@!7eGDlR;1QX1u!@qeFxsT zs%IF;18T45?#;hst%7zcX3g?7Wwte=mJyA1bD-lE|7&-fM<9?C@Pq%V{;0RQ(B|WD zV-L30aWAg6iCU#iPqX8gO-6X92v8y$yt0xIJoo9|z&eOc%G}xD>_lbsqO}Znq zrlOy_4tmbmGxqJYkN3!lf}g7PZ%t*~i@)@_X0Yn1tu}Xd$TFLEv}Of0CX-M`wp+dJ zan&=J(JWJSlL$_7^sM+4@+H1nO4lX~%E&T0rFAC}3Hv7NL|xgNZboDj`mTQQAutsG zYtkRebn{6`>A?)xAkwDOWtuV(3{6tT5nLJpZ>Q*AX|>7gE_X>3x!gY6CxLybI}D7$ z4^6trEtf^5n0wo6Qdw(tX1E|aUNtIn!aoc$!Z2~YTX(yu!?b3Z_1;+0bi+QfPzj+= z>e$TNf*23><!MCv5utk@fZb0`7Cf<9+@kucq)JR|dK+~9)1M>iNP z2Wb+jzV>Xx>R4cMKm{kN)f=%NFH^SZj9tcHg2M@o99MGAaSY8lgfjWTEIH+x2*Ozq=r9q{w(nZKMMA5Yu)tDcv3Ev_96QoMn8x3H4a z>6^z6k{-b@?2ro%h>HarWZKd8;U`Xob>-7Y$@cVVMXvxb zFl1vq4FCWD00FjH;1Y%7Ndd$z7rCzyF~h=*8$mh{aiM=;&?xjM*9myY2u|6;%Gi7Z zlLB~G-$uvafEG4*V%k0+1DO@LuxJ*wSMBZ|;^bL_fJzx~>E%RCraoJT!Cv~K&v9B* z`WLX;d_L(db;w72|L47Jmn7R@?6M0Ht4LTz8jaM|{3U#%YL_3;S8Ws-tiEba9%^u*;OBrRE-3-w#hQ`O|NYu zl~wH+S>GS(REG=pk^=v?8qtz;!yvEpLIs^Qmt#QURTT&c0-vPoq6O*!lE*0lMhno5 z%R>Mxd-9}tLH$Mph2vs!_PUO3p*>GjtxZ!+7RZp9nKegds`AJpS8sm60I%(*>E8D#gzionP6?eDH5)On;w z1JidDS%cYa^Bo?SD*u#vUgoIFKa*boJxD_Ti>jc8KeWDk|5Q9sC3^M=WBOhPr-#_x z@btZ)IcPD+5a=O!Q)-mY{#oySj=rzf-0150T$^IYqN3nG@gyOaennhY_y@f`;5F}a z6@UBtJNl>hZpiKC+hs?m6)XN`DXDIQ4hDcz17ruuf6xyF1yf6bw7RQQqL&0275~7@ z-zgFR^FV7$>Y8{S1N;zxKL%8+O920mr zBjF@|r6p9+f$Fe6$Gpcq3PPA(KJfl~JgB;RmPu@^?S=JT(SQ`e83Joe7`zZfkYV?| zOTnO&Uwz{8HxW1piq(g4<25=pYT#j3!oa zrQ0G+vPOky`qpGQhMtkSLDR{LPmsHnsvXbX$V_u1h|~^=K-h$K_;>QeY@N+*z8I>r zdn(4j+9E<>%DnO^rCqgd*<|l#-b-b&OHT!uI?)Io0t+x97QHz2ZO*tA`By{9)HQ;k_T_zHGEeX8UtxxpQYXHg;IMm0`xArMPWr%P7j6R+L}>`*zG7i9%Pr z)PQ1*L4p~02L8&cqswUO2*@Fyc(`}7jp_TbuD03mDb$|`L&bI%sk`YvA~yrTzzR!Eb^T2&n^}nJqd45liVCt*CgTPVA_gK#E?wwR#sCO@R zY>NXOV{+d?!LbD1uxZ{WBIJxXrKZt6l@NlNA?jc|LsTVal7L|I@cX1Mu>;>F zH1DX1{Xf&EeO3FzD7%6Jx;v!Aev(nRVr*Szo$r4nooX2Mw%f-MxE~EZYRCT_B{|g6 zkO5DVo44(-PCp1h9r&q1bP|<%Qt|XI0K)O=p&?K+Wx_>PtiSXS%Yp#>N*>iCzkVwQ zGg>|e%f0^zb`K!@zur^_?7xtC@HzS;>W+agtI2iMa#j)NNPws5A^`fLkEVM4QFSk} zPpHK?jq-@RxWfm63p9Sumge+__z)#gRhA&8iXVgOw|6*i1eqV4WvjOh~6TCCwCR*P&rA0g=-c~^G2c^X+OH>8sOPBURQYG@LWeQOi& z^tzbpSF!$4uX2BsLVSX4m1hX>R20)Y4A<64eci*fEdrTI0wIDS%(H3I>sgVjIiM(g zN8Q_rAxmW>pnwQdz|Bu?_TAsiZxlv%8Rl|Rvo2NEe>#+mDS_bQt0Qj#&& zfy7HTn^-_MeFt>_e`~TK67DNk?{2t?0?xb0^l8t{qoz1bPQH_z9 zS3l_*#F1v9C0hptJ##6#*f`h}{j|%}f3;=w5xHAo|B`oXOcDudV$IAq!ECl9WE{xG zTQ{h!6NNPS48XMSln(c45@Jl@kf!Zm*06qSTz;f|T+hZ}g7!-`ZXFvL=5));U!~1) zmI+~$Tm5Mih7sRzXDrd115ygxXq0~sk#|ng*s;H10>s2krnQdx`}*DxaLP1tc=rxC zk3P;>wC+0T0q~&|{N5A7Ix`Wc7Q@FD7oy0CV2Ow?0-ArxD|X~J>+uq8x!!a(Yf0qv zZtv4?7;ZM1xd`%R$qNB(n9F^T9A*b)Lcl5d?em#B-HasWbu%Wrg5P({RbA{^GsLUE zRX3Hqnp!5PmSi{#7;T(#Yvg$hi*7tfIl0ZMZo1L>sWVu^-`m6LznD+BFV)gv` zU|j)At4WtjJ;XMLY1$>}R>yleg8xKl!xo#{ZBIa06x5u!r>z@mu^e1Hy4&v5skQ&x zFlS>&LYdX&i*~eqU8s&;=7xJ2S{cl@4_wg`dNc}TT>EzWTjrF&`oJ}-p)@0=;;MJDs%qY4k=WEjtHc3T^l3EPGw$;+8QR@Bq445WwDZb&~Yr> zt#X5k);JXL1O}|dtYCrzgTxdB2>j;6uo1Yso9Yw*H(N)VcqqEnVs6k|CTkhtOGr;- zD7tw-gb3ko3V@~lrmntpv6C@Py^*Ps>d2W&b7Qr%$&8`WFex8vx3esb6Zm);Q~f?2 zo(gNhhnnSW<<`EoZ(*Z=MySpBv>dQUGvto@VFr^W5dyK1xS|R9_{Rw7q(M9e*1uC9 zSLor@FW=_$Yj)loC)px6cn0$boiOAz)80}U5kW>W7BVV(25mYxxA8HNNjT9d=E=gj zo{>ZJ>%zU$7*P7y&7qSpFl1xF0{{R300FjIa3lWK`@P-&|1a8KtaI)$U>Ut5w?D6sq)q z3SfMI>RMNdrsh@KdU9T5PqpxFpR3%na}=z{+>dsdUK(Cp*R%4`2q6OclA_uTwiyQiNIu%hlWEfBpK8ms+NXlZcTXh=zi2Tr0j28ZaaY z1MojSuNxna<}9GSUxmcqdv&_H-8z=%ByG_1!jQvL%D>+=EmEpy`|hjokM1deXWJCb zhyC#yv0(3aF-23l)M>hCp2p%>F$#2FvH);F?ps8u>yw?ekSKXr`$4pS@L*KJ!SDVI zR`1NBK$o+4UHP)Gu0ehEz=B`p$8ji%SlJdDlSTnfm3mM@E=~u*z@!JjfJV#(55S<5 z4;KJPWIFW-O1(sJ@ch5u?SO4i`9oF%E&U=c@H`*>X@`Z?2KZd&=>RFNPoMR;o@~#` zRcZra0`I-v-|)0qx$h}!&_$!AKu7uKMHloAs=gk7^A*p_t#5})6~urR;G>5U1cg+( zf7BUqF7OMF)%>R)xhi5&+lh?-d+x5Ee}|xFc-rmdYdESaPze-2;th)%&qVURkwF zQ{8U>&@sw*3Ux&T!Qhl7#a;+vSN-mZa1E&u`T6xb_Os;2yB18iM^>p~dwcqZ!d$zAnYi^Sc8VC#! zADQV;4G}>BgedP}$a7Asm$sQJQ2X1!F70lW>C=6r%{oQ&q~7(n34cI|&m+~OfrBF< zlsq=7Lk=pOJo3HLNzO{?Kfd?tXYHhErfyRH;LLWt3>?mOpA(y!VbRW$?sr$G05C9Q zV+IWX00001wp!p46fCU#Oik9t9?{+y7rCsjgyM}l0X zyX|e0x7OZP?&|f|3YSO;T;wNTslmtFgv(`ynA0LQFQ!=5V23Z?TSn26e@=?~Sv69U zfopWSlE0;3npI7KZT~E&g?(*5K`k>~Ob$O4XvK#I_kn2P*&3rfHgx4!3u4xf6@I;N z6&3xCVX*u#jS3xajlglf*3m4zE&Xacbg}q*pyTm-PlZDGjsid6EjZx#FXTu@#LDKF}R@#K}DQQ-2mba#7-RA0C%e3z2W?9t84 zLYb&_K<-QK?sW4%W*{;9;p83Ut+%vn7+kASr(DLW{wc{~ zXp9|P-stOXHuCgCD>=czru4)jf1u$zp0=NROqch>EwD{M4j7^uP;kU4<%jYHDblbD zFSDyPMB(;3xryE~?XNVApmB_2pszc^H8kMi3|g}Uz*F{?YZ}u4<9<^#Y#<8?dUrX^ z?=&ocnpEFH(mg!uN`IKmu$Adah<=({oMX9rPbENb5b#trf*ez4R_2Hr6v=G471=mr z*EWp#ziAV@hd`zW&>>${|M69Y(|??r(69r|KaFEAJHfqz5`{& z;y@l&0lrt@cz6THmp-HWkTVhlzIuVt?_M_xA@%q_7M@kJaUuJ?~_jU>zRV&5G2-4g#1XAXRvg;1395Lb39&Zv+t} zydf&+mI1*87$F0A167G>{t!SkN(2R7E>*kWY=htX-jQF`@7@qqgaIr`s+EK=4-%CL z3f0p15J9|L3|?FT!d37>65(+8NiPOarYfq{)e49IFCQ02j$R{CYaK+iX`Reom+^mpG;WE-xRC%hg(5uj>6|_w1EgFaN%vUS~{~sxMLN zC%xI5T&~`BXETliWv4* z)=ahOSupz5w1mHWqiOUc?(Dtdc%A3Ta-^K$5%MQieCagCd8@u&jT5ffnlM%Sxs4xe zj#328)f~s$_|`ag>N;+$H>U^J-`m)rj5i))z8Fb3ygrw`9GgsH){%G=*k&Y43(LLr zVG8=mUS95OL6r?lhSu(v48c^9mdVI=>$Y z8z}$zaa;rP0gU8rG>NIO4+bA_Ac6(0Rb|)pHqA!hP;h=Fs!>$Z=D#X|?f-u(Grd}$ z22fn6e5||U&nr=8U&y3K?_2GhTdN;Tw5R=l-buU{1vvu%cn1AkE``bmun*&E2Uq=9 zvRldCOIDQE!H1skmZ@C7^|Mr4#Ka)*AK!uu`oD>AxVc(^AWK$UUI#u0Bvw39uOJ*$ zr=C~!9_)v}KK)nXwJP)F-}0~e+`b-{0vUCM|4<|=9{Vix66t^WtxT^BY)q|xFCW0|E<)gW6RI6&9 zk02OyQT^CSyOyLl1wODq93iVO5|s&Hp;TU|8>*G6@Q^<*22cmp%d`|9u~_!c`sci> z|Np?`_y7NS;J~8?fd3Q&mC%F`0few^0K)1dfN+!uG<**O0Z26+57ZaY@}Ok^f)LdS z3KgsRrEnQu0D7@1MN$a^{{!HE*Z4kx3))3vqOfMfq1=UCap|M%}E z!hl6TXRg~2STw4YsN7ZRrMj)M04cNxhw1?U4hd8PRlsk6YN7dXlL5W~uo*}-R3ybk z#nilfB!nc}No|ip) zB&;ypt@9D%aQOVaUjKR@hjx%?_xE^4uUYK5hJv3nSY>e8w$^f4QUdKLIH16U4k>lr zjN2AwfUyE5SQ1*+LJmMkKhj`S_1^k5i{95Hs6k*w)h3`=ae+b^)+Al0l3QOoPIV3Y zQ{gtf8Fi$@RYiTM%r!PokbQmA(8wcF)c?%sxO@sNCYyUlZxIqx93ldnl{-py{dt#7 zO;a(Y?xbmwsxO1T$;<3(Zj|!oYK?5)Ete%-wMkSEFx_iTD*U5uAKbYS!rkkSU_kBe&1TpU;5jPYbj)F zshXr=2ma4?PIpQJ;uRduM$d^V)!Ks&eq%k*aW#`?H;uqn1_CO4-nOycLR=aYo(gHE z7O;?>A1>`_>5#vOPlXZbzfFzZoVOhix1#6yt)-<8E2 zwp%$TTd1}W(_^JJ-VJ@}a@lzp1$pJc$AHX=aAjk)X`uMGS}x9UGg?@yv!!*clP~#i zcV(irffwfJ#Sm6xw2&*)TiqdrokR{M18fc5LK%emHiYYwh)=uJ6!#3s$5D%dAU^lK zLBT`xlGwGdk@acj-xi{49W*@DVxTG1j&J`R&TCB{@`$%gTgC~UZX8`^%>vDxUS7kh zhm*<492C4$*%~C)lfq_hi{6&OkR+;{+-Y;IRmmETO5DK17;NKkQU$7~x4x1st}9#S z*x^NK&L0BB>Ft`+oIC|uPLtvE1-${R_8(;GJ;i}$MUb=C1wV;cfw^GCCuCZOM)+0x7BKX_v?yvE-T*bT!1OII6FAs2C{m# zE?}94Pi?{wHef;R8w8k?*28W$76V2ZhA|ZNrKLn87*xP zEYGrx1p-H|JF^M61BL8^k%Zww&@>KsXbbRgYD}0MoF^0SqY|)Kg%-IOe*tGjn_FZp zgFwI1tp`1|p9>q`fr)i{lzLjrJiR|IS)NgL?z>MzXgUWs6C3YVfZ*KtQTT`)m4SmS zylil6E<}i^P!)HUn+^eBx=kpG2p^?_ITsH(jHnTa*Z6hdzv|(8!99KB4dB*AwW3&Ue|8julMQyXOfPhQ0}bBG z!J=|2dzA5nfumt?D*$qzwstcPf9k*GURSEITA3gpE*|Qv1tO8`Z-c0&ZftOe>LH_T z;cF!V&SkA}JRGDKO$p--%a}?=6?ctY~ z?IuO`+Z%ClQDHSi;5V_z2*Oga+iWIGskG|V?ekvpj@?na%fH`y(oa&q?t0rXOxq*p z7@@%!v>pNp&_Gl_25lhiM(T0Wud&JTvjg^6{hHL&f6?Y{ZOdV1%)9}9PSvqb2`vnd zMkjR_WXovEEQsAW6u}8A5~!E){qQyql>`Z(z;#$4{2PB%4#4mRco|WA1KY?yAAorK z;y>v6+2{dd@C{H37(Om9SK;!q>5zUtQV`YrdF`_JOCBop`k4WM=l%6l-m~AD_^D6+ zT&jL;zmq0bZr*>gE6D%pU+p{v`lvqVR-25&@NbyW&ul@gv?J zi6Fp(N8(uSTSUJXg8+ZTfQS1p)kQOA>^M zkzefP64b`TZt0=;|Nr*o&kt>aJx1N#9A|0&ss35H>fD&21U>%`|2e8RH{8SxaEJ|v4poRbFls;4> zPyI%3!3jsIyyoS-Y}&d%R3+Jc1aEm&;EJdYR5op-wo%eN!U6B?tX(+A1+-dGn0^wb zXy{hYvU;ON4mJqy`+Jfb)H(~c6Oy%_AK|%2uIp;zue9oSq*MBgk%&S;_TO?;uRNZ@ z!&T3hE5DbMT3QTLeS6l=C-1`qXQm9*Dea8L4+Str75`@KA^^c4y8)Gi67_U-@PH5` z*dtIr0lU=kmL!G;{*(0ZK?Vg9k?=r@tN`_NfA~L>d|o9g1p#y}SKd`$S5=o;GLKcy z-5y8Qwx$+FrMo-p8%fj$!k-Jj6?{q(?cYHpeyOqAlIlIaQ8vc2le>~_jtC$fA$T!* zzbU^jAAn$!u}4Rs!@z@NtxNj32fsWe2`ZF*Rs9GmU?{w*s^1U(q!7e^{aU%d^&NR> zYr%)%2z>xJ9s~T4VemsQFIDh_KOd^2^=04oK_OjM2h)4`e>3xtc4~Sq*9KF4=47;5 z6zCWbi~awJCHG)3D1?mFU%+@j`9T8q=Q(&z4e&u!KKS~2sUGgt5Okv}eT=50O6?MW zIJg7V1Q3QK1S>)UtK#s2B#(eQ%KZQ|AP?fQ;86Wk`lWPzo~o^%-1h5%;6Kd>U>qdR zs#IPjD^+YR|Hofb;V6Gq&j~8FMMdN9RbL*0kJr{n-Hx-2%I{{~W*P#YbW8&PG`qrB z`{mZ?^-+9E2Q5U`GcJ;=vP{Y8s*Aolvn{Hc{n3r3#1eeq60oRH}Jjz~O%1sJ~SzuY*2fff`J1Ns1zGXE8C)VC}X~doZpRi*4(Y z1+5QOf%r+gF&{-7I2YM0GPq)2z4R=#>Bw8b?%)GAFKcukuV@9>r`eV{-XgqyG_o{l7~ugez-#IY$*^u9%ZFZ7Db zi{Ny2_#YC26cYaw5By%M%Kh(`57h}n@hnJIKN1yC zU<(ol!3kf&5JCtF#Gnom!H3=e@$md0mjpTi<-p$($I(b12uh$p1H|Wj6Z7ZvyZ&Zw)~Vc=b8cIlq5#AG|NN&Uu{)K2*W;|2x{bh76HFE`Siyil;NS2< z1}=cYP#_6Fz=ROAO9SwHUS6xhTB7ws@%DIpK3C;I3Ic$<6pBmyKifo`=&Qk!ilYma z4}I?`9(zY#Q}OTeZb9Gg?rfyy}vWNj3zx`THF~M#6 zzyFo2oXi;oFj&>AQl{VCWTby`+OLJeZ4)d+Xp8KcGshGG={fG7*&O+))5|uouZDF zz8qlDVX&ilt(%8s*Kr`$G;9iVRPOh%o^v~Q31pRhO6HRgD5@)K8_p+$K}-=C`}WB@ z%b~EO123RgVNUGW~tX z*MS-!b2oOqKQhBnoopN&oe{RJTBf3g8z$9O3RVC1*6JtSN;SzF?vsH@ThnX5beiWy zNJzRc3N*U-onv`nCE&6OnC}+CQQpx996H|Rx2@r=J6j1O2HG0JN4SY?tp8arU*9ut z8j+O36EKq!akD{9P*Ozb^8s^>GXc#7VBl9B1W;XcoGA3mERjJ5z|L7Tp;E5GH$ z+in8mG^0cPOh(njv=&1z!jN%Ulg&k*g@8#5Iv{YyUft=+P4SRX(y1Dzt5GuRtlwKSXApNGJBSyG#-$>6^!2P6%&ehmT z^ldmDKt|CBq!s2upVXY%Hw<~z+JNH?>fzxtd?^PFkEeN1I5a*K`gSPoxjL9Y(W@P4 zVC^uyZers9?jXxS5O7SGA`v;rC=LRIBQW*glh@2`F4u0b{_W|~3%y|^Xvobeu+89} zacN$gaFDgZMr~S)4~Z(GpH?{aLK(M6LI>7udhc*x^(8Gn{Od^^PDzdtw(0AziWu^x zO_A0~(c3TlHPQgEk{xq+_{&GYtGipfj+&U{sjFrIeu=BVu=;!#LsO-P-vBT$WMjYs z000000k&Jkx5VVZ=RLTG4U;WQG!Gq(c~RX{mB&79_t(PpO^RjZPmH1j6jSUlf(bh1^}iA&?7JZx9UfO%YA)6761P2 zll)LVFSd_AKG&sy#z6plUIC@{D=+W|2|`kpeyXeh@%aDa%VfSE#bQ^i{xBqUe;W7( zE2I7iGyX3wABiL3*f1&!%hhayTbmzVoyTLU^s+}7e zOJ&A@b=t=@xj5ic1jrXzZ^8hQtxOVXg5`eb@+yFOD3WvHSo*6`|9kRQzdcoTSAZbD z6>b-Nzv{ifQHb*{4%GO1r~LijtDWKOuzShrYRbJCOz#&<7MiY=AD`TR?^k=(>9*nK zn}?GG!kq6|^MOR4*rZC&59H(Y*6s~Jr*_X0#G%+h5^<0X0NEO4SeFH3!PQg>RUrwu z)WI&PlQC>HRjc^$6nq2fs*Xu2uPH9}FOgPVG6CXQb^~YqR2M*QU6$llul0arrvG~X z{_pB@G{J*hgSt#w|i9}i>9v`c!)dAp97Z>{f zzHRD9*lWAf-3zNNygtd7&li=;3;2AWws1$=1H2ceicDoZ>;NLh7v{e zJL^PYUq#l~7jk0F>^+rq0JY>y*o4Ce0T16}tcC5y+rzLUXE?w(tj6zGU>tlX_7Q^d zW!Gf(@iEv>tF#K_#DPoJ2^g+rI53U2BG?G`FR?`E!jn{F$<}y;0TAUo&{jyHfAiC=;vfx4H|2uWE8`%=D z$yIRRCgx$FqgHLMGx&AL&HU%bi?u;C;f@Bo-l_qj;7=gwkWR0OdZgs6E z&j&_%18^NxRXTe{V_I=+@W0>}wLj&fHGEJ*(b55nwNo#?7;x-V{M#i=*Vdk)d_e z={hIYF+DkW!N4g!wEnYcM(m4AF%T}g*#r*y3kh%*p-ILA)%2_|h*s*+lGsPA42Jb_ z%ZDw7g789qvm<-8HiqqJ%p5ohkahAFv(R=C1s>cPvte&W{UN|l;NLF=v~Z8x=z8tB#recxJcnVhK>ohO$lp%69vp+UazdQ5c%6|0r*(N^K?Tik9qWpk zB?6M`O=>}_x@TLXT0>D00#8ofnp-er@FBLhuoFLOGup-P4*04iLhw`V@cM(77^MlU z){kXjgN0oMaHv(C42pGaz20zVQn+tZPT5*Md43&WYKuPP6!{la?jLql-2~~~IDIO2 zaffNrk|Lz7+^}egSHz~l$ixG4WWCP9pi3eylc96u|F1QxHrBhWqSV~)PJ)Rfn{Q~l z-mzRAf7z4r+wS{Ynj?WnDg-j?^$K(Hty(~xh7G1p(I`>qMq(px`m4A(TLCa{Z-I8wYl3P#L{v!{^{}HuV`8jx%^o8oj-b&MHOQ;u2+O`_~ZHc^7 z>fU7DULVwKif&mA^6+Z}2Kmy@@Y;*OpojXO)Xkgn4Vy<8>4$M*F_T^2YQ+8XSnee3s0-<%+PS=AMW=P_6R^VuyATM z3V*j7y`jb*igt~PAIYeM4M$h*8I3fzW@`lj`P)hk^Ov}IDE6X#88f4SZ>w?^Rq=il zxe30#>xf_wt_(k#74HXgMdv`JHM@F4aLLM#cw0pBirtG zTk2N29GF#aJE=M>RHiU5Rc#{;ydjp9_95XE=cOP`GON%?@IISf)l)vq|Cm z)K=uTgpp^G223HP1`@%9pm+!y!GQnqp&^xRYd8>rgW)7Y5u&EY{zco*Cxkvr^ zfeAn$(0~d#>O26!Aw2Qn@HdChyc^*JOTYZ0P(lbQhf4YV0PXkH@uvB>JL!QaN)m*E z^>DrkLPEN&T~qp8salXbXK&SX4qsgVH)k;S_w%zAU>K9b3%~d0Pp#r0$ROI|LwQRi@lp|THeR5 z4*~l~FdYYgf_0#84uCKo0tawqVn{p>_j)0Z#I<4>cao175#xSp-Koc7FdS@tUy)Wd zjeEoL<{01ju(SJyL68CE;u=x zUZ)_;thy6i;W#O(Rm_}XmLqifmM>iCnr}k{d2=kn6<5C+T=L^48gfS9TeFS3xxhSlo zTdreTgyFQ9NdtrErd7qyBB?}(1#$1^N&F9`de(nVgOcm;AW6 zA*%)*DiT2iym$%$@Euj@Qas*6>9N4K?05t~?F=K>f(Rq%qWNfJQt_#k;% zc>KNuDiFl6B|!CFs;Y#a2>{do9xDC+yk0&Mm8tYDFCTktcgL6rL-}xitKU+tXQ_Dg zpZQh~r`1UnDyoaTuBxT*eyqOy{qLpB+cS>$)y6w$4hl5~fPnNK0tB#P?}G^d@DLq? z0B|%fz)*xBXeD?_$({yNe56Uuw}6NBXOt?EW!qRhr;}NXM#hl+R;^98$=$q4@F}kP z#IY+!8!ESg19lBrLeQ~<%=8w8K;z(3_5HfMB;DO@eFqht;-wjq*;_m}7`1g~n-=$> z&?`njQhR^$S&_DSRGeZTj!D`uR6vj%Xp`5NiH#H?CnnPP@;TM>(>Qaw`6AsRo( z{KqC=bd%f6*F;cgKCv_!5qnjV!h1<1YjMPCdNm@JjgIlTQ9HO#E7Giys(Wl?U|KZ~ zqrH^m{zy)x>-P-!A5oeg=K_8EG0$uoJ4#n{_Q5v~0l5WT1o6;>2SNz)Y!ZZ@Don2K zvY9$+U+Mqs-EAcDvhTFpf4};%&)IAHWyTuX-3#}w=!-_M*p`q;Rpn#x`vuH*lU1{g zP2h=>Y`?1_$!qk*q9nJxAaB@dMkJtwDxp($4t zOq=yez5o98U)8Hu|En+m%k3VkueU5)xp|;Gzte6u%uLZs971x^^=cxkF{4HQ`ku*^ zH5i33SizO={9RRBx3`j^smpF@8pw3$2dni}&6a*mnZNGAC}I@W zULBfpa^d)xo3Z}c&1i^YG<#2~+DDCW&{6nHG>&&p(p$r%be$%hk_mlzD`!|tMAlnI ziSyp=++z)>Jeb=A>G;$pW1J%Gk_)1h)+SDtY194~dZ=Py&mT?axZ4Ero=DNsu0Jmf zrpgdQIG*V&ZrPkCOC~n-^y>zqZ8RTEwb7rYsWp+a-W1c2iB%zDnFcj+^iBmAbl&NX z6ux`61-9CgM*`Be6oG1+wJHM z7mDqP(pRd$8N3MITIu_3I=NjmQl9Otp1h5gJZDiIJ&<1| z-d!iv4a+Trvk{Pt?&gqA688+Zz1(2r&l3SopM8--BT*Te`==(6onTl6I@j!A`zT4| zBKJB*(3{=Y6VqBcPs(9BZHL3XYG}_pmh(9rg<8~gnP&zhU!z*V=?F+Wzp?$;MjPQZ`#ps^)X@-ln-zYJ4lh)?Z7R+u4yL zXj#VFClT6A&|UkDMhQTt`sdi@=P2b2Zx-RUt6G$`e(t=`=SaQ|l++6yo& z_4cato$jU&;t_?`p+9eJpgK-5p(s&)9Ft>B=>+R>Ii56Fbyxp=c;KeC;G5Tk^n*?u zF1x#iK&E#2wKbKnQJLv^+&lU6QBCO%N>0h3QRm3y{@0f()iTJr$t}FdluT4Y1uogN zmoDt{LbW{1)F<&=n;3?|e<<3=}u_kGr)%clq*KDmJ z%9*w}7_?|9wdmO+4ahH@=W~hrQK>03%H5N5I~pC8=XKncWo3y(Awq0u;8Xo)Qs)|N zq{T+6o!gQ#%9whj|fbgRh^=e1&E-pak70rObh&nD3 z^%2!;lc-ODMf-9kui*ln;&b^7Y{jbX0wU)e$1{|op%r?o5as*b>F_sgvv~Rs`D7=g z`v%6~OMT<$qGUF)&^GI0uUoVebY35DZykV{RC0u1W|l8TMIv zU>(7QoQySL&`I{S^>TJwu!aeQZzV;o39`$}oL^llMB9~_ddMT{hH6fG{!;}y3ymucjw3=!RQ z(91FUiX65Km%GQoQqP2JCvIuQTD(2F&sm)T#4pzY$5AzAfkOjOy6D>K!pyT_R(<_2wT4FG_ z;S7n0Glc%7*6asX&aP9U z&JZ)us1X#}yGUD)WPxOW;t>yWCio#5&04^Ai^HLO6!mxLh8>J41)*`m#BU^T$vxz0 zcVWmmIg*i50|Rqxbm$^_#~9Gx3vbrn5sfBPia_H@q2dC0#SfiuLqmod*UzJPCfbmCi@74iB_v zg^i|MaxQJ9(17-p;I^x~&kkH}${U}nAa>DodTt=_cJcE4lOb2U+-qw6o}f_Q?=2#H!s?jo3dKhNPyW! zl*PD&Fcijh3d@FfkYit~Nr3S2sy|ES7*y6Y3US(2njF>|w|7}?`##xYTHAbInf@nnK^Q}%>FXknNwasC>Zp|WNTP!ysfA1=$LL2+brdlRiA54v5}K$7&tnA5KwU<5t~xlvf>Gy!3eWq9A@%r-9l@x9x)=k5AuuSP)}k4W z!GF)*YYp^T8Vf?(vMW{jqjZ1gkq3U|QHX$!YmZR|`Q2@9--|9E@IAO4G7T zU$mrO(w8~877Ola!t2={NBh-?Eu;gp%=bycm?nY@lY3a_Aih%n`p+r@(0;6YVwwM_ zP^#j(sw*yq{)P2aSLj|{{Qmh~$>7W&**Cpl@c;iVE)t8OcpC^JU4`ER5FmO%EuLsF zA&E;*r^E#x7&h)~iN~gc;GHttXrZgXug)mfvunvRx2oxA00|0P52l=4h1|I`JZ7&Fa5&$?! z2Ku2PP?iw%d|p2v0|F2SgeVXMvHGI*YOU}f31EN4^;Tbg`QX$_2Y`UoxO`ru8CWm} zgb-r!@cmJE`|wF0gTQ~9tPeu*@E#x0AiY>IaFh^$`oC`dU%saIZbQZ6^?wEh0-!yA zOa2THM2N3zN{fInA(!zQxQ=UXmi__={!UM;Q|yvAi&2e6l>gej=_nMzb)yD=cr=CI z1`>&0U#ir_YRkPP@E-UdRiJlzrAn`Oect_Ai7)@-*BC%D-%sZ6H!z3_eRv0}F86{C zNTbTjfI3SWu|WGX}T(JM>u)NBPaQo_do zV3$Yb%INpnv7X{#HMp_rFxd68k&vPhcGGtE%ni=YuA3oo)o#F_~oV0w5{N!Hh}(%bzc} zsZW%Tp#DpD-yitAem>qo*TkXqsuaaC`rj*6@X0w@;r{Ql-jYe4kVNzrWks?Tg}=$z zH2?GV=Q2i8odTQxPn4Z!Kte|)Y#i9UtQ>ep-e6#nFd~J*6~IkgfTd^%rU0bZY#a!~ zAH+zwee5S8SR78qgn+%fWJcO#Qc}K+3rK1cnvnXh1mCIkqhxfNr3kbfab{lGl51VF zif1?{V(xHO#YV&D^pd=HSE%V6lS6nq%+|2!0YFa4;wY zq4)+>Q{D#PF?cYRtyJ=_Ra#uAO90aNJ}*}&DAkuJqxrrspYpQ!0bm{^iD1LgC&Ebn zt1l9AdYAs{ypt!Tw?~UAXp_a>p5@t)b$wf@=Q&sHoZ+&~X9nU4FTAQ3DIQywduj3a zig9P zfg4Xr@uCGh860BqP34D(?Yd8dcUICGMDi&kIU7Eh^8~~)Nlf{Rfb+U(9|m?~slt1# z?|LrDPf>_wPU-f_$Idc%W~LV+>@&^lr%#1F=R13Ce>*wD87Q8h8ih348jeoQQL1~l z7uLZXmcbhYf9+>HSlysYCYzI)YipiJ$-y7bESDgmu(enHYgWXAkl4zlDtj1c9Ggv| zG!*&0!q9UVLF`|)G}94uvq>bCt%(>)l?cUwjV#c&hCrvQNkJ1@<878i2%4tywAAZD z3H`d(>*34Qk%f})W|pQCg*NA=3qLJ4nMJXdMy@D?!9u^VNi9lx@yEt@z{c5RC!!|1 zw?$3x_$g^-@?M?)9)gWJKnQC~|GnXz+&JBN&Ql;+Ufz0MpXp@7MVLm{Myhnx3zMD6 zT~2Y+Rv4AKHoj>Ab2>*w;Erncp2X0QpEEuFwm^-*jhB;5WAPCP6!yLvT@&}bG0bT+ z2XjwO^#i68g(F7`83nBU$!c998=X7_HFbL`th+6{l1ic`V^WP6wxx|H1j#jtiK_!S zF4@ct2AxL7L14*hP?#xsn%_|Z)I)(+rQ2p^66A7o11H^};Ph)GHmG`-XQzg3`*S%= zXK6IEvn@GsInUwnQRs^Ddd5%x8zJc>%;vD*rdx|v`WG{Bj%6q2etUaP^@A@1d_OBu zUpVz+ebaZO=``Be2tJC-u<+)J4o&WAE(?kYfqk2lI28hsr){`8qGR=jk)MKKM!Ne6 zPlYEb=JvG7p$z-zSV?}$T6wXkjiz*I`@^5^5wzQK`9Wf z5ww$s#3d*rX=biQwa6I8krMPAjv*l&Xw7g}t}q2( z)TEcZ>hGnjg+WQ;6hmAEtl|Fj+0e3_#L$CJVb9}ijRZcHz?w>aA~x`*k?qH>)2FcM z^_C$5k*7_RVBO=arWhow5QQ_Bs8U+{W$MldWz5zbUS1>9-=xa_T092KMrTN$_S&@v zBNf&z?ZuDV(h_@zK;MW8U2PjC(-(yi^_oS;UM#r)(K~-=pm)Pll^NZ>|#N12ZSN{ zc$PjcFN)QPAD%ChY?gtKh|1*4@6}etO5pSP_

rqb8{t@d!~$k)PMeFYsaS7%)$M-B6_{&E`CqAJhJvIxy`UfP^8#_f`#Ky z>rmY+@X3-WOTwd%;3#5q(eqS%LTqX2%OkV9KV~ao0gr;f7@kSS;HC&oBv;m}7qfvl%Fdd7gP$t|^sLwQjp*xv_QuyIPsKSNPYAS{N)?UI8dmAm z6eA_C%T+hG#+lqxt4v3&+$fh`-6j9>w?F%>(^$Pu6fd>66Dsa3+7@S`OhjlfM-xX!02J~)q!EXTat!(eE94{kTFFp->; zxb2m+RLb3K6jAkL^9Dr1w0fk6C zvBgoyDu|+l zwvJ|8!xJ{s1%=UrqVmar>i^(?rMfT{fELCFE&$@B#>994FA?WIcq3CJH$EjOD&@K! z6%MdFWi`ARBBdiFf$K@$HKEM}P}k>%lK@2Jng4KdK*m`v?7uJh6pTb>m_PWDK}z_S znQowVFfzzQWOBn7kWnqWjI}}O2naryS!UU}IPP>hat_2UsIEW9A0YbH@pLfagNI3U z-*2Cz_aiR@sN4Fk{Yw9h4ENJ8+x)0`@oYL(<>$E(ZNHV!O#43CR6BHz=jE_fY~|!E zji(WjBpKJg<6+*sKiQOp-`B_lg+UqZ-rpZgwxC}^L&M{*&fgYazoTg>H=3?_gH>KL zSzv(2$?nv+$W_ttaxWsm)z(UL- zLq52b?P{*j#K}Q*2WztV_##2nZLP!cb}7nW@TY*lxAQ`j?dJE}3i4_p?y3(xeYo*k z)f`QiN*Sd#oFQ{&*N(xlpYoo5a{2yauIY6Zp^_GF+!11f(2@uO6-taB9AL_3#=|O{ ze>G+MyO3Fh8O#msnjPQt%KdL>IU2D(rqSv&vis|af%Gn)4TP4q5x~&AuTZ{TWksFb z9H2A4i2rJ~-4-uB2z8*YQdY@~d}kN?HW5a+Jn~a-wSSjnNxVg^smJl!>Z+$z^isX? zS7b8D6W0ZDwt`4|rQRX^OAuw-JBs_H#Yqs|3!1;AZ@mGw$V z+%fi{JB=cKVdsyPk43Md+<#<7`K7!pQzg-UtF=q%Dj=? z?byJu&0cMD#1-Pp--t#$LO+rab5R|;B$M#6$lnY}cxN-y3u|jVD6`T{*$3YTuN{8B z<}DPJ;cMP|@k6TRv6rLYgI66HJIT3nU)`QcQA_>BrR!8%_%80b5I5RgLx|g^FGjMI zYVz-)S5471^|Cf+SHlM+UKJ69>*(e+^ZtDCy0Bbdt&oA?F0yDt z_4Z_{Bx(4`0RRRm`tCgE`pUo=`gAMkOS>wSlfnCM^lB^$5uPh#Zz9y?jYkB`%fDsR zHsfUPzb4@k{*^dMMr%klpWeXpu;%IV&)>6kX_4GT(7c?ue(a~^G#S|P?CcImeKgnf7B%7qh+$E-{ac?Z+z41p;FG_V2%t+FIsV|H|}rT+I5?(V?Nl za=!d?a{)YbNT9Tspwzo;3Ew1vX1lmJNjVx|Cv#ptE9OH*I!DaDtgSfj59NJs zFDqK5S)tNxN-VGKV`fi>I+9LWB(SQUq}yvbO)*CDjt8G(zSz+wPf*?e^AR3%OK2R? zz#HI+Lck+{0z9EiryeSr9hnyOfW`Gi@g3rm03nE!7DpxmD}B}+$k1E|K#T8h@h@es z^IVMxqAvo+17SDh7tpNkkrWU9D~(W?fAA+8*!zCYXV`hI^fl8N*S~Wapm>ypH^CCs z*DBiP|2!0Fn+fyAQ;C>yX@RJYK;adDmmt{GyImo)R2VI#@(j7=h1o*Zb<1^E9TCXK ziq6WEy~G9FBP!^(h`(-*_LDNeM)u3l`-?Jp1e^{GoK!ebVHRvS;Stz*6<8CI*iW+l zn3>D+0f?iBSS6?=QWF)8TfnZjb6o={hn2MdmJAFALSE#>bHO_(`|D2XxPDffnT^HU zy33FdzPL*?`ufdSPiDqm*qgkSNUKvWh{H4nxxxMIQS(#lP5FT|d%lzsAv$?C&*d2E z6+z=Xue8EkdaI5o$w`MdmA}q5Gj*9HeiR|BWuFJGot@^=*NpZmT=V%f{|?*2z*pET z-o=h5fPjitP#JissIh1qqmuQW*!J<+7+R+((Jrl7*T`<{z*bj1yRk^3bZ?r_bZMpG z#kZxpYNOvdKV&ET2)S+Y*BC;*o;8ChL+u)}Amt6df&!|;nXanmkE~MzR8z*kwpa?r z4uuUZl1t5}FuS}%Dodb5#26|Va0s{ZrFuLGB-^TcIdu52}7gSsHI}_Ec zAD{S#(u)UPcEF}t!z?DQl4S|P?#|x4yTvq;6TRuwo%YXlb9zVqMJ+`5+%U&UVDTxY zWHau@-;_k!k5E=+c9IahOs6~#7p@#dW{izfB$T3M`3Q$8c)SZ1P9^s{=C;p`e=m-dT6bp1w{iX9!!y^yeulT$7@m$z3?l*uOPFvKJHpZE zGayY$!E>EwU>VK8ARO7Vy$#&=u_TzW0)RXpb5fW@2&pNA@F8x*L8S^KmOV=O0!8~_ zgGU9OTmN?Gex2*2#*FJL$~=k`Z2pR11UVxQOkOd69x;rasNDqnu|ftWLM5=xH@&7*~(Qgz9G6eqydJ(7zu9ak85zQaJ_Bns0b-Go$65&7rJ1Uhlj zD9FLUo2iWn=3wcA5%F8CJ?I5Y`2D}5DYg4hf1#7tM!|(A4R^jC&W@>}DWkz(Gkz}s4iU(ExLP1!;F1n{xN*M6ud zHf+M=D)nSj_Ao!0H0GqJVp-`cok=EUfTWTS(TQU!Tn5y8__M?igzb=H`XYi5^=}cj z(0EeAQZ!&X(_+@$IjUgfy!CG5V|&m;m759oG8q>{$@d~vk5)S3)4J$da%O)SES~%N z;lZntgtxH~XU^!CFPu~3KkB3%#L})NF#OmqXS-k#pm*4Ith#*Xls%3bL zov*~h@iM?=rUY{CG2gV|^bdwykhJ?&`qTP-4Bs2Xd?j)tTl1OG^1O=O)7vTGnp-{aa^_ zAX~luF`y`CU>b=4`3d^5SD-R+-NVZ%f6zT&$ck3J)h}qSsgW_9>R?~wX|Cz|FSmYU zFCZNO@UKEN)07ynA4P&n2#WjGlZIHo>8AOHqy^MmjpPP`Iz}mls(XfzHdMHj<+Q1EkagKB1%QH74(xC? zLRDbME3=}OE()xiz5v2H6e*d|7;@Q8x5Vjo(=M?2y1q5~R#-LRMjSm>W0ErC}7s;9qm$MbARy4H0!%A{6_;%g@RA2hN;@;Lhy_B{qu;DA$|(u&hgmMPa}S~d z*l@68y|ef~8|OkV@v+?LT|_R{&A_w##j=l|R0J@+5WUGYy2dV#$+XZ(D@m0;b@+ItdC zg9nqTO0^fWdjiE0i%?o{*}ULS)V0?S6G5K*xN%v7PZ#gBqD=0PHDnXJi~*Gc$F;`7 zPaRPlK$!ImR|&)6blh$WAQt^p2w6{n7?^fBuvf`Pg`z_yF;T5j_&YdnCiw<-mMV_* zaI3kmVLxvcQy=Bp*_@SELaYSpJ*t<4a4gIjaIV1iKVMBXk{uJVvUWu7KBSm_Mpxk` zF^gHyE$eu;jtmW2v`Yvj;a&cuh=7w;zUY2;X;=xk%+mx$D?66#pt z*CrPFe{}>6F@GJqe0k)G;=SQNg9t5fW#4tHj>?*<Ft>Xh z)7qlFO?$30td=Y4Ag%`0ov~XV&Tx`)5?f2pI98-&!P)r|g$u)bk}s%#GghC zsIkK0bJf+kenMWHa=M)AlNP@=XsS^hDz{Gc-$cmJe-2OmPc#rFt_#KuRyij~FW{zW zAJNYV?YLW0V)^T-=yj#pW;Ax@bUk~|eQ`6uLmp5>gWmnM{AQ)dZPEg6!Q(6aFN)FQ zQ%kceyS?|CuCvxxvHs}&<6LW=c7t-2uU!qYy^HsM1w)G@zsv_D(%aeL^YYY6Ty*{R z^i*~DOtQF=O1jgCV&oyM(mz=};w9u%$*f?Jf3f|zorJn{nfb%Dbtc5lOKg@pWFYoP zykN=9Q4PA`1+jZ3Fy(JJ)1CNiY4k}|$xPk(lGUjk1^>#;i>r^}pHbwD6c6jC9TssP zywLwUOnYvn#0}9E{Tuq*C*TD#;HW}=u30SotuMPKlk-t8H2(Lrz3BpBhsJ?T$e6lz zSAvnv+bAv^^9JA_jq!V|`2r}Ml-4WafqSZGSP5fomgvV6!Ph-~Z?1~Be$Lvp8eI2z z&F$PMivacJ(Qp7c_6ZkEHiO}#MDu@MDtT`3!D2tZCWQ_gJq~#|?o;3W zmy(x>J0FtlcbX%QW+DstPOZ;FB3}sR{nn`{VFybF&EqVNX^Gd5%E>l_C&=OB;^}1u z=B=@&TRD+w|9Q^xmR!(iRgpYgog*}8PJHN1RB4AGZeNHLb+9pYFGH=rW~aIjzNip( z?v`CHG?Z=m#ya&qM%UycPFx9jy=$aC$x3&h9n3d<%20=kJ+u)aHJRfDV1-(9zE@$00~zkD#B zb?!W!E9&c3fU3lYHFeCSgbx7T!c}?+;6k0WUD{!^5K3wO$I`a|)-l|g_CJ~`hZ18D zGq+AqO%G{na43=-^JV_|A!!c+(t}TlO#}R;nalu8GMQNoU`+u^0>IDdXvS9tPND?z z{V4XO(Kw;>Jr_e^T5E}Y*^Pr7THK-oadVF{OI=b=myEA(jg&!_Q7g_jLGQ!pOYvW@ zE{`Tgek<2Zp_Y2|(AxRoTQ%iIdig{?Rxvf25Y7$}_1UFKbA8sHVe)|D{Wr31{*^6N zjiR(Z1091g65)x;VV7EWFo)=Uhq%`wzm$}%c`OQz_w9`;F9K(*3y$YV30H6;9=qSK z=w!BC`AVcG>r63OoDt&PCYqj`arDQ}hO{hlJ)}ElouWLDh^%5d5?9z4lFBm-UHnz| zV>ThtD{7jXdjhqWcI8Dz$1-bwg+Y2E+2FA6a=HSgJxVQ;no`kBGp(Ul--RGYCRRGb z?qs@VPCjQj=*V^0^vfq(B8OSMV#I5|dX2Y$aCLF*>`!H?v-HRMs#&bk=}sw;kyd>F zoNVlRbED`)qm)zJw~sP}7(V`OH@%5e-H>r@7iqw1!_GNV2QbmM%E`ebVVC@8k2QiK zuZl--!)(my*fQ_Tf(kDIf?FmxzIiA$&EnNT`A)CCFl9oxjLJb78r*pLNiY`H{;)7`O?BW%44nS>`PTl9=Hxbv&fQXhxG0y7OUW^#Qg;*0m zXNDs*pEa!ReKvufkDJ6H-OiSRf&G~LQd1`sdD^^KXH(QW3n8kE0InGSy z<}NC?`zT;@SU=+H( zV=WQWir6muNM(xBWWY&Gh3$WO{uWjG4FEwpnxDr~D7~dD;UUl(JsXu*!1^)mlpfzJ z?W-7u{QJn+*!6j)b7P-nR)0T}MvE(KSBd}bY+tL|=Eo=2ri;XfAe3<&F$f_wO(|!i z?vWY%x3ZaZXyKF?R(V$=G!4Of>W@@$J1@)c#fxX_l3<{|uKhAu2GOljHY?2?yFhu0 zY4JS^an9glq0+E>Mu;u0c5T{lltvfwlrb!zlCj}DX6J-+z#;oVxLy`Pdh>FV0{6S( zTe=K6?<*4$Jp1LE`$|5~R_GMco?Ha3)xt3%o1Zxn zVxJ5kaRUF2zK`av(6xXw08H1tnDtQ*#fV+CkrwZ-TL+QqOJaNvmMd*`Ys*T zMm^X5&f6e6baiqRR2CJU{{#g7;}#euxCx?IpRg#WKMs^|${)U}?2m7$ubf{#>okmMkg7jzvtH&I z%NW`2x4;u<`>9{zo1yQ4jW#p)_YkjfUhK9L7U*~=Xzt(8knEOb8Zk6T+4La8L0Z&N zfMCe#s6TWRe6pazWBN3#4af5PxPZjF?%y_vCvG({Pa#&$Z=028tWKJqq}r7{ve5QS ze-2KF5K67vc~h|I79=zHCBQ$|IuHG|$Yz*bi}+uSFVxp(*|a%<#eXoA%=D@0d-Y10 zoOIZ_iU7t{#RdAJ*|%b}-?Qd{&Wgf%lZlC;*J#Cs&xO(Yn?JSEQbzYwe1--&GQ*0= zIG^~bn;r<_Z74O;W*Lv<|kE^&!?p16^)pLt9 zDxB#vJ4CH;hf{QGGe*7htoY3rQA+OOT$@yzr3t1U6!DAfvu;yCz)>E|&HPFa zP;FeYmr|#quvyN?)Uk|{t)Rtt>zy{9=B-oR9r?anpNf5|NN>gXnBsN&2S#X~j!*22 z&)fR~bMLYmJb&4gS2Z~%ZLgD9SR3i>w^`njVz(W&p-V!SV0yylznMrY!d3-ahAE#_ z^p9lg#1?3oj;jo_eNTPCmK;7U%G9!7C-zu`X~vYu*}u%tk(Sd>788pbAzs_s^qO5+0`@_CB2!OL>dIg(tx zx6SwNUz`5n9FX5(6GqMb{os-ssBZ(aJ{lWo)3>h!9 z-IX0XrJ0NxF!FJ+IuIEQY0}E+xJy*qtkL5EAsqWj!;1TDj`nTK*~KkQW)7?j*fM5F zW^6bj3SM$h8BV{mdU2zJe5oRp`vS_sF38i&(|%&g2x=OtTrYcAj{fP=UXmBe@*_a# zxIJ<1L#tp(RP*9UufobEi#u09@V~dMAMjtb8#0i&`4b|3=)^Kry?BBcd*}TkqQ6F_ zzt=?E+FZcdA=v=+jrcxWHHBfzK^H`~{8*I)#Sz%9lghp*Ja_!}2R|Oshr=PdKYEsVSH6(C*c+@``u1r-`=}6H-Aw+_&=0|Jw4}JazlG@jlC}(0 zh$%Sz{Dtg$pkdE{SITCR=BtJV;ra6w`5bZmCKN8!L?3f z_(w3)rKmR;8~p_h3;r$I87p_M~ebl@B)ksBB0Ax!@ASZq2a#y1oDC zPguRjJVAgaTiRXu40As>Qh()l@`o)cy@ElRz_M`1$8Ja+wp92JG%IZn>wUf@HFEP> z#QUQkmDk1UMGhNwg0DtZ^bhH6mYrW&#A;83sEl}Cmi~J9Bc*xGL*bPg`xdjWoVC)g zH%F*tvhT7z{mkh?iC3k$ANHpVS|-D_V!k0bXPr`~FgnV2xp@r3MRNjwNgGNPFpS&) zsA#i_-mjD5-}2`1N#R}j`OfN!I|qSzLH5L{m>5^F9!-X_9c@@pA$(m#rs zB$91jZD>$PWe~w_ey2)&r`eT*XB}-vAJEstx0)~)KYpQqZyMhbI;r&uRnOQCQMW}HwNk^?>b*O>dVF%qUoDqSHJ)crL#oqQz z8jb5zw5(wnpddU<4ZR4O$}ZNdJnW1Q21bzNVSfB~dNJ_%2vH#C8kM(fKp(|UX{CwF zG4{vS)O6Wk*iYFMau&ZdHh%0j<|#R8A>@~kEtYok(NxxN%lqaryqg*v4oH#J_K5r* z)ggs=wz-O6<}5iw~F6Zs_PUhU$58kO}cjA}B5C z&y`t!DyC<%35)(<4m(y+XD&vQNMQK~hL`f5_1TQioyg>j&aCK+hM*}}p@&cj3yrOw z21;h0fGBKTxpf^#>bLx+F zOviBuqHNe2_#hLkaeJ$^W(J*gi~%6*(igKyeBH0zIdtOiHgztqOyhKPE-)d`oiqB$ zIhh@|Wf7TqII?>aQvq`kbDABy=5~yZX~rl<k!u;7lY|lBeC47hYk9Q z5p+`cIH^m;dg+AMMO-NivyAanY|7@e%t^ufAQ^?_uwPNa2G<#i1$3mdS~i$l4XsY9 zbD39q!F9~*oF9A2zC<4c5%iZOhd`U(L~d-t3^uRJbvZFT#7xKT8SM8L9}AsDs8#@( zJ5cBU*PrZ12%d%8$Rp_3;`pDyW$+6~Hg&W?XB{~@T(C=gF6|Qg4AS39i=*7LAHFkT z{Q(_B5cx5jI(8eg|DmhzMMXh>L19*bydGH&r(4IDagU|6Fcw^6|rWz7{)= z>}?Jnm#lxv(GKBE!p@dU35`D11OzDuWqo>cb8X2)T4fM~GUlEU%BzD} z%&YZd$7plFU9msTa4Qn{S zU{-haDWWk;>7J@1_|-U|_3$PJ4RP=jP07r=-^NgP0sIFT4tfTmd~ZT$!`FDUR~6c- zU+mIOD*gwBL3+MW0bB4?d93AwdV;$YUypvODQa0H;@j1DLKMkWU-XuEd|hP`$FP5T zz%xVf_TqEn|0!4?1H`}<5W(|*=kOnr;VKtTpUT3|1^rBoSaSZi5wI%nD*v zfyQ5$pdJnZ6yU&cNe7MpozzP5rEy)`@!5CB+3)97gD0x<)mOe>75m?x_o(x5GcWhO zM^`ULs!Q|uqqwVhsbSQIz&%>ICV4#MV)Q_4=5b z{9k@QR$i)q)9T0S+Wohxr{Q&PHmXySZ8>6{tysfC2^Rf+qNo4*fB#oi_PraO@{CzG zI*dj|hy0tUha#ec|28Au4kVjQ&W14f6#YuKGLXfEF-yH3=zSxTb{;kbJ;Tj<{QycN z;+gUMReY<;6z9G?RkNFWid&MlV?DJh<^e!nuizXIze|;P5d09nJZnB*_+GB+uQsP` zv`3;n=mdpv7PTu4p%?*A%`B=QC~FG8@IQXmvx-}ht@h10th~8cl&+T#{Z$wGd-S{Z z4`f&2>39Dah5oTxsv@eh^N$1p`m*sL7!bMZ3xW_gi>$aif><}eFiKZ>)i_>15yWN> z7l3&9N(ey0STI2Vz=a2a2pR(q!2|<>5c(+|2|`hDLoc9tRQQalEBd=6$=A)+r6ac| zn#;2jCQr&pimpHYIwj%a7IsN>%Il(yshMQ3$s?^$`AiY*P~1SBGIoZ3Q!cZKD;0{6 zz!XiMsl)Ek5EZzH^8vUyvoQQV2y|EGxzR-JZDM6z3!%`a;La}9^au`5PU2*=TaZEM z$H!bF)ns55;`LHSbG1Zl7~HWU4h$o6tToy&hf@Mk_p&yr81JDFK>lwe@_f4zWi2{f z3_v9)`_lqUAu9EPK337Jopv52d)ZzkW6PoRpispZg5%7A$Nk1eU$xDF-%hhrrY1cd z%UXtc)8=eE0FYCCe{fGMLN_J#d3J7eTQJ~L<2}jI%$1br2o(MwQ0=ll9iHGghze%< z=c(T+^i%=C2)cZBB^LxJ3SUJcSJg#hzxqpv7mjeky`=U^m4Ll@?1NA~rl?h;mB@e+t^5XIE9{l&XKJ`}>)k^*Q zePbb!c$4sL0MKd(H-+HT14NL5UaMtQ?~lv=UIzzL_&dSg@;P7j-33%=a)tanvE<~S zQ-c^O3PK;3O+Epo!d3W&;D!OffD8y@|9jwq5JCX?PoVw;FMsu4zgYv`rQmZe4==B8 zOoPCGPN*IN1F(QyG`zeRynoD4C5d84`BGWnhOJlaPjC7=<-^zBm-$uWI6St}s59{N zNc9HjmOSH1KXo5GS~Ch@xCa-}^**P-5Dx+U7(5IEpuqu{KSmAlCW93THNeWjfII^r zLiqH#K==c<$_vDy_(KxH9JNQ37pe$q#FbVn{ZOb7gs=YVYO`KH@RhbjFtRH?Z}yDLN8fhbiTW9a|=QM_f2~(?5?x%Oq~~ zS2Xn+d($jDPLfR-&BRDZ0SmjWvC~gn)Ztr%YIZgj@Gpl=h>BhKrj5mNYpZTPo#C_i z`}1K=1wo5|JR9ZU7$tymuYRgjB>wqd=hRWI5)8qC02|^* zr@;@q^c*+^I08bD1AYeac>H<_KimBF+jUPH(olF0@<2Qob`Zbjt%I-85m+fci}_ic z?52ysi;M07M&m99v`O)Ka2{7VW>~rK4-&6D@OTIreX(5p=3RsVOc4PgC&l2}K85mr zOle%p*(y(gUt8+Mc=$6CL4ff1AVTkn7w~=Wcp(CS5X=7n?ew1ms{g-LJu+}~J?qEC z^DD%$Ex#~JaoM_ z?j-tolQh7Or=g-9YzCSUd9qWjUiJo}&jXP_)%&4pcu&J&K+q91mxgCgnnzmmoFz(V z#v3|^U;oda0-Q1fa6<Q>!Kv4f|w`;gn(%P;LsUd z50|5`% zC9}cza&O%S{Z{aJ5A#8&@l_v43?r#i=^Gw2Uubc- z1A9S$@};lU(%e8&ee`lYk9+HqCa2nWQ6_R9htcrpRq75Dr>LK(BASpEOK?!1 z^-{lo^U~ymncO1=5`n%x z`FV1$c~$+Y<>c0b@&GA<5H}tN0KhOq1~2sYz%ad+$Nc>FrjbPp8YJFIApauh&%Rr9G#_kuJ^>2>}3o@hD32 zui~ESZKvN$o_sRFr!1xdqJtaUYhm+F3U2ed9t-@CTy%lK}xZB*yQ25Pl?HuU)I55hw?Y9pbYENcH&4RM_=MDi*;DU~+U#*-%U`hbt@bDq3 zRy`4w*atoG{l3ZnXQGPURX_5k$G@JRc>K9nzNJjqqWa5fi{)ZG`k*!a!4_O$w}W4P z`J_adnz>QG;Q2-T{k_8ImVIAWiU*R!%GLUl+Z8eIOP=<# zo)M+3+}hbWfBx@R_Zm2gViKLSg)kWs|4)BU*YM&k)yL8_dY`yTBhKm1bZj1sl564d z9(4x>!NI7=atRVGO_PEiyR!^JE?w)}K0pac?&lhF4IfBThUvAh=b4^3;?23~g2Dx< z#rd8&JL%(0d_Dj$Fl1vuCIA2c00Fj8;1UHU`&^8)xP%=xYUWLxy3ByXg14rLj7!@) zd92tt5XNygEKfDSQ`DPPW)k$X3S7LmXI7{0ICe-)B+Tu#oYJ%TZ+6BSvA|PZuUoQZ z+T86cx5%GIec6oLllU~I@`{(6N2x$VS)6Z;ih!rS_^#rz00F zZr$CSn9R5!KW{R2;%rBVu!>B#l2$T8xl4Pio&uk*xt8&(cbSESGp6QDlX)A<4_8vMXwhSUo8D@S&YVbnvPf@3U3>kZa171vhdHw_5Eu6xM50s8=OZ%;N|7vb7_7 zI8fUuSbuWyu1Un#aHcM?xf-`CajMppF0>*3-0wH7VM=k36xZI#9hYY~cOzR`U@nw% zm#w0~jot0m_+vBMnYp}pQ*;e<{gZJiq{$bk*`ZuZiH_dcvM$JBYKAuJfT`B9?duVm zDjT{^^Qg?sy3@o&YSgqt`4XGl#?HK-G}C(O)L4xXI&1TD1@agoayv>%B&!g;BW|$m zw&iV%f(|9%v=IMYyT&#GPVt+`TVm~r_le5s`!8d6ZOM33!q;|Im*j%f>9OFX_W5^r za^22MwG^2rQrLP%l0NbhrwsvO6k6JD>wKGcDqSczX2_)=etT z{HkQ;Q_0dis?^h5azj}vj6I%W7CYaP`spAP@{%=Xx44L2owVBoBt(p?WdQ}s+-)=3 zW*-V?Q5)xRu;^>vX8pe9=*Rojj!*xM0K0GZy-Fx*wI@5ccnV;I@H)oTf$x8rGIC5E z;@0sIS+!E9j5R2Io8FxEtvde~k1kL>iqubE#$+3k0lX30jLAkmz1`uUDg4`;z3lHP zq12Xg?r$*~Et|~D?7V-=hpHxiYKiu`lr=oA!vALa^H_SR3;GNrYV*}qEO~zVY_(eq zi-MZ=sB2T!iyuUJ*%DbPUJOCFu33ZKV?3?3jeq`bf7V@+%dtZI7N^Fgotzw%9sG^u z-Hq=>08@R|NzJtsh;_FopemqxwwtB0Q0+-ip`m4UJyVv=9x1Go!XF~4*|9j=$zoXT4N3)~ai6SEb zPv-T!jl!|fL-Q|ei5?IF5FmsAK~+n{f&Yi)*5#$j%c%4DwO7FR>Vqwclt9&g3GDYFc#-?KuM5tC9IYxGcCsxGR4ksE`%^CLfG1KHlGhWEC4U^R4TfNiB z=WjUvB7KbZc2^>q*@2%9vv7_ic?$IWC7m-ilOtWtIO~Cccvs7lv(w$+Qv$K1bL8G{ zUS3_#32J};DK5eF$U0XQs_$i=-w$SeN2RKMJ)Q(03#*ffY5|8rSL~947(w#6owEW3 zRc&uQs`52d-4UHC-RmyfXRVvFD}bg2U}e|x?}=)?^+2No!|+41{~y8np)7tsS3VHn z_(-ay2t&{3{pDYRQh_EaA1Z))o*x5hzXJ(sPPCk z%N7PQ0f5jOR0tr&%9SgC%7KKK5I<~GCH%O*#la8q{74^y91_RH&~#o0{qpI2 zI|HN08IOG>v!9hz-+o&t=}i%0M0=WfOyap{A5y9a_xoTT9{hN&EBE}Zxj!z(=T8MN zGGPRN;6RGYyd;TA!2=8DXedkmBnf}=q4$-K%Y=|os{_Dq@Vns){#WrXNn(#U0}G%$ z2l@XmFCPg?fxQ)gU6{8ewH zor9?~MHSI2)5XfT2Di~g%Oy@D{AvcqP#y#JP+&sVv7ZA#K9NTOmS`RW_P+tvv2Ilj zFg;Gii1|N1&N#q92Y~%l7)n$mvHcLi4KN=1ue7WnD^?053lLOzLL!jc1nIBX`$Vs8_u@T~AZx zWU_fi$$3$!?TZpgNR1xl*{rC^xwGHdbguqei*EkTOxfPl5SR*jU;T|vL+}aONjYV^ zW!bFK#;P`@=PCSeb0$lX>!-DE}TGSS*=6BQ}z1Z+^LygjWpKj)^&z2 zZ(2=-4PoB5VIBylQCYLd04~1wY@}7?EzUj1i8-uNd2Cin21sPEK`m1u?UU zCl>Cc=@hfPn!>>3#Gq66|FdM4c5n*sGcD$Ij3AdWo;P`mZdy~$!+c&zS2dl@yhJRv zY_jI$o0x(&a_;6yIg2OO#4(r@*8g+1Wy=#XdQZ>kY?Kolx15!w7q*l#CcTW@$l6Ru z!WGTj#K^fzQjTfF?A|ee?HYoev)n~YO^lNMx;`>083`jG!)@u##*B_gxn(o4k>46U zj!d1mWbbj8YaALx?p19W5xQKKGJD%oHB9416G(P-{;tUMjM~#RepAg}T)B28trZ+2 zNC&{0u226N%2ao&+MLrGMcg9_aKQmwS7TqAau0$Zyj(sA3i4zKWA$HC0PUdItMW$f zNjBzHK2XqSw{fB=;_vUMAxf$jSYP=(JyNQv5+b#GJ_ud|;$OV0KBAA+tCnhj3*wt& zQbiy@>`<;7~s!W-NTDfanXaCE!UTa@f zvr8}kk>b#J52C@aAb@Bv@EA%SiA-n9hspk&dv3Ls{&}XD%e(n;p!?(Yf2uFCs%cL8 z@GTkuaQ%Vi52BLbI1Tm^50$C)NvyCvVWxxTYg>QjV@lZe`hULuH6*2n%JX~HHz#U_ zrx)!}3o0W~sc4)}%JT=wzc-fq-nX5(GY1>ILnO&~Nbo6vefTkOH`OW^Ya+Yy>KuF1TO*z{2Rdp zAp?FZs=^TddWj?PAR8*O7;qh>kUR>euFhW)& z55&LvD~kQ=LJ@cTUy4cp}C$D{v<|6h(BwY;ik5`(~hd-3o>fdkSBUshk0 zP53Yef&p@mw5kE%U_$-!d+-YqLKq+ysr3hffP6@iUgv><1OtJU|Fb$g9|Q#m<&}7% z*SB+>%Y!?)lFJy@oCPpH1V5$M-V*;y?f0Q@{Zz5NANY8x0UHx8PCf($WAXWMcrc12 z1ORFfM2N55fZALAP|7}?~fOQP!6-j!{P8KLK?bWBv1Sy zct{dePr!yHeoq4r0tl90cRTv4%9VLf|8v$lgdjlpGN25l-ySId@h9LPVEX?1z=xyu z)%t(;^^~^S8L11<-npqkGwMR=Zf8&dMy4ja<28?$(OXX&&}TK zCIX#WG^}lh+bYI!e&HO`c>kSCO=mgNuqb>nYpkwd-aM#W<@cTalTcZ6J~Ypl)bvJ=YvBe$0=8E>?mDXz%THSNnd zbJNo?IrMEwDLr(@rj6ONfV*;rIQ0`6F^; z(NoN6kSV<+Nl|z*kKaP|Wzku1{-{=7s)-=ju6bE-A07{t%l7}HwO;y)JDU}|>Zq0o zVo;OssuG}){Zw5*@I#*RpMn_mQnBZ(rNi?0uBrtHD1AW+Dw9>dd*F0Rqx)5>@|o_@ z*z~zTDC803P(J?q<;CE`qT=8_2vV0kAo*C92e5kp{J;G#s_iaN2NXa;R$M*V0Q|aM zt}FdiBM$-azxR{g{GG8y=4?)l!>@nRE6S=*`9*81%u)E7>~BqhvVeo2z=90kG{7CH zM7BPp4UY@GpoBh8d+o@t%qF-!k~CSEJx}iemS(m%`D2TNc~CG1g9Ip_z|#bVm{0Fw zw24(04#%4sk}(+E5}tUW z>9Lku^L0>y#&)XzydGB6mYp!2DDE^L=qSMcd5rhl&tU0B#-a;r_|VvX4%wT&ce4C9+wFqhSMKyAnzFRu=z1? zd$Cfn-g#axWt{B3{l@)lwlv%um_EwCR?B}mH(=a;*zZwG#eq)Nr1Cj#;{dGjU*~nh z5MVk#1_b~xmJQ+hK!6QL;xe(&j;rD_b$H10?c`3at&(C-M(3d8F9z)8{z)Y>#K|$K zlHNBYgDSi~72>4|OMVOncWRD?>LfHy1u#Rw1Q0+l5CAwE;S0i0LjdvZ_(BMO2vC0k zsegbt8i@f6Au2B~E-waDB^Q@xz2#-|_8zpL?gm6xeepmzSx_jEH>uK7olWw1y7{&hU9Sk3gQ-CNnoBUVl{*K$qX zGsX|we{X75#dpeltJ2SQH8xFMM)3Gi?KFPzdwWN7x|wfdN^VwPn%(4LY8;AfXB))U zIyOxPq;8+*PUXDN{^O<1jg5E?PzOH&Pqr*kk2JfoE)5H1wF>knKx@!w+k#-uZbQc zf#qUax(Wb3YqAZ!4_@M7Pbeo6Ci)Y_jrMWrsn0lp2H} zcnDuW@Eq{?Ap?O(Ivz$+Y#jp$DRC(AD^u~U{9#xrKV`D&knaFKZf`2jIt`5j0*Dzf zF#H&D4T`~puy_yL!3YnJJ`jO~aU>vObe|EM1F0h#*Rdx3TTSC>rbaVvmu#2UWU^Z)XF7s7XJ7PFDS`OuzV&xj zwK?MPAA~u|6atxxO-uRqpL-``W`YTKwYj7q9wgxEU6v_p*;>{*iZ*Lbrjoz^)2H^# zmYgqF@2Vb7x3XfK?^>G^=I->Gk3Z`U3V*IkIeE>?yNly^ouua!+Z#>R zi)qZ=Zf@n5mg6^teNPQs{F!c~n*v&?Mg5j2l5&&DdNT@Z|0w%2ZsRnmA1xPt;(;C_ z;D&jP%CEe$JS_C|9c5vTGPL02&Ha+{b6Y*}I28SS9nIz2&E+VCkMA?O*zU$VeHghJ z8Rw^Fa13x@rB28@{LIUldbE(M$eQs9dmzvazwIV@{Z4vR%qYnq(Bf#0(-GwyLZ(pUBC;Krd81wh!YNZbMSpA70vZNGn38H&b4IL8^n=~?sJDTyzbi#w5PJ$dm<^1)^(eufIKOgPr=f6 z0thmtYKwscyh#$ou_;jc2?fQ)`a1A3g@6peR8CsV@WN@W1c_ zRY?_u3ZYuE>RjM>59q;*!H3}l7$Jiy2Y~-P7=9(lsaQ1x5O@d{$HG{UJ_sHF;UGu{ zfq=9a0|DSa%?4Bm3WSAJA%qZzlmn)hs}jq=>3)wrC$GRR^K%-V{#o4vnI+4L8_Ro= z4 zAHLh4_d7Qai@e4IVqk0@wm6bz7Hl0bJ`&Z{GJp3mNpO%qllr379$`9clss9;sW<)g zTXD9rncjcF+Ks-Qi>Z*>F7vSbP9-;d(B~7TitM~?M$eXhu-Es}_qoBmn1&fUcK4Ds{<8dr5x6<$7S0%TDYkFxB;69;pRx2?q zOs;W+qon^$^xHQQZ?4(j>K`)J&F$rsonb9dmOt{IfM6d8Mf~Qexn=KX5yj1t!ep5R zD>Yi5Kt2$C`qEEx5MJ48#Ux4t1Of2y4ugETFzmgd*za0mCrpxU-To=vx3`i>c%vfy zUYu47J!e$27}XuIA0@o(Mie-XnWm=sT-9-n@3(__J$5d?ELfuOfDKBt+YI)w%M8rh zn^?th((@_@$!T-BClDqZjJd zD2IuCD?D(B_vc!45p{tl_drYSV)NJJwictz=WZ*J|3#t(Xq<&!^nI`$=Pq~^-5lL1Yy@>c1blHNx} zrrtLvBwM+YTILOXTC&M;on@@myRmkzw(rZb=F2-a3X?Uh0FWu^wes^htn9=zQ=I7Zf;Cy)##upz1-}V(}TOEjiDM#6wFgGPUM*i zqvh<(oF|^1pXAFXy?o`1y3c-H%~Vf2E2nvZLXtQ3X&3+0|hLAt(0#yLh6cWjE}zwiU)V+j|irk1`D2 zj;fz~;rQaM+_qT;6!L)4yb?28*>3}G-kM%7U_` zt;^d35ujQOsJuTCwS^o7Hv7<*K3lTVi)X>IN%5|L?`OcW5mm@0%PUz)4uMazy@t4!8r&@~p}r>LLHk(GBe^#1kcAll6!Sl= zR#KixmLJx+ZQe;c*q4YgJuucw&9QX&nr0?P+UnlPyk^)~34G@=;sA&#q}`XfCC$WL z<*cldK3-!s%aM&^xwpH`N09yF5^a)5#7`z`aR$aA`Hf80yTJ0viBd=AHSSA$SjFJB z&2O61Wo;Dcuqpc8wsP`I#v(jwp&;&AtGCW1)*0EAf`9F+^Sh6&@MlYAb9zuMo#z9V zMxjsS%iT=_@tXV7*tV{7k(+SCaj6p4#ymzs6v};U z;TdwR-K@&&+l*yDaN}ZeovW&pT1RiOLg!WQZ6p12V{0-P8$9GhB;++0WR zz5UR}qZGE1wX$5_H+^`tpyx=JlljDB0B}`p=X!}^Ei)^v)4+ZkVM}Q|8Cnq(5&uWn zqrX#7&h-(fvcW|O42QK9>Im{pD!@7WcKeYU+yT%N=6dwqaS2j*aMs>m-Y<9w;B7ED z>Fo5fA{3_9TP8#_O+%sJ;?B7=*@mFP5&6M9SGjiBy+Dj+a1Y`2#W#B*-Cjdo?+vWv zwUYKG{Xu%a+h`kac1j9z^sUJx#rtz7c?z;c`6H6*(R;eKaT4lk(PMq5Gc2;)En%&; zl{BrJw6(Qr+t%Cu=4NJgcF#5G+r$p%wxYY~@^cG|($zYqd#iF1@Fq>Ftao*(g!~$O zzu9sVd#ll@AsSbERy8=@X$l)xooxKQNKDgj1V?>l)?G%o9tb&68h0Ts@pHE&x>$P2 zqLCW^^BURGanU&MTiufK-RRky%yp8@mbILfyl0)tmz=W?=dR_F>qBmx3VN1DSB+%i z{D@k#*5u@FOKg`|;_PoVCY+fkZIVrrQ=wvhqw3^hcAJnpXDhSnb?m zcD5ntS9*y! zv-cX|y**7O`i!hmm@QUWj+g#&S5nbsWGkD3q$+0))jqiHd z@T5*wrEbHLuLhgZt+up4)Iv@QO-=CpPZ%Yc1Sj2@S77KpQ6lVePpL*3A^3 z0-clYotDVlKC!MZ*Hd020&=b9b!pyCo_W8dhwK=Q*tzVv3Mud@ZrMugCH=FON>8v( z)Fa8#P(LH)W^Qcd89gy4_^UHIh&B`EyOkA!c;xqGUxJv4KrjynE)r47nPMmoAOr4h zf#Jt_0fu&PG6z$|vR_*{ITw$y?1QEn?SPNK90{^r*sl6ponNPUO(MNC4iqw3n{r~@ z0yKyXuZrilm)4_R9g^wUmxhQC(c=T}RHf|shBD->x3e)%_GVUafG15 z`500pCjK?%a>Zl(tbLZC8vQIEH-Zj%pM6@XQpAj~t*QE%wyc8Mry48Ov%Ko|3%h8^ z#18?W{C_j?ir2LpE%tdIJ0d&EwV5q&8^iWf4vW4&FK-%a(F#4PF3)-+-|WhxBKuZ{ zR4+@7@7ul{Hm~Z{wW}|w42N^t>3qNTm9Fpt!;akrH#VO-sgIYi_Y_iQCdPS5P|~%35)D* zxBcb*@kH$E*%8S*SF6)di8sINJ;UDQ=2HlcZe6zY@F4WAwSNMg z*`qdz8o0|wwE-$|h*L|`ZSBWgox7wt?^~124H(w>8kc3IsadCEeYcYERsMG{Y82{Y zJIk|&WF3yOw7%VZQ>i2CSf|~`7N`&jqcD!C+Ax0 zR6ZPwf}iNr-L_4V4%Y8d&NALp+?HkLN!xHnZ#%e(S#E3aN8Z~qOSVL`AxcRp1M!Bxlble^Sf;tbK7jEe^LzEt+*(v+oS?FVeaKM{Ne$P?raOd z`_?qD4CVCLrdiQEmn!`%`n<{5SFK!^`6}aThC+%&U>UTH4@ck2yKY$&<9@wct66s! zI659T;825ophZoxZ_1JhN>i8n>D$a_J)hLW;_~#BrknlU^E$&BG8SN46>eGY(X|dc zWLlHfSk|q(-o=Wpf7(kF%A?i$)3o2EttpJ-O-b$97*}VjVSVd7 zl}9*gEg-vxRZf*1+t649cBjm=ahDBSdrcGzjtb}+>YP<8YXY^KTmNF+LFst1-h&gM zZGZi)VNq}W*%h<8tuFUHmbR6dup1&1hQ?$na$Q3?k3B5yLk@VHf|^H zQ)@@{OLV-aCh%Q?DlCobb#%XAS%ZTCCC%Pu#?r3JuK^4wzWXK@2#32e5> zaz-qx?Z|^y$5w9$Y;2axx4Bul&Xch_u{eixUotW&m+qC~Mi9ZHmMN3-k<5aNoeCW%TwCQ;*GzLrdaS0Be;RpKU<;f7h ztQB4B$oJmTV58;OV`1!E^ZH;GKH9xm&7TAQp?jn?!OUDT2T)ANUA&{N1G3k%c2Bd5=A3c=iT)yfzfKU(Ub7+_p8GKH5$}pKd)cFl1va9{>OV00Fj9a1s`9 z&}81{GR~|KScxDajWlQ!)@_K8A6n*b8xTe7ZffT=+@|-mR53YG3>YUFh%F0-csYS@ z8PZrLsSY!PAf(m@IjlfA2-1}XrBi7IEd-D>Hf4?<^v z;QVNKJ~9Pv{tDRX#3zVL(@@-O*nD`SY5s~7PBcS1-NFWH6Gn*$Zr8xM+=38v6sDgL zg2}YZZWV+;;m;Nta_F>V?;_(~9VmMb3QtZXJT@%_aX4}gNKBlhUNgqPF?tRrV>_~gR|#^TcSpI1BH;esn`{PfoQELg(}2E>91AT55I3p5F- zNx-J6XbJq*;tkLmUH`F&crHeK4oPp>%+zP~Cz#xsMza&v%>_Tx^=37wSY#1+2>`$t zTmky0RNqT|#)|{&eDLl{yp)@5=O$t1M*@&3y07GGZQ*E|FT36wiejADrZJ~yuH0>z zf*JWsaS#fUC>x1@rvBwvu)UBKyBp$DkiPv?-~JCI+ic`0gBuicvN6#FbJLy8P1>8O zMIcl1>)|Y^T6(Y#9|>U5Ob9+!5ENf{zr*!P%c%S6)Zp|F$yIEcJIOe9YMCsu#tHyi zV{3X=0_5*k+W~eIZaA%kqiP%^8n6oBk)pbJcCS}GVP>Z9)AeacI3W6};6G$z!TE+sjEqGMlA5YWdPw9ce5V~|6DLzrBws^17uUmM< z(sV7e60tgbg`}Igj6UY?DIJA!T0(n-vFN~`i$6H-UEC)Em=%F{B-H`oEPh;H1f^g9 z)hdeZp8x!-+>u!Rd8J#5l&iDAfALd)^mvqB0peGLzXSsChlBY9tRGli;rg{gNHU*~ zwt??OYNQB5|62jO@#fw|bTb3rlmpAXXRLr20p;VrDpTctF979GynXxXth?jEEPh-) zF22=8R)QH3TB?h7SS#=m&{%LHCEi%7KCrcZ8Z;rWa_I3=sG8)O4*!YPD8b z|54}1O6!M~;eTZwDxXRRd|iO6(8l}TSK|9{b?w^#z8eCV889wtL=b@o&>#a3M3V9F zHC7M{hyDZwAqWsaFd%(U{a=9(z#cpZVE`DvLISW*K_O88!dJlP2iUGE9efxvF9ueu zNCxnf5DS72;E-QO%7mJve+$H-;p(4{3+N~U75Fe5o-b6ds=ewx1TFc%4Dome{C+%v z2)MsNv`7dbeN{O8p>X(o zB^M8brA5=;_mv4=!SQi)jVh5;^z_b5byBZ06=v@}6vk{*)+`z-S5zvVnr@YKt9km(CjyuoV0E@60k9ju!ca6X62zc_5I{T{0|_JV{ZNxckYI*B5=5YcC8}1f zQlNM!1i`)#l>-+S;PUvtz-rhB)ek59r~-?`s$30!@MQq-AL4*#@_y(*0PuJS9z=oN z25WvKfO&ZT>X=s*K_RUk>;4Y`0m*-rfN($rD=-@e%B9HA`BW~<<4P>6W?}IpzU*Iv zPkT>a@Bh5XqBp0bOGyH7Hh8=k6kzZWJBc}?poR>{99CSWv$&kR6b)-F(&74Q!z?5Q zAJ(U^4duEBziD0;w8~5CVU&SD2x^PfD#76SpNLUkhr_`wQnB!sJXWh30}X?8d|s7R z(x}sH2tQ?}_**&r*Mlen*uD}~;y5|AZu69&;6n$1gbYifSUdz0BlMcdSOx zk-rbe3Z`5UV?qw6Y9MV_v=Wt}A4~HX-)31aHx^wQ9`~z6aEw_ky3l=d)7`t`&reyl zBDP!Ec_mm@1vg)kF2VS_QOpe%U4((s=E~5uvzo;Sxf?n4!DTtO~Abcof6`+z7 zm^iE{5N8vElOIM2{XwAP@vYQA6*k_{nMdu-8UnSe?$MJqD+Fyl zIZVM!*Y(GdB@eCH)APD%ZXYJiyHRU|N&e6GCAGYDpQ;Bgj1kU?Lu?TjwM`17aI}Vy z@gELC@5^x-=n7U05BM7*3OCTS~b^LZ^BwwXlbwNCOaPvBt)ci|`lM33$_ zq7Uk?7JGLl#=g&6-1p;IBUC;8%)c!3)i)!^1HgSE3QYwFJOmD)-UuvswUOZW%qpIp zx9t6f|1U3OsgpC?Cfm80Fs_~y{KtR)^Ro|3Q|X-RXMLBqJ9^+Y zkRhs#57}R;R~H{P4*?1ohphx4VGMYrtzS3TAZt16;qrJN{|i5p82z4VZs3?LT)|EynU=(G;n--SFz18n9i7o z0tyfh9*+T;9}j$75L13JrQ#RUd;e)T^KSp+@L#34Xib`FD%`R4Y`c--DnyuNBTO z7nVz|8-leVzk4UWCD|mNrI0>1SxkfzL0?N~ZF(0V8e(!~lR7i?B_qLg!lS!o7<%y; zuq^w0ycsoPY#rA~N)}fcGo_?+gpLI#Z=_5MZ6fUKW@f@>$^mW&T`SYFM&~r)yI9c% zb~}t5BzE*;tDQZ{Wh~;zPbjgK*m`znEY;ja`@8!s8 z8hfNcx)1obT;jRZZoBW&z9U1~z^9^Eo4}?9L=L0```vpbC<=?eRiRuR> zFZA>;R$cGPJXdm$WXlx~XXyBPUI&x{8F&PNjO1>%STdN~Q!wxwC=9F~0t!%OSO(BQ z1}_95`0+471p{)irOdFG8}?0^=$Tt(*|~$3=k}NSx9X!kJ7JL42qWNNQiB(yNInEH z*pdg*d7ti&o1@?i1p>LZ^`@1Nm0TU%V`8gymItjx7PP{j?G_f$Y9z4QMM}Wvjqj&} z{ftuK!d88XCQKYN5n?03DR*2vK zHYG}1?OdkvCS(BO{Cd zBVPOXi5c)VECnHOFb9Jq1zrKc93crz4%_$B`Chaks`8IC(w3TJNMT&rjbe!vmTh@wAGhoIq5Yo)g`ZE5Kljy_17G+X|5TuOKfVPScZ8s9R$pcCLInUJ zcn?l|y-9dx0jk86fA)U<+NDo`=f1}jk3L(AYi`d0c-vDGH#&b&5f4P zXL%}*Li(vd^;W4vw-lKIji%K{#mc^`J>_2Ucck*KNt539GA8su=-6+yVW{Oe<&d_V zqe70qq2dO37l=&=Cy|O}6EUE~C=_kHLqx{vHn~^Yk0IsZymoJR>R3k2XbTgref{D1Y#V3I$`(Vc-UdAj*Oc zDlbr62q6G?4Wd9H)lhstR$M(U|G_}4zE2mbzXM^Ak`Dp?O!)awJO}mQA4|auA%Ji^ z1P3%B0tf1^f#ozF1N?C({-xqo1LNR7?_eFeKAXy>12`T7^Dtn94d4t1frG>z1No3@ z4+8+$1{VWWK=2>3gTO%^0p18IfuKSX0v!P0co+#m;6E{nKCHNWsal0_APFC_P4cij zUH;DiSFAyX$I8I8nSbuyh_Y~V-jM>5yIH6>FWjw3!I|hW1wH3~kwbS+-XFE<{qNi9 z=I!T7SXjqQflbbcvy4vD;$0HIFEfnHn2@Zo)kSVi zAm*{I3MR3Vmww|arzzSh|`m>*gHDU&u<=osN{(}nlm5$KOSRZh_w z_VK3Kvq(Ox!?&wf-Eb`amT;sK%TWrf2L))9yk}u4e(s9_PTM+C#zq+$VR!N2`NM=Q zrtc2fQ!sA9Sq9Tc^@;lrko!$m)04PJbScvJF)Re6;(RGc3d8ppDFkQqTAUm%0*}3> ztp^9jnO`(D957|37ijhxDF+Qerqv1lNJ1qKr0^j8eqZtMl&S+k@p7yGmqjJx;DQj< ztG^zrF7((CN5U$VaZ@n3=aUqSRjSn={wuj;nQ3>FvD*~cV=BG)!I>+3vu0{N7fh6U zF37HB#_>%5H9wpMITW4&;Md^A!cde95J%KW1TpYI22?&D2?gFSqwrzyLk6Ybf)K=! zBuN1A_&gu|7vFqJ2F`o?;L30Nxtrw@UF7fjji{sC?(p$aU|KgMj7gJlV<(hPP1PR0mdMrm zmLwMe@Li9A;J(vJYOO)w;9tPT0*_Xp@lX@EH!#Q4SxY}JVvW4sES+UolmGwr86yQ5 zP81YLLApbf?h?t-FhD|)4rxXzA)qt{DYellIY2;CX{ASpz($YU|N7qd?-@_vI5w{1 zbA8_DdA?t#OHo;aHgDfrI&D719jPd3B246on7F4!FI9+FiG6JiK^~eBWOP2(J3Bb2 zEpK%1E_a7JA6bG(V@QhR6j1aA^?tLbUCWUBpf~BtHN|B$R`j_N=c~$r-h(96xDqubqT#_7NL*zduAg;_IVx-_DGrcv_T zE@FZxKKp%LAnnz7H={MP4NLJWl0@W+znw&B6^JqaOl?y?6*Qw_=&4j0Z~d|?ai*!{ zO(Yq>efUr=yTQV*`n9wjr9So3XXN9gw92mKui9+rh?r6RrZ(uW>1s0%u&D=D*wncP ziLy@KE^!Jb091il{+WyrOVy3Me}=cu?^b1{?&>VQ@KRcIyjFd}zcy_goFuILtYV%N zC`f5)8`^D|rrY^wr+EYN4YV+K)%?Wqmu6ohFLzZTLtjtEYt@mauo-M?@C69lHoOSU}Gq60UL}`}?2dz(X!Q@b5$hidJN~dwa84Thi%=swm zd|%jQ$U{xf>MeYzN>90;ePPy=u$*l*pJ`J2bs_Ip`HzTLk~F0!UU?TckT!&#`uy3; z0gEO_w}t8H(FSJr8BOaii4%`vWOXEdeT|BeZAmYW(wplH;u97rkJC!g+T53DJbJ0> zVEId<23t^V=36oBq1VU^<7}}Eqb2ye+3rN^5G$E6-2aiQFSNPv(;tj&F^&3^l~3>L^!#JKA{{*W)IHaI2*e+>D}egL({*jY$N>gdqjOs zl@i|}5wZU7HkFz4ufcxyFKPEw-x19qf#b`9sB`KniC1IH%*MsgMckOWS>?%B=7xv*oGWj!Q+CXh-l<9B=JTt0k1-+YS)#g+Ho@A*$8Gq^6(K z!U1*IDn&Mf1hR*+A;R{B?-nHMNC{Qh$@T5GB<{@go}f}{q|l{+Keu&(cW=ehx( z^y3Aw1-NTF%W6c`Rz^E|V7v~=aqWI+>u1;-+FNV*EMpjpy1DRJh8~*%Q|=|7Kv2y> zx!ygy<^}83M?bUd>`oakN6e}a8T-8q7b#@0LF!X6o_}uWemRoDNbv$=D9MRr?6@*O zKifS~(@xRdaJRp5S~O{osfMGKzlV?$nq}Hix@NYh5z{}>xj#A|U2#IA)M`TW8Gku? zQh%9m@Jp{~q=|s2KQFAk+n^(SA)6qB;xKZsZD0Y6*HUxm!|qq-48=YvA_^=};8 z?1EMObU%KPav_AI$6T}GJx@iqUyNstcN9;I&uF@zj@@>*Y3ipaqB$uWnSf-Gp;@4O zxgs_+bEiAWBOEXFGC!DSna(U{O&H6Rdb7^)qb7$vm}n_e7KS!VD(|txY!45;rcfXd z5nkLthvxUazAMQqOg*xP(tS>AC zQ|J{a_`Uy1@X?-5FRT~PT#x~rdGn{mc7Ob2Te}}MUw|;^t=R8^h6C^a2*mXJ`!7=L z{yM5^@OmGpLg!n)lYcDO6em`(Oq33H~PVV|Ij+g8=;IU)FkU=t0tV`_rp{JYyQiWM9VkyzMoHst)hhj+b{Ve+?-lZRb^YmA)GaaACrQ205%Rj-r{$Rq z8-YQhJLxPQY+J4CMc<_8aXa)rVmA4h?cZk8^Sklb!iQI3U z=t@z{QJLk2lH8d^H=)dKN%Gfsd_6oKdCsfwfDdNg)|v#Lmu%nrBf1Cq_v9C}L@sQ% zTu`tP?_wwTaOB1wxKlw{{rmgEh#7`{|2yIMI*%kFQfMWwhi&)jy&hVzC!}df)2)y7 z;%Fi{p^Oxq!!hPS=hG97F5}1YM>&Jgs=~L3EKf8~Zv)S-H38e^arS-zXRMJ?NbRlt zp>cUy3!|SvR8EYsDS2AYrNK?b*Y)ei4xr!zwDgw2pSn!T~PKcnXEUScmO2pj;2psU%CLM|)koD0oD|wjAcT zLAVGD7~pER0^QWAIac13%bf?Heg-f^gnE?*42BYn>sa#3uUt%$-8|R|IioW!OZO== zpxuG3iMMJO5N4=HeYB{DmnrUrKO@TRfKs#cS(j+B^yQf*SA@XR&M&rGIYaa=14ehq zu=^$_5RgJ{YY0xRa%B|fu*88zxT3FiXWuN2vK&!epBy|t&`e~3d0$*vRUJj$x2$i- zt#3%(ng1}7YRT!}d_CNF>e?6@m2b~MqTa$4{jRMpi?3T{dE+!b!4vnA&egZ^=KFL( zepjkIinz_LM@ugSZqSpiO@RCjXZd*fX=x}}V+65xX>+xQ#TSYT_@iu9>m}JB7=yp4 zz?iGdkyQ^Ug-c`x=bcTBnbBN6j*nS$I04wx8c`oOB)VmyocyP?ftF+&!$O0GXr*S0 zw@9{kiOy=WqZM3r^mbS3d993CmlI2kw=SuWG~Y`rqie2r8+VH>?%?|jEdr;nZRL!u z7`OhYHRI$o7})gxa);mKbr&X!V!3-#HJ|L=rm)Z3JR;0W31=zA@ zN2xxL8t^^qh+H4H3;9gGCFZ(%;amohYg{|Mx(vkRx$zpyLstxg z+=lQh%&|`{5W`Ko4yON>jT|rjN?{v{emTuXesm>R3{{h2we3xv23twoSm?l!>)|SC z?A3%dch6Eq^IApZ*_$`{s&jup&CoF-o2at7&|i-|D+&Gg9ZXVchflw~9wrDhFJ4Zx zWj#K7+VYJCEGp+Jm_+qX=iA4;L51Rhsp8+=@-L<0so>PKY{*Z z1`E-CB$vMO7z!4&+c1!1vc<(d2T!hi{)kdZ^wn>X#r_&Ghnx(DI7gitZ`5GDosFb8 zDYw{D+Oe9C1`U^TI-k1QSh$)(a~v;B!LHnkFayulO`A&L8Jn@@6ONCsIFdEFZfSD7{Zjj4G_bG#pR}LR^WV?C zpK3~Lx$aUFy1vS8u>bhlp#fLdgRk!Os{uj?dsyaa&lRPh%Y=fw4 zQ}0Hz#lOfVdnoIb3n$CJJ!bQeOlpoq;rNA%6dj`Cc(=Sc`-P^>s`ac3b-Yh1t^D{p z@u{=lA-#v$bXH*IrN~9u*2B+7j-*zyk~#xX|A=R|TrIx@>W$1Ae-n8P>`A_bQLb8*2-jPK4ZC%YQqrM%r8VsTa(ok*Q;+G!>9HxxfOM_JcZ(wxMeiG7Ha#V-MCcmZjUE? zH`kKRKpUuFp7aOwcCb?c>7~1oq zXt1s3{;nB$c5D!0(y2rnPMN*-Ua+tOXqk2+5dDmov(Y5N(u3b17Qm7Fgti(_g)8{?@IShaImDTd zIvHp18T(T~U8Wxi3V?J=ayTc6B%+C4;jp@gc)ga=gOe$vyif^L_} zILbpWWlZc}{@gK~-@CZGKDlQvmL?<5!qY`?i7QVJ|9bGHdm2&tn?A=4Wh8xsy_9y; z|GP;A;Q!bahMmHn&y4N+wOig)3|E%T&Ba}eOLHx^?9UB)g%9<+>R&$~VZ)`$3$g_0 zC0EWzZmCrM4)!nAJJZ7j7Mkn<)O*Rv_vpn4ovHvE=8eHW$kU!5Tb+vv=j}nR$S1|| zr9Lbf;aDVi@jO?r&vM$AB|sP3Hm#@_S~N&!wp&oFA5!jNPx|zj%m{Q57d8-=^fXwT zg5@t~iN0Muy%8^-5omb%=P)4c#ze*vYRc(*s+RX&~^&Se|AU~jS@?e=c?5+=Flo`DEP zaw(C&NBj{N!6hZZoF!_(tew1kCcl%~)*XnBRS7QYBT7)&7!!_w5xyx(pp*-6dOub9 zQ{2J;A`YfYPna8&ngQm^WtKVR=Dw4m$PEtVAk1=YhhM_zN-r1dCOsa9i8BOqMiPes zbrPIDbfi7owthROlfa5;DyYlS1nVDK4l^7T+NQdeBW202497W$3|zBwc#wq!4$o%1Mf_(jmi+y9kh!`Ks%7OTAgbg%dB}|-klr^-3PLYJ|V!Msa57_({gpoK{gvv+g z7Ono$lyBUJ%bW$#A=;fLk#jrF2ZZ^E$AoqCDGyVvFKwO?3q3EeHwoH$OrER&^ZUWd zQ`)J)a||VtUc`v_3upBIg^+@r+sG8CX*m2SUL*x?ipy~j*T&TR)Nn&IqfC4_|ILE#~GjgF4sHETh}e&Y@*~3`Uiu?3G?t z*Xl{}?^f3QaNk?MxBAEljjcJS4KAG*axN~09&Udsec`<)xRLvKa#Mx_v_##!ll!uj z0is*qFn4+puMFsm6OlwdSAfCF&!A}xA3Fz>aVZ%R>JwCxYXOi_paC@OYGZ3@dz#~B zOp$9hshhKUn#Qr$E56Ri$-xuO=TGI}pQhdKl0E1IZD9jjK32a9PX2U@5Y+ua_O-f7 zvsFsfsb$w(5Krs15;(o%yio4AU}nKiK<@1Hmcn{#@IC3D-)yQQY4-8hpDTl#j>R5J zp$pr)ufDzhGuGH&vxxJRUUwmt*IQ8jcU#x^%zw~ItHwV#zSIP#UB?1)OozmI#3b9n?$7`WrHtyjwO|t!gt_8`AfC(=Vd7H_MUYHvOYuQ&XNv?UPj$N0;_hnv5n6kq$9bB0b`Fl$JHSLM#!sT!jcCI}C$WQrIEnzh zpEOq~Y|p^mwDGwYJ4rwPa30mzwE?A+)QN#P6iQ0@$DtT_Z3#K0c)QZ_u{EBTd|V&qw?YJvi92&eJd(*PJ7ijEoXjW8Hg{JZ>YwK^_!)$R zXVP`hFc2+$_K4!-ZI%%tVNsl8Qsi^B^i{ea70-`TKD?2S7XOA=S}v4O#cct-;{qx( zvi5!)26@l?MXTNdwVMnq%t?c2*XH60e?6Stu?+*Llhg>>UCK`?`YD&fD(xPj)L?j9jjw=>O@y>bPa^>&70%4k@$qViz1x(`P2ZoKP z5^8doe=8jev?LK=-ooz;k2q4rquaj>K2@`(mSN#dr3vIXiq@YX7SyqF>| z1}fb^Ki>44X6*B#02-`PJI^A%HPDLSUTqDzo|&wsczb$xDJW5hvnI9v z_Y1f;`Szp8{s*&eFJ?_7bOU71nY|d{Po76hlY!s-rnIPkZ7R-r2{?tdFjVw-3u6D3 zM|wFts_;dR^Q5sHaxE=-O`R_s!|Dn22PxuHMs7j_hKwqb@wS*rycwa&Q`k}Ah>$f|-rx6Kywa*!Q!;*qSg!O5S{iH9 zcVEt1EgVoxN=h>$sZ^CS*-H5{YiH67${tfULndv7g`+R~q z`)-g&OA&(sm}qz%STOQ7*q7NNG!buUU;$^4{Cldke6=U_W8ny}-vH6-(*T zAalGG0WNkWSvi;bUt?U-{~+d=|H&=iQ=Q)>{-RwIpCKmytfi?K z{if1wPa~w&i5xl!%0E&9-U+uA$#Z`T9V%x2XFo+)saId)s~qV-m=RhS*yk_4+27w^ zgMxDa+rx=@aC3=p**9uzxb&P1_(J%$Jc)pp3-H!ixHxLd_owCvTI*6Jbh}7W(*jQe zc~s5uV4t?moy*Hg}8?7%|NSlJF zG{6ih%lfwwe0`heEo!mRQtIt58NhOKWQL6{-QA~a3&yB|b7W8GqN+8XMNrSWNS6?d z+E&2rox$mFL-~5-JjUhvp8ib3IZ&1AjZ|@&gF@$T&&?7yZ61mmJ@%mMSOWFZ>45#g zRAEwD=i!|2%H5QOjD_7M5=5h^<#zJL!O|vP!?6095x|ik)T@~KVWbh>1I56F9pez> zI$xi2U3#TUSh%E|xjOkgG5kJF*FRU90XUqAZF;)Y!_%VV4fX}h^G*wE7X&9=yKyZ8 z@8Y?Il5m$A(Kvtj0RictcdS%7)I)kL$jr;q-E)2URGyi@m3b=5^{`w%{j;%bY5pr3 zQeg(F-~K-3CO?S50iK-X2D5Q3QQXG|BV!dQv?=VK8Ox2hk%&CZ z*w?+3%Ohe#f)f%u(vlzqcgDNFd8!Lcv&JqC5 zu1NtR3r64~I3r!#@r3I-je{C2pNV~qtyegX4bp(&c23%bsx(t01i!a}qY6n6yA)C_ z$tM%r`j_Tw1qVtyaw$&V(6v{Xn3OiE;sCEfHl!G26XpGt&T>qmiDdWe?e$w!V zOG<#?(`-HLbZ-SW9#o3dqqt@~I}O)~zuO3%1XIlA4qcIC-(9@CrxniagG`DqIk?I@ zVn#u25zAf&gf%ka!SCpa_JKS#-NCC{=f&;3mu^^;wZ8&cF0&kzq%wlxBV9&fR^;fB z5%t97#=A`UHn*fH>Rb%`(_5gIbz%%)&4-vQ0az;_Kl|Tpg6rF@{6ahPkL$>*W0tub z!;g6k#0sLY8-r*c8lsmUti$0sibPGO%48<$Kr0}He98nw!@7)4?wIT^g-vel<<;z& zLrTk2i~=+SJGzhob6!RV;b>6IJh}Ns>^rX?>#9D8eLt^mEK2%V4MFk%oXavthFzyf zX-xTJui_K*5uVIUgwu;vf&+kJ$R(<0*(?yPTarOHdEI09q}+jN0E^lOcvbQLVzpMk zizVF!>Cn4!ny7d9EoQfGe-7ZdwW}$Pks&|D0Htg_q2yuLmkgTen)#O3_HpbrYw)<+ z+|@21$hd+XxP%c=a@v$9yAa?c?V*?=}vgK!ughYkiG~(oq23STH*rYDV zYQA}lC56^<<&m#TjhUNz?jn4KoX9P%cWBrI}Y)>{1|@DkDx+pXo6SErSi z#fN_j)f+?ebQ`%OU$q|O1Zy*gB60t=I}TdzH?4^UaUvgO$r5|;jD<~+H3SBj=MeC@ z{uT6=Ktpo2A%J*+EneUs^1d_rosp!yItzmly{+g$VB+y|&}rM}j?exgntM5D4ZTgc zR;otDGUj)-1kTlRCuuvxfX!YkF|M?AVdlI zhu}%u%QVxhT2ls!vE`AoY-3x?TW-8@?r)GzIc`Jlt&Xp%IgM*Zo`{HvZzd+@3dr5C z45OXnz(Q8z)^>E(!^r`QXLi(2@@od@%A)C7QGQ+m(>kd%Q-LmR2GL zmAs$bg2u;PXT3*TbWm4+j(lbNqZYJl#UylK0{M@_+MLx~SNT2gXfI%dP}QSmznG|* znGWMY7Yb{%XWk%16dc=092-~6_K*&w&Cfj4KlNhlclj`1-Eg2V!90Vje9Q@$fAdlLM*?uWEug~Kl+HdG>+V1;bTdm+IW|S z0(5f37AJy8Ly=pf51$Y0$Q_4<2ey;3zN_b{S{N3WPw~$DD%A&Gt{k`Y=f0Br zLWHy3Sm3zSGO1%&>NyQj&8TSnYIN^rhsMsff0{Bo)*t8az&hPt&HKm2VUj!TSMr?S zrF*}Axw`m${6t>4>eTvFr|;o<-``kq%7)lq#OeL!+ShQEYE~^lW2<0mPQ;%s2|XEO z&ZOo1Ga_8Nr7YP@alzbmCxfUlErUZkP)ci}W1E2v`IR;0 zQujQ>c3cJFL`kOi5#5)Js`%+z=)D_!A=#40p_kbvPtafRC&=1-);-s`-h&@;FF%ac ztBwce28fn4UK_|{Oa;z=giC78y?G&hdo{O5*!FR`!GM7WUGFF7L%_rOYy?)+u}y!U>AsQL171LuCD{mXQ6LPe#90_f>8u8PE#&+X@#ziX}K$e5FwrHSbah{DfHv`u6ztvMIN z-Iq+hCc*DHO7Vp2g@t4JgpA-BV<+(rx9~-e{Ljb4zgqb-Y3Bn_gKOm3(K6Flese6};8Btb{ri zrn@w8ZMeSvvxR6c==l@#S<8)Q05Gn#LqfF7^J!{;26}rvN#257z|m6LVGdf^3k(e8 zgK@x+V$-s8Jm-mG9`msG7wNn8%@c0_tW!qCJuX8_m6~^-$h!CuwvmhMmLrB>JFuSf z%^YU>q*l1NFCim>mg%?dnR-;-Mr(dB&5V`MPct_?iex5ZYRghR<@1(<{gErvONdCS zmQhLbLX|A+RLvUYYI39ud2T^oF!OY#tv%!X%SC}%?bX+y`m%p}gUc0+J~uovw@6oV zBG1VpC_NNlM1PM>CMREW_rPzxd;J*t9VpB2RnqiNd&!233qa$}I`V~#$z2{T80(_nEraNnfCm=WWO7(!Y2ABm@}3byOS+(}$8hG~mrYttnX<@Q_sbjHaGYadkyMpG-@A}&!WmFp$v4m3-+$$x9XJ@c zKfjt=?f%t%f_?rT7JQ+)n?FZIa=Uhp458xk>+Yz|~C6Z!h28pmT z3ldfJu>q5+$M+D2Z*h@q{0PBZ22$amE6d`E2Pqr)VB!aK^G2bs@a5K@k`=#H`Dj1u z?G4u_2^vQW^urrb2gRm!3a>0@^GLfPcz?{p)!yXQvK zscD&q#XHQ?--ryI*%2=;;RFk0)ayl0b)sSswk6)NtAnJHICY!i8U&K4+a%@A<_Rz1 zr)tl(+&!($p=TkYuk3CqqqMhF+^6iHo&-X)BQn`Qz`Xch3UYOT`Ub0i(pBdnE9HOV zGpzuQEX>!GEfz*x)d!5>9{!MhJhjZG@ICUOFb-y7au0=TqL<|0AB1I2a1Y_&9(_nP zY-h)2lkOK)>rD^^Lns79>_Q$9PCpn4!a>N1(+0xZX%!WUtw9(ZE!!n{68KLAA}N*s z!4&y57dIrwJZ$+Hz&~-c*xc?WJ&`<)`%xm?!IkH@qFc7dZHRADB@kmmjrsH(DR~Va zg>y!gUufh)fS3?*)>OSi`|+80y9{UOmC-^_&k;?CRdz-|j{tUKquBlW>8_L|5(zH& zTM;Caf5CeEY?K~$rYzhVvOT4>6Ku=-!p1P4_fIJMe2V`!{Nwyw*-IRgN&0U^W{jVg z8k}(1vTd|nQm`;Y*gGM(xcK#m4ZKtMHt(;5o|$qvWo$i~C(tEM{C$c`q<%eH%u(y} z*YJ)?P6S0t9|t`z5%yV}kw0KG{HVN6ToZ&HOWzk-?p61FW?XsHimpi-Jcs*M&kTDx z6+KIorw|hRLI(`Mv5)dvxo$|TGoEA{xhK9ny_&*f1BBt8BSjQ>9yS0|I&5(qb03}$ z|G5xOX~m?21{dt$%7FEq=Cb>|%Qe_o`B9c-t9^SpS~+wK;hGi*~-1zY^sx*|4ahZBU0cM0Jyu>d}0~ubrQ?Ijz z#4nb_u8Un?U6MUIFW*N@{w9oP%3KK=&$RfOc^sJKBrC3~LQnn;UElQj^K9@JbwnmR zfDBdIyO^)hzL-P|_wZgzU%T!$e6`Fz|C97Q6noAOZ~KNZq@aHPo=SZ{XQf&{8d2Ug z<~#g^n>l*2l%x0v+oDb}ah7*h88RjB{om}n#`gc6TLTOo<#j)|4TTkQKKZ%vr@zly zq*6(Y9=SADQt?=*ar$ev&8=tVhbPg_JW(wfv$6h?yQ%NGeVZpxC4A<|1ER3Y4|eBl z@z4iaG&eN|!hc46^6+qWMKO{yjeekO3HA2Y8hmcur4;6|+U?$|!20%Gz|^S}GXvGD z7nwmN1`+BErcrl~bU$_XvaR2eWFU4~7z%r&FB`m+F*8#P`^RzIr_&}$V{e>o{NRS# z(uiS#_~)?Y6d#X{n-31Z|2XF(zu8Ch&C%$BX4|RIJ$GG~xRoSNL6Bj#+z8v4U@P;?L#_;#CIw^W~K(Jc>P`DHno+qQ>!ok!4o0S%ZaN zC{i39)uM=_ei9YT9y)2;H2=+CbG1?B4)rnmYg02E8T(F}C;7>@-nkq1xeo1jY)17p zrpkQFhuw%5U1dHPGCfFPY@io6zhA}uOuredmp;h8%SMbe&$2^=&olC0AXl@ zn4slnQ@wMfec%Yut5!ksz?G{kC!xa5vpo zg(8%iEv;TEF<+UaK_-z0_5Rjnw0RylVg(7kb9w+4%?gfxw+JlF96=T1qdq{0aIUzI zxR)H@5WQqeYg$lL9@oMdpnoGrabz7d%t1H=;Zn<3&X^h(byJDq17|R;O0I*a>De>j z1X^tkuAoO;94mx@2D+m|4w@9Bg0O&lR`p%=F9>L@xEc0R8A?nftW5NmgOXk0Jj|Yq zLXkoXjbje`0jyuKL~*`AS}7bEmBQMgQH9RM8-wq*C$aIJI9AB1eS^-lH|k!?OKe9p zm-{Y^PI*RVGGxt-VNZav1*oLgsI) zp-b_Pst?o@Hb1H#u!$Vt9Bc};o@jC&RNaJH919#7Fo(!(2sw{2U&{}z^%irY1P|`_ zSjg_c#HuQ0T^o`oR9IbYAX;)vN%xH);Hyp1ZwWE{s9_3){+~mUZ;AxnO*OKr1#+dn za!os3XFvb?Rz&hl(<0zw9}r$oO|SbCRoAmyyUTaT|JbZtE%Gd_ydA&X7+sHQk8?<0 zOIxXmpzXj5uVw9XXwL%3w?ZesCn(_l-(~Xw0;2fNb_t1yVXNXSBupZ;mw(q5qD?|) zoy8CqQ@f{#;9{<7owb(3{v{#k`I61@x?j!on0n{A%eh@YHjsDl%zk&F+@a2@TRld| z-d<*3cUmv=H91KC0A9c?J*=-NL5PWeP6mJfj_a zGhFr$%*8Z&JKa9mf3VKL(5Sh2^RANzy#7s7mwNP&XY^iHwy)#(&L!vZTaA;=tUXcp z#Zw3AR}J{v^rW_-Q$q3UKN z2{Rb0r-Tg0$Uh-tx{x+NkufDtC*7`@l}Sl2fqgzMHR|CTs-UvL(dI>xuVLli zed61kn4SohFk!}JsEv#)c&knezhc260yT?xymBX8@Jhng^EhrL)b0oOXGz6g-RniO zyW6u*MRrZ`s8^nOeB*@Zw1hn@N6Hmyrq22RnoXSf{I)CW1aK*2Bt%k%!je_(^hBF0 zwC!?GWJDNuFA+4yf8b6?7ljhApo0?$VIjF=kP+NY z0(#UDC2yz%@_$TH6@9FLm^7%t*+_58oL8#)`3)$u%2G`tS^QgBBS3F>KjW$8$j*_{ zt;Urj_hVlSH0Ld+jxNjYzU9W9bWY+;(pmnZRUkRzf)T-cYg^P;Bo(K z_q56uP~rB=hWVT?QGWMc>yPgKf({NrUcGeN~9*-KFS+ zZ^jHw9H!DkRVo%fkzQrd;`T=PF*a1zQ;vgH-<8BiZ-4PH8H=Xj7AMSeg&X3EGGN;kOHalz&JXez{W}Wz_<4OXhkV&mVHiC*x4SpRs&}>_f-Ke>A(Td~xgp;XR>0vofgQV*!&XnO71ybqI%oBC$!V z$s9=Nc})6*v{z>Y4Vi|WQ+Qkc5v~JPC^(qmC`v^m=VVQ zNe1fEqP@#hT6x5@5cs<{waz5c6orZY2dMB{1XVapp8wLk;i)Btk@YDVIQDV|hR|XY zF1PS#Gu<`L$Vi$t;m`1u|8y%0ecTjmT^U;WSak1tQc!z|1Z?VBB>dm<{Y}W|T1Qi`@am`SrOXKF=} zT8ZLT`tCa6g}0x*;q{MLe=v|Mv9Pax9Iism0qQY(6j)PUw_nOTM}{Gf6>!u-i%5E? z{LC<1YeVI=t$vlPequAPFY4$d#?hDmEScRbu+Ac?Bg8>T>T%lcb9o^kebBKb71-6B z{F>H#?N0JQzEhSyA7c%S{w#AP*Pj-EfPZH#reMv>T@YgV_i&%TD)9HLW1#5pi#b>G zqWc1_YEzlpzAh5RyhDCuw5@=c(eiM<6ORi8#f3-QCf3rEVsTqCzR+GBZZ_1)Dx``B+}XV6r{cS2P)#lEsR>jivbbrm?qw{Qh$vx&#w@ zHTz_vA-N5=zTd7ZqWpuBhjr`Ema4exU7PxRd-C5ET#jqqgXVI2?X2(K z*AQ0jbK$9>n4X@k zf>xL{lE{WU<1!f3(WM~_zzC2=vOjqi{(6^8L)ZEm+y{+r`0f4X*K&5D;-y|iO*R&d^DBT-Uuo5a*n7Dtzqr(cVD zl+-6J>MpZ4*dA2)hu)igSE!TjHK8^d`8KCm&aBPvZO{AH9`1T??Y(DeHe)`2`K<9? zgqJm0)&%M5H@PUj_694-{37stmHsBFl}J7)Bkgetml*@^a^IGOertxXrgQQY{sutibgjX1&<^0lzZkKJB&LIs ze6an0f(nk72_+QSCWr$j3ZjX7dgnaQNxe3meUL@|JZipdzSLr&v>?dB(=KKO zNI8%D+T0GG4&OR16DtQWim1Z7Sh0IGE#6|~1Cd1?#jlzv3h9=dv8)}QdXh*%W4Lx3 zRXCBxSztXJEkweHdksS7A3Lm6@}Xe_)M={~Mygb~y~riqy#B=a-r`xq`wnH;KN$Vw zy{zCIA#=;>4i^)gjd+Sva*}#~1pv+-uR_k`W2Ff_JL!GzVY|$H`f;CF3KLf7<$&#& zn$LDkuZ=l-LC~PfkyEqZc5xO}xTY}sP9i!T=Qew=`KvPYuy!OS4E4yxpkN0N;b;JM z#T!mzUQNhwL_H#8zvz0ros}vN-j!^-`EHo&7^S)rTz%u0*?Rs=(L!zwzxK42^@Jvi zQl;-#$s&oUc&`QOInu}365FtFw4dz3@^vsIEIU3Ayt0fqYIEyS4CvF90L$^E# z%a+hEQKXR2c?nSB1n;iF(woagjsffKx#*S40s=&S(`viqJ0cW*Cz41{L6GDYE897J z6o?}jD9rtRMzL8!tVCQ5?RYDn6=%dfJLN9%Si*0%x~$hOA!HFD4GsUrQKazqYp#t9 zg+Sw3DJ-PWxopcr$wBnZo#{@}qOn4)p3L@*_LRd`!7zko#B9om&(ukQ3wr=59} ziu}oI*lp!?IU}=LK`+nrLG{NBc^+oZsM#ouO4D8$E)6y`S7I-C7SFc{Uaic~{ELoudGP(W7d>}IWv~H#+g^m3e_<)84#A6bI z#kVr9G<2p8m452#foh}o&dLkXb&Bps)C~(m$uma^%QiBck6g3fgKfwI%59SxO2w#o zj~9PCy!V%N;~pZGMl`w>11WU;{SmTAHLBb58S;Xrd7#- zFbIs;g0iOW6!F7d8+pFT#>6%+o>RY_Wn`>)3n zmMLFd;jej}u1~JYzP1e+4%a9f=iCeP7bn8J3mdq{6l0bUI#E;W>a-o!Qy4Tbmp++M zn;iE?*D#WcjL@C8Y$NyCiSxIk2XeueL@rarD#i+d?Bo6&od?7sz_~Lsl93%l23371!DjE=G4(r=i(0)q58$ zjBpzJ1Uuubp6{Ax70VWICQ6qoPWpOL_pE0_r>nsF&SP(R}vnf|C%O@4@cQwCIC4J+rmc&e0SJ;p?Q z6~g{=MQP2uwh_({Z4D(ES97nqR|ebypJhF$SZm0BD}VdjgA31wtgc0uw&9bWCk~wH z%n83W;!c77U63Km)KKxAv?Dc~o^FA8jE;JocvLyrLw9p> z3a;Ac(ej^uapk7f)VF+|3b&$U{4Rk7WWYslrS5-6n{+2?&z&C4m$G6U+*G7Lehz<4 zcUf+(R8AMALrc9W{hDIeU|{6LZ^m1oS@k`D*xrLnIn%TbTg1Lk#(0Fuf;v*0Ybnv*7+l$*C>+c8Q~TU1TuQ(0&Q_JmMx{>H+w(shsE!g?du zgF)l}SUT@;HsAm4Ct`%spwuccTcbr$)C{UrY=YVpMXl1>Bv!2&RfJNbc5F&*K~+nu zRiiaZtQs|9@8r2Z-{13}!{Nwr%Y9#0a-HwJNb5PVRvV@is#Gm*nhyUKuKr} zM>)N%P`u_~1c)!p^fb`f>Ef%QDsvT3*-05`%a)m(i{Vh6@raY`W{)!Rb!Y28 zaNG|sWcAdWDOZt*)W_(vukG^0XhxK#dslfkuIl?2KYLiw{PGts-#ZU@FHFhT%Rm`^ zM}s$`9qq=Wz_Z9=j7MQ0H3UFAUJ)hz*bfN(sTsnNcc5F{yz{~;!`dc-I}0=kpZHeC{s%&a<;_Cs(zPF-)ab>iT)iazUZ*0AKFZ}(sIwQg zG({{XDyhpA)KevixUDhe2sOP@Uf~K$%@Ucy>ho+oKjrj zX(C_hS8kNXv^}}x?fv0yJ&hs21GRo4q;C97qyc1)IR1Ac>w9Ok08`_N<-+)~P%a=+ zaltd$w=xzGGz*2|54N>wIzy(WT3x@Gw-uS>!z(X_IJxHuBB}2uR1Q!yBdEje9QKv5 zC%U6XmK9&W=EUArEbIOvc=wa*N0DX`?8~WzW1DTySC7`y^&iS{)|hV%&fKuGt(`OS zX9hRK%;xia5wxBJ#*xnXJkLW6cz?tkG7|!m=;PX{1$~s@vbbW>UbfI3`3CXGr*7VU zGuEr0W#!aZmEY4nOaE;K^xe{lc3KT)SU0cSNQQa5P4YJe#4aR%OX)F!3rz{iccD9Oj(g``hTfw5yE|S$Vc!q?;|?7v*m*g6lbM%1ICQan zHi(}Gj1-KCe+kzxoYq2jx=CV2+x;3u=x+R`Z>>}7+Om!LQ}Yg-TX37LUB9uOSuNSD z=5M9>8F_m%^b@UXJjV8d?EY}`+n)+S(c~K=wU2jhLtjUDeo8jMTQ%Mh&WJ>W1cE;Q zR$%GS&Vyh%i>Mqda+PVg+CQ_$u25g)tGfafr@28#Wkneu!&@@){z>pR*E#C+n7;04 z=qJ7hHG1VgQ@=j46tWoSWj>Wsm~WS&*Lt@W{XwLz_}!QI13Bxb4gN%qLix(U74jw8-xg*O<7Pp0XzlU}x(EMT?e=nF)Xqiq) zcYU9y@eehf9)xX6h}+*WN3eWBMon%0Qo(bD_`7#!^Q~PGqy+`8Aw3qNu=9h_vg4pF z&DB3ayuA{d!w~!T#O)syoPSoY11uj^KT2L_A+p4G5#s#w%&4&Y(${bOUB;q6%rG?X zGZlS-NLAk$&|7q$dLka4`QhAMIB&X*XENx9C&IUQx4Fh^dEsJ3q!pUT^!sC#cZ2^n zi$LYKxAXwS_wMCRkit@y^_xBtrNs1~m%tOHBLQ{pHLpN7f5Vk+j~h_Ly2t2Lld$BI ze$Cm_wvU0Yviog$m9Mq$(Ais=R(~-58|PK?r=+V)S85qvV68W?>ch)H!XOt(>e}t9^vV!#Zgsp zixWcoZD04JVr-4vGaojG{Cwaw)%;-UDfs5)$d`PTyUz;3>u;N|Er03gwJtjGue=3M z8TV>@Mr69NL2QvBh4ADA}!(&d&Jls}wPcyyK4nJJItf(}$>mokU=zrZI<++}b z1nFv#+sfTHZK6%I-?SO#e>ygKj)yS0Af=40oK`;*Snw^PPQ>Uc0JQt~e+QzMquAJ@giTfJjeHB#AtMOcFyn-pN$03uGHKkr z)dOymUo@$fjIk#L+11}O7h>vW`N)xLPoNGK`&6p?ra?2;?u4Lxvoq__6~C;ppQRIQ z5xO${!Az@19_LX**}jX4lW}7*=);P+J&(7kuRE0U_l$xVmd+Gs57suTr=K0Oj6|>y z(oZWItYZ(Zvzs6I@6=>@0cwBqWZS^$hc!=Q|m4B1)w;JiU zw#8TAmrjwipu%9eRW|W2za|W**#6un@#&tuh*AOa#gwTk7~v+%5@&>i&hA4qI?YEL zcn4^Q#=(>}j0)88_|)Ul)sXNxrdUk_etMcWkhR}DDWxYb61Nqr>#XzFExh@>YtwGb z+{_=osErcupM7|{-o8VN)bG3&5YnVWf_fzXK6st?MSe;Rocl#0IDh7`+{Ne}?Ul)L zx)7@K!kEQ=1n~_2ThYPNE_3Tk!~$A4^^b|Pzs?6H)C~1cT{=D@uk&` zS&4yfz3*q@iS*61V74Y#!sldf4U@A8nma(m89@o}T%RwJv{` z+Yk4oOZo)xXc^Hke80H&A)DOrwyQ36zd*zu zL!5l}yUEuA{vzJWmu?=SLt2+_Jsy(!dTaZ*bTjORPTTD}&9&}V66E|H^I|MQN?Zrk zk=hsBP}G*7gJA|a<0_x%E7OjSu0WO9zvjQ+So)|3GjEUopKOONt^(z&`2xJ$n8ol9^^6 z%0*ev+i6*#eyl+W(tt;-RzCK={s-%RfwiOyuLyUxZ zzhzMm4G{1J;^N&MIUX;&wH8dFns}Zn^rZ|V!kS(0)rdKg2qc0YI4w7-*cLEmcc%p% zktN^|8GzFrfaxt@fU3lSF@k8ydLe5{A$uNhlljpkLg~So4?QaAaH?Sq8_w^#YCe=0 zyWB^HY4wvNP^9m;b8@%n#5E|0V`o*JnKgO9dA6n6aBuJBNkwCqp+Z-~uOep*E*Hk+x5h@OXr47=9Frm;!T|C6C4q*By}Vj3;OC{8tPCe6?zrx^_%+BH+o>}k?eQg-2>wD zY*A_w5=z#Y-ZPE-zS);xv7H0 zY+!c}MKlaOQuU^>t7qBUrG~dILGQuS!5lr!y`jkMPKBTD^G-8 zEaJ;-p_~mHJnt-zxD#fsp9FFaCT%?nwE#Ekf)E+Rm7Q9X3iYVJFU`oDq*=SV11${CEQ3Da>wGr5C|g$n4ZlY zT7OnL>g9%fAyrtwo!CzTKGwzToQog7X2n*bFwx2<0jK3bc|4TzLbEb8cIfP_Wq;AX z$yF4GVjZiA7p)zZYZB>tOr5fu4KC;O;~Y)OzYdD~u8+;Fa(9yd`KLwbve<2A0#k&% z<}m$u_m6dI{T_yjh&$$_9HNfV;VsO7Yq}6X&?iX6(bJT5*iv^1#=Do3m6XhnZSBaD zUF2eH*rja`%mDI2aNt9q8VtMpVKQTlYnBP?K6$2P1TwQyi=B5vwy4@!&Ghe|7)S%#Q98r=7 zG-r2WfUD=_OX8q?O7f`@@A!{gHZ`EKp~h>w01@}Hld~r!#-T5r5lnA zglKwI5FTcxByJwFA!?378b98CKtm`nPcXG&i$z6j&%vBbR{s#Uo_5x~8TZ*lPr~}X zmk5!bIjsaqH7)<@I=_ck)CD7PxVji6#td?Q|KPpaO7q8^L>VD5_=Uw;U-!_?N)_tB z1a10QgO>rdj37!f&cd|j=^(U-A6&j?%Zu}ThLYrEd}v!ZR?|LU-Me@uTbXb9+N9;opH7W*&nuKzc=&5Qsl$qXNTgrl`~qD&jN=yP@9VcNv6}QGCTcHBu9b}7>mmyQ0LXy8j>4t1~Iat)+-57y4}_!TG9oX z^A4rEi%vwdK6v+)@_`3`7(^@olWdnl67J0T zTix>hi`WTIPRxiKopnLnWeE{}sgV2fv#So&kezXLHJm1~2)(9(k>re)w5Nx1EdU0% zz45^AnXdEG+I3C8xgw3c-|2BIcos>p2Fhp&z&=+11$#ZoSl8I|Tl9FDRsjEUTFD); z|MuhPBHkg6M}h}C7nuyUUS&Rf8OEX`0mS(~Qw+w(4R$tWHOZ9RPpZnYG0b#4x+f z_@^66IPkbFx$67jLw}1mjUvjWEA88;#kX_5o}>8z2W!6xrFcPhMz#M%whc+2D3}|5 z67A_|Fc($m9f>|*f3CZXzMBF(ed-pOLX_OhUGGMCyG)~g*nj4MoaV>#_E1?+)7lr1 zSB0zQLe@7I5Z_SECV3}M(HHLJ{p(s(lFaPv9gvUh>Wor_+~&Z5RtF_=+GMUZSWU%v z9$eWW3B=N~fcuJyr_efAF-I1#ix6jD0Kp@C=^0V+Qm0thfQf*OEcid?2n2|IQAdFs>u>UXs3 z57@&*5}14pKkEfazWPU*5kN~~JR)4h@kp3^2Rpg+s#LDjb)|T`NTFpr28Y*hbTTg) z`t_P0{ds1tWnn;BgVe>H?7X6Vz<7FUw|!S%bTAZYCU7BwDUmEBo^=^2o9u40!4glL z63$gnEv0iS$2G_Uf02Hf!^N_Q%XX1mRs?ldjsoRntXb(%>7%x=qnXX-?e@%lB%M9b z?Ld(QWGKdNp@9_i6X4tU%haYY7^plQ#s+egj%1`c z=86X4BN@?Y=mkuIhbjSvSp z8}2I}`oSOC+fBF7JGvzIN}-+=$sp6c$D6<2c0~@Ip~&PjncI{fz-smgAyu8c=?|Wp zpOjvYra687clKoN;?F-i9}Mu8^bk}#&oTID7!$c%#R;$?GipY`ssO}TC>NecK8OYd zc3&Wbc_^qE0R{&uMOf~2ln9V$kpVnak`8>W z<$PquKr@FSG73e3{G)pDVvg-Uv z-3n@qi6kErn1weG%HC6+!TJJ0WV{&#a=}hsDj|(b+&_W=%hU^~VW5P?KdoN0c*X~T zVdUA7m|}8<6m??GL!6BH_THNeJdu&JN609UJST3ddJa)R4aCT^Q8Q=Qm~KbWQW%W0 z_3P=eMbR4Sm@(4)jF$<;1+}T3qvV|WW&dubRh_I0!O}h85f|s@d4@I;9m=1pE%uz} zeKQO@{7cUs=T%snkY4BvC9XZ2{Rcsa3|X@ysm&M%aOt#G?)Ouof}^}QjW)gJKEwf# zO61Vcn3#FVgmuvt+bM3&2CGzbq{?c09CV;+s*D5Y3L03Jhb=o=V2SEDd*SsGRl-Jh zA3@2wCb@%76H?)4vnHA7dXPb>rgv8a9bFq5q(rmbbBSz*vx^|e(qx3$#nzkktL`^W ze0hqDj(Kine)78{Tn#>lLjRkIzjG(>G|0$*+}5bNzNKx6@6tut&1fYdIyVDSr3B9g z(=v~6(N`;_+DSFzS-DmM)UAoxvx(L+#j&E(XxpbVIH`h ze@vNw*53V`O`pzQmpM3de`{lSAs46ecY_S+ey4Yen9RG_LojV2qCyKT^W z2PdBz1d};Hj67_>u3M7O`DQT3)49EB+JrRs=b~wS`L4kMp%jE^p`bWy3pfrTu7%7{ zDS^RgxTnv>Ye(x24H;(S<|VNk)&d_}&85S=58f#$ge7O0D?D9`x%(cA5!8P5+38>L z+TA~Vnltjc|I=Rnvi^VVg&;^59BvtcXRde{A-qENfhTS^FxTCLl(d@e%2gB$au^&XX^8I-G^X+b9`GIL5`Iu5zZ&uXK&zG<_&ud` zbFnLS9DYdriGH8k_TuTV-s0-L@{`qlOvmuv{QF<49M=j41V%MYgJu;ON@4p)1j)zJ zcI)7K_U_&GU(5!gs5J;*6hVg0i#kmogK?D&)$+JRkXTQu@ZPrXYFPiLSvqCi&$(;g zkqkV6!`{@7R&XZ~I}1+4NMk7YZb<97-|p^*Qw4;-}v=YiVKB3DXZ9sai0Q4x}}r3b6&x#v8lu+Y4Q z`|gU%`xPT;h@kUPPd`e}Rpk4jM)8rVV&}@YwVE`r$5q~&re*pj$wFsVzn^~1j)>yl z_6SUQ<~c0xwoerCaiaiA*xZ$Jb44mpPuJ6xQ7CJhRgck;=}$BxAI`pYI7W;t+o^7A z%u2nj*gG6xGcR0GUSB$5gU5DS&%i2{$nsL+K2BNJs0OrHLF(1P)qyXsQZNNPK&dl+ zmdYYb=F#~)yu&m=`=C=&h06HawC!Sf&sPpwEtEW+{qcj59}S_a#~sL+QPk4rYg9_7 zN==aV9v{be7<^Z7{tyTuN{QMbfmzn@s!PJ8@!q(nTYD9&WSj9^InC zOgM%`OGJTBa-)6(3RBr*Ss$2IB#`G5pZ=x?%grZz3A)~ut2}(S+vy*DW8p&*cD)ie_Q0}nK}Ja5cQQ+oBFiRf8f89zELEV4Cc?01 zCj|9Exm#*C4I@xn#|DzL1vdrC7jFej1YwRE22>DBSzIIU*S z6d_spqYX@@!`rx{#r%5C`>JwzT@ot(4#$+Z5ZJP%!n6FW=jUREBL;iB@}DOD5l`M zj1Z0>9PM-a_P2%G8Hwx6h9=v^U0)K|;G(v7N~Yq~W0Eaz;+pJ8HEdY6Amai<2zU-g z4p`jXks!Qd&WIp8=E1^x*KNq<5K>BAtNCU@-;aoICZRx(!ZE){hA7gg%*0kyt_;Vf zWKdjdx}SVBmv^+}1@mqUEIxXjphYICi<=3uMZWNq;DME)kppCL^Mi-W4$#&TPU@|& zd)~Y4zk{L0jG7dN)eDlk-sbV}8=!ZK-#H=7Z!i1?z}%9dE-y^!pS4v4zn2!9&AcR%W;uhm<5SMR?2Zu_Qp1V>Fo_W$T>%!MU8!6HRd z*pvnN!HAyMuOW=TM2qQ)KeYI?wchT#{)$^X_ClX2vLSK_qD3$7a z*K)#%Nn!0<@aJwiet46gEHOeqARo)2I%CJ^4ZP}(xGcZguUk~NPV=^4oX1WK6pU#M z*MCIYDcPyf>45wDm)AemsW*cch)Te(=6;?@pO~`!{cSi`S=huRPz$HZ z-hPv&i>yQSfs-02+JASc++!2iBFb;i1(|1v81)NpXcJ3Wq9t$=W_Bv7k%n^o4GY`2 zBRqKsb6Tg21Uhz3^J(pfXW2K8o=y5jrJmGY*pcYH$r7i{)7EjxV@?Le$xkZ^U$N_7 z?vx6LYFVy6+Egrk;c4nOXm&p|EJfVV??G?QP!-g~cO%`N zhLS5vG=L)=kUU%gYaS>;9uZxIY=yTyb309*7C@;bs6h)ms{lyP9i3 z-5#MQqbLS{#QeH{78q*&RC(zk((i&Pv~XymHrXFZ0%8F`tEQT$dcyWw?Ji zlRmB|m|}uz4HqF*9g7h$i6cI;f?E*m>BLoF5ugMI894X9p0}4vuOPQSd)m~IG5i{< zo1-t32V^hBZghzwB^n!*1vuLpEwG_drL=eC07GhO05OX3NFd1-P@3&vIZp6P@*?a$q3ewW2e zyi@22FSqhj%$4+YsT9Fk3l-6ZV2#h^L0YZ+0cYbDNBy%W&*9i6g2q0N^FDcKb?aVT>zk&C?b>(72dh<115q<8l-dM-M9 zQ-rIku?mKme|$E|dyScfd~^iR*C$UVE}ld(`J@X@lc5vRWL)EUO93p*vUx|Z!KG23 zRtSGh2z!PE9B%$qU5`&Bgb$YWq+9qJGxx zV*l?8fign(QUaBxtuh>CTP@;d-jj|MpQ`6G&T&eFEkEM$dF6_&W3lJHK@%=65q0tR z8yo;+iwSQl(J>tEqkUF+!z?SRz&=LU;^ji91)#EgrUsfZ^J)6XY?_XC-r+VhegAG} zmGwS+I5Q$+a;KA`!N|~r;x;&TjGaZYdk!-=uzYA^1dc#h|*{rvkXLbkF zq6-4qsn-I}8HdKB?+CQk@kZaV<;Fqq-vT?0{L9^}bBa2Rf%GQiIiP|pM}zsxDJ7$h z1+;DL@L~xKS9t)%MCk#rX|O;l&>6;ue^yl@MLAamjy>IeS<5tVFaijSYi#W^ut%D@KWa*q#cPm&wGQ?TdS=4-;& zPM>_dY8OQ>;U?kAHnxH&y{b0C4CK#PgHmp6BdPjlOC}z3xIU9)M(kaCXL zce3NbDn05NX+)molSXCrP?t)E;XgvI<0}G2h4}*Dv!f2IxcbFzw+gj(CuAV&jf?u``7MR0b<%@V((SCCzi_!T z@w}An(@c5TEx5snPNIC-gjq>_DO-*htgjA9o~|M>TtQ${0(S<=PS)#xtW>A|3^UeoLSJw?)cCQD%WZjj>sCyYPX-4-FlE9KUJd@~FwB3w=|Fq{h@+RG*6b zr80^+cwmx>inR|%ugPrY>|P!5)TB_K>X4h-%zCmG^&I7M9xAl9;c*lUNXmS5x_r?+ z2m}c4aLX{Qs-wGq7XFM&S*cK$m|DADQ<<0F1fX}wo=ml6Fjxo9;Cc@ranTX*9HuFW z-7aFM`6(Cd@C%!wlPl_H=bpD@%c54$n;nod#gl2aCE`tgPnl)S!h{|pPm)9qoIb;2XK5(Vo!9XR-+rH)G>-uLM)B#x{{q| ztF~Gs=xnOIg)Y#+`%qonSI{TZu5rC5?E;J`Q#*z$9-ya&Duxfn`Y>f(tGj<3Yi-*B)R7lC&osLc6 z#&*Xv+hL|^>v5$!MT6A|rW|KF8CaOdj6|M;4eGd(k=7lN<`+#xUOgHTL4NUZ$+{^~ zi4pq_DwKEC{N%>|QbsnfW?r1fcvteJz(^GDW8I+V@>#bNOH?Y4WodSm23&`>-sP^3U${SpB{4I1u(1m830w)3khmgzm+WL5E0GM8aDF)Xz)i zX`q@ZeI5(phGY9mlo)gQg>?d}WtwGZDBxbe+^*j(u}9CPhD`o#UO2S9Nwkkpi-eM$ zV~)OF?hfPEUR5;iffbvTR3s)~%0O;v{1k|m>%jW0Vu!@e0pxnrZi0&W?%L$VZe;>4 z`}?%wxqEWX-uyG8h3#r*1^RL>d=IpPmx8l$d*mpQjO_NGg4+MVh4YV#ZX^2ln=3JF z%4+xKkI6LMR?nI0$t1w#x3?NB!jUVnY#0|>J9^(IY*y$di^x9YK=64P#oOo#9$P^a zd@eTKihS3m0shOOP9)k(Q>?;Ay-iS9kVs8y$LuMdaAW$P1}G59*i)b}Zp)A9@3j_( z4|XV(^WjQ+p>i2L>y_0lB5l!NqPhe#Xl1)P4Lbcce(`<(RIO^2T5S3DO1kI1_QHXG z#d}r>I~amRfo$F8JdHHjLim3f%67A)ueL3 z62NmdWl!fTLMvo&jZsA*?axN5@;3QED}Cmuq`M(NS!`hL3pp}c`!rRV6-VTnyvWWo zKj}Ijtm3HtYTkZLnR$pT8BCc0?zeJ5D~&A;e&;HCVkA7ge6+IRrHffwDky!b&#|~o z8oJAgHJ?Lf37npFo#r;~y(X0nb$W86Wc`!34kxZ2c?FgC%=62){?d=+6pt9ZieYq^ zHF~yGnDr9T`bz{OpbsMG;ZmSfp#8n4FciZh2oZ{6 zb;nE9#EDYMpi|&$H z6wS>34_VcIg#X+Ff3JHA=1aY~qn-b(~OC}ffJClmeKXo5iDs?WuvQ9$DOaRC~ zS@eVvd7Moj=nn1CRYKX}VoBT&EtUb>zXJsuLd8}U#j3_b-urzAN>`N!cuTtbK{M&v z#FU2eeDcZ|c*wDH^R#feuLq>lBk+)OEX+=Xo z?J?$|;yWOmBQad>S?uAeqzelRy&ROra)&Od<`lPcw(W>4Dk4YO63r~yphD*uG^dQ|yF$81P)*j+CrO$Z)9_(kK$dw+xX@5$A`WB8-q#4^8* zz}r*c8`jdbXMgS7&>J^S^Bh)#`eeTOn`;d45zD4ie4HHT`--@S11WSDBXb#mK1?5& z0MJ~kU4ubTQ*vPfX`?PhM90y)J?Lhqv%hvJ*a82d&^#9aeQy&&8)sgI;&%bFl{*XKGRJ~U(hs$MfnA90%b1k_LW^VnQ zb!2UO+hfrB*fIW*vdC53r|M77_8S!qE>}I2bl@1?sW~?3*kg0trpTLc*K2C^gHTz1 ztd7x9$jX^?Ykge3)Yidr?EmsQMc!M{^e4UofgG>>C-jl*o(0DwqyXfBM2>^x3v`nd zQyk01E!5pJvr(pwmgcBeWeVT6p`HFZiWLX1W=mak;>WXQwq`TE)h9S?3W368h051w ze;%`4%S;P#|MkIl6!)Q3sG`R&$^W3O^T)VAb%(bJlznD2qtxF_*gbn*uV}qmDN~19 z>qbTZDYwEwZP>0!?(oZ(V$I17ZkuVv>c+^>Y@rp-r7`h%L46N8Hy8b{OMALMqh9H~ z)$8lq_8Z#aq?GTvVxapqsm!J99i;YZ_VWUQVbMtwhaMfK%(TdAaFe{}QSkABgoiYX zf0@<7>_4H1k~=|C1_FQBJb&7YtTi=1c4@uOGdKM)3r=@&lH>bs^_?DLEKT)2g2>i4 zuWh}hV4ItP*Phso*A%Nxh>UR8a66Hkho0WfLn(~OJzvB9Y0+SMSv+=DAC;|8KX&pg zKbzrCwm)}Ye7EBJ?2gxL3VE+feo`pgaOGqxtUcPyhHZN+TsZSaj*We1Y7pjj94LP6 z@0am(@LrZ~eid@keUwA12iveU-S!)5D^x>~7nixU6&w)57V$-H!=c%Lm>h1myT@Rr z_jhJ_S%yk(_4Citcw3t_|AXo~H;1jh2ylbZ;jP{Ck-|#+0mWzrUaQxlPn}( zf!x=TO&^nPCIP;GZS(oVJel2t)@Q(y)>P~j)`H(sUe{aK6)67GU~72nqNK&WyndLthw3ji>pU!`7 zG0!_j!FpX$Nq1SA`#cRD8uwG?sy3#TxhgDYob+CnIqg=;z0?V%dval}@Xg>^UD35h z>)~%sUc${zktZif*Ck^;B5pj-6prLgRDa{tnCAKAZce#L>Ahv!C%2Gw#8>37w9#4Y zp{+g&uX_;B+@pDvuAiwy7oIukm)nk6((?Yh`>-A}NBc-`@(FXI)=&58+qfHFX8r}W z$j0PuEJ=HM)I!q3I3x6?rcgo-6#RjTRvE8Un_3hWrc7K1rDD2n{(OddvA=yIL?NeQ zvDmHM$2T`xq|xl}9#kyEE3fqvcJWeWGK{_~7TvwO-{C3JYJ=v>pAu*&k|l9d5_G{IwrVwpqP z*$>?=lB>?}gZL?9^XdR#Rf{irhL(7PsOqa#>?}M(!Zq*cd)nr;F={$ z(m-^Cup@jH=ZxyGaX{2OZ$Ta-K)pmYN?vs{#-YTFW8P!-Hd<52>M`=2RyV7`cA?K> zxv7bvPsC4`FIdk=GY8+-r!_$)u;}b7P`aUyZy4r4Aa}-W0q%k{Sx8WN(9|lL3B< z8YQ4h*rjBM#`KLFc14K-E#c$VaFUFQxT)Q7*>qYi;7C%V4^~Aj5a^jHsNe_~b2Ls+ z{kTRIQZ;A^gbk1?g0C2tm<@oq7R3}Kz|=Foe4H$6x)zUxkmOfURzByK?s=sa_r_Or#u zrH=(3el;i(Yd!z1m_J-@6k3#sQnc(an)q|auWQKcpGLh_vyI-6jKs|TRM?rld15m|E!L)`PiZNU52yz!s6Y8uB(X`O_6mhETcJ! zLG=p!_lm8uWe@W1e0V9iC}4ABzH#rGY~0u8r|BEohrm3zyxqRBLd_9%kj9XU9MRu!U4e0{ErBLMW@7pSP&fDGNLpdrBKJAsUreFw}?>r%U3+BZ`JFD1ro7>vwVVm4>WOLDAP8WKPC}V zZG=;+7$ednqXs|5YvHz+P!K@<-sxqOkpKbSO_*9N(Bn0wLM?6z?%TN@WO#H!fHCtX z-3!UTaH4zoObYOfb>lIBX$R!00bCW}*9ldP!H*jv0Gp9F0ALBF7oy~a0W#h>(o8T6 zMZ5#)BdEb)4n7E|uP8hk9%Nl_SzaK`OtU_QZU~vCtNSdO&WKm-y^mfz(3n3;pR1fy z=KY8FpLjFRZqh@r=g!A)GSbYQru^{6S`=Dr_m6dinXVow+i(5#3U*$Mj<{t*-E(6W zi|&XEB4MG*iA1``RT8zA(P03u8V4m5w9E_e*-<#-lp zi~}e9*9(BV&tx zH_N9CdXQ|RVwoA)(`d4NRGhs@Y=y*;xT(54jTntc0{};ys)+f zdM#Q{Rr3zxAC2pbQ{K^80sg(~Vy1R$5vkX_=Hs`mypR0G-;OKW8JMQmlge{`_y-+iJ95`L;bA#Z3--^B!4KiP9!qs_8Ts%|Wrahc4(oGuE z(h!r*m^UzSYJCKWW}qLfZNy|K=Cu1h6YRW5D&_df_Qcaq&WWz~t+It%qtv(S36>Mj z3IO_Gt|~1{z^&y-jI5Ram9yg)1IDipa8SQd4<(v^Zv);>p0-`xx8KZVl9H*Xj5 z?S4BRs~b+!uYIpXu~ABGll|tFOm1OZK0Upc=e~%lSocf1Gw56bkH6`|GD!i4y5nV9 zbO7Ci7mp~2()vY2!bPb^gt;*IU>p?#v#Sgx6Hy(~3lz4fNB~o?PQ$Za-5$Yq{8eDJ z(b``FCJw*T=oyg_1l8~2AOftQe;&$9DTy#u1$sn*Ne=}`m=g1XCXoa{1sDjgemIyXIDWAjQv zj-Har(L9l?VkZ;S>KvG{Y-C;~jtPF4uyo!ZUKMUr1QJQEq<;Z;4I8PCCDq^{*H`x< z1cBPWXtBC3h#?FZMBL=CD-7hrp7nhOr|OWqY-v7rN04D8A$x29>9hsR+eeJFrB}%p zhuY5q`wYE`nUJs{4nlzmx1|{mXZw2MBZ+`&4&o}E#hC|sEnctMu~0613xX zsF^DDA&Z!Z(qeRrLgw&U&}7dl)-7e)#YOq^FN>@ql?VR5mph!Y=d_}>uEfgYf}{vi zBZ>0zm@7{D_q;` zEBBjNJfr8K1!pBt501 zkD*M0eG__8Kv+`xYlMRaS8w4`^wK3Rudt;kHpZA5LE7!N<`kk3RiG-Q0xyH>?|evG zmL>{BYmGp|W%3eS)HJ}l46c)?!KDs~0wtA*nJ`9Cshi7F{_*H<%SKoLVE=F_DS$Ob z6A#3h^M@$QTWbH)2;Jz4qDhU`M@XE) z3}<|Q-zqu1%49o}sP*;gfz1@#ea@TP>yPfrCn3Db?^M&v#!P@Se50lmVzQ8TeZx;9 zUMbBt96rN8v^LI*i^|d-x3?-${9|`DfhEp2>!k8y!Bd>$;Td0B)*B!-9p~m=U;Y5W zvF|74r{@+nXE38ff6t@p(KQ+6Eak%4yPs`lJod;=_wGAQPab@!@-EE$q<8oFM*z`U zcX4s(d)4OFhiCq_m0PR}3pVlk*Lv{Wc2%C0L!i*_{ak(leTt1I!K!jlHE-_OCyhC4 zWqa4W72iCYnQ?)KwI$$WG7GpTpGRfb!|*h%LenF!aYo^;~~%H_7lQp7Dlg=2vW1TpK0aPc5f ziE5f6psF(?c1h!Pqf29t^)GB+9-|6din{XLviHhk?z%VrvmTmgTd0v%jp@s!zo(~Q ztQ@>PGJBV&XEFD}Mk;eQ#EV;68ncOYW)%?LeCs{Hymka zq{J;b!*KWlLw#&|=;iT~w(Zp-dFR-7A(n!A1Nh00pG1DbS#d=gl>Pdp<(7m>B2~P& z!jxT9|FysZVIe@m-09@D*N>zF{fWEh)i2Y~^T|OXI=|v+Y%-@FFP=5d_ z1?0t?E$g0BF0!GUu?i$PcKsF?@UM%_B89OueKKzQ6p5FDs4P%Z#d z{ZAxXHH!T~W}ic698Sxi9I_L)y0T=nCD1Ai&*9Z_TH;@pUd4(GRL@h&i28(cdJh&a`P}O*E3&MvF;MqY|Ry1EM{%07;QTg<}plJOd6H zs^!xlpYT43t#lk6$X^1lzJlCMbboeuc(|nGO~L+mUOm)V)TOVbry^3*3aGO_-w1ct zy|d#If)Kh@UEKb>Xw)Fpe^CJCkr8b5bmIO4Yw#^KA^X_Z-K$E|m!rO|kLdfBw%n0?bsfS_lXn8caNYE%C&CKL??;&h_GWp+UY+b$ zQn{Zq=>NJBQzEbCw`5v(!xCIt>JohCWO|a54W{4PD!bM&mvpIQVVS$i*`}^^)3b-l zbo??B1YB`>n>f_k*i;w@No9;geI{-=4O0diV{eM87c-nojS8;L0Re!DZ9EE%0Q4{qFl5QG+YO&+w)GJJm)GT$IP!)&T_ zr`4nOsf#?TB4azQeAU#${q2Mxi??k1d97@i{a1Ytnd%9>iutC1@0 z!a-OUQNXMcbk=hL?xPJC3uJb+^wR=EvzziRBArb59; za$%eEq{oF9AawKrVUE?tq;KpD-}N1_`4Jpdm1_m5q`k(2pl(-utbS5AS3OG=kt9hb zn;%p8*{w+tKb#vYjVYG|d>=y!`5zj1X}%eP^GFOjW)e%Q3N7!7asQ@slr;(mI8DZ6 z2(#dIo64(3!0K!w3dKatZjn)`4^t&W&UG13%YhJ5xgyJrV>?0z;CvrKROB&us{&JY z^lwTGyOen*k`@%jE29Qw7loY$vv7DthlYu69h%oUx5nVil=s-;Lxgxl@uAWi?Yr_mCH;;6fooMlZ-;UZna>_LQ@RzrZd~EgQ!xKpHC8$q%43+30wT_=2Fu${c{7 z?tR_LxoH=lo^Z%}fTRr!9_fMe7T2lBN7R?18dHNva(U1uPDmcUzo%%jP$&YRjRVf) zN5}^p5I~^2elNr$vkYNzTy9pAeZXW&E1MFxVkFRHqqvrXiVOZsHi!?dL#wzIUpaj? z?6rX~RPQQdprbOn2r>>N8>PP%48bBId9)6nX0KHJILV>oP-TE=Um)x+3gHAH_!;-) zvEj_%5N_IHxb7tz0t_D_t2j1C35D|SzTmAXNsfGaDW0)cG?g1L1~*KV=}kY*gs407#SUw3io}`Q)2b1A3+M3XpzkG3hArOHV-*G-C_~fbS|pBTP1-1ci!-1 z)1OVXgt0q%XbeRaaKRD9_bqw_t7KMgzgG8m;A>SC9|JJ8Q`asCMmxdqUPxT`frE3DCE?!H`Pe^-|>^fFxUvCPAifQsISq4&hw)gyOPqbu_!(I z2LS~#|08%O-e;i23m)3*hutPsADc@@tT%`LT_0)PnSO!x4mtY%B2-k>LG|~_vEJ5s zn^0%0pgN%!Vb7&~Y@17`@>EKS980w0a8N%iwlfipx||VDY5FP8B*lh8 zg*V##nD+zu!vDxQqJFQrZ-j|kA_q#4vFjC<8SDcOoQ**|;v+tZuK zeWH2lu^}3`mvu!B`}M>i&DIu`kn3+;{uX5|u(#HfEGxjRyx?12*>U}yM^B{TkV11_ z`kl0IpPoOcoaeWEnA^qv!K!l9*DpC8%tC%=?RgnBHL`xQGV>ZumMON__~%Mcaq_!$ z55v;psx_rXtCz@|4{Y}mj$OXEE}0fzZEkBt#7X?P1%}JDgjn;*8UAcYrpabQY|**= z%lMmBO5Lu%yF17q)bjhDo~COAPP(6ofXqX@wS|J_%-PmabM4Z~CDT0Q-BKZGfZ zCdi$Ds=B5Sw|oCyU>{X}EPP-o=S0ASWl4W6>@wmWnBAfLByV4rCUmJ*X{|qUx7OLs zE5cRD=bXU}{$s`1nqP_QEa3w)F$(==r$#Uf(ROxT&84H3Kn`L{&OYrIbgMeV9^f^k znc%14>SR;*={8UAY+m4Ps{Zx|w|Qx!I0L>kV&nXO#KAIWm5Mt91&2Oq#x2QcOj9X8 zDF|+lcXje+1TKGxDiJ+n&m~m?6_0Dz(OF7a|EG!d04Q<`7#F_ElEcOGAjKe zYb_F-A3NGjDm+shAN$XCtCa9Z;VC2HS3qqDL+qj{j?gW30nLXKoA}yVG@_hN`drIwnnWJw3&c-&J{v}E^hRs2aYZ0bB<5x|I#m}T)IWuv$kfA63 zb~6AFP5to9m#?SuI?H`IK6FE2d@or4&a}1rR%(?kVYostZt!wq)W@5GcfA>rpqMhz zXfP2d$up^o*6q2-`rY89&eOnC)$`CYG(f_ZDqbI|hnnLkH&V`sS5b)>;is=1#(0Cm zZK|>T>wgba$@_4O^XO*N=)tcZE0y^ZREIdP@r6!EpA#ICMxy%&@3*< z=W#~(`2PItE|FUxnpG3NeCp?eyj$@ERY_G3W#x#N6X$7~kH{`|Qh)49*npzbe&1$s zPjdO*V{7mvK2Mr_JM*L-tb0|fI!CZHqU5`mn@s8k;o8ct9hmLWiu)O8tfUF6d-;<3Q>%-UFZjRI6u=y*# zUN3*IWh?F4np4MAj?9$h{TT961UI$eYf-xJ`LvCZ1mntQ$(i?+(oS4jT4|$w&`#C4m6-yW`X9F?AKpu-78f*qne$6N z=O9;uVjvBAQ+W5AR`A!I;6PCNG{>Wj-RiI9`JYm+?|ew${1AI3FJ@uVP}gbK=3>4( z++t!?yF2gLF1lgIGDZ*>#3blVm~kbtVkAeyhqHiu3xtLRJ^iv_t^ND!okcJI%OMZP z9!_y4sa@7^@Zoah+q@FXnEpOf`pv=hO!X{j2GIGYV}_=n>HW+mb*e6G?J*SOo`_LD z<4I9k7VPNLqL_6zRSb2p1tr2F*I8<8p_Q_PcA(?AqAOe3B4oiBWR-@SVr0xaM8mH_ zz(n-lbYO4#4xyT)u)TuiAfK|5*r!T+>h(@t?zO{6#R1fM6%@ihm)+9-vDQU$^Zc#^ z7#Xfp)~+?9)JyPX=>Vj23AIZRqGwpN3fV2dY|E<3Dqq<~R1%#iNE-O0B`to;7vatL zld$^&)3X$rM}-52dl3%x>P_39$XNyhQTPvLL54B*qc&BIrGU9Csj1#ULcsoP5&7ny z=&hslo-)HTd0&yLG~ESE0;T!UWA|#Bk4xcK=mr=*dEX5RE@OuUgqIvVuwkR_!~vng09iRkB)p==e#3%L7h;(21(f zjuR;txkr!`7)*~5g^0dX3tuoRw`vneVMX&)qhUvF2Zg3DH8wjnYH{eOld2(yGW3L*`Hxq+w7N&PAza#DtVdU`|ECH z#EuK`5BBY+p?HmhH|XxOPWA|18(TD^@-q~%qcb*2XvJARe_KG({{DV428uieHsF9N!0##^IL*`=aHmUA3O6hH8Yjzjhw5DDC8OMXC&1Ev!VzqtKY2g)(r zTf5{1X7&#Ka;*nw{3AjI1H+Xe;ArZD+o39u1h@Xa6^l$3m1r@p_B+y~6tH9QWlBn> z-TmG!Yh^_G!ci`*wNtA{EYFv<0t2LTI9W4?T(7cM&)$I9QwH!Zy?k1-Yu{gU+YYw3 zeZ)U{>Z#90ZK!v@@cz$61ONLUlEcOKl3!te{Nv4go$oR~{4GZ_z0$XAntA(ms519S zWtK>@oDc6KB+1gJ&24_-jm_le=|8d_ZquI!>i+y(wb{{WO!&hw@;T$W#a(f)dyKJ4 z(DN$KZ@4#i$66PxT#$9t1EY49KUd$nB@o){IDw47K!RMG-dIF-Rw0)9aS$XR|skqx_clvFbEw@vQs>h9atp%=1 zF4wl4inN2p%=A|GZ1{MgPvEy~@mf#2!EE@}Pz3Fc{y}-NvyvrbK_CwD1 zqv>nYKSqDF&fS0o?@m4UkmcT<-@YXhIx`7*H#ra%(=n1})!bQ1B_|fg2#FZ{Bgbjx}?+Vs^;3ibTPdUKU_?0q9P2r8HB=Ip++ zg#?tkGc?q8bQmf*>hM1FTHkM^1U{q8UnJxZMc70n+>y*5Qs!_~olPU<4b6>bbWtv#b=Ji9sO5 zOUhb7ss8l+_TBwrv^}`R$!dA67X_)@nAgwfI8Q(n(X`!JWCokvn>ib2Hc@a&HwU9c z@iLn}1k~P!8YVA;Mfl5R!-bt@=duHdZMs0ee(QrelPXlC0!C7Xa&!V~mce4tSSC}f zL_4H{P9=px8DGg`wqmi8WR?!y~wPLg=%wk2G^L%ATwNJyFq<`x} z){VbsKb_VHnA0mn2kKFc#i&R@tBme5&^=_lgTF_$EUNaFcod|MNf}1-kVbth&amX- z)!ixkO(dubg`yt%!bBBFJG=ot4`zWdgPkQIR%I}afkJ0; zJ3vG3;(o}f@jXFeVv^fo1kQueKuIvcfr4Y!iJYxqS3UHA%z4<=!?6ni%@1B)2$z}G_rgiX#|9A72#N}D{Pj_=^zvVs)2)3}IiOO@g z^Y5#WysIxFbY^b;y10Vd=av7@bkS$s-g`yXcjiBMlv`MFr@w#n%FS(ud6FIoy(!h1 z4gS=hc*^~8;IFI~Z;_4D_fqUVP))O64*1?EpFF+vRwm(L{Po}3ushpdXjKk<%{yDY z_7XW0I!RS!+`A@XYu2+}HrmfBYafckQm<<8D=yTq8N^K`1Da4_%3?hIOT^>fY=tQ`g>Y_umipD_8Z4ZGRi{<%Q@Ca_AM^=f96H6jT*y0a zRL>K9f}62_xSzl9mSjNAYdvA^PO=hYuOM50nfp$GpZU&V1!C>4316chIkk=DSAki$ zOEZ1EwqW6IzWkJ#UFme&DY>TyuQKxKajB|I$g>zxy|em6!{vqg9;ctSirk&4BBFX( zOq%ca7wvRjwp0+-f7Ir1<@28mI(vQ_eodcwsZBw{0fl`dQ`*N|{>d>bbDDQNF(K`N z)aYvPuX?}cM{=gi#9=*OOH()Y*Uh~lrGM)@-G02x`t>r<^jN$4=89iyk}DH(`7E^e zQ|GRXIj*dx(h8Pd`ym>O3D`T_Gaa6DjYWP)yU!iaw{x8uW5{xKW$FWjIVxB5_l2D; z=0{g^)zA8e?vhL|Mh(lHpWPDEm`skgQa0T(6(7#rT8}+oE_lm49m~ z1e1036{pZc2)~?ET1DdS$-BzLYoDh~ugxu7V|g{bvwu0P9W#ooaFRsiA*%wu^hcEL zzRRIGDj^w|K1U{LQru;9W zx^oYH+8}yYB(SYK$S; z$Ku0}S}+Qd5MF1^iK=*E%N9xdXF~AVsQNOILCdTv<<4&2miEl4DD5H+i4t}$gl$bOGsqB+QNwNp_LtQz-Hio!Dr)1bh6D3Yk9 zZA~V7NXj_bye%&SpCK9}sHFPHDxL{Y62FC``iYfrvUD*PQ^7THaduj~ZQU=yU{t10 z**fQTn^0BwI!~Jj->Lm|*6f+}>T_(TDZQsauYI>(QyDL}UfhaWV458xBapffjA1=? zqM-4_FrIR0DX5(&W%021@#Oh9u}q&Urio2~3&ZSIdZXqrzU1!Nx;^pmT-(?S6a|Xx z03!SO%{GLHP64T66q!;`EiG`-XA}rXsx$^EbW|}m_MaLsFP6p4Ns7+vnmFg`TY-ky z(tt#fmiC*{)9vlExQ?^^f!&{vl_o#%Z8XW9#$aAY0$?veWHd17kBJE6Wzml-UJCCz z?||VS7Daen$Wr!{S=@7K#CY}0R3HFC+lvUIVJW6v4BiN#m^~H%!3I^J@$)E!7Yh)g z-a+WgrZ(Ot{i6mkBimyw%zsdV+#4=KkfP{098S#^UJlT($2oLJt)8Mx-UEzqq6*dc zW?@U2pjbyo;ySQ22azy7wG^%*AGPI)?PKb@lnDT zU6!sAP+D$l*V!mb?nU*o*gu314N2tkfjBH{^|>uwtN!q*5)Ni&gNAjCOOq+3ac(!K z8}#k-)`jMC%Zp~pW|c6I!b-3olSRZN*QExyvWNf@6XmxMe2w zOyKjsHJ0M6=q)7r4jZ}LXS%0zUbKS1;22SPNiM_%Xm}-!#KNMIzB($A_7aa{Exl3y zVn+BH6JQup3HkA9OyW;Wq=Xorw`Jl-YK|hkB9u>Lgc5u8-yS>E(x*C$Xz%8Y~VGj){CbtZ|VHWf2bYixB7;7vI7@y=9e zpkBUXkt*7at7Sxd57J&rPMclW*cga@VQ-k0lxxcM zAMz@lW#|~B)vx3Q*a{?nl`<86^w3JvHXO+}*A#Z`7xGeFW9O4geyi)sNN8VkVc%uZ zzx;HXrS8+7vUS3$k8z-LQTwx=WAC@+wz-X$sVv&%pLE@=9m@xeuN%LaVR4ss4Yny= zFet6=H|L=w4X+f#VS2h$CG%*pUn95aPS^d^8H$T(Z{Sk;v4v^tc6Cark)B*PExxn4}UH__Z`(=z-6 z=q+lggRW*OZ!bk|W9)`074@14HQ>?^%1U{S;aqvo3NCWAPWkJYpn~=tgpI+WBysC% zK>ZUzjN-BCX>+ElnN>SawBDc^TATE5YW*;+KjUXA>qUj zOw|hoAz6)m4QOk!mA5m)I)vHn+|GtApBC7U7WEJSMJVbM(K5t#+W}-Hq1Wtzw2U)rl^TS z`BCI|wxb#ExJSd!Za%S8uu~!3V$eHx7mq4q2-Ot;^?p;*MyYe8a^Ol4xPstXinMm) z`s%vBv-8G1yaOAA_LV%bt*zri5@=6QK(hAdPx!Uuq)RJb`S#DhDDYQ!Yzp%{|ibDhKnKE<#KCVWj$nGR_+~VhQ8?P{P%X?&cn9i|3 z;2CP^^(<+)g0}J-be^1(`(V<#aU~Sbr)YcnHY)f*iTqW1IYi$JY4#|7jD3V9CF(I7 zo01b&Z=Jncri4$R44Ai|ddnESPT5h?iQ4CiWxO0zGP;GT*I}?jrjcaL&+IfT1sCUf z<_|L**cg}O9C~k9$RJKxyLo7$sY+z2mob7&ckP<|>{O2K%8ZBQ2Q0x>uP5$opL>Wa z{V9@*=Ri>ZUe^?e8EW4`iF^<&3%hSa6n~Hib!@I$sjdT`56r~++WgLWo-xX@j}?2s zIL6CEyA;?&qaoDWcgh5e@GwAOqvyd#B(H!Jo_b8jPOr9g$xZ~np!s8x`=)3xTjkV- zVsiJGt+T{oja}^aL?}Mhrb0>X>>9Ei`KF}7zl#gs`2djvQ9JKmx5PP?+-VrRsGx|z z&?u2aE7Mo}O1NMD>B|q|OV3Ih^!Jo)vFlvl?wuK)E{PusfpfEldU?g}k2ILRXu{@Un;?QN zyz@-|pW#J9XEjRdz@WTQW*>?Ko-WJ8#GTiiqe@v#8l|#4x`P9^kAGZVuX|tX^>VaTuF(E}iD?i1e{BQv z7PWp{-tsVtd`k#lk>0c#yBTA63F7k|KDV_Ymnf&eiFgZ}JMq(pKbzT39&ag&d$<1B z5-gcwJ*Va|%RUI=EdA~D>%Df5(i>RpYwz`{zs~b~do6NB3UYNekJ4GwdOkfnTJ3tO zM6;W+^8|Kkk~6YsXuVK|T7KJYC%k*26R7 z7p2kWZq0eLsQq%Y^ZgP7njka?JzBQ6oF$?p^7V1g9~5 z2-81oZ}rauc-$1rHQZSpk8O^3CI=((s5K0iAW>MoxFlAUxC9}8F5=FFF|d|p+Es!g z0+R5wB@zPur}owDXRprifsf4>hELxNLmobL5Rd(&mnTWP$VH8yxJ4+J0W&ye704?Y z9tJvbiQoxYAP`;#&|o_Qg%a$gASwOdTXqxGSg9zrM$S@N{!4o{gyIgWdM#{z27B?) zjWmFoB}#MMjJm@-*WB8OdMWly*h-nE^%*ggg>yfeCWh9>_Ja-W#RX~sMT#k0Y5Yz3?lS3+gig ztZB!~ESR_9k84xH8gEJg1X59C_p!;d=u2_-(XF^J!>Nl6pZ}~LP_u{=HGuUXH%5mL z$rqIMW+}N(ClVLMFXt@W*z76=FIAZ@ z-@aCBWXue0*N!+Mi z*qk*TgH0tD`B2(>gNEg;-5q_~e%*N;pTo(_u@kGSu=i5?miD7xLPz1DvUs|pO69A8 z*waOU;MO9RubRpN9SWfcj{EeRoj%IkD3KqZX+GblNi8|1NS#AqDw$>H=291tm4oT1 zM}4%-LDO$#7dqd{vy0@1i_hMAVfp%bh1VkxwIPd1s^hU5XwgK4fwpgT9E|PF6Kh~1 z+K5E>!z-z0<)6(8Py=f=P*L~^7=&3QxMg{~15b%0u$a2!|CL%U5%0#K5@-KPkjlO~ znIsXRfh^(I*ej&XY(!B;Kr;Ci1Dt46*=`wLQ4c@{79;z>)LJ~Ci9x&rGX%^`&1`nw z6k4V;orW~VSJ(RFWLl2q7R0oq3_<<_NXFve$Q#45LPc*y#*_TM^+TXB{w=0LjkPn1je~Xd z#m@pNULm5Nv1N$wzk}u%MI6q>Wq3O6EZy=QO|V?W30Pk7y{M$QXWO#M0JSaQ-YGky@GlQG=I`Z@QOwo zsWQ=b7I3Na72pwcwv7+cb4^==e&X1p!BpNdnM_oFhlacI0a)Y~i+zxNyLQ0ua%ns% z+_$LjA*LUHyV;0~Ufvzat15Z;x~!0z6YKuM_g;X=78mVy>lfg2VQ9`$Sx~suVwptc zQvx5&XY1sbU?>gs6A{VmVLVhTJl?If(!1u+UV_PKN>q~4_SMM9h@DisOnS|Ofmgaz zZ#KD>u?exxvgRDKiiAJsb`9w|8?CI`CGa|| zpI5IhLI6WF29-zX4WdYN5GbBjU;7^XgYE#);3M>(RU8P&dM|Yb>UEeNTQLp~=Zndg zo8H-^AYnsWvrjM2(Oy30z7ori4#i)RrsE`PK#(C5LJ~wsHznrAmhuPaq8M!5Vb;U+ z^OtZ1{b(0jC@V?U`!& zGQ4-8@ozf$@O(Y+E(H9i>nZb!)?$qqkLxmvL`}8fO>cGOG8(7HAbJ@&LDi90-o(w5}k=;^1$@MwCfqr-5ui zsFp6(7!VIEJ}eXY0U|jOJ4h;;MK)=Li-PDpU(gOycb0w(q$My?%yQOO?8i(`DjKJC zYu!B?)ahju#P|`GsXC;VVh!f?+g_;=P1j&{w|kGTKlV-L_(A>tX}?aPypkx*$E~-g z+mth?(E1%7=I}$b?c9&g63(0n;Y$D=dHF2+Py03pH%S2>c0kEkX`9YY?69hy#0CcP z!6z4{(Feg*XYzV>w%Si|)#+M(!t43{n$<}0qKdFn7fiS=H2h{;?TN)m{nVnt9U>Zi z!zPfs%b41dbJjJG#7q^TEY(f1F+SbxfGRe^=bJykOiHmKA@z&Om6w?57X^Y`V;71l zIonVM-U3e&b;Vp_04I{rmc?W zD@Wwrp1W%iW~@1z*#>Gp0fwVz`;Shsfh_J63eejSFb#Zc`m5vzgIil=$o4Urw6v^CV&!9T8vVBhA$c7=x}c?iqei~pMS0`DA6~4 z&uIFAXbkl;L!?zf0BDX0rZv>rt4~h(hPe{?tkPOimO3ObI0|{SN^m@?B>eA;^BY^7 zyH)Y8*8@&-O}A*WL~Ye{=YU+GOa;I|D*W1*v=3zNu`vN<`6*H~FCBx;HV|LHcUW;wP`a^mE@F55#Jpg<|{w$<1I@1<~eq^yXJGk{JWkhZ&{k$R!J|Z zu*QIA;>Mok(eIK?U@B?8m~sbk?vi7(LQg*@{?~RI-dm=!66T?gV~TLjE0NbR(65rP zI}n|*E&a{Is0=?`A%IgJQZ5Oeeu1JD8%&vX z7i9@fF}l;>mNZoG^knl6Zf#?J10q#7)WwR8I|&SCk$L`?a?Em%+s?!m1&4Y-NW{Re z8)Ye`2e^JTwqYW^RZ$TkFF~8mhukamr8b@SLiuy36b#1E?X z^}hJ`9yK1@xV-xHd1Yay7@-u>AiG3=#lcpnc)lCTHWa{}GOn+f0vJmjQWaz3iA1g= zDDGWfiVho705rnu3>dAp59bedUYvfl!+ux@50strdw{yQhk6yURKiejsyKtzf*YHL9ADyREm#)A zlkgk;ids*YF3%h;uL0+Q|BcEk3~~&Gw`7*@EBm-lag)pbMr7O9ULVqZJ(yfF=sGsC z<;?vHX-~X*p^^FDvlJF9{-DkQSzD#Bo|J~BpoKc2$Aw=1TR$wb{@-i_wj{C$0=Yi? zo-HxE`Co#}+o_AI^hamw!Ea;T?~ygzc1}a?m%sK2zSXKS>Dql%&t*4XQ)}nVmDT9; zLDHT%c}llb-%{yMV{5wr@}<91*;@C`^AY)11KizPT+fbA9ETU=z0u=6hdTjCSxFNh z&4DA4L>D$}7CwGKUIOX?Nf-qjKXeCIndx3To$1+#>;3w@-8?43Ng|XzQ57#{EhR8N zsK0SGo1tCnev1mPW+pQB{ElAX(nGc)Y*OnMBSm|Pk&;u?XGgo>Oarc?y)Dg!0Tt%Q zaOe*w0n1IWl%y!$G|@nPNCi}-4^B@<+j9ml~AdP{%*^_ z3{i0R3T5*UPGY>(?zM*6>$?_qDw=gBzH%U<2Z@oEpkRWG#MB~w=OHC1`{D76W;WH% z)?wPnPhMGmOjLLy<_lUH4jHjB4KBw5q8K;W_#A?g=C3f`CDpEn!xASzZdu=f@;wp} zqnKYLUH>cvSe(5YFdn&lYv8BfX=|nC%`W>MQn5LqGhj%0Bu`+}(wDI=Z@13!rr8wa ze4z}T5iVLWvUtzV?(5rNWV#HFGAywfauxeisl_&=YMkT0102ooZ?ON6qB=Acx%4t6 z(f(7K*Z2;?NC~@BOm3;08w-ak30)&>@|*;piRMJ@~S@NZrF9;)ELNtF{{Td+>h)0`tyq%G<=}ph{-;wia{XErnuVzawZ^k+|j9S&V7ys z!ad^Wg`ph97-h7vZSv!zCs2CoL=AE!i2KTKp@z>r(JI zi*5EYi2KW3PR{p%2TV{GQ8>)4;)hdq!<)m6n=0KI-HTx`^X){8M8zUJ+n_kEvlJRD zR72=|7%rO9-$PQ&(2K=`-j@WzZ+uiVQ~CLO`(sHND){lgc8W0th)hB-dgb0k^eSGp zsXR|2wy$rM5|Rjo-){VPAgCvR%5iFFVnLaO*56$mo%pM6e@4JO?M03t-gTB_%U%T{ z*nL>WX#9WnVFv=@Q*>+4ij&3*MW4au*&Swz6eYs=|$N!|l<+%Qd}p%6QYo zcg7Ko&ML?DJ3@k2dPy%~ugK5hQ(G4jZ?%;h5wOi$b9uGonoUGdJ`0-MXjf&0uHe7e zA#%@5t}dSj5{1Cvl;K^dEiSa(3QbVDIeAQ{pGmQPEUA_F@V=NHJz3 z@?5K@PH(5_~cb!_nQSGuaPTOQ@l(rW++N)mP?C1Z5o zREwiYsPEZ#&Ri!rQhF0BmV)fj%_7jr&Cdzne$a$*SO9mSshfflH$%gsR2qF%v@`rN zo?O@%e$?>2f(@jE>J)1CHt!s(7Icu(aNv-`vuWv0N)q{uY$l94AJp{tF2_W>Ow=IN zh+CZDn{=e5V8`m_TNG6+%HC_;bV-@c+L^}mv<`qN$wjJ#*2DjCR=;`)b0q4F<{;DY-Go!5u6?u|X@=Di!JMum@|j#AC6 zGRIH_RL4y(&mnu(E#azjiE%@173m^41C-H4_R~+R@XXOycTGPR&Axm%B)Ti>Dl9ZW z#Q!uGm;0n+s(8-g@G{?17$)dM5$BgQs(2h-?_*=G@^~aQC@&c!zn)J0zIz2m6_*Ca zs0=oGZEU`oydvU;J-;*VVy$W2t`#jK9yzT#RrzixPA`Ec1px}J)c)~z47QOD4)+%g zNNOCqsQ)Mz5>dj~M|VS->7I}QC)V>s#KEmnKlWm%LXzrjCAS^yjv!M=Lq*`ZuDMq} zo!9_{Y4INS4;T=Ay)i{cC-hE=wpPqQ;-j$^sygFCpRwv+slOMx3pG!Q?xuz-o(~oE z6>ASqq-=W&fx;Ctrz&0@S~Y(5Ryz$f-nG~w|NR^>#Z58CUs%~I0cjb?ojz4~XIyAw zmzw^HVOv=2&`KnLOBMtZ_0qy;w?snL$3pTbXJH=ok2aZBZoFVoB9&{G zU=)Vcy^9S<=~BhV_PwEbc@R|dbaBtIQ(w_fAlEIT8WGjDdkJz6+-i3!0w>Mk)FUh5rR)1Ow_pA z@;NixI8%2jS02%Agdj_u4DZ}XAq}hSEMtx{XQjUm-rSt+!eXQEkl!aYkl$D`%uqW99pS~=Qsk} z-4%NG^Io}?^X(#;5-l$H#|?vX5b;UQ6aUalYB+olH|_<0+4T@YmHtuMcm(&fji$4X z4Ad5B(Ra%Ew>(Qp&A1~!gjifKQ=#ASYg0<^4;gJz9O~xV{gBTTPI=((71R>6NZ2oT zZOWFqT<5V^k^izS{OfCuw(!ahyxb17ND1lkJ|s&l@s{-JFTCDEPiBi!`27nzl3X_hW)tqj|^EdnwA@x zDzQ!*D84`RxY@hvD3xehN(>Bo^x~yI#`p;mMcY&eKQP~qPhT9R@D=sRJv|0~tzKbN zBj2=oVNp8Px<0A(=jkLZ>uThng9m)FkRv6$f{%3bsz9GM8Xn$cyxkxsg5w7}^tMY7&nZ-y) zhGB7mLNTj8#feiWq@tOko|cK(z8%ciuW}XphM55MrIDGz5Zy;p^VYY5QH>DJo|`QY zXdv%0zGUU>*?W9%&!1=QT~D@Mo}W^`zyBP)vk=ctONbLnd#ma#U@xz32dP`>G(B1+ z7gzjB0;NnqKRk}Z${u`)%QhqlMX;Kdc2Lv4)q9g0w!8mRotYsO^_mo zs`n+@RNI%x@Emv)K=3VAHH26G{H z%L&dZm-ys6Tt^-^eD(>z{{FA~FzkEt^)Lxz{NQHcNqemP0>SkUtWhK0)B7XT!h2b- zqWPoFUk1^To!KjV@uGYhyR`8!(qI3~E8M=i*3aXgllFzz!C8f6cm|Vo^QN?|#I$Hf zN4N9BGlLU}t1Zte8(!f(2E@k)L*|7I?=G-7RM5{#pV*l6m&`o6nLKzJWOe=;kO#6= z3ewO=48=5+tJki(6*C2X2}dDg6w34oRat-`V|fX9Eb~EUEsv>EV{9F34gxS`=0r9o zx97pt@X1TVZH%`mN&8NH>;nqnZJ^?ny{aMW?P2*hhg_D^zP`j5uXLwxE8}vme_W=Y zoA{#<$%~$r%@Sl&=I4e^YPQ@@{s}RKGYNmeZZ=ZPM3yoYnH)`^>w~PHQHa;dI=|6I z_d7g3|IVwydAei_Km9LKx{HpNR$H~`PWtpl-Q`yl>_OF9 zW_7@#-S2zO;Tr>AzMSgThPXHmoYqx9Xx@n~?X46$E#?AQ3TX9Y836+xorMP3#dp4z z3OR$4O==x3G0+J~Y6H$kn)tW>kEQnxXY>8v|E=0B@}hJQirS;J)ZTlK*sDdATBWr& ztyQ(9w%QW2YLB2*HA?MT5hFH<*o6G<_viQh&tDFX-1n91dS2)Gc))K&h~_jVcQBaY zjEJCyjc|gQbnmhrtdI?cLp-fRio`Pwo?GDaJWg7lsEiIUh03JVr~VGy;;nB42m9)K zoEos{PD@pFMWh8V>VFmel6rugqJ0pvk`WwH+I#W1W<^m=;}akG+cWLgdkKRFxJXtNUr(H8$ms zhvOA29!@@`E7-x0r2+50dL?!jwiGzV{?lEd6sOda2*#2qL_h8QewG}1K$4U9h#`;w z5z^;bo8KYwec6~x*;An&sZK3Jgl-w?1dhK1p@L}!-IF$v{k%p5KoN%(83n<}Nhjj( zo*0EvD%LVO;v-qXeQvF!=3vZUQax^UFRUi$QJ--%0)8IDdM0^!mWs2$}sL_&$z`^^#z$upEOavTH>!zBwmhOaj2U(l60qWg;JA1tSxU^ z%~E@dd#aPP^Q)mO9lj_M{f;q#hvhr_&Igh_P#Qc!~#(p zI60;c`X^!9DN14LYGx~bIIJ6MN1ypOn!jQEt)Gug^o6G+#fKYRX)}skT|b}imnA11 zvW57l6yD=sh4l*mlf7}o`y~IY|5(IMMcs83u5km)q`TJ+53wmhl$~XMK{-Tlkpiw5 zR5St<8M{Kn(J!UcV3BJ2#(11JkYA2=3yZj~R1$ljXngti3$+Ka4@s$X+`u!SP}Hll z1u}^L!he2T-l81PC?XXzc~HbYXgc`j`dFl7pMk$4A_zwMrI@gjxx9&&NoEb9Y17Cg zSpV$lizp`_N)A0mlXwHWVOnR%c~&tql{1=PzRM0S)<*nE*y}z`7Q1PXBfLW29}=?qMY?Cj*3cBp4D_ z0EUwmutAg8+h7FAf}@HP&*GlD%g>@y@(po(#-l;(#`4P4y-_+6@D5P1?pf*?n>1qRIFx-28z@T-xMFbh=|~Z@{0(UEoXPL2^h+Q1kDA< zh5*a&g)VgK$Ze6FguR%YPkr`ws}7+0Fg^DU7s7t}(qnQ*F%oqrA(Jk)f4N&2?8h%$ zRCLDD4dT6;9ljjiY52-#al;ISGT)hY;8?^$ukm1FjLz|L*;$$<1?K~{qJJ5c9TP-> zp-ZM*OMe)OltBXO?VZN#iDU@oxdh99gcUCH##%vCqSJsX?7C?tK5nM<7; zRSbcC_aZO9%o2!WbYVEnWD}AW?$n1JTTNCe+0lf0+HN!<^XOHe+Ojp@E2lKR47kwp zApi9W%QuRIdz+@O_Tt(hW)#{ho^vljo$Ara_t+%y`oB*19$uw)WmP4<%VbtXxgQZz z*$)tgcN4yVF$0p{BpC@#j#=-#7H)7B#aXcpeZ#x1^eK#klTZ8V9uH%l-5-9C_5?r48+kcm|pV$M1Ln`KbLH=O|0 znX@_Fqc0}Kx_^a6naXE>uV_cdCTOEC1Kp4c+LCdCOk}hMkWJz_PxGiR2$+nXd(DYX@Hj z(F&3}ejmzpJB2b&*FCo>IPp=hBX{}mNK74{o!y}3VX>ix{_^|{k8>KDzbg?NBWMQq zXu{a!o6L#rt5z(}?UXhy9~Y+iT!od-vEHQS2~6ATVfIEm?8T9L_SP-0HoKe!%IsIn z>I6_%G~8CBb6)B%_=$o^C_}9T{#ASv?#hcH@_4kuxeYh5E_AI5gfd*~l_6RJ#km{h zM)mz$QqJBmVO9;sg8$nfftyN5EqK#5tIOXlPG-oV$d@tA>>)xL(wGc34dRbf9 zJpXx$y^TI_Ku#C`wERHUYh;&Q!Jsq$-JcQJ_o1WAo&KIYZ$#VC$*#W>-t8spcFV3E z+)g*#`6jb_U5uc+eBa&tepaxH6U+CBgCkzGjQ_D7vcMD;L^=Pp&aJh!b5p-;pMK>bS2WQoE&PN0PxID!iHOA$*fi=P8OIj$ zVTf>q2@;%a;sEBjbw{8OyKf4E)!-0U*K;SnH{GzzISum};yvt9hl*5%+>!)i-%m=v zNX%x>&yyip*g&_DfEfnog!$>nJ`~fWO(4KTM9Ak%L#@h`71Ox1gtCHnhNna5;1CVJ zdrnT|Ase@_RSsU+%}XeR)@%UNO}5IM$%!jhJ-z z=J#5jgIX0Awp6WOqvE`-ztuk#xRxkNK0A4L;+os;zlMGAcVsODEk8CG|K$7=X{2`y zM*jzTLUWAL_X$G<2_RiJWEyuB<-6JEVzS7j7bKRl`VxTUz?{FgGntu0Z;p|G&G96B zXqPRrLTcZ&yQoD11GQy;$G_0pegp4Q+{8lF6IQ{i+HZ~9&R{yXb z`fiv~@#7iQj>=`K{qw_ljTak726`?O)e6i1cb=@{{D0?36zG;hd70JIZ%G2SVm|l8 z9N*_hhelhywa*D@Mh^-o{f+K>w{`r`iTcg{X^T*Mf8Qq-ouD7Yp++oAoU5k;XNhlI zJm))qdsBP1>s2^f@Jp0T7o7QO_bcVYolB*h9OGk1qQWp9`5vtzBn6v$h+K))DT1e+ z#rQ+RQPqG6QskUt_@r^O)9iatUusu>&8cr>P7t`)&xFK?{by z4Gs1@PUxAQ0LHVKeS^hU)R5*8b;8tXC5C#Yw0hW4^u7BV%i&n`t22RMKa%g(p5i7d zy?TMxEezDbo%^;&O&#wGj%a+pNGUMSCsdoi<*|9rUvjh3kn{azWucK=X(qrPD zcvjog_+|D@p}^s*9VzgucxD8#XC>zs+z6$mR9>oX_0ZT2Pu`C zd+{(k8VvtQW3Pd-MziZv6X6!B(we!7Ov0S6fDNkRLi~NNfr87CNx^r&1JUu@vTvD{ z2hBa)KRhZadp0tf9cUn_;QDd%!L}4b1^4=+(Ptljoa8#mw3VyAY0Ul^N+Yr8{QbDN zdBWFn$@%M$gwX@fCtQmQS`?3aDo(GF$U)B?iNDRsREM8l=lt?MJ{ZpllW=b1_vJ9C zuZXqG&o0I7C$)@lr@VVEk|mbeeiG`pINL3DvtEhaZCPAXttofVO_;|yE>Q^_rGBtbNChsrY|KvB6k`NEiwEnXA`@>QzSVU6@ zij$n4x^rv1*=ks<#b{f_-(b|v^KA|P9S#yE?IgSA|^vxES|dg zxn-i*om0!9FF*ocCoFqS)+6?J`GIZL#=z2nwJ-HYJFDo52C3$K_XQc#rS<2Cfa6W( zlY1Bpoj5`2*YDlIs$AEHw`OA_Dw?PSY;}$qAzVM@%>WN!o=C!GLP*I?~++`j_0dIQ9#X?U^ zrcH$Pf`e4Qo-0yvP_pO4TiKMF{x&!{ivFDOkzy)MA0@uEHS|!B9nJ;} za;CZmafgIyduIMqxJU;^8@P}G8hlW|<-&0 z=%sdzzQ8nFT^8n*`!JBHw1I6N42p#dYEByqlR)Ra8k_xD;z+;AW|=vLCQ;DP%Cw1# zLO--FSyT$IzKTP0!L>BCnca%rarJ!iy|kh0vDe2(m(S*!?;16XS_k_Ep!hCV zWruSuo!`Zk@a0<$=bB7Z(`ymFy`o0=jJPH-yH~Y*RcmFZjw%@WZJ{He>w9O9;E^-w zzT-Jmurg9z=HC2_>LO@@JYDRs`PTO|Gwj20T+;UU#~+>c27^n_QcF(JeJZExw%c95 zKFqL{GLS~v2kEu`IQ~BEbV!!vD$o7&t@)(5@M( zxcHt)d>{vbH|8P;$N#EepX0T4xgsDqKzQlc^X^#say47~cD5t`O`5;4nItU55R(jt zCY{XE%`Y+E68$T4uz&iC8?kCpwk-lm|N9tBfy;j2iJ*Fjfv4mg6(9!;1vXsp{N}B5(oRbMXWAVaYW;vm z?`){ct_Dy)nblZ&D`Hh3uT!{_V+yG_NRlfbf|NS#0Q%pFBMu#i2lqSt_)0(!1dd@< z{$t@gnZs?b-12=|tD5B7;j_+}O>w_MN7D|>^Rs4N3`FIc?)voV%zbIwXBe6IrpWsy z|6Dz;pj-~P6ef5KFZ}s!cE@%*OU~J!mu`evFuxjr3(r|w3BS0irr){LQG@sxRs`g2 z3cY?6*zLINg#7Q*WW9Kc+qm!4F_--(wp$M}wzidouiBCd;BPKNvJTXg_w71ASAO=7 zNq42VABH^q=5$beZ&z;M?M=g)i11p6$!*aY*GFE`T3i=$zn^@w-%xSN)>){(;ofHX z<5|OcwF`}zx!PSC_I+1bBP*c*yT({cKAN~D%XPgq)Ywy@rq#X16DGZl4y*w>ZvUi_ zk6C~H-C{_ycqT7>{c^Fi$3&Hx&k`fJjrf!O%PK0X(Z{UT_E*kTP&Ysf<8>Wk9 z25@Lrc3jr0C?z&3>dF)I7z9-~5jMhp70ni7X3bB zzsZhMr~||y}U#UVh!eRhimiX^kLo zJfaQzyHgtsjaS*SOvN-TVyN_F`fjLl z{m_Z-eyZmSE7Qam&2{LOC)qw(FI>m=uZ(?bmelG*$_VCLaxgEAK+~P8nhFUon$TjBv(q}jULn8_-40}hxEyj&Y@lE zS;{4C?>$EA>qF*tRR4XU4x4X~g5yY5omy0#{`$vxgh}W@C0IvGxDuQ0%ifY3+fQ1c zuyk*BI^LD(5#p+f?*&l6z#9#$q-3H|z~2TF5e61kU2WllL+`BrfoXzB$$(V|a_486 zb|qHp9Cr=rHctQoIixb-ga6qIZ4=3ok)yEm^2yUNq5+f~ue0c<$GNmbfHm43U>e)w z!wD~&yxAzO>Vb87eb4A8a!$}=r&r6bB~<*uQW-_!^|oph?&LWT>}1mX#T>M5M&nCU$s4G7+xo$9?^AFUd_nT2q|tMyLZt6!8r)mf+G(Go9{i@<{!RHL7Zb%#*e&c2O@xUCG= zf|pOjBCo=JYP#8Dr!Rb!J5~K+A`d7N=kvSge!;BKt<;0v7qfw!K@MifjU6R%BNxHskklqGr~JY=Uy^W z0!G3XbJIf&ISD^mXMN{?`q7ZZ|MGSpTH^1clnm-HQWc5X%MnDQyJwj7bYp66+$TEJ zj$K32dp1|c*q)*PB|6od(S3#~Hnn5PG1UO9UCFd$Mnf`3X zNd&NwkmG*&8K*GBb!flbe{ckRU;=d6O;?QCF2ZN7S_6nK@og43e`udbx@guhv&6<0% zEUhsXxNRZXNstHqjQF6;!cO(HU4RUPR@Thm(z&2O_xEXX7=E;+@I~U<0jp5UIUEw~ z+7rtJjp4R8=7tNO9VlZ+`CtP(3jLfTrZ51`oT8lNYZ?tmr~5RffuuGe(-UGC@VwIo z_&O%uQX&7Jih;b}p?}p-sRZ@%kf+a(oEbPE5Tn>`BLI5Z*ht1%U3XIHH)46Zc$pYq@$ zyeot&7;zDhj=6z>FO2+xZ`vR|j>U@XqynHwvitGuNMW<;2g(eAfj$Tb#E^G73?>5c zdVO_sS;l0TMu7-#Fg)W!DUuNr{XW87N<*HeWl}I+0Kk zQyQ%L=EA+X<;U!4UYEnJ>HItRQS+!8#n*RdWyXGzfwu>D#;HS*3C+(~`??DP_1@(N zJM|hkwgmaMFuVP_hywMyI5N~uRg=`aO+FENz%}@*Xe_E{or2il80{hH?!XX~a{L>~ z6FTQU>W6vb(c`DmI$k@JS!Bu8r^vTHU-=^VxrWJt^nQ))5;V>8ZiRKpgT!y+8+Cjs zeC?NIVQ1GD5UXE)?h!`}Ekj*N50 zCATSeSDD+XGm;LIiN9~W--wFFMtDgwBC8|#uQJ2Z9Y+Lz#V$R*twa09Y(ds_eQowek0of3<~2nstgc?BoOB+I7& z!r&>1a)&UupTRis)vNJllL#z$K8iS~m6Ey?49`@(ECb`aNwO&CSfWW*xXafWiU2|| zUi1zsh1;Ka@VNNnrq#3BXlj>R@2r6H3kt zMI4+E7W2#z%^D>tDKc@rcJhJ@L4d4mhYx33^1zy?Q~6JMbpu$!Xt|=Oi-6vuz%7!7 zQ{_W`0oZhy04x@L10MgIq

xsxmad8R)gz-H%%9C`|7rc3OGTh&;K=uwfV8CQ1f zpb{n`lwX>t|11Apj`~&239JE@dc9k6PzLOWX4O0|O z`9BcA@FmDKtT&Hdr(zwE5>$c>s|%C9?6`0^ZvUJ)c%i^JoLO67#vS3H2<#WR_(9W< zcN|2omB~OTX$4#zbBqYx6eP^FmjALzdjP#GTnH5~{C9po-Vqy9$w8%p^#ZKL~{82NGhbc9n8$w#;_*}K5H zosxFd%y?=^^5U`=$qz%x6YzD5y?FCRr1>&@+M&2*+@?A1$Fg-m#e7*WS-7kXck*zi zO+qZ1@B!C{^&oq)&oa=blr1pKoG+PSV6M5p~<~b^v}qiJ+i6;F#3Hfhi?NiPn?knfb&HO3q3JGLI~$= zdZ*)9FxwT19^Pk+h}74C%w$xHfSy>0f_=d@ zb?uPFea<*EeMu}b>vPm8W>+_tJ?YoP@dSLP|3q)k;pVo9K|{{q&>ZQvW^DP{mw$ur z)=i|ohIZ);ez^=TCo0eJcqTM4BD2ddy<6eoX!Ym6-#<)}-n?!FAmL|(MT4R^y%(Y; zlT#zux;yBO$69}k`_8*pj2@%cQ{{o#jMOi3sPpFiu6ZUmr+O4)$K*w`%Y5ss(NX+M z$Kh8Pzry5~xDguzfxdjdhWELS7unWrHK{^B0$WYROx!&0>3%t951yCZuvq)7XX^W0 zU*@BW-&5bY#url;CH%ednI(B#`rlsu(_Ea(GIK_$Pf~(?7CPBEwT@x&spa>pF;==y zYTk5@X2XO!yvM;ccj96qFSDBVyiYI99&*xcA_4*eY)xk*u=^)Qk~~xZ@^<05(>dct z%(S=e^VHt*_qJEMaFGa_+yS>&`1cASmEDKds;mF-kk}RR%IUpkb;O8h-5p% zNBY&4$&bNw|Jg(1y}R^Kl8j38jmt-n{Oo5>c9|A4Xg7@?o}Zlw4M`uzcWo8fFs_n` z5Rm6Mf83xOzcBDDFt&CX#4e=uVYELh0myueYf#5Fe;r+^Fk7GkO>u`)FRLwc6qPrW z>8c-@>y%qhyAwkp2oq8cULM?)X}L|Tub$OO4-6rQ01unWheJ*FX!{EzZ#LHG-tZYN zpo4LCXwX^IeZ}NecO3l8@K(qJMqtHDL>>7|T$qlW9_U=RbADb;zH|Ayvs33H)Xa_; zJ*9b`dBXhTs+leVSN75Z~zLtDEZ7#%9rX z+iKhZ+Y#!_fS@Xa*of%p75pV483Hq<*h$1xm0}bO3RTV$td^S@em+iJi4Qat=_Y@M%-h=rTLgI%I=AzLpO1u1f=P zwaqjb${Ca5EKf5BJrKD0-+E-l1qZ}DHHInlOj=G6u@`7aFKvLW*z%3@AUjJ* zBrzJjb4}rRq{G!IiCAKxicInf?&nU^c{K}5gKqNY)l4b)gBV-f|HNYsZFo<94VN=K>K_&2a($*?wpR4*72{uPxYlsiJyee&Q~R=D*^% z`CdMcZ5UYZ^=J?)8Q3>3W|sdaRF%s-Bkr3t&OplhAZ5QLOVYFDEy+DIH}g@=A#0U` zGMj>*Bd?CXE5Dc}9@LQh*i&(7yWu{Z9vl}izwoTnzua@{(K`V{@~1Ajay&M5M>967 zV$XjB`h4T34mWK`{5Nh=a(nmSO(=Iq=j$w|*X!*_R~E)=`C*|S&8#-cB`u z%q3NdR63V$J5gjSh9=W&Ze`paV^-Rd7>nlZzVM^N{A}PSvJu98#l_jmVYD#8LtU8Q zapN*IB;aRf`?u85AG6e$8AuR9WI_E;I?H!b#+`^V{R!Prv- zxKW2Tiw+HUbdBVA8m))RK1N2}hP*mK-Iwl=8Xft)tYVRIo@S?TquXpPd31VI19|A$ zvHR!uMW()_79vuPKMOE;RhSsaH&1teq{N zFjSb7GT*=&zAaKnh^I@H)%%br7y71oe0tLHk9{M}|MBCl<>Puuo=I$V!kJ8}gs2p? zn8YmcPbHmA7B0(*_dfjN#j|1+V&WNCMuE(Nyjf$r)#DiYee|Nu4bL-aB}p|?J4Q?* zC>*SB+iNr8bo2BbUkf4bkB5T9B%($CQa6e1=f~NvT6c3R9A+4_Rh65k-3GV!(`@7l z1CmbFSFT#e6Ef4=Zs(ERt1cLJ`D@EF(el(ul`~5zWe1m?l2l9#7TSF*&2lB3T?=Xv6aJ@+P%)>YC5EkZMAJ zG1nYp?O}BBR%5{Cmi*wcu~NnKnAX3h{V->jBw4-))LV8KX0VXDVz*PwpLpn^lnC+-DlHOIv5&raiUc0yb*plU2<=;P+wmq8F^E_o(03Xk5Pf zwo6%~Dc+|iAwK!vxK~?^3)RV_xcQ1Dy`gC{wYonX8@q z)2xTM`?NMwsh8?~kC2tgOwdgA%w}8^sJgIqfye6=W5;=ptv*Rl z`Aj-&Y@E#|Z*xJ=m4d1`i>pI$Z|018#><2i#1|4BjqVAvm?S$(_qP_U9UL9~?#|bA zJs5X>>NgN?zVGd_a!@ZmoMfdu8HV;Y4xb-gYX|;c zZYPKO|Ih81dvj(62RFAlhl_X}U|QNvr{~uPD%n1M9*dHi| z+rnntM6bn)+_(e)f@x+XfG#jZoreH08H9J6MF#!BK7;}XWscnO@pR;7{Y>qE$>GdO zf6bQer(awAmaH83EEY~I=xz~xNhenNO{rZ`DguJldB21AA5Cib;b!q+cJ$tXxD&Or zvt@HrDwVyQaw@x`zbpX?xH5AkRk<;U+nMQ&{M(`3H`^(E(e=T9aZtKr`bu!`!$6JJ zC%CVeX=OYs3`PEKYv0@e=iLESbJR3?J?uNmQDR0w+%-6jd8ipljp zIG#}*A-wbTTQX`_hXT%i}wi@yPscY33kBV&8m zXz4k;MKh>DSZC8#((4On1fi2V^4h$poYJw)?WJJ^Eq4?E`6S1xkWt%F8fpm}QXFYy zyPO8UI(-@rut1AZ|0{%0(ozyl5}laq^ih91Q5*W1dff(b4Pe84Q2A%?aNefsHomW* z)iyOmV-Tqi`Ip&!{t%C46A^*ni`*7425K)k@516UR|<8PS8)fsVR3gG5>_~GAOH=v z(ohzcxLXKOxTv9=YchDtfsF+`=5CetOacjrl><0L2N-e5%ZU^~#vc@Vtz`738=Box zm_unqrcSAIae~ zx_`s7^f{WzY+63?CrjXFsr%&TI0-ZjtNZg=nbNq{m+vlNSiW;AS`w82d|_gT&u4?6 zEM*EE$A}UbaVe>`dvOA#8Q)@hH)}Y}wfSE6*%QU&V!>0DwQJ#ntDRSLqFpxWVusJj z5pOt`Lj#}gwfx6$1HcfpGARK=&8^>*AJJAx|MBEXY_x`-7=~1aV=iIl7)ZED3EIyk zh?>t0FHdVLhj!euBSxYUC@=#H8Ya`3ZkSVI1Ye_w0o)W$c-@!F1etJRi8LTyg|+!C z3_e);7yn)FpFc$41S|gj`8l0{5Si3Nc@fBoov_HEKj9KyA42<8e~lWlzjO9y|ERIQ zC!|x8Ob(UJRB~=piJ-}ZlOoMSY;^{Tf-VIb7ZeJ|j^7W-jAZ2%zV+$g$$VBa zj!nkri$J6b&%D*MK5y+R@W=iO;QL-pQ-=gy;T2j?^zvRqv+jgw$y-kf2awSn`OU2? zn&e-J+8uBPbV`}$bXSuT_R(}A`)xd?3pZyT<*F$kvbgI&6sSlJZx#HtDR5M`NU5fc z_P;SJj ztTITon{dVRz1=N!;(Vb+H|R1*kin>rG)!`r3MoYdv-B&!_0_Xe}>vTtPmsX$N1dFHA7~FG4oK zOv+mb+UeLuK>q>Saa@rCciLwdpb*-%tf-s%oH6oT-c*haT9tL@qC{e8>}IF$Bl01->%8OuhB|jZCSsS!E&HjZlr0dE$OTxEv;~8v?M8qcY85LkL*h zea0VmfVrk-1|#)eeD3PhGzBhE2t(pNXRPwIR$8#Ao= z5iWI!1k6?<+K2bMdWT5;`)@i=$G9hc3Y#`_M}`}Rj&OZ(Mx zrx(mS9z)3t+F^#fdD;JcZfAA6#W{)5?cxPOm_2T!#DQdmQ*0G_=AO+>dS_?ynC@1{(xT zD6!cP^C!cIK4>I;8Y%zO1^V#};9qN1VZ{{^?DXe{Xmtelz8x5zI*Mzf#Ym+xI}vn$BwajkZ< zuKT?7tD5!4ylAtolKf6!pC|fp5KrKlJHy);5L@Rp_i7lN6t`|mTV`oEQ5uPBCm^`wkIeY zXf?CVtO#CPrhZ}U{U^LDV$_9q$)$?Po#QE@kLo=aWdkQih;oq0FhT`#K~o@YYb0YveO9BF&_FX_3JVXTt*t(io%1H<)Rr~@{%ie?4Djv*^M`&4} zB92a{lYYgg9p2Lu-ZP}L4h9lv+dc>sVJb-eH0tx{0blHJ5x_@JoQ9G;T4C1#g>r)+ z2K?a92&O~(qB9AlX>;dSa+Qy;F*Ix-1mUjMsLi`{FZbl<#doVt5K9QeLOs z#=fSH%p^5@!DoTjrhH`hBp^lMYye5Pehnmt;@vuS0oDhC)QryM+9@~^#jXaVK*ETz z4tIe1vu#9A5v8csaf2q<%r;EnY8ePX0faMOF`Tv0D=D^)Ccj0H<($ z6j$Wx#p>=Ae*^aNuOVI3{RYgy4FrC(OsH9yz4D-B6T!DY=rYHbJRzVB>+2R4ZEv0H zwClWH!u}tl)k7Q{`*Nw--Tra6{nb##@cMDW8G0EB!!KSBf!uat3;G_hVSn@yl7J1V4Yfa-+05h@7BSJVEXcIqAJe|Mi_LJ+E=#pX z-97ue{FC$R7biF{`aid)L8r@qwW>+G{@{3c{pY$Dx#n}92p0HhvTJ|5^WT_+e@o#T z>84i1K1|x?G>0T@D@OyUIu_rP=pu}|3*+dMF6Q zpEF7b3~ zpS{hMm*(0Nzwg3sa+;*``?WR}y2AyH5xT{49wHB}WV2UP2E#$q{P;OJXEQFk>tPs# z+-ZgquCyu#2qEv@Ub=WJ0M|19d`%5UYEQkDp$cjqf5cS~u=-3qc2RGqf^zzO?A7GQ z)&Tm1rd0mRjNcC5HO(*9HWe$HN&Rdp$V|Jqh3E8in-hS%qTK&-8xqM=Kv4R^yj=Oj z?Ks@|%I>(+B)WSqUCQ9hie+PIbnO*U1xh#KOX+JflLx7-irkn?gYXHy4Wrqu?OJ%D z0Bc+Y-hPEw8%(D`U}FyZYs{BgSk-C76ZZ2@jmDMdN@kdF%a8eq*Y#7t_F9uVi`S0( zBl|%H$n>vaow?VR!{YDi2!a?1i=?7D=U~TBvah@>AFm-vpNr4D9i}%k=Cj0rYC+E& z@pr#c@O0Q;Svy!{ zI@t3N?y-2%22O%eNZqU`-9``z+aER@-vSfLS#CE0(Q?GKkS--*SwrX6UKMWJMf+1< zyy|4x9L+!>E~>%}ZMWX{tVySDC#V@SM_$^>)U)h6d0(nP#TJs}?sG()BcH zA8kQ)s~1ep3`F?3e18UKA`;&OX|)=tbdy1LnS(8!I; ziBINld=rbKV?u4I3p3=98h)pZ^gV=St=KXX>rQf)_i(Wjg*T~o5X~pbR|8F7)yNIN z=r9OrMh<7|8DF!Nto$`HShlM&t(w)*Bqf-fKqS9{RhtH zvvany=lMSOeO*1d=m@~RMC&2K!*Kgofc;mO?-;$@3$^76;g>MBSja>aV{Kl^!0p$l1cAMsh_5x;L1Y+P4>$g*8met-k9$`2=A*@c2I96Cd182 zs>ZH=ko@==@F~3DxXy<}4w24LJTkPVWnPxkDcInP=kdu$k0_N5cAJd&0|X&AbWE(& zxYCYE-%plmKBbCY>~y3z$s9^9S(EuP223fGhjHsKmCOTIzRAso zLrF}EqK=|;V}p7{vNdjc`-&qMgiCS)Lht!>)n#{s&l{HfmGA!7n{#kU&&@Bn_EP2X z=DZ*#PwxPYltb9e9^U;dz3rgzwNp<1%+#w%9GIY)lxA3Fj`%C^#;#k-|!SO$dg@)oMB88v;K1Ei+ax%6RkUQA0P20Tqvi1*?oW$G; zrUWxTQE52q$sc|8I&V<_z7TCt=WVS+iQIl6TfJgb(PBPPwNBxYWZ9tZaV5VPfc08xtSC%v)8HpqvC~oR;g$(wlvk}upq6F)l5q?a91wSI9=C>d!C+H<3 zv8klxpkQBenUvcu&+^W@jmI7k)3YWEA~l>9fb6Oxc81?1%#z-AVyEzNh3TtGO6k`) zl`N_b%|JS8*z_#2A#d41kD|H^RBNL0x+{PKiI(l{EGVN(ZGgrVj^a(a>1zAy^=(5; zj$$o-Qb0QD)Ok9~jhaEA1w)%f(S**9RQSZdvP&!P;Y6~m^&Lt=`)tHBM5+}Q;C1s2 zn9d}lN+DTG@H$Mt(p1#D!;Wh}|0?n!Ko>~`Iqx*R(2v zkB6l1U`=Vq|14LEkivOTa7~%ARV>-LHO8;jW8^x(K$BrL%1QWvd`>A7Uya{cJeOxp z=PQx9*S|I{y<+ZUO;q)p@&ixFJ*%w(r9Ym@Glc8bFuSy)g`y%N+Fu!Ptj{~_x#~$} zi3ev!MW@+<@c$zWng0K}-O!B=&^szglaeLUX8~9aNZne!xw^br0&MS^UL~7AC{NsO z;Evy%irAO)nLD+AN0$VQY$bzU>&>$=`^uN0WpGp7cKBTAa^~h`lW0Ek&!5U?(H9y#Np0L0 zAU@{f6ANKTTM8i3ah_AUjoi|Y_G=R8e!{dCFw$>(CUB|IW3bn!pX8BW=%tsUdE8V> zV!DSl1k2e&t7QqHYHuu0I29Y~RVl~V6P|8dj~~A%LKTulml!alNW&t6*a~jb%+19o zCCmK)BNfJ>3?z~u$OF{iG57JQTN0U@EKhk!gxyC*B-0egfI;I-9Ci3u-sLNKS+RjU zcJDy<*uYT*z}VOww{*m&Z(lxsnXy>-eoOMmJnZdaxIzg&;%E=L3*bNkpT%VdCSxsA zX(%pxk&J)vfo~i=%bIJpy?r;&$}Yx{hWF@oN#Cz%FRt2A|WkJjgIk(QK!zA`mR3| z$N#0cepMT-?AbzvMYn2dpO!p8>r_6tDZq1YD5m5xCqJJ!qW-$~EdRo+_59=Po!d`k z-?SQ1He{Ue&X*Q|tlm8@lUT4BoX)9++*0*E_2&BrF9*Akk&q~Jqx5~TIFDfbhcxb5 z0(UkQ*4U^ax?8nBPJ=2KA6q%{L}blZpF^`g;Q9GdR-0SHo(IXFsW_`t}Zo7hBfJ)>B5J;`BZ-sFQTeH@M z5pcJXk{}^qmdwALgcnyb67EY>o5G@1AuaR~x!|7fmT>r9_F&hsr#_FW5e7-TDz9b> z=nS&fMwmAF1o&7UDMI0WJ<7v3pzu8t-p%`-9vOUNr3ZJ98PB~EV#!0`chmLZc(5H> zt7F{D@1se!@lXlT*f@Qi@@akY-$}73r4XhJn^Sxvj2`ThmsUL#Xldo|!XO?m48O6dh6A-82eXyA%gRl)SiR= zYILYu=g3B&;XdI#(B}L}D}_T<`my_KA?d;U8!kAirW>8KT6eoL5|gu2Np`WYk8qJg zPHlPEH0C{HbDLB7#uP4^D4dc*is5S}uT5n{I;Pfenu;F&0iH1hO3D~n+;NYJCL|1pcFu*X5e)FK<^9~#+$R`F(56C)#r*9wFR-j+CNso zpmB=3Gsr&=76TmSt6tw{F-}Zf)=8kV&|kg`t|C-ggK?C|B$CXdZKBgHpQ(2q7$955 zB)I6vHFY2|Tcy&snT;L(aQCSq6QTkHd85THPfA6xAQi(g#jnZ_`y|?FSc9tqYZ0>` zhg0->b8JYoN^A-nmyUQ!ml74rW&L5^etKg}VR@;=^Mn;rL!#d&b@BQEpYUnFc0@7v zaog@c!sR7;@gY&jtxVA|B^s4_57&fC{UJl8cD0#)ZFXR-COYXC>>J_ig(gCR&!}kJ#AbpGk$}8a=T`evh|rfLDoD$bSQ8K?j2%%YiLIEvdV8Vw*iinh zYzRJJT+eFpT;r?EeF-XjK+Y^7=_Y5^MU|bqS1rq?*tVY%T;I_b{M;Ru6YqGi(ftWg^RfN?@1zsW_%_AV%9wkTBxfd? zkf-jVWZHISXj)}(d;-WStd()i|5DZD-dXzuQgA}>O@b-Io2q)mPlMXMGxc-g`7qeU zzELUUO9Gi_wnNjAVyfX)pRVKFJ?Zv3MVJtViHw5S_$Aw7tH^Mf^nh2>{uRnyfJvZP zE682vG2Z&uvkhefy@+}?P8xnrn-eBHR?atLx^nIE_>Hx&j_Co z39j9T1zL#1R=Cr^ZRI8CFi&Ob+!7xL&7zN=2ICd`c>s!VyGFUejxHbo8G-*i;TyaX z0|+wJ?Y#5SvX(#(pJ;u@z{L#<3Ai*N8LPtD4$+-bXXK1~5m9Mvz;GG)LCv6rJz4J| zO}`o}#dqg2=KkvjE!xBgJ8}fPM~*I&$aA4Y4FDt9FWaBsdw|O8^Ms8#LMZ{q#k@p5 zUBQdwtHr;a?2J~zy7`1$4WtntIJKxsu7fr>bqbzC*KSGJx=c$_#cq~*EKYTSPQ7Sr z;U(`t-D%1(*eVRW>j_PVnZsnt?;#?q})ZhnR_Ty#B(xrj?6|K^`t z=OQhDV4IYUi_x^o9{p#G#a`kT3ms2VvCK#`!f3 z1}aS((&s`13Z=i$$xJUaujB$q->m510GA{#Lq7MMBW9kcY_}oqC&&GBt2@Tlt?m-C zwIp1|%7I z#|k)dm|tu`^%AId*@TA)AM@Gt969?WubO(D1yn0D47u9FR9Z*t6VNibMw`gLNZ%$R zGo=<+&y!BnxOh9)bp#20VhDqKsb|6=q!k|Uq6ZH}c5je(M_Un*`Jxp{{32CNPm(%d zr2SxuqIVE-?k8MCA(mm!r`&v+sZ067iZt6F+6IV7;P%X-n)!92n<@wwE&qON{YwNY z{*G=H$21qz)`mvEB7b#$@!|LUI9FMqt+;|b&t>nI5cU_riB>#`cr+{&g!p2Tz6?JS zkqsLj90S725wf_T0Y>jqp`kpq*6D99smGi(oh4(`W%QjM87mOQ65l;4*Qh+j&axHl z2bYhT?}lZ6^KfkZ{Wu_@wHSj2kt-Q7J9(iIhTSw3RERoZF;DqsHvu_p3^K$ZuZPW9 z{mdB@sw~ap5QRP@cgi|?AScp)_C~tKTIrl8lRVH-kq;HHqN&5ERPmWN{W7&0|QomE#lS|z>p6Yajcg89YdT2cq8}Ojv^rVcegbh#}OMFrv@sD~>b%ZOo>p?e?3fa(=7cH*gEXDQ5;G!U}? zX~ntb91~Nj{$0!Zf^JTnE1O#drJCF<=P^b!m&ecaNJ&SuC;PTK^I4^zKmVTF^F@>Q z?OQ^*4gMapNUg?=EDz){$p*L*!bRq2AFJv*%?vr=E$WkB<$k8g80sPzR!I+{nRw$; zkts7+sf}~dLb6cPt&a1Fl54_*F_e0IcO`BfUKS}K<+R{Htsczbl%y=Wl$bDP?eBL} z5hcjWRPpq$ZJ6G`TgNDnWs&!Y7;?&&DL~@J`n-ud{;}^n(~3R2bjvQ+NAm0!5}T4= zSwc7+i*q!lzUecfMV6g&l;m&xB4KJ-X~gJpHS_0P_^n1Ag=@(Ga=i&aO;*Pj{x5?)nPw%DpHvInxekvy&PNpB={i5+rC zeBiLaQy=EA4*^}OIa-Qv(%EqdOs*Fdqv)<)FV|ZQKkcrxz9n^5Tcl|8qC3z9OmmkS z$0K9k14EHQBg-glg=eaFe=^C%hTEIFrG;O(3RHTxCN%r5%nS(J|9C^awYST?t6ZGU z2sJxYW0&lM<{?vV^-!sY=HhVmF&F-~O3PpQ)AByD`;t6Cjcii8m$_O5o2s^zvdiYl z4sD0~S1{a(3yI2#Sydw3Z>(O_lkt2%I;**!#~l&S zUl28TayvBLJ5y7{@?xwdNp$+_q;N*Gbh?{d=t$V&yH2v?Ij=&UneODkK^$$c)gHSY z#+f%)o&Kw9h4M2n|MiZb`B*Y%i$IHzi~e?+(O0$O`G6j%(;=0j?Z480qDd`NG`iR} zC&wU;MQnGhPsY6f^x@WdW8DLeu@Db>-Xb?F;AIW)i^e<{{N$w3Y0PPo zWR^2a;?>=LDdgU?~h-kEVRc!K-P^QP6F$@I@#VU|^ zKZ)qeqcX9yc)K1kjWpI23;CuJ4PmNu2&3#FcCaZU3sw_NdY}?D;m`!^v{F+sY<=@upt;8Kuq7e|&&nB4&H(v*FA3AC955(>KcTGZzbqzEc`Wvn zI)|^_mv}%Fhvvw)O`4m)1q*rI8+Df7UZUooQ}3k*qX(kz^T^_$DPbco-AAY|g=O#p z+#AR+1xI2%acHRX{fxV~xLQb+3O}_~j_ZcElOUDXH$xWx%6J`)sn7CH0etfuU=n>> z=%%+aau<(XR@*896njM6lZI+8s~9Z{eI~}*e2g>U7h{*3iA`>fr#e1jc%nti>xFH- zO0mpFeU*ah-H!v{!&mm=llksW6KSPccJ!)eOd@j_)k5^`7ZI|BK8aBj7Zab5jg|~N zi?XUC=49e2$9}sdRjV-`H#R+<(Hj4goOcFUp732wE2`~hO68?R%-?$kN5e-&H+f&l zi`Qm{_?#pYzTJuZZNH*Ey=3+d)&3ws1s-u|vMu~oU?;&AR{0kj zw;XIs&v@oPT8fHqHf*bL`L|JCAF(`9sHOLbJ?Ybr^L;WVt&f-jHxUWxo?bzW*^Fwr zs7`x696R-wcGXV~@@&@%+8b0i8cKgCEdZoxxx|t+3dK(i%gg~MuFM=0KDJpGZjU`| zps3#PRh^o3n8i>D0^&2R07ghi?5K2qP%MctFBKqb8cIEbaPn1d4#%U`Yw@H=VZ0Z^ zQcEOK{4*Rza7w6iOGEfP>}`1~9sXoT4qIcoCAZi2Zy6&3&XJ>2p(LZ6Q;YtHg`oEV z-xH!@lAY*86MsL(V0ss6EP_K*$c>|8!cniTJfT+a-`sq4aZJt2fFX8c7eRZLS~;pQ zZDYap?C|M{d+si)g02`rIohI)$y{o*6qqQEJ-zPhk3+M7yef{&t0Z0t%4q2lls^2+ z!d-!u=R^Y3>jkmJ-0`_AOImQJ5Fag`Gr6X9tVd(Hb9ZC;yb;MZ9jWqHl`6n!7DRV^ zx|zKy>?--Ei;R-4Fbx{^Y{UzvW@%~S7ehA?VPsE^&M=izTP#KO@jjITeX;G9@w&73 zUpYFsJVCJHzhWkTw<+xQJ2Djc4_RFQ-cuEf4E>DsWA{gF;GlUrA}xmfqwk_2}zkTelp%;9ioJ zAs1F9yc^TR(WjOv->_$>m(Q^OGE=|mYDpL@zuhI-!2oAwTJ_jA@8pAMdu!ZyO(}n0 z{t>p}#&6A+CwpIG2b6h#(PcXi2j#v;hJZ{uNBSqnV5V=}9s!?P4_diu-2FYutRzm~ zYtAkI9^y?rKB|(;>|qX&{*4%6#uBFDl-$oY+K`wKu2bKVoTj3uGv^PlV#2MNGmek@ z5&ALjvd;09H{MwWn0KXP5MMw0Mql9bW2=a-zi~k}j&xaPb=$&t%U=p&Ks4!0s=hK` zu$i{<8yImm*5`Xh_Ks@BOPgh`J+7ONq-=bS@qdzIbMXAUs9kOLe8Ld2JJe$fmE?ti zlnNyd<7fOY13nE%eNwMYHO^h+aI|aGT|VF{`^ETlo%0ALYeTBY=~PY8Tb-b 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

M=w0yEe|?Fd*mnKk6P8fTqdVwX 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

v~H__-_yMsU)PHZJA^CX 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

{s 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 z

J@^5Xp@fafrx)@~YBd)Vsg@(_8 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%0A2N0uraZH%e95U3xx0AU#^wGj?uhT3i8&> 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