Skip to content

Commit fcc728b

Browse files
committed
1.2025.12.21: threshold: add LCaM method
1 parent e033042 commit fcc728b

8 files changed

Lines changed: 122 additions & 1 deletion

File tree

src/imageproc/Binarize.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,31 @@ BinaryImage binarizeLAAB(
874874
return bw_img;
875875
} // binarizeLAAB
876876

877+
/*
878+
* LCaM = k * [mean + contrast * (256 - origin + delta) / 256], k = 0.75, delta = 0
879+
* contrast = Imax - Imin
880+
* mean = integral(I,w) / w
881+
* w = (2 * radius + 1) * (2 * radius + 1), radius = 5
882+
*/
883+
BinaryImage binarizeLCaM(
884+
GrayImage const& src,
885+
int const radius,
886+
float const k,
887+
int const delta,
888+
unsigned char const bound_lower,
889+
unsigned char const bound_upper)
890+
{
891+
if (src.isNull())
892+
{
893+
return BinaryImage();
894+
}
895+
896+
GrayImage threshold_map(grayLCaMMap(src, radius, k, delta));
897+
BinaryImage bw_img(binarizeFromMap(src, threshold_map, 0, bound_lower, bound_upper));
898+
899+
return bw_img;
900+
}
901+
877902
/*
878903
* WAN = (mean + max) / 2 * (1.0 + k * (stderr / 128.0 - 1.0)), k = 0.30
879904
* modification by zvezdochiot:

src/imageproc/Binarize.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,22 @@ IMAGEPROC_EXPORT BinaryImage binarizeLAAB(
275275
unsigned char lower_bound = 0,
276276
unsigned char upper_bound = 255);
277277

278+
/**
279+
* \brief Image binarization using Local Contrast and Mean based Thresholding Technique in Image Binarization.
280+
*
281+
* O. Imocha Singh, Tejmani Sinam, O. James, T.Romen Singh
282+
* International Journal of Computer Applications (0975 – 8887)
283+
* Volume 51– No.6, August 2012
284+
* https://research.ijcaonline.org/volume51/number6/pxc3881362.pdf
285+
*/
286+
IMAGEPROC_EXPORT BinaryImage binarizeLCaM(
287+
GrayImage const& src,
288+
int radius = 5,
289+
float k = 0.75f,
290+
int delta = 0,
291+
unsigned char lower_bound = 0,
292+
unsigned char upper_bound = 255);
293+
278294
/**
279295
* \brief Image binarization using Fox adaptive thresholding method.
280296
*

src/imageproc/GrayImage.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,67 @@ GrayImage grayWindowMap(
974974
return gmean;
975975
} // grayWindowMap
976976

977+
/*
978+
* LCaM = k * [mean + contrast * (256 - origin + delta) / 256], k = 0.75, delta = 0
979+
* contrast = Imax - Imin
980+
* mean = integral(I,w) / w
981+
* w = (2 * radius + 1) * (2 * radius + 1), radius = 5
982+
*/
983+
GrayImage grayLCaMMap(
984+
GrayImage const& src,
985+
int const radius,
986+
float const k,
987+
int const delta)
988+
{
989+
if (src.isNull())
990+
{
991+
return GrayImage();
992+
}
993+
GrayImage gmean = grayMapMean(src, radius);
994+
if (gmean.isNull())
995+
{
996+
return GrayImage(src);
997+
}
998+
999+
if (radius > 0)
1000+
{
1001+
GrayImage gcontrast = grayMapContrast(src, radius);
1002+
if (gcontrast.isNull())
1003+
{
1004+
return gmean;
1005+
}
1006+
1007+
int const w = src.width();
1008+
int const h = src.height();
1009+
unsigned char const* src_line = src.data();
1010+
int const src_stride = src.stride();
1011+
unsigned char* gmean_line = gmean.data();
1012+
int const gmean_stride = gmean.stride();
1013+
unsigned char* gcontrast_line = gcontrast.data();
1014+
int const gcontrast_stride = gcontrast.stride();
1015+
1016+
for (int y = 0; y < h; y++)
1017+
{
1018+
for (int x = 0; x < w; x++)
1019+
{
1020+
float const origin = src_line[x];
1021+
float const mean = gmean_line[x];
1022+
float const contrast = gcontrast_line[x];
1023+
1024+
float threshold = k * (mean + contrast * (256.0f - origin + delta) / 256.0f);
1025+
1026+
threshold = (threshold < 0.0f) ? 0.0f : ((threshold < 255.0f) ? threshold : 255.0f);
1027+
gmean_line[x] = (unsigned char) threshold;
1028+
}
1029+
src_line += src_stride;
1030+
gmean_line += gmean_stride;
1031+
gcontrast_line += gcontrast_stride;
1032+
}
1033+
}
1034+
1035+
return gmean;
1036+
} // grayLCaMMap
1037+
9771038
/*
9781039
* bradley = mean * (1.0 - k), k = 0.2
9791040
*/

src/imageproc/GrayImage.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ IMAGEPROC_EXPORT GrayImage grayWindowMap(
206206
int radius = 50,
207207
float k = 1.0f,
208208
int delta = 0);
209+
IMAGEPROC_EXPORT GrayImage grayLCaMMap(
210+
GrayImage const& src,
211+
int radius = 5,
212+
float k = 0.75f,
213+
int delta = 0);
209214
IMAGEPROC_EXPORT GrayImage grayBradleyMap(
210215
GrayImage const& src,
211216
int radius = 100,

src/stages/output/BlackWhiteOptions.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ BlackWhiteOptions::parseThresholdMethod(QString const& str)
236236
{
237237
return T_LAAB;
238238
}
239+
else if (str == "lcam")
240+
{
241+
return T_LCAM;
242+
}
239243
else if (str == "WAN")
240244
{
241245
return T_WAN;
@@ -329,6 +333,9 @@ BlackWhiteOptions::formatThresholdMethod(ThresholdFilter type)
329333
case T_LAAB:
330334
str = "laab";
331335
break;
336+
case T_LCAM:
337+
str = "lcam";
338+
break;
332339
case T_WAN:
333340
str = "WAN";
334341
break;

src/stages/output/BlackWhiteOptions.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class QDomElement;
2626
namespace output
2727
{
2828

29-
enum ThresholdFilter { T_OTSU, T_MEANDELTA, T_DOTS8, T_BMTILED, T_NIBLACK, T_GATOS, T_SAUVOLA, T_WOLF, T_WINDOW, T_BRADLEY, T_NICK, T_GRAD, T_SINGH, T_FOX, T_LAAB, T_WAN, T_EDGEPLUS, T_BLURDIV, T_EDGEDIV, T_EDGEADAPT, T_ROBUST, T_GRAIN, T_MSCALE, T_ENGRAVING };
29+
enum ThresholdFilter { T_OTSU, T_MEANDELTA, T_DOTS8, T_BMTILED, T_NIBLACK, T_GATOS, T_SAUVOLA, T_WOLF, T_WINDOW, T_BRADLEY, T_NICK, T_GRAD, T_SINGH, T_FOX, T_LAAB, T_LCAM, T_WAN, T_EDGEPLUS, T_BLURDIV, T_EDGEDIV, T_EDGEADAPT, T_ROBUST, T_GRAIN, T_MSCALE, T_ENGRAVING };
3030

3131
class BlackWhiteOptions
3232
{

src/stages/output/OptionsWidget.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ OptionsWidget::OptionsWidget(
7979
thresholdMethodSelector->addItem(tr("Singh"), T_SINGH);
8080
thresholdMethodSelector->addItem(tr("Fox"), T_FOX);
8181
thresholdMethodSelector->addItem(tr("LAAB"), T_LAAB);
82+
thresholdMethodSelector->addItem(tr("LCaM"), T_LCAM);
8283
thresholdMethodSelector->addItem(tr("WAN"), T_WAN);
8384
thresholdMethodSelector->addItem(tr("EdgePlus"), T_EDGEPLUS);
8485
thresholdMethodSelector->addItem(tr("BlurDiv"), T_BLURDIV);
@@ -938,6 +939,7 @@ OptionsWidget::thresholdDefaultButtonClicked()
938939
new_radius = 10;
939940
new_coef = 0.75;
940941
break;
942+
case T_LCAM:
941943
case T_EDGEPLUS:
942944
case T_BLURDIV:
943945
case T_EDGEDIV:

src/stages/output/OutputGenerator.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,11 @@ OutputGenerator::binarize(QImage const& image, BinaryImage const& mask) const
12601260
binarized = binarizeLAAB(gray, radius, threshold_coef, threshold_delta, bound_lower, bound_upper);
12611261
break;
12621262
}
1263+
case T_LCAM:
1264+
{
1265+
binarized = binarizeLCaM(gray, radius, threshold_coef, threshold_delta, bound_lower, bound_upper);
1266+
break;
1267+
}
12631268
case T_WAN:
12641269
{
12651270
binarized = binarizeWAN(gray, radius, threshold_coef, threshold_delta, bound_lower, bound_upper);

0 commit comments

Comments
 (0)