Commit fb0cb3d3 authored by StevenPuttemans's avatar StevenPuttemans

added multiple thinning algorithms

added thinning sample
added thinning tests
parent 5deedac2
......@@ -71,6 +71,11 @@ namespace cv
namespace ximgproc
{
enum ThinningTypes{
THINNING_ZHANGSUEN = 0, // Thinning technique of Zhang-Suen
THINNING_GUOHALL = 1 // Thinning technique of Guo-Hall
};
//! @addtogroup ximgproc
//! @{
......@@ -110,8 +115,9 @@ The function transforms a binary blob image into a skeletized form using the tec
@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::ThinningTypes
*/
CV_EXPORTS_W void thinning( InputArray src, OutputArray dst);
CV_EXPORTS_W void thinning( InputArray src, OutputArray dst, int thinningType = THINNING_ZHANGSUEN);
//! @}
......
#include <iostream>
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/ximgproc.hpp"
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("opencv-logo.png", IMREAD_COLOR);
/// Threshold the input image
Mat img_grayscale, img_binary;
cvtColor(img, img_grayscale,COLOR_BGR2GRAY);
threshold(img_grayscale, img_binary, 0, 255, THRESH_OTSU | THRESH_BINARY_INV);
/// Apply thinning to get a skeleton
Mat img_thinning_ZS, img_thinning_GH;
//ximgproc::thinning(img_binary, img_thinning_ZS, THINNING_ZHANGSUEN);
//ximgproc::thinning(img_binary, img_thinning_GH, THINNING_GUOHALL);
/// Make 3 channel images from thinning result
Mat result_ZS(img.rows, img.cols, CV_8UC3), result_GH(img.rows, img.cols, CV_8UC3);
Mat in[] = { img_thinning_ZS, img_thinning_ZS, img_thinning_ZS };
Mat in2[] = { img_thinning_GH, img_thinning_GH, img_thinning_GH };
int from_to[] = { 0,0, 1,1, 2,2 };
mixChannels( in, 3, &result_ZS, 1, from_to, 3 );
mixChannels( in2, 3, &result_GH, 1, from_to, 3 );
/// Combine everything into a canvas
Mat canvas(img.rows, img.cols * 3, CV_8UC3);
img.copyTo( canvas( Rect(0, 0, img.cols, img.rows) ) );
result_ZS.copyTo( canvas( Rect(img.cols, 0, img.cols, img.rows) ) );
result_GH.copyTo( canvas( Rect(img.cols*2, 0, img.cols, img.rows) ) );
/// Visualize result
imshow("Skeleton", canvas); waitKey(0);
return 0;
}
......@@ -6,31 +6,60 @@ namespace cv {
namespace ximgproc {
// Applies a thinning iteration to a binary image
static void thinningIteration(Mat img, int iter){
static void thinningIteration(Mat img, int iter, int thinningType){
Mat marker = Mat::zeros(img.size(), CV_8UC1);
for (int i = 1; i < img.rows-1; i++)
{
for (int j = 1; j < img.cols-1; j++)
if(thinningType == THINNING_ZHANGSUEN){
for (int i = 1; i < img.rows-1; i++)
{
for (int j = 1; j < img.cols-1; j++)
{
uchar p2 = img.at<uchar>(i-1, j);
uchar p3 = img.at<uchar>(i-1, j+1);
uchar p4 = img.at<uchar>(i, j+1);
uchar p5 = img.at<uchar>(i+1, j+1);
uchar p6 = img.at<uchar>(i+1, j);
uchar p7 = img.at<uchar>(i+1, j-1);
uchar p8 = img.at<uchar>(i, j-1);
uchar p9 = img.at<uchar>(i-1, j-1);
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)
marker.at<uchar>(i,j) = 1;
}
}
}
if(thinningType == THINNING_GUOHALL){
for (int i = 1; i < img.rows-1; i++)
{
uchar p2 = img.at<uchar>(i-1, j);
uchar p3 = img.at<uchar>(i-1, j+1);
uchar p4 = img.at<uchar>(i, j+1);
uchar p5 = img.at<uchar>(i+1, j+1);
uchar p6 = img.at<uchar>(i+1, j);
uchar p7 = img.at<uchar>(i+1, j-1);
uchar p8 = img.at<uchar>(i, j-1);
uchar p9 = img.at<uchar>(i-1, j-1);
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)
marker.at<uchar>(i,j) = 1;
for (int j = 1; j < img.cols-1; j++)
{
uchar p2 = img.at<uchar>(i-1, j);
uchar p3 = img.at<uchar>(i-1, j+1);
uchar p4 = img.at<uchar>(i, j+1);
uchar p5 = img.at<uchar>(i+1, j+1);
uchar p6 = img.at<uchar>(i+1, j);
uchar p7 = img.at<uchar>(i+1, j-1);
uchar p8 = img.at<uchar>(i, j-1);
uchar p9 = img.at<uchar>(i-1, j-1);
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)))
marker.at<uchar>(i,j) = 1;
}
}
}
......@@ -38,7 +67,7 @@ static void thinningIteration(Mat img, int iter){
}
// Apply the thinning procedure to a given image
void thinning(InputArray input, OutputArray output){
void thinning(InputArray input, OutputArray output, int thinningType){
Mat processed = input.getMat().clone();
// Enforce the range of the input image to be in between 0 - 255
processed /= 255;
......@@ -47,8 +76,8 @@ void thinning(InputArray input, OutputArray output){
Mat diff;
do {
thinningIteration(processed, 0);
thinningIteration(processed, 1);
thinningIteration(processed, 0, thinningType);
thinningIteration(processed, 1, thinningType);
absdiff(processed, prev, diff);
processed.copyTo(prev);
}
......
// 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.
#include "test_precomp.hpp"
using namespace cv;
using namespace cv::ximgproc;
namespace {
static int createTestImage(Mat& src)
{
src = Mat::zeros(Size(256, 256), CV_8UC1);
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(ximpgroc_Thinning, simple_ZHANGSUEN)
{
Mat src;
int src_pixels = createTestImage(src);
Mat dst;
thinning(src, dst, THINNING_ZHANGSUEN);
int dst_pixels = countNonZero(dst);
EXPECT_LE(dst_pixels, src_pixels);
#if 0
imshow("src", src); imshow("dst", dst); waitKey();
#endif
}
TEST(ximpgroc_Thinning, simple_GUOHALL)
{
Mat src;
int src_pixels = createTestImage(src);
Mat dst;
thinning(src, dst, THINNING_GUOHALL);
int dst_pixels = countNonZero(dst);
EXPECT_LE(dst_pixels, src_pixels);
#if 0
imshow("src", src); imshow("dst", dst); waitKey();
#endif
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment