图像的距离变换实现了像素与图像区域的距离变换,使得最后生成的图像在该自己元素位置处的像素为0,临近的背景的像素具有较小的值,且随着距离的增大它的的数值也就越大。对于距离图像来说,图像中的每个像素的灰度值为该像素与距离其最近的背景像素间的距离,也就是说,
给每个像素赋值为离它最近的背景像素点与其距离,一幅
二值图像
的距离变换可以提供每个像素到最近的非零像素的距离。
距离变换的一般步骤如下:
1.将输入图片转换为二值图像,前景设置为1,背景设置为0;
2.第一遍水平扫描从左上角开始,依次从左往右逐行扫描,扫描完一行自动跳转到下一行的最左端继续扫描,按行遍历图像。掩膜模板
mask
为
maskL
使用下面的公式进行计算。
其中D表示距离,包括欧氏距离,棋盘距离或是麦哈顿距离,f(p)为像素点p的像素值。
3.第二遍水平扫描从右下角开始,依次从右往左逐行扫描,扫描完一行自动转到上一行的最右端继续扫描,按行遍历图像,掩膜模板mask为
maskR
,方法和上一步相同
4.根据模板maskL和maskR的扫描结果得到最终的距离变换图像。
具体的程序如下:
//距离变换的扫描实现
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
//计算欧氏距离的函数
float calcEuclideanDiatance(int x1, int y1, int x2, int y2)
{
return sqrt(float((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1)));
}
//计算棋盘距离的函数
int calcChessboardDistance(int x1, int y1, int x2, int y2)
{
return cv::max(abs(x1 - x2), (y1 - y2));
}
//计算麦哈顿距离(街区距离)
int calcBlockDistance(int x1, int y1, int x2, int y2)
{
return abs(x1 - x2) + abs(y1 - y2);
}
//距离变换函数的实现
void distanceTrans(Mat &srcImage, Mat &dstImage)
{
CV_Assert(srcImage.data != nullptr);
//CV_Assert()若括号中的表达式值为false,则返回一个错误信息。
//定义灰度图像的二值图像
Mat grayImage, binaryImage;
//将原图像转换为灰度图像
cvtColor(srcImage, grayImage, CV_BGR2GRAY);
//将灰度图像转换为二值图像
threshold(grayImage, binaryImage, 100, 255, THRESH_BINARY);
imshow("二值化图像", binaryImage);
int rows = binaryImage.rows;
int cols = binaryImage.cols;
uchar *pDataOne;
uchar *pDataTwo;
float disPara = 0;
float fDisMIn = 0;
//第一遍遍历图像,使用左模板更新像素值
for (int i = 1; i < rows - 1; i++)
{
//图像的行指针的获取
pDataOne = binaryImage.ptr<uchar>(i);
for (int j = 1; j < cols; j++)
{
//分别计算左模板掩码的相关距离
//PL PL
//PL P
//PL
pDataTwo = binaryImage.ptr<uchar>(i - 1);
disPara = calcEuclideanDiatance(i, j, i - 1, j - 1);
fDisMIn = cv::min((float)pDataOne[j], disPara + pDataTwo[j - 1]);
disPara = calcEuclideanDiatance(i, j, i - 1, j);
fDisMIn = cv::min(fDisMIn, disPara + pDataTwo[j]);
pDataTwo = binaryImage.ptr<uchar>(i);
disPara = calcEuclideanDiatance(i, j, i, j - 1);
fDisMIn = cv::min(fDisMIn, disPara + pDataTwo[j-1]);
pDataTwo = binaryImage.ptr<uchar>(i+1);
disPara = calcEuclideanDiatance(i, j, i+1,j-1);
fDisMIn = cv::min(fDisMIn, disPara + pDataTwo[j - 1]);
pDataOne[j] = (uchar)cvRound(fDisMIn);
}
}
//第二遍遍历图像,使用右模板更新像素值
for (int i = rows - 2; i > 0; i--)
{
pDataOne = binaryImage.ptr<uchar>(i);
for (int j = cols - 1; j >= 0; j--)
{
//分别计算右模板掩码的相关距离
//pR pR
//pR p
//pR
pDataTwo = binaryImage.ptr<uchar>(i + 1);
disPara = calcEuclideanDiatance(i, j, i + 1, j);
fDisMIn = cv::min((float)pDataOne[j], disPara + pDataTwo[j]);
disPara = calcEuclideanDiatance(i, j, i + 1, j + 1);
fDisMIn = cv::min(fDisMIn, disPara + pDataTwo[j+1]);
pDataTwo = binaryImage.ptr<uchar>(i);
disPara = calcEuclideanDiatance(i, j, i, j +1);
fDisMIn = cv::min(fDisMIn, disPara + pDataTwo[j + 1]);
pDataTwo = binaryImage.ptr<uchar>(i - 1);
disPara = calcEuclideanDiatance(i, j, i - 1, j + 1);
fDisMIn = cv::min(fDisMIn, disPara + pDataTwo[j + 1]);
pDataOne[j] = (uchar)cvRound(fDisMIn);
}
}
dstImage = binaryImage.clone();
}
//主函数
int main()
{
Mat srcImage = imread("2345.jpg");
if (!srcImage.data)
{
cout << "读入图片错误!" << endl;
system("pause");
return -1;
}
Mat dstImage;
distanceTrans(srcImage, dstImage);
imshow("距离变换图像", dstImage);
waitKey();
return 0;
}
这里使用的是以计算欧氏距离为例,执行程序后的结果如下:
在OpenCV中也提供了相关的函数
函数distanceTransform函数用来计算原图像中每个像素的距离,函数的声明如下:
CV_EXPORTS_AS(distanceTransformWithLabels) void distanceTransform( InputArray src,
OutputArray dst, OutputArray labels, int distanceType, int maskSize,int labelType=DIST_LABEL_CCOMP );
//! computes the distance transform map
CV_EXPORTS_W void distanceTransform( InputArray src, OutputArray dst, int distanceType, int maskSize );
函数说明
用于计算图像中每一个非零点距离离自己最近的零点的距离,distanceTransform的第二个Mat矩阵参数dst保存了每一个点与最近的零点的距离信息,图像上越亮的点,代表了离零点的距离越远。
第一个参数src是单通道的8bit的二值图像(只有0或1)
第二个参数dst表示的是计算距离的输出图像,可以使单通道32bit浮点数据
第三个参数distanceType表示的是选取距离的类型,可以设置为CV_DIST_L1,CV_DIST_L2,CV_DIST_C等,具体如下:
DIST_USER = ⑴, //!< User defineddistance
DIST_L1 = 1, //!< distance = |x1-x2| + |y1-y2|
DIST_L2 = 2, //!< the simple euclidean distance
DIST_C = 3, //!< distance = max(|x1-x2|,|y1-y2|)
DIST_L12 = 4, //!< L1-L2 metric: distance =2(sqrt(1+x*x/2) - 1))
DIST_FAIR = 5, //!< distance = c^2(|x|/c-log(1+|x|/c)),c = 1.3998
DIST_WELSCH = 6, //!< distance = c^2/2(1-exp(-(x/c)^2)), c= 2.9846
DIST_HUBER = 7 //!< distance = |x|<c ? x^2/2 :c(|x|-c/2), c=1.345
第四个参数maskSize表示的是距离变换的掩膜模板,可以设置为3,5或CV_DIST_MASK_PRECISE,对 CV_DIST_L1 或CV_DIST_C 的情况,参数值被强制设定为 3, 因为3×3 mask 给出5×5 mask 一样的结果,而且速度还更快。
参数labels表示可选输出2维数组
参数labelType表示的是输出二维数组的类型
函数的具体用法如下程序所示:
//使用OpenCV中的相应的函数实现距离变换
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("2345.jpg");
if (!srcImage.data)
{
cout << "读入图片错误!" << endl;
system("pause");
return -1;
}
//转换为灰度图像
Mat grayImage;
cvtColor(srcImage, grayImage, CV_BGR2GRAY);
//转换为二值图像
Mat binaryImage;
threshold(grayImage, binaryImage, 100, 255, THRESH_BINARY);
//距离变换
Mat dstImage;
distanceTransform(binaryImage, dstImage, CV_DIST_L2, CV_DIST_MASK_PRECISE);
//使用欧式距离进行计算
//归一化矩阵
normalize(dstImage, dstImage, 0, 1, NORM_MINMAX);
imshow("二值化图像", binaryImage);
imshow("距离变换后的图像", dstImage);
waitKey();
return 0;
}
程序执行后,执行的结果如下: