From 66cf9b35f63443b2f3bcb252c6fe6f668d78304d Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Thu, 3 Oct 2024 17:28:01 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Improved=20image=20processing=20?= =?UTF-8?q?speed=20by=20optimizing=20the=20thinning=20algorithm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ximgproc/include/opencv2/ximgproc.hpp | 61 +++----- modules/ximgproc/src/thinning.cpp | 135 +++++------------- modules/ximgproc/test/test_thinning.cpp | 56 +++----- 3 files changed, 79 insertions(+), 173 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc.hpp b/modules/ximgproc/include/opencv2/ximgproc.hpp index 099205126c..3bf2d81934 100644 --- a/modules/ximgproc/include/opencv2/ximgproc.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc.hpp @@ -1,38 +1,6 @@ -/* - * By downloading, copying, installing or using the software you agree to this license. - * If you do not agree to this license, do not download, install, - * copy or use the software. - * - * - * License Agreement - * For Open Source Computer Vision Library - * (3 - clause BSD License) - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met : - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * 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. - * - * * Neither the names of the copyright holders nor the names of the contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * This software is provided by the copyright holders and contributors "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 copyright holders or contributors 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. - */ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. #ifndef __OPENCV_XIMGPROC_HPP__ #define __OPENCV_XIMGPROC_HPP__ @@ -68,6 +36,8 @@ /** @defgroup ximgproc Extended Image Processing @{ + @defgroup ximgproc_binarization Binarization + @defgroup ximgproc_edge Structured forests for fast edge detection This module contains implementations of modern structured edge detection algorithms, @@ -124,7 +94,7 @@ namespace cv namespace ximgproc { -//! @addtogroup ximgproc +//! @addtogroup ximgproc_binarization //! @{ enum ThinningTypes{ @@ -179,15 +149,20 @@ CV_EXPORTS_W void niBlackThreshold( InputArray _src, OutputArray _dst, int blockSize, double k, int binarizationMethod = BINARIZATION_NIBLACK, double r = 128 ); -/** @brief Applies a binary blob thinning operation, to achieve a skeletization of the input image. +/** @brief Performs binary image thinning to obtain a skeletonized representation of the input image. -The function transforms a binary blob image into a skeletized form using the technique of Zhang-Suen. +This function applies a thinning algorithm, reducing the binary blobs in the input image to a skeletal form. +By default, it uses the Zhang-Suen technique, which iteratively removes pixels from the boundaries of the blobs +while preserving the overall structure and connectivity of the objects. + +@param src Source image: an 8-bit, single-channel binary image where the blobs are represented by pixels with a value of 255 (white), +and the background is 0 (black). +@param dst Destination image of the same size and type as src, where the result of the thinning operation will be stored. +This operation can be performed in-place, meaning `src` and `dst` can be the same. +@param thinningType The thinning algorithm to apply. By default, the Zhang-Suen algorithm is used. See cv::ximgproc::ThinningTypes for other options. +*/ +CV_EXPORTS_W void thinning(InputArray src, OutputArray dst, int thinningType = THINNING_ZHANGSUEN); -@param src Source 8-bit single-channel image, containing binary blobs, with blobs having 255 pixel values. -@param dst Destination image of the same size and the same type as src. The function can work in-place. -@param thinningType Value that defines which thinning algorithm should be used. See cv::ximgproc::ThinningTypes - */ -CV_EXPORTS_W void thinning( InputArray src, OutputArray dst, int thinningType = THINNING_ZHANGSUEN); /** @brief Performs anisotropic diffusion on an image. diff --git a/modules/ximgproc/src/thinning.cpp b/modules/ximgproc/src/thinning.cpp index 76e8ad1542..b3a7dc15eb 100644 --- a/modules/ximgproc/src/thinning.cpp +++ b/modules/ximgproc/src/thinning.cpp @@ -92,117 +92,60 @@ static uint8_t lut_guo_iter1[] = { 1, 1, 1, 1}; // Applies a thinning iteration to a binary image -static void thinningIteration(Mat img, int iter, int thinningType){ - Mat marker = Mat::zeros(img.size(), CV_8UC1); +static void thinningIteration(Mat &img, Mat &marker, const uint8_t* const lut) { int rows = img.rows; int cols = img.cols; - marker.col(0).setTo(1); - marker.col(cols - 1).setTo(1); - marker.row(0).setTo(1); - marker.row(rows - 1).setTo(1); - - if(thinningType == THINNING_ZHANGSUEN){ - marker.forEach([=](uchar& value, const int postion[]) { - int i = postion[0]; - int j = postion[1]; - if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1) - return; - - auto ptr = img.ptr(i, j); // p1 - - // p9 p2 p3 - // p8 p1 p4 - // p7 p6 p5 - uchar p2 = ptr[-cols]; - uchar p3 = ptr[-cols + 1]; - uchar p4 = ptr[1]; - uchar p5 = ptr[cols + 1]; - uchar p6 = ptr[cols]; - uchar p7 = ptr[cols - 1]; - uchar p8 = ptr[-1]; - uchar p9 = ptr[-cols - 1]; - - int neighbors = p9 | (p2 << 1) | (p3 << 2) | (p4 << 3) | (p5 << 4) | (p6 << 5) | (p7 << 6) | (p8 << 7); - - if (iter == 0) - value = lut_zhang_iter0[neighbors]; - else - value = lut_zhang_iter1[neighbors]; - - //int A = (p2 == 0 && p3 == 1) + (p3 == 0 && p4 == 1) + - // (p4 == 0 && p5 == 1) + (p5 == 0 && p6 == 1) + - // (p6 == 0 && p7 == 1) + (p7 == 0 && p8 == 1) + - // (p8 == 0 && p9 == 1) + (p9 == 0 && p2 == 1); - //int B = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; - //int m1 = iter == 0 ? (p2 * p4 * p6) : (p2 * p4 * p8); - //int m2 = iter == 0 ? (p4 * p6 * p8) : (p2 * p6 * p8); - //if (A == 1 && (B >= 2 && B <= 6) && m1 == 0 && m2 == 0) value = 0; - // else value = 1; - }); - } - if(thinningType == THINNING_GUOHALL){ - marker.forEach([=](uchar& value, const int postion[]) { - int i = postion[0]; - int j = postion[1]; - if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1) - return; - - auto ptr = img.ptr(i, j); // p1 - - // p9 p2 p3 - // p8 p1 p4 - // p7 p6 p5 - uchar p2 = ptr[-cols]; - uchar p3 = ptr[-cols + 1]; - uchar p4 = ptr[1]; - uchar p5 = ptr[cols + 1]; - uchar p6 = ptr[cols]; - uchar p7 = ptr[cols - 1]; - uchar p8 = ptr[-1]; - uchar p9 = ptr[-cols - 1]; - - int neighbors = p9 | (p2 << 1) | (p3 << 2) | (p4 << 3) | (p5 << 4) | (p6 << 5) | (p7 << 6) | (p8 << 7); - - if (iter == 0) - value = lut_guo_iter0[neighbors]; - else - value = lut_guo_iter1[neighbors]; - - //int C = ((!p2) & (p3 | p4)) + ((!p4) & (p5 | p6)) + - // ((!p6) & (p7 | p8)) + ((!p8) & (p9 | p2)); - //int N1 = (p9 | p2) + (p3 | p4) + (p5 | p6) + (p7 | p8); - //int N2 = (p2 | p3) + (p4 | p5) + (p6 | p7) + (p8 | p9); - //int N = N1 < N2 ? N1 : N2; - //int m = iter == 0 ? ((p6 | p7 | (!p9)) & p8) : ((p2 | p3 | (!p5)) & p4); - //if ((C == 1) && ((N >= 2) && ((N <= 3)) & (m == 0))) value = 0; - // else value = 1; - }); - } + + // Parallelized iteration over pixels excluding the boundary + parallel_for_(Range(1, rows - 1), [&](const Range& range) { + for (int i = range.start; i < range.end; i++) { + const uchar* imgRow = img.ptr(i); + uchar* markerRow = marker.ptr(i); + for (int j = 1; j < cols - 1; j++) { + if (imgRow[j]) { + uchar p2 = imgRow[j - cols] != 0; + uchar p3 = imgRow[j - cols + 1] != 0; + uchar p4 = imgRow[j + 1] != 0; + uchar p5 = imgRow[j + cols + 1] != 0; + uchar p6 = imgRow[j + cols] != 0; + uchar p7 = imgRow[j + cols - 1] != 0; + uchar p8 = imgRow[j - 1] != 0; + uchar p9 = imgRow[j - cols - 1] != 0; + + int neighbors = p9 | (p2 << 1) | (p3 << 2) | (p4 << 3) | (p5 << 4) | (p6 << 5) | (p7 << 6) | (p8 << 7); + markerRow[j] = lut[neighbors]; + } + } + } + }); img &= marker; } // Apply the thinning procedure to a given image void thinning(InputArray input, OutputArray output, int thinningType){ - Mat processed = input.getMat().clone(); - CV_CheckTypeEQ(processed.type(), CV_8UC1, ""); - // Enforce the range of the input image to be in between 0 - 255 - processed /= 255; + Mat input_ = input.getMat(); + CV_Assert(!input_.empty()); + CV_CheckTypeEQ(input_.type(), CV_8UC1, ""); + Mat processed = input_ / 255; Mat prev = processed.clone(); - Mat diff; + Mat marker; + Mat marker_inner = processed(Rect(1, 1, processed.cols - 2, processed.rows - 2)); + copyMakeBorder(marker_inner, marker, 1, 1, 1, 1, BORDER_ISOLATED | BORDER_CONSTANT, Scalar(255)); + + const auto lutIter0 = (thinningType == THINNING_GUOHALL) ? lut_guo_iter0 : lut_zhang_iter0; + const auto lutIter1 = (thinningType == THINNING_GUOHALL) ? lut_guo_iter1 : lut_zhang_iter1; do { - thinningIteration(processed, 0, thinningType); - thinningIteration(processed, 1, thinningType); - absdiff(processed, prev, diff); - if (!hasNonZero(diff)) break; + thinningIteration(processed, marker, lutIter0); + thinningIteration(processed, marker, lutIter1); + const auto res = cv::norm(processed, prev, cv::NORM_L1); + if (res <= 0) { break; } processed.copyTo(prev); - } - while (true); + } while (true); processed *= 255; - output.assign(processed); } diff --git a/modules/ximgproc/test/test_thinning.cpp b/modules/ximgproc/test/test_thinning.cpp index 7d5c5ac480..e3b523b538 100644 --- a/modules/ximgproc/test/test_thinning.cpp +++ b/modules/ximgproc/test/test_thinning.cpp @@ -6,52 +6,40 @@ namespace opencv_test { namespace { -static int createTestImage(Mat1b& src) -{ - src = Mat1b::zeros(Size(256, 256)); - // Create a corner point that should not be affected. - src(0, 0) = 255; - - for (int x = 50; x < src.cols - 50; x += 50) - { - cv::circle(src, Point(x, x/2), 30 + x/2, Scalar(255), 5); - } - int src_pixels = countNonZero(src); - EXPECT_GT(src_pixels, 0); - return src_pixels; -} - TEST(ximgproc_Thinning, simple_ZHANGSUEN) { - Mat1b src; - int src_pixels = createTestImage(src); + string dir = cvtest::TS::ptr()->get_data_path(); + Mat src = imread(dir + "cv/ximgproc/sources/08.png", IMREAD_GRAYSCALE); + Mat dst,check_img; - Mat1b dst; thinning(src, dst, THINNING_ZHANGSUEN); - int dst_pixels = countNonZero(dst); - EXPECT_LE(dst_pixels, src_pixels); - EXPECT_EQ(dst(0, 0), 255); -#if 0 - imshow("src", src); imshow("dst", dst); waitKey(); -#endif + check_img = imread(dir + "cv/ximgproc/results/Thinning_ZHANGSUEN.png", IMREAD_GRAYSCALE); + EXPECT_EQ(0, cvtest::norm(check_img, dst, NORM_INF)); + + dst = ~src; + thinning(dst, dst, THINNING_ZHANGSUEN); + + check_img = imread(dir + "cv/ximgproc/results/Thinning_inv_ZHANGSUEN.png", IMREAD_GRAYSCALE); + EXPECT_EQ(0, cvtest::norm(check_img, dst, NORM_INF)); } TEST(ximgproc_Thinning, simple_GUOHALL) { - Mat1b src; - int src_pixels = createTestImage(src); + string dir = cvtest::TS::ptr()->get_data_path(); + Mat src = imread(dir + "cv/ximgproc/sources/08.png", IMREAD_GRAYSCALE); + Mat dst,check_img; - Mat1b dst; thinning(src, dst, THINNING_GUOHALL); - int dst_pixels = countNonZero(dst); - EXPECT_LE(dst_pixels, src_pixels); - EXPECT_EQ(dst(0, 0), 255); -#if 0 - imshow("src", src); imshow("dst", dst); waitKey(); -#endif -} + check_img = imread(dir + "cv/ximgproc/results/Thinning_GUOHALL.png", IMREAD_GRAYSCALE); + EXPECT_EQ(0, cvtest::norm(check_img, dst, NORM_INF)); + dst = ~src; + thinning(dst, dst, THINNING_GUOHALL); + + check_img = imread(dir + "cv/ximgproc/results/Thinning_inv_GUOHALL.png", IMREAD_GRAYSCALE); + EXPECT_EQ(0, cvtest::norm(check_img, dst, NORM_INF)); +} }} // namespace