OpenCV:distanceTransform距离变换函数
1、OpenCV函数distanceTransform():
功能:用来计算原图像中距离变换图像;
void distanceTransform( InputArray src,
OutputArray dst,
OutputArray labels,
int distanceType,
int maskSize,
int labelType=DIST_LABEL_CCOMP );
函数说明:
用于计算图像中每一个非零点像素与其最近的零点像素之间的距离,输出的是保存每一个非零点与最近零点的距离信息;图像上越亮的点,代表了离零点的距离越远。
参数:
src是单通道的8bit的二值图像(只有0或1)
dst表示的是计算距离的输出图像,可以使单通道32bit浮点数据
distanceType表示的是选取距离的类型,可以设置为CV_DIST_L1,CV_DIST_L2,CV_DIST_C等,具体如下:
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表示的是输出二维数组的类型;

2、细化轮廓:
代码如下:
#include <opencv2\opencv.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\features2d\features2d.hpp>
#include <opencv2\core\core.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcImg=imread("raw.jpg",1);
Mat imageGray;
cvtColor(srcImg,imageGray,CV_RGB2GRAY);
imageGray=~imageGray; //对灰度图取反
GaussianBlur(imageGray,imageGray,Size(5,5),2); //滤波
threshold(imageGray,imageGray,20,200,CV_THRESH_BINARY); //阈值
imshow("threshold",imageGray);
Mat distanceImg(imageGray.size(),CV_32FC1); //距离变换结果的Mat矩阵
distanceTransform(imageGray,distanceImg,CV_DIST_L2,3); //距离变换
Mat dist;
normalize(distanceImg,dist, 0, 1, NORM_MINMAX);
imshow("distanceImg",dist);
Mat distShowImg;
distShowImg=Mat::zeros(imageGray.size(),CV_8UC1); //定义细化后的字符轮廓
float maxValue=0;//距离变换矩阵中的最大值
for(int i=0;i<distanceImg.rows;i++)
{
for(int j=0;j<distanceImg.cols;j++)
{
if(distanceImg.at<float>(i,j) > maxValue)
{
maxValue=distanceImg.at<float>(i,j); //获取距离变换的极大值
}
}
}
for(int i=0;i<distanceImg.rows;i++)
{
for(int j=0;j<distanceImg.cols;j++)
{
if(distanceImg.at<float>(i,j) > maxValue/1.9)
{
distShowImg.at<unsigned char>(i,j)=255; //符合距离大于最大值一定比例条件的点设为255
}
}
}
imshow("Source Image",srcImg);
imshow("thinImg",distShowImg);
waitKey(0);
return 0;
}


3、查找物体质心:
int main()
{
Mat srcImg=imread("raw1.jpg",1);
imshow("原图",srcImg);
Mat imgGray;
cvtColor(srcImg,imgGray,CV_RGB2GRAY);
imgGray=~imgGray;
GaussianBlur(imgGray,imgGray,Size(5,5),2); //滤波
threshold(imgGray,imgGray,20,200,CV_THRESH_BINARY); //阈值化
Mat distanceImg(imgGray.size(),CV_32FC1); //距离变换结果的Mat矩阵
distanceTransform(imgGray,distanceImg,CV_DIST_L2,3); //距离变换
Mat dist;
normalize(distanceImg,dist, 0, 1, NORM_MINMAX);
imshow("distanceImg",dist);
Mat distShow;
distShow=Mat::zeros(imgGray.size(),CV_8UC1); //细化后的字符轮廓
float maxValue=0; //距离变换矩阵中的最大值
Point Pt(0,0);
for(int i=0;i<distanceImg.rows;i++)
{
for(int j=0;j<distanceImg.cols;j++)
{
distShow.at<unsigned char>(i,j)=distanceImg.at<float>(i,j);
if(distanceImg.at<float>(i,j) > maxValue)
{
maxValue=distanceImg.at<float>(i,j); //获取距离变换的极大值
Pt=Point(j,i);//坐标
}
}
}
circle(srcImg,Pt,maxValue,Scalar(0,0,255),3);
circle(srcImg,Pt,3,Scalar(0,255,0),3);
imshow("SImage",srcImg);
imshow("Thin Image",distShow);
waitKey();
return 0;
}

4、编程思想:
图像距离变换的一般步骤如下:
①将输入图片转换为二值图像,前景设置为1,背景设置为0;
②先遍历图像:左,左上,上,左下
使用下面的公式进行计算。
其中,D表示距离包括欧式距离,棋盘距离和麦哈顿距离;
掩膜模板mask为maskL;
f(p)为像素点p的像素值;
③再次遍历图像,右,右上,右下,下;
④根据模板maskL和maskR的扫描结果得到最终的距离变换图像。
为了减少计算了量,采用了一种倒角模版的算法,只需要对图像进行两次扫描玖可以实现距离变换,该方法被称为chamfer倒角距离变换,该模版如下:


5、实现:
#include <opencv2\opencv.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\features2d\features2d.hpp>
#include <opencv2\core\core.hpp>
using namespace std;
using namespace cv;
//计算欧氏距离的函数
float calEuclideanDiatance(int x1, int y1, int x2, int y2)
{
return sqrt(float((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1)));
}
//计算棋盘距离的函数
int calChessboardDistance(int x1, int y1, int x2, int y2)
{
return max(abs(x1 - x2), (y1 - y2));
}
//计算麦哈顿距离(街区距离)
int calBlockDistance(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);
Mat grayImage, binaryImage;
cvtColor(srcImage, grayImage, CV_BGR2GRAY);
grayImage = ~grayImage;
threshold(grayImage, binaryImage, 20, 200, THRESH_BINARY);
imshow("二值化图像", binaryImage);
int rows = binaryImage.rows;
int cols = binaryImage.cols;
unsigned char *pDataOne;
unsigned char *pDataTwo;
float disPara = 0;
float fDisMIn = 0;
//第一遍:遍历图像,使用左模板更新像素值
for (int i = 1; i < rows - 1; i++)
{
//图像的行指针的获取
pDataOne = binaryImageNaNr<uchar>(i);
for (int j = 1; j < cols; j++)
{
//分别计算左模板掩码的相关距离
//PL PL
//PL P
//PL
pDataTwo = binaryImageNaNr<uchar>(i - 1);//上一行
disPara = calEuclideanDiatance(i, j, i - 1, j - 1);//当前像素 左上角像素
fDisMIn = min((float)pDataOne[j], disPara + pDataTwo[j - 1]);//模板
disPara = calEuclideanDiatance(i, j, i - 1, j);//当前像素 上方像素
fDisMIn = min(fDisMIn, disPara + pDataTwo[j]);
pDataTwo = binaryImageNaNr<uchar>(i);//
disPara = calEuclideanDiatance(i, j, i, j - 1);//当前像素 左方像素
fDisMIn = min(fDisMIn, disPara + pDataTwo[j-1]);
pDataTwo = binaryImageNaNr<uchar>(i+1);
disPara = calEuclideanDiatance(i, j, i+1,j-1);//当前像素 左下方像素
fDisMIn = min(fDisMIn, disPara + pDataTwo[j - 1]);
pDataOne[j] = (unsigned char)cvRound(fDisMIn);
}
}
//第二遍:遍历图像,使用右模板更新像素值
for (int i = rows - 2; i > 0; i--)//hang
{
pDataOne = binaryImageNaNr<uchar>(i);
for (int j = cols - 1; j >= 0; j--) //lie
{
//分别计算右模板掩码的相关距离
//pR pR
//pR p
//pR
pDataTwo = binaryImageNaNr<uchar>(i + 1);//下一行
disPara = calEuclideanDiatance(i, j, i + 1, j); //下方
fDisMIn = min((float)pDataOne[j], disPara + pDataTwo[j]);
disPara = calEuclideanDiatance(i, j, i + 1, j + 1);//右下方
fDisMIn = cv::min(fDisMIn, disPara + pDataTwo[j+1]);
pDataTwo = binaryImageNaNr<uchar>(i);
disPara = calEuclideanDiatance(i, j, i, j+1);//右方
fDisMIn = cv::min(fDisMIn, disPara + pDataTwo[j + 1]);
pDataTwo = binaryImageNaNr<uchar>(i - 1);
disPara = calEuclideanDiatance(i, j, i - 1, j + 1); //右上方
fDisMIn = min(fDisMIn, disPara + pDataTwo[j + 1]);
pDataOne[j] = (unsigned char)cvRound(fDisMIn);
}
}
dstImage = binaryImage.clone();
}
////距离变换的扫描实现
int main()
{
Mat srcImage = imread("raw1.jpg");
imshow("srcImg",srcImage);
if (!srcImage.data)
{
cout << "读入图片错误!" << endl;
system("pause");
return -1;
}
Mat dstImage;
distanceTrans(srcImage, dstImage);
imshow("距离变换图像", dstImage);
waitKey();
return 0;
}
