From e3e1ef5c08ef0991874ec2e93724ba61b1d1b081 Mon Sep 17 00:00:00 2001 From: Prateek Ganguli Date: Sun, 5 Mar 2023 12:01:50 +0530 Subject: [PATCH 1/5] Add .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fcebaf1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +binarizewolfjolion +result_*.jpg From e179a2c345649e5f22570a5787abe72c9a59f068 Mon Sep 17 00:00:00 2001 From: Prateek Ganguli Date: Sun, 5 Mar 2023 11:23:09 +0530 Subject: [PATCH 2/5] Tidy up Makefile --- Makefile | 20 +++++++++----------- sample.jpg | Bin 2 files changed, 9 insertions(+), 11 deletions(-) mode change 100755 => 100644 sample.jpg diff --git a/Makefile b/Makefile index 882eabf..e0b7546 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,14 @@ - - all: - g++ -I/usr/include/opencv binarizewolfjolion.cpp -o binarizewolfjolion `pkg-config opencv --libs` -lstdc++ + c++ -O2 -I/usr/include/opencv4 binarizewolfjolion.cpp -o binarizewolfjolion `pkg-config opencv4 --libs` -lstdc++ + +test: all + ./binarizewolfjolion -k '-0.2' -m 'n' sample.jpg result_n.jpg + ./binarizewolfjolion -k '0.6' -m 's' sample.jpg result_s.jpg + ./binarizewolfjolion -k '0.6' -m 'w' sample.jpg result_w.jpg clean: rm -f binarizewolfjolion + rm -f result_*.jpg -test: - ./binarizewolfjolion -k 0.6 sample.jpg _result.jpg - - -package: clean - rm -f x.jpg - tar cvfz binarizewolfjolionopencv.tgz * - +package: clean + tar -cvfz binarizewolfjolionopencv.tar.gz * diff --git a/sample.jpg b/sample.jpg old mode 100755 new mode 100644 From 269bf4ef7b83a176ca8ed514e62cbbf45d203727 Mon Sep 17 00:00:00 2001 From: Prateek Ganguli Date: Sun, 5 Mar 2023 12:05:25 +0530 Subject: [PATCH 3/5] Update sources for opencv4 --- binarizewolfjolion.cpp | 671 ++++++++++++++++++++--------------------- 1 file changed, 320 insertions(+), 351 deletions(-) diff --git a/binarizewolfjolion.cpp b/binarizewolfjolion.cpp index e92d124..47f810c 100644 --- a/binarizewolfjolion.cpp +++ b/binarizewolfjolion.cpp @@ -12,47 +12,41 @@ * Research notebook 24.4.2001, page 132 (Calculation of s) **************************************************************/ -#include -#include #include #include -// #include -// #include #include -using namespace std; -using namespace cv; - -enum NiblackVersion -{ - NIBLACK=0, - SAUVOLA, - WOLFJOLION, +enum NiblackVersion { + NIBLACK = 0, + SAUVOLA, + WOLFJOLION, }; -#define BINARIZEWOLF_VERSION "2.4 (August 1st, 2014)" +#define BINARIZEWOLF_VERSION "2.4 (August 1st, 2014)" -#define uget(x,y) at(y,x) -#define uset(x,y,v) at(y,x)=v; -#define fget(x,y) at(y,x) -#define fset(x,y,v) at(y,x)=v; +#define uget(x, y) at(y, x) +#define uset(x, y, v) at(y, x) = v; +#define fget(x, y) at(y, x) +#define fset(x, y, v) at(y, x) = v; /********************************************************** * Usage **********************************************************/ -static void usage (char *com) { - cerr << "usage: " << com << " [ -x -y -k ] [ version ] \n\n" - << "version: n Niblack (1986) needs white text on black background\n" - << " s Sauvola et al. (1997) needs black text on white background\n" - << " w Wolf et al. (2001) needs black text on white background\n" - << "\n" - << "Default version: w (Wolf et al. 2001)\n" - << "\n" - << "example:\n" - << " " << com << " w in.pgm out.pgm\n" - << " " << com << " in.pgm out.pgm\n" - << " " << com << " s -x 50 -y 50 -k 0.6 in.pgm out.pgm\n"; +static void usage(char *com) { + std::cerr << "usage: " << com + << " [ -x -y -k ] [ version ] \n" + << "\n" + << "version: n Niblack (1986) needs white text on black background\n" + << " s Sauvola et al. (1997) needs black text on white background\n" + << " w Wolf et al. (2001) needs black text on white background\n" + << "\n" + << "Default version: w (Wolf et al. 2001)\n" + << "\n" + << "example:\n" + << " " << com << " w in.pgm out.pgm\n" + << " " << com << " in.pgm out.pgm\n" + << " " << com << " s -x 50 -y 50 -k 0.6 in.pgm out.pgm\n"; } // ************************************************************* @@ -62,351 +56,326 @@ static void usage (char *com) { // // Version patched by Thibault Yohan (using opencv integral images) +double calcLocalStats(cv::Mat &im, cv::Mat &map_m, cv::Mat &map_s, int winx, int winy) { + cv::Mat im_sum + cv::Mat im_sum_sq; + + cv::integral(im, im_sum, im_sum_sq, CV_64F); + + double m, s, max_s, sum, sum_sq; + int wxh = winx / 2; + int wyh = winy / 2; + int x_firstth = wxh; + int y_firstth = wyh; + int y_lastth = im.rows - wyh - 1; + double winarea = winx * winy; + + max_s = 0; + for (int j = y_firstth; j <= y_lastth; j++) { + sum = sum_sq = 0; + + // for sum array iterator pointer + double *sum_top_left = im_sum.ptr(j - wyh); + double *sum_top_right = sum_top_left + winx; + double *sum_bottom_left = im_sum.ptr(j - wyh + winy); + double *sum_bottom_right = sum_bottom_left + winx; + + // for sum_sq array iterator pointer + double *sum_eq_top_left = im_sum_sq.ptr(j - wyh); + double *sum_eq_top_right = sum_eq_top_left + winx; + double *sum_eq_bottom_left = im_sum_sq.ptr(j - wyh + winy); + double *sum_eq_bottom_right = sum_eq_bottom_left + winx; + + sum = (*sum_bottom_right + *sum_top_left) - + (*sum_top_right + *sum_bottom_left); + sum_sq = (*sum_eq_bottom_right + *sum_eq_top_left) - + (*sum_eq_top_right + *sum_eq_bottom_left); + + m = sum / winarea; + s = sqrt((sum_sq - m * sum) / winarea); + if (s > max_s) + max_s = s; + + float *map_m_data = map_m.ptr(j) + x_firstth; + float *map_s_data = map_s.ptr(j) + x_firstth; + *map_m_data++ = m; + *map_s_data++ = s; + + // Shift the window, add and remove new/old values to the histogram + for (int i = 1; i <= im.cols - winx; i++) { + sum_top_left++, sum_top_right++, sum_bottom_left++, sum_bottom_right++; + + sum_eq_top_left++, sum_eq_top_right++, sum_eq_bottom_left++, + sum_eq_bottom_right++; + + sum = (*sum_bottom_right + *sum_top_left) - + (*sum_top_right + *sum_bottom_left); + sum_sq = (*sum_eq_bottom_right + *sum_eq_top_left) - + (*sum_eq_top_right + *sum_eq_bottom_left); + + m = sum / winarea; + s = sqrt((sum_sq - m * sum) / winarea); + if (s > max_s) + max_s = s; + + *map_m_data++ = m; + *map_s_data++ = s; + } + } -double calcLocalStats (Mat &im, Mat &map_m, Mat &map_s, int winx, int winy) { - Mat im_sum, im_sum_sq; - cv::integral(im,im_sum,im_sum_sq,CV_64F); - - double m,s,max_s,sum,sum_sq; - int wxh = winx/2; - int wyh = winy/2; - int x_firstth= wxh; - int y_firstth= wyh; - int y_lastth = im.rows-wyh-1; - double winarea = winx*winy; - - max_s = 0; - for (int j = y_firstth ; j<=y_lastth; j++){ - sum = sum_sq = 0; + return max_s; +} - // for sum array iterator pointer - double *sum_top_left = im_sum.ptr(j - wyh); - double *sum_top_right = sum_top_left + winx; - double *sum_bottom_left = im_sum.ptr(j - wyh + winy); - double *sum_bottom_right = sum_bottom_left + winx; +/********************************************************** + * The binarization routine + **********************************************************/ - // for sum_sq array iterator pointer - double *sum_eq_top_left = im_sum_sq.ptr(j - wyh); - double *sum_eq_top_right = sum_eq_top_left + winx; - double *sum_eq_bottom_left = im_sum_sq.ptr(j - wyh + winy); - double *sum_eq_bottom_right = sum_eq_bottom_left + winx; +void NiblackSauvolaWolfJolion(cv::Mat im, cv::Mat output, NiblackVersion version, + int winx, int winy, double k, double dR) { - sum = (*sum_bottom_right + *sum_top_left) - (*sum_top_right + *sum_bottom_left); - sum_sq = (*sum_eq_bottom_right + *sum_eq_top_left) - (*sum_eq_top_right + *sum_eq_bottom_left); + double m, s, max_s; + double th = 0; + double min_I, max_I; + int wxh = winx / 2; + int wyh = winy / 2; + int x_firstth = wxh; + int x_lastth = im.cols - wxh - 1; + int y_lastth = im.rows - wyh - 1; + int y_firstth = wyh; + // int mx, my; - m = sum / winarea; - s = sqrt ((sum_sq - m*sum)/winarea); - if (s > max_s) max_s = s; + // Create local statistics and store them in a double matrices + cv::Mat map_m = cv::Mat::zeros(im.rows, im.cols, CV_32F); + cv::Mat map_s = cv::Mat::zeros(im.rows, im.cols, CV_32F); + max_s = calcLocalStats(im, map_m, map_s, winx, winy); - float *map_m_data = map_m.ptr(j) + x_firstth; - float *map_s_data = map_s.ptr(j) + x_firstth; - *map_m_data++ = m; - *map_s_data++ = s; + minMaxLoc(im, &min_I, &max_I); - // Shift the window, add and remove new/old values to the histogram - for (int i=1 ; i <= im.cols-winx; i++) { - sum_top_left++, sum_top_right++, sum_bottom_left++, sum_bottom_right++; + cv::Mat thsurf(im.rows, im.cols, CV_32F); - sum_eq_top_left++, sum_eq_top_right++, sum_eq_bottom_left++, sum_eq_bottom_right++; + // Create the threshold surface, including border processing + // ---------------------------------------------------- + for (int j = y_firstth; j <= y_lastth; j++) { - sum = (*sum_bottom_right + *sum_top_left) - (*sum_top_right + *sum_bottom_left); - sum_sq = (*sum_eq_bottom_right + *sum_eq_top_left) - (*sum_eq_top_right + *sum_eq_bottom_left); + float *th_surf_data = thsurf.ptr(j) + wxh; + float *map_m_data = map_m.ptr(j) + wxh; + float *map_s_data = map_s.ptr(j) + wxh; - m = sum / winarea; - s = sqrt ((sum_sq - m*sum)/winarea); - if (s > max_s) max_s = s; + // NORMAL, NON-BORDER AREA IN THE MIDDLE OF THE WINDOW: + for (int i = 0; i <= im.cols - winx; i++) { + m = *map_m_data++; + s = *map_s_data++; - *map_m_data++ = m; - *map_s_data++ = s; - } - } + // Calculate the threshold + switch (version) { - return max_s; -} + case NIBLACK: + th = m + k * s; + break; + case SAUVOLA: + th = m * (1 + k * (s / dR - 1)); + break; + case WOLFJOLION: + th = m + k * (s / max_s - 1) * (m - min_I); + break; -/********************************************************** - * The binarization routine - **********************************************************/ + default: + std::cerr << "Unknown threshold type in ImageThresholder::surfaceNiblackImproved()\n"; + exit(1); + } + + // thsurf.fset(i+wxh,j,th); + *th_surf_data++ = th; + + if (i == 0) { + // LEFT BORDER + float *th_surf_ptr = thsurf.ptr(j); + for (int i = 0; i <= x_firstth; ++i) + *th_surf_ptr++ = th; + + // LEFT-UPPER CORNER + if (j == y_firstth) { + for (int u = 0; u < y_firstth; ++u) { + float *th_surf_ptr = thsurf.ptr(u); + for (int i = 0; i <= x_firstth; ++i) + *th_surf_ptr++ = th; + } + } + + // LEFT-LOWER CORNER + if (j == y_lastth) { + for (int u = y_lastth + 1; u < im.rows; ++u) { + float *th_surf_ptr = thsurf.ptr(u); + for (int i = 0; i <= x_firstth; ++i) + *th_surf_ptr++ = th; + } + } + } + + // UPPER BORDER + if (j == y_firstth) + for (int u = 0; u < y_firstth; ++u) + thsurf.fset(i + wxh, u, th); + + // LOWER BORDER + if (j == y_lastth) + for (int u = y_lastth + 1; u < im.rows; ++u) + thsurf.fset(i + wxh, u, th); + } + // RIGHT BORDER + float *th_surf_ptr = thsurf.ptr(j) + x_lastth; + for (int i = x_lastth; i < im.cols; ++i) + // thsurf.fset(i,j,th); + *th_surf_ptr++ = th; + + // RIGHT-UPPER CORNER + if (j == y_firstth) { + for (int u = 0; u < y_firstth; ++u) { + float *th_surf_ptr = thsurf.ptr(u) + x_lastth; + for (int i = x_lastth; i < im.cols; ++i) + *th_surf_ptr++ = th; + } + } -void NiblackSauvolaWolfJolion (Mat im, Mat output, NiblackVersion version, - int winx, int winy, double k, double dR) { - - - double m, s, max_s; - double th=0; - double min_I, max_I; - int wxh = winx/2; - int wyh = winy/2; - int x_firstth= wxh; - int x_lastth = im.cols-wxh-1; - int y_lastth = im.rows-wyh-1; - int y_firstth= wyh; - // int mx, my; - - // Create local statistics and store them in a double matrices - Mat map_m = Mat::zeros (im.rows, im.cols, CV_32F); - Mat map_s = Mat::zeros (im.rows, im.cols, CV_32F); - max_s = calcLocalStats (im, map_m, map_s, winx, winy); - - minMaxLoc(im, &min_I, &max_I); - - Mat thsurf (im.rows, im.cols, CV_32F); - - // Create the threshold surface, including border processing - // ---------------------------------------------------- - for (int j = y_firstth ; j<=y_lastth; j++) { - - float *th_surf_data = thsurf.ptr(j) + wxh; - float *map_m_data = map_m.ptr(j) + wxh; - float *map_s_data = map_s.ptr(j) + wxh; - - // NORMAL, NON-BORDER AREA IN THE MIDDLE OF THE WINDOW: - for (int i=0 ; i <= im.cols-winx; i++) { - m = *map_m_data++; - s = *map_s_data++; - - // Calculate the threshold - switch (version) { - - case NIBLACK: - th = m + k*s; - break; - - case SAUVOLA: - th = m * (1 + k*(s/dR-1)); - break; - - case WOLFJOLION: - th = m + k * (s/max_s-1) * (m-min_I); - break; - - default: - cerr << "Unknown threshold type in ImageThresholder::surfaceNiblackImproved()\n"; - exit (1); - } - - // thsurf.fset(i+wxh,j,th); - *th_surf_data++ = th; - - - if (i==0) { - // LEFT BORDER - float *th_surf_ptr = thsurf.ptr(j); - for (int i=0; i<=x_firstth; ++i) - *th_surf_ptr++ = th; - - // LEFT-UPPER CORNER - if (j==y_firstth) - { - for (int u=0; u(u); - for (int i=0; i<=x_firstth; ++i) - *th_surf_ptr++ = th; - } - - } - - // LEFT-LOWER CORNER - if (j==y_lastth) - { - for (int u=y_lastth+1; u(u); - for (int i=0; i<=x_firstth; ++i) - *th_surf_ptr++ = th; - } - } - } - - // UPPER BORDER - if (j==y_firstth) - for (int u=0; u(j) + x_lastth; - for (int i=x_lastth; i(u) + x_lastth; - for (int i=x_lastth; i(u) + x_lastth; - for (int i=x_lastth; i(y); - float *th_surf_data = thsurf.ptr(y); - unsigned char *output_data = output.ptr(y); - for (int x=0; x= *th_surf_data ? 255 : 0; - im_data++; - th_surf_data++; - output_data++; - } - } + // RIGHT-LOWER CORNER + if (j == y_lastth) { + for (int u = y_lastth + 1; u < im.rows; ++u) { + float *th_surf_ptr = thsurf.ptr(u) + x_lastth; + for (int i = x_lastth; i < im.cols; ++i) + *th_surf_ptr++ = th; + } + } + } + std::cerr << "surface created\n"; + + for (int y = 0; y < im.rows; ++y) { + unsigned char *im_data = im.ptr(y); + float *th_surf_data = thsurf.ptr(y); + unsigned char *output_data = output.ptr(y); + for (int x = 0; x < im.cols; ++x) { + *output_data = *im_data >= *th_surf_data ? 255 : 0; + im_data++; + th_surf_data++; + output_data++; + } + } } /********************************************************** * The main function **********************************************************/ -int main (int argc, char **argv) -{ - char version; - int c; - int winx=0, winy=0; - float optK=0.5; - bool didSpecifyK=false; - NiblackVersion versionCode; - char *inputname, *outputname, *versionstring; - - cerr << "===========================================================\n" - << "Christian Wolf, LIRIS Laboratory, Lyon, France.\n" - << "christian.wolf@liris.cnrs.fr\n" - << "Version " << BINARIZEWOLF_VERSION << endl - << "===========================================================\n"; - - // Argument processing - while ((c = getopt (argc, argv, "x:y:k:")) != EOF) { - - switch (c) { - - case 'x': - winx = atof(optarg); - break; - - case 'y': - winy = atof(optarg); - break; - - case 'k': - optK = atof(optarg); - didSpecifyK = true; - break; - - case '?': - usage (*argv); - cerr << "\nProblem parsing the options!\n\n"; - exit (1); - } - } - - switch(argc-optind) - { - case 3: - versionstring=argv[optind]; - inputname=argv[optind+1]; - outputname=argv[optind+2]; - break; - - case 2: - versionstring=(char *) "w"; - inputname=argv[optind]; - outputname=argv[optind+1]; - break; - - default: - usage (*argv); - exit (1); - } - - cerr << "Adaptive binarization\n" - << "Threshold calculation: "; - - // Determine the method - version = versionstring[0]; - switch (version) - { - case 'n': - versionCode = NIBLACK; - cerr << "Niblack (1986)\n"; - break; - - case 's': - versionCode = SAUVOLA; - cerr << "Sauvola et al. (1997)\n"; - break; - - case 'w': - versionCode = WOLFJOLION; - cerr << "Wolf and Jolion (2001)\n"; - break; - - default: - usage (*argv); - cerr << "\nInvalid version: '" << version << "'!"; - } - - - cerr << "parameter k=" << optK << endl; - - if (!didSpecifyK) - cerr << "Setting k to default value " << optK << endl; - - - // Load the image in grayscale mode - Mat input = imread(inputname,CV_LOAD_IMAGE_GRAYSCALE); - - - if ((input.rows<=0) || (input.cols<=0)) { - cerr << "*** ERROR: Couldn't read input image " << inputname << endl; +int main(int argc, char **argv) { + char version; + int c; + int winx = 0, winy = 0; + float optK = 0.5; + bool didSpecifyK = false; + NiblackVersion versionCode; + char *inputname, *outputname, *versionstring; + + std::cerr << "===========================================================\n" + << "Christian Wolf, LIRIS Laboratory, Lyon, France.\n" + << "christian.wolf@liris.cnrs.fr\n" + << "Version " << BINARIZEWOLF_VERSION << std::endl + << "===========================================================\n"; + + // Argument processing + while ((c = getopt(argc, argv, "x:y:k:")) != EOF) { + switch (c) { + case 'x': + winx = atof(optarg); + break; + case 'y': + winy = atof(optarg); + break; + case 'k': + optK = atof(optarg); + didSpecifyK = true; + break; + case '?': + usage(*argv); + std::cerr << "\nProblem parsing the options!\n\n"; exit(1); } - - - // Treat the window size - if (winx==0||winy==0) { - cerr << "Input size: " << input.cols << "x" << input.rows << endl; - winy = (int) (2.0 * input.rows-1)/3; - winx = (int) input.cols-1 < winy ? input.cols-1 : winy; - // if the window is too big, than we asume that the image - // is not a single text box, but a document page: set - // the window size to a fixed constant. - if (winx > 100) - winx = winy = 40; - cerr << "Setting window size to [" << winx - << "," << winy << "].\n"; - } - - // Threshold - Mat output (input.rows, input.cols, CV_8U); - NiblackSauvolaWolfJolion (input, output, versionCode, winx, winy, optK, 128); - - // Write the tresholded file - cerr << "Writing binarized image to file '" << outputname << "'.\n"; - imwrite (outputname, output); - - return 0; + } + + switch (argc - optind) { + case 3: + versionstring = argv[optind]; + inputname = argv[optind + 1]; + outputname = argv[optind + 2]; + break; + case 2: + versionstring = (char *)"w"; + inputname = argv[optind]; + outputname = argv[optind + 1]; + break; + default: + usage(*argv); + exit(1); + } + + std::cerr << "Adaptive binarization\n" + << "Threshold calculation: "; + + // Determine the method + version = versionstring[0]; + switch (version) { + case 'n': + versionCode = NIBLACK; + std::cerr << "Niblack (1986)\n"; + break; + case 's': + versionCode = SAUVOLA; + std::cerr << "Sauvola et al. (1997)\n"; + break; + case 'w': + versionCode = WOLFJOLION; + std::cerr << "Wolf and Jolion (2001)\n"; + break; + default: + usage(*argv); + std::cerr << "\nInvalid version: '" << version << "'!"; + } + + std::cerr << "parameter k=" << optK << std::endl; + + if (!didSpecifyK) { + std::cerr << "Setting k to default value " << optK << std::endl; + } + + // Load the image in grayscale mode + cv::Mat input = imread(inputname, cv::ImreadModes::IMREAD_GRAYSCALE); + + if ((input.rows <= 0) || (input.cols <= 0)) { + std::cerr << "*** ERROR: Couldn't read input image " << inputname << std::endl; + exit(1); + } + + // Treat the window size + if (winx == 0 || winy == 0) { + std::cerr << "Input size: " << input.cols << "x" << input.rows << std::endl; + winy = (int)(2.0 * input.rows - 1) / 3; + winx = (int)input.cols - 1 < winy ? input.cols - 1 : winy; + // if the window is too big, than we asume that the image + // is not a single text box, but a document page: set + // the window size to a fixed constant. + if (winx > 100) + winx = winy = 40; + std::cerr << "Setting window size to [" << winx << "," << winy << "].\n"; + } + + // Threshold + cv::Mat output(input.rows, input.cols, CV_8U); + NiblackSauvolaWolfJolion(input, output, versionCode, winx, winy, optK, 128); + + // Write the tresholded file + std::cerr << "Writing binarized image to file '" << outputname << "'.\n"; + imwrite(outputname, output); + + return 0; } From faa0d9776f1c67e8331a4017653eb97572bd0904 Mon Sep 17 00:00:00 2001 From: Prateek Ganguli Date: Sun, 5 Mar 2023 14:50:45 +0530 Subject: [PATCH 4/5] Improve code quality --- binarizewolfjolion.cpp | 243 ++++++++++++++++++++--------------------- 1 file changed, 117 insertions(+), 126 deletions(-) diff --git a/binarizewolfjolion.cpp b/binarizewolfjolion.cpp index 47f810c..aa08e2f 100644 --- a/binarizewolfjolion.cpp +++ b/binarizewolfjolion.cpp @@ -12,57 +12,53 @@ * Research notebook 24.4.2001, page 132 (Calculation of s) **************************************************************/ +#include #include #include #include -enum NiblackVersion { +#define BINARIZEWOLF_VERSION "2.4 (August 1st, 2014)" +#define DEFAULT_K 0.5 +#define MAX_WINX 100 +#define DEFAULT_WINX 40 +#define DEFAULT_WINY 40 + +enum ThreshMethod { NIBLACK = 0, SAUVOLA, WOLFJOLION, }; -#define BINARIZEWOLF_VERSION "2.4 (August 1st, 2014)" - -#define uget(x, y) at(y, x) -#define uset(x, y, v) at(y, x) = v; -#define fget(x, y) at(y, x) -#define fset(x, y, v) at(y, x) = v; - /********************************************************** * Usage **********************************************************/ static void usage(char *com) { - std::cerr << "usage: " << com - << " [ -x -y -k ] [ version ] \n" - << "\n" - << "version: n Niblack (1986) needs white text on black background\n" - << " s Sauvola et al. (1997) needs black text on white background\n" - << " w Wolf et al. (2001) needs black text on white background\n" + std::cerr << "Usage: " << com + << " [ -x -y -k -m ] \n" << "\n" - << "Default version: w (Wolf et al. 2001)\n" + << "method: n Niblack (1986) needs white text on black background\n" + << " s Sauvola et al. (1997) needs black text on white background\n" + << " w (deafult) Wolf et al. (2001) needs black text on white background\n" << "\n" - << "example:\n" - << " " << com << " w in.pgm out.pgm\n" + << "Example:\n" + << " " << com << " -m w in.pgm out.pgm\n" << " " << com << " in.pgm out.pgm\n" - << " " << com << " s -x 50 -y 50 -k 0.6 in.pgm out.pgm\n"; + << " " << com << " -m s -x 50 -y 50 -k 0.6 in.pgm out.pgm\n"; } // ************************************************************* // glide a window across the image and -// ************************************************************* // create two maps: mean and standard deviation. +// ************************************************************* // // Version patched by Thibault Yohan (using opencv integral images) double calcLocalStats(cv::Mat &im, cv::Mat &map_m, cv::Mat &map_s, int winx, int winy) { - cv::Mat im_sum + cv::Mat im_sum; cv::Mat im_sum_sq; - cv::integral(im, im_sum, im_sum_sq, CV_64F); - double m, s, max_s, sum, sum_sq; int wxh = winx / 2; int wyh = winy / 2; int x_firstth = wxh; @@ -70,31 +66,30 @@ double calcLocalStats(cv::Mat &im, cv::Mat &map_m, cv::Mat &map_s, int winx, int int y_lastth = im.rows - wyh - 1; double winarea = winx * winy; - max_s = 0; + double max_s = 0; for (int j = y_firstth; j <= y_lastth; j++) { - sum = sum_sq = 0; - // for sum array iterator pointer - double *sum_top_left = im_sum.ptr(j - wyh); - double *sum_top_right = sum_top_left + winx; - double *sum_bottom_left = im_sum.ptr(j - wyh + winy); + double *sum_top_left = im_sum.ptr(j - wyh); + double *sum_top_right = sum_top_left + winx; + double *sum_bottom_left = im_sum.ptr(j - wyh + winy); double *sum_bottom_right = sum_bottom_left + winx; // for sum_sq array iterator pointer - double *sum_eq_top_left = im_sum_sq.ptr(j - wyh); - double *sum_eq_top_right = sum_eq_top_left + winx; - double *sum_eq_bottom_left = im_sum_sq.ptr(j - wyh + winy); + double *sum_eq_top_left = im_sum_sq.ptr(j - wyh); + double *sum_eq_top_right = sum_eq_top_left + winx; + double *sum_eq_bottom_left = im_sum_sq.ptr(j - wyh + winy); double *sum_eq_bottom_right = sum_eq_bottom_left + winx; - sum = (*sum_bottom_right + *sum_top_left) - - (*sum_top_right + *sum_bottom_left); - sum_sq = (*sum_eq_bottom_right + *sum_eq_top_left) - - (*sum_eq_top_right + *sum_eq_bottom_left); + double sum = (*sum_bottom_right + *sum_top_left) - + (*sum_top_right + *sum_bottom_left); + double sum_sq = (*sum_eq_bottom_right + *sum_eq_top_left) - + (*sum_eq_top_right + *sum_eq_bottom_left); - m = sum / winarea; - s = sqrt((sum_sq - m * sum) / winarea); - if (s > max_s) + double m = sum / winarea; + double s = std::sqrt((sum_sq - m * sum) / winarea); + if (s > max_s) { max_s = s; + } float *map_m_data = map_m.ptr(j) + x_firstth; float *map_s_data = map_s.ptr(j) + x_firstth; @@ -103,20 +98,26 @@ double calcLocalStats(cv::Mat &im, cv::Mat &map_m, cv::Mat &map_s, int winx, int // Shift the window, add and remove new/old values to the histogram for (int i = 1; i <= im.cols - winx; i++) { - sum_top_left++, sum_top_right++, sum_bottom_left++, sum_bottom_right++; - - sum_eq_top_left++, sum_eq_top_right++, sum_eq_bottom_left++, - sum_eq_bottom_right++; - - sum = (*sum_bottom_right + *sum_top_left) - - (*sum_top_right + *sum_bottom_left); + sum_top_left++; + sum_top_right++; + sum_bottom_left++; + sum_bottom_right++; + + sum_eq_top_left++; + sum_eq_top_right++; + sum_eq_bottom_left++; + sum_eq_bottom_right++; + + sum = (*sum_bottom_right + *sum_top_left) - + (*sum_top_right + *sum_bottom_left); sum_sq = (*sum_eq_bottom_right + *sum_eq_top_left) - - (*sum_eq_top_right + *sum_eq_bottom_left); + (*sum_eq_top_right + *sum_eq_bottom_left); m = sum / winarea; - s = sqrt((sum_sq - m * sum) / winarea); - if (s > max_s) + s = std::sqrt((sum_sq - m * sum) / winarea); + if (s > max_s) { max_s = s; + } *map_m_data++ = m; *map_s_data++ = s; @@ -130,44 +131,44 @@ double calcLocalStats(cv::Mat &im, cv::Mat &map_m, cv::Mat &map_s, int winx, int * The binarization routine **********************************************************/ -void NiblackSauvolaWolfJolion(cv::Mat im, cv::Mat output, NiblackVersion version, - int winx, int winy, double k, double dR) { +void NiblackSauvolaWolfJolion(cv::Mat im, cv::Mat output, ThreshMethod method, + int winx, int winy, double k, double dR = 128) { - double m, s, max_s; - double th = 0; - double min_I, max_I; int wxh = winx / 2; int wyh = winy / 2; int x_firstth = wxh; + int y_firstth = wyh; int x_lastth = im.cols - wxh - 1; int y_lastth = im.rows - wyh - 1; - int y_firstth = wyh; - // int mx, my; // Create local statistics and store them in a double matrices cv::Mat map_m = cv::Mat::zeros(im.rows, im.cols, CV_32F); cv::Mat map_s = cv::Mat::zeros(im.rows, im.cols, CV_32F); - max_s = calcLocalStats(im, map_m, map_s, winx, winy); + double max_s = calcLocalStats(im, map_m, map_s, winx, winy); - minMaxLoc(im, &min_I, &max_I); + double min_I; + double max_I; + cv::minMaxLoc(im, &min_I, &max_I); cv::Mat thsurf(im.rows, im.cols, CV_32F); + double th = 0.0; + // Create the threshold surface, including border processing // ---------------------------------------------------- for (int j = y_firstth; j <= y_lastth; j++) { - float *th_surf_data = thsurf.ptr(j) + wxh; - float *map_m_data = map_m.ptr(j) + wxh; - float *map_s_data = map_s.ptr(j) + wxh; + float *th_surf_ptr = thsurf.ptr(j) + wxh; + float *map_m_ptr = map_m.ptr(j) + wxh; + float *map_s_ptr = map_s.ptr(j) + wxh; // NORMAL, NON-BORDER AREA IN THE MIDDLE OF THE WINDOW: for (int i = 0; i <= im.cols - winx; i++) { - m = *map_m_data++; - s = *map_s_data++; + double m = *map_m_ptr++; + double s = *map_s_ptr++; // Calculate the threshold - switch (version) { + switch (method) { case NIBLACK: th = m + k * s; @@ -182,12 +183,11 @@ void NiblackSauvolaWolfJolion(cv::Mat im, cv::Mat output, NiblackVersion version break; default: - std::cerr << "Unknown threshold type in ImageThresholder::surfaceNiblackImproved()\n"; - exit(1); + std::cerr << "Unknown thresholding method: " << method << std::endl; + std::exit(EXIT_FAILURE); } - // thsurf.fset(i+wxh,j,th); - *th_surf_data++ = th; + *th_surf_ptr++ = th; if (i == 0) { // LEFT BORDER @@ -214,27 +214,26 @@ void NiblackSauvolaWolfJolion(cv::Mat im, cv::Mat output, NiblackVersion version } } - // UPPER BORDER + // LEFT-UPPER BORDER if (j == y_firstth) for (int u = 0; u < y_firstth; ++u) - thsurf.fset(i + wxh, u, th); + thsurf.at(u, i+wxh) = th; - // LOWER BORDER + // LEFT-LOWER BORDER if (j == y_lastth) for (int u = y_lastth + 1; u < im.rows; ++u) - thsurf.fset(i + wxh, u, th); + thsurf.at(u, i+wxh) = th; } // RIGHT BORDER - float *th_surf_ptr = thsurf.ptr(j) + x_lastth; + th_surf_ptr = thsurf.ptr(j) + x_lastth; for (int i = x_lastth; i < im.cols; ++i) - // thsurf.fset(i,j,th); *th_surf_ptr++ = th; // RIGHT-UPPER CORNER if (j == y_firstth) { for (int u = 0; u < y_firstth; ++u) { - float *th_surf_ptr = thsurf.ptr(u) + x_lastth; + th_surf_ptr = thsurf.ptr(u) + x_lastth; for (int i = x_lastth; i < im.cols; ++i) *th_surf_ptr++ = th; } @@ -243,7 +242,7 @@ void NiblackSauvolaWolfJolion(cv::Mat im, cv::Mat output, NiblackVersion version // RIGHT-LOWER CORNER if (j == y_lastth) { for (int u = y_lastth + 1; u < im.rows; ++u) { - float *th_surf_ptr = thsurf.ptr(u) + x_lastth; + th_surf_ptr = thsurf.ptr(u) + x_lastth; for (int i = x_lastth; i < im.cols; ++i) *th_surf_ptr++ = th; } @@ -264,18 +263,35 @@ void NiblackSauvolaWolfJolion(cv::Mat im, cv::Mat output, NiblackVersion version } } +ThreshMethod parseMethodString(char *methodString) { + char method = methodString[0]; + switch (method) { + case 'n': + std::cerr << "Niblack (1986)\n"; + return NIBLACK; + case 's': + std::cerr << "Sauvola et al. (1997)\n"; + return SAUVOLA; + case 'w': + std::cerr << "Wolf and Jolion (2001)\n"; + return WOLFJOLION; + default: + std::cerr << "\nInvalid method: '" << method << "'!"; + std::exit(EXIT_FAILURE); + } +} + /********************************************************** * The main function **********************************************************/ int main(int argc, char **argv) { - char version; - int c; - int winx = 0, winy = 0; - float optK = 0.5; - bool didSpecifyK = false; - NiblackVersion versionCode; - char *inputname, *outputname, *versionstring; + int winx = 0; + int winy = 0; + float optK = DEFAULT_K; + ThreshMethod methodCode = WOLFJOLION; + char *inputname; + char *outputname; std::cerr << "===========================================================\n" << "Christian Wolf, LIRIS Laboratory, Lyon, France.\n" @@ -284,98 +300,73 @@ int main(int argc, char **argv) { << "===========================================================\n"; // Argument processing - while ((c = getopt(argc, argv, "x:y:k:")) != EOF) { + char c; + while ((c = getopt(argc, argv, "x:y:k:m:")) != EOF) { switch (c) { case 'x': - winx = atof(optarg); + winx = std::atoi(optarg); break; case 'y': - winy = atof(optarg); + winy = std::atoi(optarg); break; case 'k': - optK = atof(optarg); - didSpecifyK = true; + optK = std::atof(optarg); + break; + case 'm': + methodCode = parseMethodString(optarg); break; case '?': usage(*argv); std::cerr << "\nProblem parsing the options!\n\n"; - exit(1); + std::exit(EXIT_FAILURE); } } switch (argc - optind) { - case 3: - versionstring = argv[optind]; - inputname = argv[optind + 1]; - outputname = argv[optind + 2]; - break; case 2: - versionstring = (char *)"w"; inputname = argv[optind]; outputname = argv[optind + 1]; break; default: usage(*argv); - exit(1); + std::exit(EXIT_FAILURE); } std::cerr << "Adaptive binarization\n" << "Threshold calculation: "; - // Determine the method - version = versionstring[0]; - switch (version) { - case 'n': - versionCode = NIBLACK; - std::cerr << "Niblack (1986)\n"; - break; - case 's': - versionCode = SAUVOLA; - std::cerr << "Sauvola et al. (1997)\n"; - break; - case 'w': - versionCode = WOLFJOLION; - std::cerr << "Wolf and Jolion (2001)\n"; - break; - default: - usage(*argv); - std::cerr << "\nInvalid version: '" << version << "'!"; - } - std::cerr << "parameter k=" << optK << std::endl; - if (!didSpecifyK) { - std::cerr << "Setting k to default value " << optK << std::endl; - } - // Load the image in grayscale mode cv::Mat input = imread(inputname, cv::ImreadModes::IMREAD_GRAYSCALE); if ((input.rows <= 0) || (input.cols <= 0)) { std::cerr << "*** ERROR: Couldn't read input image " << inputname << std::endl; - exit(1); + std::exit(EXIT_FAILURE); } // Treat the window size - if (winx == 0 || winy == 0) { + if (winx <= 0 || winy <= 0) { std::cerr << "Input size: " << input.cols << "x" << input.rows << std::endl; winy = (int)(2.0 * input.rows - 1) / 3; winx = (int)input.cols - 1 < winy ? input.cols - 1 : winy; - // if the window is too big, than we asume that the image + // if the window is too big, then we asume that the image // is not a single text box, but a document page: set // the window size to a fixed constant. - if (winx > 100) - winx = winy = 40; + if (winx > MAX_WINX) { + winx = DEFAULT_WINX; + winy = DEFAULT_WINY; + } std::cerr << "Setting window size to [" << winx << "," << winy << "].\n"; } // Threshold cv::Mat output(input.rows, input.cols, CV_8U); - NiblackSauvolaWolfJolion(input, output, versionCode, winx, winy, optK, 128); + NiblackSauvolaWolfJolion(input, output, methodCode, winx, winy, optK); // Write the tresholded file std::cerr << "Writing binarized image to file '" << outputname << "'.\n"; imwrite(outputname, output); - return 0; + return EXIT_SUCCESS; } From 5632c1b44e9247bad52865ce7375d14f90581103 Mon Sep 17 00:00:00 2001 From: Prateek Ganguli Date: Sun, 5 Mar 2023 15:21:34 +0530 Subject: [PATCH 5/5] Update README.md --- README.md | 21 ++++++++------------- binarizewolfjolion.cpp | 4 ++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index daf7c43..e3b4cd5 100644 --- a/README.md +++ b/README.md @@ -14,22 +14,17 @@ This code uses an improved contrast maximization version of Niblack/Sauvola et a ## Usage: -The executable is called on the command line and only reads and writes PGM files. Under Linux you can use for instance "convert" (part of the ImageMagick package) to convert to and from the PGM format. The first argument chooses between one of several methods, the first and the second argument specify, respectively, the input and the output file: - ``` -usage: binarize [ -x -y -k ] [ version ] - -version: n Niblack (1986) needs white text on black background - s Sauvola et al. (1997) needs black text on white background - w Wolf et al. (2001) needs black text on white background +Usage: ./binarizewolfjolion [ -x -y -k -m ] -Default version: w (Wolf et al. 2001) -Default value for "k": 0.5 +method: n Niblack (1986) needs white text on black background + s Sauvola et al. (1997) needs black text on white background + w (deafult) Wolf et al. (2001) needs black text on white background -example: - binarize w in.pgm out.pgm - binarize in.pgm out.pgm - binarize s -x 50 -y 50 -k 0.6 in.pgm out.pgm +Example: + ./binarizewolfjolion -m w in.pgm out.pgm + ./binarizewolfjolion in.pgm out.pgm + ./binarizewolfjolion -m s -x 50 -y 50 -k 0.6 in.pgm out.pgm ``` The best working method is 'w', the one which performed 5th in the [DIBCO 2009 competition](http://www.cvc.uab.es/icdar2009/papers/3725b375.pdf). diff --git a/binarizewolfjolion.cpp b/binarizewolfjolion.cpp index aa08e2f..f6acecc 100644 --- a/binarizewolfjolion.cpp +++ b/binarizewolfjolion.cpp @@ -348,8 +348,8 @@ int main(int argc, char **argv) { // Treat the window size if (winx <= 0 || winy <= 0) { std::cerr << "Input size: " << input.cols << "x" << input.rows << std::endl; - winy = (int)(2.0 * input.rows - 1) / 3; - winx = (int)input.cols - 1 < winy ? input.cols - 1 : winy; + winy = (2 * input.rows - 1) / 3; + winx = input.cols - 1 < winy ? input.cols - 1 : winy; // if the window is too big, then we asume that the image // is not a single text box, but a document page: set // the window size to a fixed constant.