From 3ec8e0ac4caccce6312eb2753bcc2f818aedfbb9 Mon Sep 17 00:00:00 2001 From: saskatchewancatch Date: Mon, 1 May 2017 23:15:20 -0600 Subject: [PATCH] 1) Modified cv::ximgproc::niBlackThreshold to support binarization using popular Niblack inspired techniques. Sauvola, Wolf, and NICK support added. 2) Changed niblack_thresholding.cpp sample to allow experimentation with these new methods 3) Added/updated docs where appropriate --- modules/ximgproc/doc/ximgproc.bib | 37 +++++++++++++++++++ modules/ximgproc/include/opencv2/ximgproc.hpp | 27 +++++++++++--- .../ximgproc/samples/niblack_thresholding.cpp | 5 ++- modules/ximgproc/src/niblack_thresholding.cpp | 33 +++++++++++++++-- 4 files changed, 91 insertions(+), 11 deletions(-) diff --git a/modules/ximgproc/doc/ximgproc.bib b/modules/ximgproc/doc/ximgproc.bib index 5fbe8361b..819c8b39e 100644 --- a/modules/ximgproc/doc/ximgproc.bib +++ b/modules/ximgproc/doc/ximgproc.bib @@ -222,3 +222,40 @@ pages={191--196}, year={1997} } + +@book{Niblack1985, + title={An introduction to digital image processing}, + author={Niblack, Wayne}, + year={1985}, + publisher={Strandberg Publishing Company} +} + +@inproceedings{Sauvola1997, + title={Adaptive document binarization}, + author={Sauvola, Jaakko and Seppanen, Tapio and Haapakoski, Sami and Pietikainen, Matti}, + booktitle={Document Analysis and Recognition, 1997., Proceedings of the Fourth International Conference on}, + volume={1}, + pages={147--152}, + year={1997}, + organization={IEEE} +} + +@article{Wolf2004, + title={Extraction and recognition of artificial text in multimedia documents}, + author={Wolf, Christian and Jolion, J-M}, + journal={Pattern Analysis \& Applications}, + volume={6}, + number={4}, + pages={309--326}, + year={2004}, + publisher={Springer} +} + +@inproceedings{Khurshid2009, + title={Comparison of Niblack inspired Binarization methods for ancient documents}, + author={Khurshid, Khurram and Siddiqi, Imran and Faure, Claudie and Vincent, Nicole}, + booktitle={IS\&T/SPIE Electronic Imaging}, + pages={72470U--72470U}, + year={2009}, + organization={International Society for Optics and Photonics} +} diff --git a/modules/ximgproc/include/opencv2/ximgproc.hpp b/modules/ximgproc/include/opencv2/ximgproc.hpp index 1df7fa54a..6ec3d1f2f 100644 --- a/modules/ximgproc/include/opencv2/ximgproc.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc.hpp @@ -79,10 +79,21 @@ enum ThinningTypes{ THINNING_GUOHALL = 1 // Thinning technique of Guo-Hall }; +/** +* @brief Specifies the binarization method to use in cv::ximgproc::niBlackThreshold +*/ +enum LocalBinarizationMethods{ + BINARIZATION_NIBLACK = 0, //!< Classic Niblack binarization. See @cite Niblack1985 . + BINARIZATION_SAUVOLA = 1, //!< Sauvola's technique. See @cite Sauvola1997 . + BINARIZATION_WOLF = 2, //!< Wolf's technique. See @cite Wolf2004 . + BINARIZATION_NICK = 3 //!< NICK technique. See @cite Khurshid2009 . +}; + //! @addtogroup ximgproc //! @{ -/** @brief Applies Niblack thresholding to input image. +/** @brief Performs thresholding on input images using Niblack's technique or some of the +popular variations it inspired. The function transforms a grayscale image to a binary image according to the formulae: - **THRESH_BINARY** @@ -91,8 +102,9 @@ The function transforms a grayscale image to a binary image according to the for \f[dst(x,y) = \fork{0}{if \(src(x,y) > T(x,y)\)}{\texttt{maxValue}}{otherwise}\f] where \f$T(x,y)\f$ is a threshold calculated individually for each pixel. -The threshold value \f$T(x, y)\f$ is the mean minus \f$ delta \f$ times standard deviation -of \f$\texttt{blockSize} \times\texttt{blockSize}\f$ neighborhood of \f$(x, y)\f$. +The threshold value \f$T(x, y)\f$ is determined based on the binarization method chosen. For +classic Niblack, it is the mean minus \f$ k \f$ times standard deviation of +\f$\texttt{blockSize} \times\texttt{blockSize}\f$ neighborhood of \f$(x, y)\f$. The function can't process the image in-place. @@ -103,14 +115,17 @@ used with the THRESH_BINARY and THRESH_BINARY_INV thresholding types. @param type Thresholding type, see cv::ThresholdTypes. @param blockSize Size of a pixel neighborhood that is used to calculate a threshold value for the pixel: 3, 5, 7, and so on. -@param delta Constant multiplied with the standard deviation and subtracted from the mean. -Normally, it is taken to be a real number between 0 and 1. +@param k The user-adjustable parameter used by Niblack and inspired techniques. For Niblack, this is +normally a value between 0 and 1 that is multiplied with the standard deviation and subtracted from +the mean. +@param binarizationMethod Binarization method to use. By default, Niblack's technique is used. +Other techniques can be specified, see cv::ximgproc::LocalBinarizationMethods. @sa threshold, adaptiveThreshold */ CV_EXPORTS_W void niBlackThreshold( InputArray _src, OutputArray _dst, double maxValue, int type, - int blockSize, double delta ); + int blockSize, double k, int binarizationMethod = BINARIZATION_NIBLACK ); /** @brief Applies a binary blob thinning operation, to achieve a skeletization of the input image. diff --git a/modules/ximgproc/samples/niblack_thresholding.cpp b/modules/ximgproc/samples/niblack_thresholding.cpp index 495cfffc9..8dafdf964 100644 --- a/modules/ximgproc/samples/niblack_thresholding.cpp +++ b/modules/ximgproc/samples/niblack_thresholding.cpp @@ -16,6 +16,7 @@ Mat_ src; int k_ = 8; int blockSize_ = 11; int type_ = THRESH_BINARY; +int method_ = BINARIZATION_NIBLACK; void on_trackbar(int, void*); @@ -34,6 +35,7 @@ int main(int argc, char** argv) namedWindow("Niblack", WINDOW_AUTOSIZE); createTrackbar("k", "Niblack", &k_, 20, on_trackbar); createTrackbar("blockSize", "Niblack", &blockSize_, 30, on_trackbar); + createTrackbar("method", "Niblack", &method_, 3, on_trackbar); createTrackbar("threshType", "Niblack", &type_, 4, on_trackbar); on_trackbar(0, 0); waitKey(0); @@ -47,7 +49,8 @@ void on_trackbar(int, void*) int blockSize = 2*(blockSize_ >= 1 ? blockSize_ : 1) + 1; // 3,5,7,...,61 int type = type_; // THRESH_BINARY, THRESH_BINARY_INV, // THRESH_TRUNC, THRESH_TOZERO, THRESH_TOZERO_INV + int method = method_; //BINARIZATION_NIBLACK, BINARIZATION_SAUVOLA, BINARIZATION_WOLF, BINARIZATION_NICK Mat dst; - niBlackThreshold(src, dst, 255, type, blockSize, k); + niBlackThreshold(src, dst, 255, type, blockSize, k, method); imshow("Niblack", dst); } diff --git a/modules/ximgproc/src/niblack_thresholding.cpp b/modules/ximgproc/src/niblack_thresholding.cpp index f2ee962b1..58716f39d 100644 --- a/modules/ximgproc/src/niblack_thresholding.cpp +++ b/modules/ximgproc/src/niblack_thresholding.cpp @@ -47,12 +47,15 @@ namespace cv { namespace ximgproc { void niBlackThreshold( InputArray _src, OutputArray _dst, double maxValue, - int type, int blockSize, double delta ) + int type, int blockSize, double k, int binarizationMethod ) { // Input grayscale image Mat src = _src.getMat(); CV_Assert(src.channels() == 1); CV_Assert(blockSize % 2 == 1 && blockSize > 1); + if (binarizationMethod == BINARIZATION_SAUVOLA) { + CV_Assert(src.depth() == CV_8U); + } type &= THRESH_MASK; // Compute local threshold (T = mean + k * stddev) @@ -61,13 +64,35 @@ void niBlackThreshold( InputArray _src, OutputArray _dst, double maxValue, Mat thresh; { // note that: Var[X] = E[X^2] - E[X]^2 - Mat mean, sqmean, stddev; + Mat mean, sqmean, variance, stddev, sqrtVarianceMeanSum; + double srcMin, stddevMax; boxFilter(src, mean, CV_32F, Size(blockSize, blockSize), Point(-1,-1), true, BORDER_REPLICATE); sqrBoxFilter(src, sqmean, CV_32F, Size(blockSize, blockSize), Point(-1,-1), true, BORDER_REPLICATE); - sqrt(sqmean - mean.mul(mean), stddev); - thresh = mean + stddev * static_cast(delta); + variance = sqmean - mean.mul(mean); + sqrt(variance, stddev); + switch (binarizationMethod) + { + case BINARIZATION_NIBLACK: + thresh = mean + stddev * static_cast(k); + break; + case BINARIZATION_SAUVOLA: + thresh = mean.mul(1. + static_cast(k) * (stddev / 128.0 - 1.)); + break; + case BINARIZATION_WOLF: + minMaxIdx(src, &srcMin); + minMaxIdx(stddev, NULL, &stddevMax); + thresh = mean - static_cast(k) * (mean - srcMin - stddev.mul(mean - srcMin) / stddevMax); + break; + case BINARIZATION_NICK: + sqrt(variance + sqmean, sqrtVarianceMeanSum); + thresh = mean + static_cast(k) * sqrtVarianceMeanSum; + break; + default: + CV_Error( CV_StsBadArg, "Unknown binarization method" ); + break; + } thresh.convertTo(thresh, src.depth()); }