Skip to content

Commit 66cf9b3

Browse files
committed
🚀 Improved image processing speed by optimizing the thinning algorithm
1 parent 80f1ca2 commit 66cf9b3

File tree

3 files changed

+79
-173
lines changed

3 files changed

+79
-173
lines changed

‎modules/ximgproc/include/opencv2/ximgproc.hpp

+18-43
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,6 @@
1-
/*
2-
* By downloading, copying, installing or using the software you agree to this license.
3-
* If you do not agree to this license, do not download, install,
4-
* copy or use the software.
5-
*
6-
*
7-
* License Agreement
8-
* For Open Source Computer Vision Library
9-
* (3 - clause BSD License)
10-
*
11-
* Redistribution and use in source and binary forms, with or without modification,
12-
* are permitted provided that the following conditions are met :
13-
*
14-
* * Redistributions of source code must retain the above copyright notice,
15-
* this list of conditions and the following disclaimer.
16-
*
17-
* * Redistributions in binary form must reproduce the above copyright notice,
18-
* this list of conditions and the following disclaimer in the documentation
19-
* and / or other materials provided with the distribution.
20-
*
21-
* * Neither the names of the copyright holders nor the names of the contributors
22-
* may be used to endorse or promote products derived from this software
23-
* without specific prior written permission.
24-
*
25-
* This software is provided by the copyright holders and contributors "as is" and
26-
* any express or implied warranties, including, but not limited to, the implied
27-
* warranties of merchantability and fitness for a particular purpose are disclaimed.
28-
* In no event shall copyright holders or contributors be liable for any direct,
29-
* indirect, incidental, special, exemplary, or consequential damages
30-
* (including, but not limited to, procurement of substitute goods or services;
31-
* loss of use, data, or profits; or business interruption) however caused
32-
* and on any theory of liability, whether in contract, strict liability,
33-
* or tort(including negligence or otherwise) arising in any way out of
34-
* the use of this software, even if advised of the possibility of such damage.
35-
*/
1+
// This file is part of OpenCV project.
2+
// It is subject to the license terms in the LICENSE file found in the top-level directory
3+
// of this distribution and at http://opencv.org/license.html.
364

375
#ifndef __OPENCV_XIMGPROC_HPP__
386
#define __OPENCV_XIMGPROC_HPP__
@@ -68,6 +36,8 @@
6836
/**
6937
@defgroup ximgproc Extended Image Processing
7038
@{
39+
@defgroup ximgproc_binarization Binarization
40+
7141
@defgroup ximgproc_edge Structured forests for fast edge detection
7242
7343
This module contains implementations of modern structured edge detection algorithms,
@@ -124,7 +94,7 @@ namespace cv
12494
namespace ximgproc
12595
{
12696

127-
//! @addtogroup ximgproc
97+
//! @addtogroup ximgproc_binarization
12898
//! @{
12999

130100
enum ThinningTypes{
@@ -179,15 +149,20 @@ CV_EXPORTS_W void niBlackThreshold( InputArray _src, OutputArray _dst,
179149
int blockSize, double k, int binarizationMethod = BINARIZATION_NIBLACK,
180150
double r = 128 );
181151

182-
/** @brief Applies a binary blob thinning operation, to achieve a skeletization of the input image.
152+
/** @brief Performs binary image thinning to obtain a skeletonized representation of the input image.
183153
184-
The function transforms a binary blob image into a skeletized form using the technique of Zhang-Suen.
154+
This function applies a thinning algorithm, reducing the binary blobs in the input image to a skeletal form.
155+
By default, it uses the Zhang-Suen technique, which iteratively removes pixels from the boundaries of the blobs
156+
while preserving the overall structure and connectivity of the objects.
157+
158+
@param src Source image: an 8-bit, single-channel binary image where the blobs are represented by pixels with a value of 255 (white),
159+
and the background is 0 (black).
160+
@param dst Destination image of the same size and type as src, where the result of the thinning operation will be stored.
161+
This operation can be performed in-place, meaning `src` and `dst` can be the same.
162+
@param thinningType The thinning algorithm to apply. By default, the Zhang-Suen algorithm is used. See cv::ximgproc::ThinningTypes for other options.
163+
*/
164+
CV_EXPORTS_W void thinning(InputArray src, OutputArray dst, int thinningType = THINNING_ZHANGSUEN);
185165

186-
@param src Source 8-bit single-channel image, containing binary blobs, with blobs having 255 pixel values.
187-
@param dst Destination image of the same size and the same type as src. The function can work in-place.
188-
@param thinningType Value that defines which thinning algorithm should be used. See cv::ximgproc::ThinningTypes
189-
*/
190-
CV_EXPORTS_W void thinning( InputArray src, OutputArray dst, int thinningType = THINNING_ZHANGSUEN);
191166

192167
/** @brief Performs anisotropic diffusion on an image.
193168

‎modules/ximgproc/src/thinning.cpp

+39-96
Original file line numberDiff line numberDiff line change
@@ -92,117 +92,60 @@ static uint8_t lut_guo_iter1[] = {
9292
1, 1, 1, 1};
9393

9494
// Applies a thinning iteration to a binary image
95-
static void thinningIteration(Mat img, int iter, int thinningType){
96-
Mat marker = Mat::zeros(img.size(), CV_8UC1);
95+
static void thinningIteration(Mat &img, Mat &marker, const uint8_t* const lut) {
9796
int rows = img.rows;
9897
int cols = img.cols;
99-
marker.col(0).setTo(1);
100-
marker.col(cols - 1).setTo(1);
101-
marker.row(0).setTo(1);
102-
marker.row(rows - 1).setTo(1);
103-
104-
if(thinningType == THINNING_ZHANGSUEN){
105-
marker.forEach<uchar>([=](uchar& value, const int postion[]) {
106-
int i = postion[0];
107-
int j = postion[1];
108-
if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1)
109-
return;
110-
111-
auto ptr = img.ptr(i, j); // p1
112-
113-
// p9 p2 p3
114-
// p8 p1 p4
115-
// p7 p6 p5
116-
uchar p2 = ptr[-cols];
117-
uchar p3 = ptr[-cols + 1];
118-
uchar p4 = ptr[1];
119-
uchar p5 = ptr[cols + 1];
120-
uchar p6 = ptr[cols];
121-
uchar p7 = ptr[cols - 1];
122-
uchar p8 = ptr[-1];
123-
uchar p9 = ptr[-cols - 1];
124-
125-
int neighbors = p9 | (p2 << 1) | (p3 << 2) | (p4 << 3) | (p5 << 4) | (p6 << 5) | (p7 << 6) | (p8 << 7);
126-
127-
if (iter == 0)
128-
value = lut_zhang_iter0[neighbors];
129-
else
130-
value = lut_zhang_iter1[neighbors];
131-
132-
//int A = (p2 == 0 && p3 == 1) + (p3 == 0 && p4 == 1) +
133-
// (p4 == 0 && p5 == 1) + (p5 == 0 && p6 == 1) +
134-
// (p6 == 0 && p7 == 1) + (p7 == 0 && p8 == 1) +
135-
// (p8 == 0 && p9 == 1) + (p9 == 0 && p2 == 1);
136-
//int B = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
137-
//int m1 = iter == 0 ? (p2 * p4 * p6) : (p2 * p4 * p8);
138-
//int m2 = iter == 0 ? (p4 * p6 * p8) : (p2 * p6 * p8);
139-
//if (A == 1 && (B >= 2 && B <= 6) && m1 == 0 && m2 == 0) value = 0;
140-
// else value = 1;
141-
});
142-
}
143-
if(thinningType == THINNING_GUOHALL){
144-
marker.forEach<uchar>([=](uchar& value, const int postion[]) {
145-
int i = postion[0];
146-
int j = postion[1];
147-
if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1)
148-
return;
149-
150-
auto ptr = img.ptr(i, j); // p1
151-
152-
// p9 p2 p3
153-
// p8 p1 p4
154-
// p7 p6 p5
155-
uchar p2 = ptr[-cols];
156-
uchar p3 = ptr[-cols + 1];
157-
uchar p4 = ptr[1];
158-
uchar p5 = ptr[cols + 1];
159-
uchar p6 = ptr[cols];
160-
uchar p7 = ptr[cols - 1];
161-
uchar p8 = ptr[-1];
162-
uchar p9 = ptr[-cols - 1];
163-
164-
int neighbors = p9 | (p2 << 1) | (p3 << 2) | (p4 << 3) | (p5 << 4) | (p6 << 5) | (p7 << 6) | (p8 << 7);
165-
166-
if (iter == 0)
167-
value = lut_guo_iter0[neighbors];
168-
else
169-
value = lut_guo_iter1[neighbors];
170-
171-
//int C = ((!p2) & (p3 | p4)) + ((!p4) & (p5 | p6)) +
172-
// ((!p6) & (p7 | p8)) + ((!p8) & (p9 | p2));
173-
//int N1 = (p9 | p2) + (p3 | p4) + (p5 | p6) + (p7 | p8);
174-
//int N2 = (p2 | p3) + (p4 | p5) + (p6 | p7) + (p8 | p9);
175-
//int N = N1 < N2 ? N1 : N2;
176-
//int m = iter == 0 ? ((p6 | p7 | (!p9)) & p8) : ((p2 | p3 | (!p5)) & p4);
177-
//if ((C == 1) && ((N >= 2) && ((N <= 3)) & (m == 0))) value = 0;
178-
// else value = 1;
179-
});
180-
}
98+
99+
// Parallelized iteration over pixels excluding the boundary
100+
parallel_for_(Range(1, rows - 1), [&](const Range& range) {
101+
for (int i = range.start; i < range.end; i++) {
102+
const uchar* imgRow = img.ptr(i);
103+
uchar* markerRow = marker.ptr(i);
104+
for (int j = 1; j < cols - 1; j++) {
105+
if (imgRow[j]) {
106+
uchar p2 = imgRow[j - cols] != 0;
107+
uchar p3 = imgRow[j - cols + 1] != 0;
108+
uchar p4 = imgRow[j + 1] != 0;
109+
uchar p5 = imgRow[j + cols + 1] != 0;
110+
uchar p6 = imgRow[j + cols] != 0;
111+
uchar p7 = imgRow[j + cols - 1] != 0;
112+
uchar p8 = imgRow[j - 1] != 0;
113+
uchar p9 = imgRow[j - cols - 1] != 0;
114+
115+
int neighbors = p9 | (p2 << 1) | (p3 << 2) | (p4 << 3) | (p5 << 4) | (p6 << 5) | (p7 << 6) | (p8 << 7);
116+
markerRow[j] = lut[neighbors];
117+
}
118+
}
119+
}
120+
});
181121

182122
img &= marker;
183123
}
184124

185125
// Apply the thinning procedure to a given image
186126
void thinning(InputArray input, OutputArray output, int thinningType){
187-
Mat processed = input.getMat().clone();
188-
CV_CheckTypeEQ(processed.type(), CV_8UC1, "");
189-
// Enforce the range of the input image to be in between 0 - 255
190-
processed /= 255;
127+
Mat input_ = input.getMat();
128+
CV_Assert(!input_.empty());
129+
CV_CheckTypeEQ(input_.type(), CV_8UC1, "");
191130

131+
Mat processed = input_ / 255;
192132
Mat prev = processed.clone();
193-
Mat diff;
194133

134+
Mat marker;
135+
Mat marker_inner = processed(Rect(1, 1, processed.cols - 2, processed.rows - 2));
136+
copyMakeBorder(marker_inner, marker, 1, 1, 1, 1, BORDER_ISOLATED | BORDER_CONSTANT, Scalar(255));
137+
138+
const auto lutIter0 = (thinningType == THINNING_GUOHALL) ? lut_guo_iter0 : lut_zhang_iter0;
139+
const auto lutIter1 = (thinningType == THINNING_GUOHALL) ? lut_guo_iter1 : lut_zhang_iter1;
195140
do {
196-
thinningIteration(processed, 0, thinningType);
197-
thinningIteration(processed, 1, thinningType);
198-
absdiff(processed, prev, diff);
199-
if (!hasNonZero(diff)) break;
141+
thinningIteration(processed, marker, lutIter0);
142+
thinningIteration(processed, marker, lutIter1);
143+
const auto res = cv::norm(processed, prev, cv::NORM_L1);
144+
if (res <= 0) { break; }
200145
processed.copyTo(prev);
201-
}
202-
while (true);
146+
} while (true);
203147

204148
processed *= 255;
205-
206149
output.assign(processed);
207150
}
208151

‎modules/ximgproc/test/test_thinning.cpp

+22-34
Original file line numberDiff line numberDiff line change
@@ -6,52 +6,40 @@
66

77
namespace opencv_test { namespace {
88

9-
static int createTestImage(Mat1b& src)
10-
{
11-
src = Mat1b::zeros(Size(256, 256));
12-
// Create a corner point that should not be affected.
13-
src(0, 0) = 255;
14-
15-
for (int x = 50; x < src.cols - 50; x += 50)
16-
{
17-
cv::circle(src, Point(x, x/2), 30 + x/2, Scalar(255), 5);
18-
}
19-
int src_pixels = countNonZero(src);
20-
EXPECT_GT(src_pixels, 0);
21-
return src_pixels;
22-
}
23-
249
TEST(ximgproc_Thinning, simple_ZHANGSUEN)
2510
{
26-
Mat1b src;
27-
int src_pixels = createTestImage(src);
11+
string dir = cvtest::TS::ptr()->get_data_path();
12+
Mat src = imread(dir + "cv/ximgproc/sources/08.png", IMREAD_GRAYSCALE);
13+
Mat dst,check_img;
2814

29-
Mat1b dst;
3015
thinning(src, dst, THINNING_ZHANGSUEN);
31-
int dst_pixels = countNonZero(dst);
32-
EXPECT_LE(dst_pixels, src_pixels);
33-
EXPECT_EQ(dst(0, 0), 255);
3416

35-
#if 0
36-
imshow("src", src); imshow("dst", dst); waitKey();
37-
#endif
17+
check_img = imread(dir + "cv/ximgproc/results/Thinning_ZHANGSUEN.png", IMREAD_GRAYSCALE);
18+
EXPECT_EQ(0, cvtest::norm(check_img, dst, NORM_INF));
19+
20+
dst = ~src;
21+
thinning(dst, dst, THINNING_ZHANGSUEN);
22+
23+
check_img = imread(dir + "cv/ximgproc/results/Thinning_inv_ZHANGSUEN.png", IMREAD_GRAYSCALE);
24+
EXPECT_EQ(0, cvtest::norm(check_img, dst, NORM_INF));
3825
}
3926

4027
TEST(ximgproc_Thinning, simple_GUOHALL)
4128
{
42-
Mat1b src;
43-
int src_pixels = createTestImage(src);
29+
string dir = cvtest::TS::ptr()->get_data_path();
30+
Mat src = imread(dir + "cv/ximgproc/sources/08.png", IMREAD_GRAYSCALE);
31+
Mat dst,check_img;
4432

45-
Mat1b dst;
4633
thinning(src, dst, THINNING_GUOHALL);
47-
int dst_pixels = countNonZero(dst);
48-
EXPECT_LE(dst_pixels, src_pixels);
49-
EXPECT_EQ(dst(0, 0), 255);
5034

51-
#if 0
52-
imshow("src", src); imshow("dst", dst); waitKey();
53-
#endif
54-
}
35+
check_img = imread(dir + "cv/ximgproc/results/Thinning_GUOHALL.png", IMREAD_GRAYSCALE);
36+
EXPECT_EQ(0, cvtest::norm(check_img, dst, NORM_INF));
5537

38+
dst = ~src;
39+
thinning(dst, dst, THINNING_GUOHALL);
40+
41+
check_img = imread(dir + "cv/ximgproc/results/Thinning_inv_GUOHALL.png", IMREAD_GRAYSCALE);
42+
EXPECT_EQ(0, cvtest::norm(check_img, dst, NORM_INF));
43+
}
5644

5745
}} // namespace

0 commit comments

Comments
 (0)