距离变换是图像处理中常用的一种图像变换算法,它计算出每个像素离图像中满足某个特定条件的像素的距离,然后使用这个计算出的距离进行灰度值的变换 。
常用的距离有:欧几里德距离、棋盘距离、街区距离(曼哈顿距离)。这三个距离具体的数学定义这不作介绍,网上很容易查到资料。这三个距离中最常用的距离是欧几里德距离。
距离变换的应用非常广泛,以下是几个常见的应用:
形态学分割:距离变换可以用于形态学分割,通过计算图像中每个像素到离它最近的背景像素的距离,可以得到一张距离图像,再通过应用阈值分割,可以得到一个二值图像,其中距离小于阈值的像素会被标记为前景,距离大于等于阈值的像素会被标记为背景,从而实现图像分割。
图像形态学:距离变换可以用于图像形态学操作,如骨架提取、形态学梯度等。通过计算图像中每个像素到其周围像素的距离,可以得到一张距离图像,再通过应用形态学操作,如膨胀和腐蚀等,可以得到不同的形态学变换结果。
边缘检测:距离变换可以用于边缘检测,通过计算图像中每个像素到最近的边缘像素的距离,可以得到一张距离图像,再通过应用非极大值抑制和阈值分割等操作,可以得到图像的边缘。
模板匹配:距离变换可以用于模板匹配,通过计算图像中每个像素到模板像素的距离,可以得到一张距离图像,再通过应用阈值分割等操作,可以得到匹配结果。
以上关于其应用的描述,估计初学者看着也不知道是怎么回事,没关系,本篇博文会举两个简单的例子来让大家理解图像的距离变换究竟是怎么回事。
第一个例子:
第一个例子我们利用距离变换去除人手的手指部分。
显然,手指距零像素点的距离比手掌距零像素点的距离短,所以经过距离变换后的图像在手指部位的像素值较小(如下图“dst”窗口图像所示),通过设定合理的阈值对距离变换后的图像进行二值化处理,则可得到去除手指的图像(如下图“bidist”窗口图像所示)。
这个例子的代码略。
第二个例子:
利用距离变换找出图形的几何中心。
在该例子中我们会用到OpenCV提供的函数distanceTransform()进行距离变换。
OpenCV提供了函数distanceTransform()用于计算图像中每个像素到最近的零像素(即黑色像素)的距离。
函数distanceTransform()的原型有两个,常用的那个原型如下:
C++: void distanceTransform(InputArray src,
OutputArray dst,
int distanceType,
int maskSize,
int dstType=CV_32F )
参数意义如下:
src---准备作距离变换的原图像。
dst---经过距离变换后的输出图像。
distanceType---距离类型,可选的类型如下:
maskSize—计算距离时使用的mask矩阵大小。这个参数的意义不太好理解,如需了解这个参数的详情,请访问本博文的原文了解,本篇博文的原文链接 https://www.hhai.cc/thread-224-1-1.html
dstType:输出图像(矩阵)的数据类型,可以是CV_8U 或 CV_32F。当选择CV_8U时,distanceType的类型只能为DIST_L1。
接下来我们就利用距离变换的原理,并结合OpenCV的函数distanceTransform(),实现找到下面这个图形的几何中心。
代码如下:
代码中用到的图像的百度网盘下载链接如下:
https://pan.baidu.com/s/1fhjZ1jpxM-tG_9NHo6ADMw?pwd=u2i8
//出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术
//博主微信/QQ 2487872782
//QQ群 271891601
//欢迎技术交流与咨询
//OpenCV版本 OpenCV3.0
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
float maxValue = 0; //存储距离变换结果矩阵中的最大值
Point Pt(0, 0);
Mat image = imread("E:/material/images/2023/2023-02/ceter.jpg");
Mat image_1 = imread("E:/material/images/2023/2023-02/ceter.jpg");
if (image.empty())
{
cout << "Error: Could not load image" << endl;
return 0;
}
Mat imageGray;
cvtColor(image, imageGray, CV_BGR2GRAY);
imageGray = ~imageGray; //为什么要取反?我在下面的博文中有说明。
threshold(imageGray, imageGray, 20, 200, CV_THRESH_BINARY); //阈值化
Mat imageThin(imageGray.size(), CV_32FC1); //定义保存距离变换结果的Mat矩阵
distanceTransform(imageGray, imageThin, CV_DIST_L2, 3); //距离变换
//距离变换后的最大值我们认为是图形的几何中心
Mat distShow;
distShow = Mat::zeros(imageGray.size(), CV_8UC1);
for (int i = 0; i<imageThin.rows; i++)
{
for (int j = 0; j<imageThin.cols; j++)
{
distShow.at<uchar>(i, j) = imageThin.at<float>(i, j);
if (imageThin.at<float>(i, j)>maxValue)
{
maxValue = imageThin.at<float>(i, j); //获取距离变换的最大值
Pt = Point(j, i); //记录最大值的坐标
}
}
}
normalize(distShow, distShow, 0, 255, CV_MINMAX); //为了显示清晰,做0~255归一化
circle(image, Pt, maxValue, Scalar(0, 0, 255), 3);//在原图中标示出最大距离半径
circle(image, Pt, 3, Scalar(0, 255, 0), 3);//在原图中标示出几何中心
imshow("Source Image", image_1);
imshow("Dist_transform", distShow);
imshow("Find centroid ", image);
waitKey();
return 0;
}
以上代码有两点我需要说明一下:
①为什么要对原图的灰度图像作取反处理?
答:因为函数distanceTransform()求的是每个像素到最近的零像素(即黑色像素)的距离。
②为什么距离变换结果矩阵中最大值的点坐标是“ Point(j, i)”,而不是“Point(i, j)”?
答:这里的坐标是指直角坐标系的坐标,在直角坐标系中,第一个数对应的是x轴的坐标,第二个数对应的y轴的坐标。x轴对应的坐标值是矩阵中元素的列号,y轴对应的坐标值是矩阵中元素的行号。
运行结果如下:
从上面的结果我们可以看出,通过利用距离变换,我们成功找到了图像的几何中心。
理解了以上两个例子,相信大家就对距离变换的概念和作用有一个较深的认识了。