最近在实验室里遇到了一个问题,就是在有一张轮廓二值图的情况下,如何才能将轮廓进行细化,得到轮廓的骨架。
效果如图:
可以看到,右边图中的数字变瘦了,这就是细化算法的作用
下面我们来讲一下,Thining-Algorithm的算法原理。
一、八领域
我们先来介绍一下,八领域这一个概念
p9 | p2 | p3 |
p8 | p1 | p4 |
p7 | p6 | p5 |
如图,八领域是指包围了中心P1像素的八个像素点。在很多图像处理算法中,八领域的这个概念都极为常见,应用十分广泛。
二、算法原理
首先,我们来看一下以下的几个类型点:
端点 孤立点 内部点 内部点
可以看到,我们是通过八领域中的值,来确定这一点是内部点还是端点和孤立点,从而确定是否保留该点的像素值。
第一步:遍历考察所有的非零点,看是否满足一下四个条件:
a. 2<= p2+p3+p4+p5+p6+p7+p8+p9<=6
b. p2->p9的排列顺序中,01模式的数量为1,比如下面的图中,有p2p3 => 01, p6p7=>01,所以该像素01模式的数量为2。
之所以要01模式数量为1,是要保证删除当前像素点后的连通性。
c. p2*p4*p6 = 0
d. p4*p6*p8 = 0
将满足以上四个条件的点删除(像素值置为0)
第二步:还是通过四个条件来判断点的去留
a. 2<= p2+p3+p4+p5+p6+p7+p8+p9<=6
b. p2->p9的排列顺序中,01模式的数量(这里假设二值图非零值为1)为1。
c. p2*p4*p8 = 0
d. p2*p6*p8 = 0
可以看到其实在本质上,两大步骤中四个条件并没有很大的区别,只是在c、d两个条件上变成不同的方向。
合并c、d两个条件就可以看到,只需要p2、p4、p6、p8四个像素值有一个为零,中心像素p1就该删除。
好了,细化算法的原理就是这么多,其实还是比较简单的,但是有一点需要注意的是,我们在C++上编程,应该注意,其实不用将图片进行归一化,其实只需要简答的将所有的值乘以255就可以了,这样更加方便简单。
下面是源码:
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
//读入图像
cv::Mat img = cv::imread("in.png");
cv::Mat gray;
cv::Mat Binary;
//进行二值化
cv::cvtColor(img, gray, CV_BGR2GRAY);
cv::inRange(gray, 200, 255, Binary);
cv::Mat CopyImg;
int rows = Binary.rows;
int cols = Binary.cols;
std::vector<cv::Point2l> PointSaver1;
//开始第一轮判断
for (int i{ 1 };i<rows-1;i++)
{
uchar* high = Binary.ptr<uchar>(i-1);
uchar* mid = Binary.ptr<uchar>(i);
uchar* low = Binary.ptr<uchar>(i+1);
for (int j{1};j<cols-1;j++)
{
int a1 = mid[j];
int a2 = high[j];
int a3 = high[j+1];
int a4 = mid[j+1];
int a5 = low[j+1];
int a6 = low[j];
int a7 = low[j-1];
int a8 = mid[j-1];
int a9 = high[j-1];
int a[9] = { a1,a2,a3,a4,a5,a6,a7,a8,a9 };
bool req1 = true;
bool req2 = true;
bool req3 = true;
bool req4 = true;
//条件1 八领域的和
if (a[0 == 255])
{
int req1_sum{ 0 };
req1_sum = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8];
//std::cout << "req1_sum: " << req1_sum << std::endl;
if (req1_sum >= 510 && req1_sum <= 1530)
{
//std::cout << "66666" << std::endl;
req1 = true;
}
else req1 = false;
//条件2 01的模式
int req2_sum{ 0 };
// std::cout << "req2_sum: " << req2_sum << std::endl;
for (int k = 2; k < 9; k++)
{
if (a[k] == 255 && a[k - 1] == 0)
{
req2_sum += 1;
}
}
if (req2_sum == 1) req2 = true;
else req2 = false;
//条件三
int req3_sum = a[1] * a[3] * a[5];
//std::cout << "req3_sum: " << req3_sum << std::endl;
if (req3_sum == 0) req3 = true;
else req3 = false;
//条件四
int req4_sum = a[3] * a[5] * a[7];
// std::cout << "req3_sum: " << req3_sum << std::endl;
if (req4_sum == 0) req4 = true;
else req4 = false;
if (req1 && req2 && req3 && req4)
{
PointSaver1.push_back(Point2l(i, j));
}
}
}
}
for (int l=0;l<PointSaver1.size();l++)
{
uchar* ptr = Binary.ptr<uchar>(PointSaver1[l].x);
std::cout << "PointSaver1[l].x: " << PointSaver1[l].x << "PointSaver1[l].y: " << PointSaver1[l].y << std::endl;
ptr[PointSaver1[l].y] = 0;
}
//第二轮判断
std::vector<cv::Point2l> PointSaver2;
for (int i{ 1 }; i < rows - 1; i++)
{
uchar* high = Binary.ptr<uchar>(i - 1);
uchar* mid = Binary.ptr<uchar>(i);
uchar* low = Binary.ptr<uchar>(i + 1);
for (int j{ 1 }; j < cols - 1; j++)
{
int a1 = mid[j];
int a2 = high[j];
int a3 = high[j + 1];
int a4 = mid[j + 1];
int a5 = low[j + 1];
int a6 = low[j];
int a7 = low[j - 1];
int a8 = mid[j - 1];
int a9 = high[j - 1];
int a[9] = { a1,a2,a3,a4,a5,a6,a7,a8,a9 };
bool req1 = true;
bool req2 = true;
bool req3 = true;
bool req4 = true;
//条件1 八领域的和
if (a[0 == 255])
{
int req1_sum{ 0 };
req1_sum = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8];
//std::cout << "req1_sum: " << req1_sum << std::endl;
if (req1_sum >= 510 && req1_sum <= 1530)
{
//std::cout << "66666" << std::endl;
req1 = true;
}
else req1 = false;
//条件2 01的模式
int req2_sum{ 0 };
// std::cout << "req2_sum: " << req2_sum << std::endl;
for (int k = 2; k < 9; k++)
{
if (a[k] == 255 && a[k - 1] == 0)
{
req2_sum += 1;
}
}
if (req2_sum == 1) req2 = true;
else req2 = false;
//条件三
int req3_sum = a[1] * a[3] * a[5];
//std::cout << "req3_sum: " << req3_sum << std::endl;
if (req3_sum == 0) req3 = true;
else req3 = false;
//条件四
int req4_sum = a[1] * a[3] * a[7];
// std::cout << "req3_sum: " << req3_sum << std::endl;
if (req4_sum == 0) req4 = true;
else req4 = false;
if (req1 && req2 && req3 && req4)
{
PointSaver2.push_back(Point2l(i, j));
}
}
}
}
for (int l = 0; l < PointSaver2.size(); l++)
{
uchar* ptr = Binary.ptr<uchar>(PointSaver2[l].x);
std::cout << "PointSaver[l].x: " << PointSaver2[l].x << "PointSaver[l].y: " << PointSaver2[l].y << std::endl;
ptr[PointSaver2[l].y] = 0;
}
cv::imshow("SrcImg", Binary);
std::cout << "saver_size: " << PointSaver2.size() << std::endl;
cv::waitKey(0);
}
感谢各位看官。有啥疑惑或看到本人有啥错漏的话,欢迎留言交流!