距离变换

目标

本篇的学习目标是弄明白以下几个问题

  1. 了解图像度量中的距离是什么,常见的距离有哪些。
  2. 认识本篇的主角-距离变换,弄明白何为距离变换。
  3. 了解距离变换算法有何作用以及在哪些方面有所应用。
  4. 学习距离变换算法的具体实现步骤,理解距离变换算法实现的细节。
  5. 使用c++基于opencv实现距离变换算法。

了解图像度量的距离

距离(distance)是描述图像两点像素之间的远近关系的度量,常见的度量距离有欧式距离(Euchildean distance)、城市街区距离(City block distance)、棋盘距离(Chessboard distance)

  • 欧式距离

欧式距离的定义源于经典的几何学,与我们数学中所学的简单几何的两点之间的距离一致,为两个像素点坐标值得平方根。欧式距离的优点在于其定义非常地直观,是显而易见的,但缺点在于平方根的计算是非常耗时的,。

1
De = sqrt(((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1)));
  • 城市街区距离

城市街区距离也称D4距离,此距离描述的是只允许像素坐标系平面中横向和纵向的移动距离。其定义为

1
D4 = abs(x1 - x2) + abs(y1 - y2);
  • 棋盘距离

如果允许在图像坐标系中像素点的对角线方向的移动,就可以得到棋盘距离,也称D8距离

1
D8 = max(abs(x1 - x2), (y1 - y2));

下图表示了三个距离之间的关系

距离度量.png

何为距离变换

距离变换(distance transfer)也称距离函数(distance function)或者斜切算法(chamfering algorithm),它是图像中距离概念的一个应用。距离变换得到的结果是像素点与图像中某个区域(或者边界)的距离,区域内部像素点距离变换结果为0,邻近的像素点变换结果为较小值,而离区域越远则数值越大。

以下图为例说明,一幅二值图像像素值为0或1,令1表示物体区域,0表示背景。

原图.png

距离变换采用D4(城市街区距离),所得结果为

D4变换结果.png

可以看到,原本像素值为1的区域均变为0,因为它门到区域的距离为0,其他像素点随着距离越远数值越大。所以说,距离变换所得的结果就是像素点与目标区域的距离分布情况。

距离变换有何作用

距离变换有很多的应用,首先最直接的是在图像处理中作为其他图像处理算法的基础,如求取最近特征、骨架抽取。另外也在离散几何,移动机器人领域中的路径规划和障碍躲避也有其作用。

距离变换算法步骤

1
2
3
AL AL
AL P          mask1
AL
1
2
3
   BR
P  BR         mask2
BR BR

1.

将图像进行二值化,子图像值为0,背景为255;

2.

利用Mask 1从左向右,从上到下扫描,p点是当前像素点,q点是Mask 1中AL邻域中的点,D()为距离计算,包括棋盘距离、城市距离和欧式距离。F(p)为p点的像素值,计算

1
F(p) = min( F(p),  F(q)+D(p,q) ), 其中,q属于AL.

3.

再利用Mask 2从右向左,从下向上扫描,计算

F(p) = min( F(p), F(q)+D(p,q) ), 其中,q属于BR

4.

F(p) 则为距离变换后的图像。

代码实现距离变换算法

  • 头文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once

// Copyright https://mangoroom.cn
// License(MIT)
// Author:mango
// distance transfer | 距离变换
// this is distance_transform.h

#include<opencv2/opencv.hpp>


namespace imageprocess
{
	// Distance transform function:距离变换函数
	void DistanceTransform(const cv::Mat& src_image, cv::Mat& dst_image);

	// Calculate City block distance: 计算城市街区距离
	int D4(const int& x1, const int& x2, const int& y1, const int& y2);

	// Calculate chessboard distance:计算棋盘距离
	int D8(const int& x1, const int& x2, const int& y1, const int& y2);

}//namespace imageproccess
  • 源文件
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125


// Copyright https://mangoroom.cn
// License(MIT)
// Author:mango
// distance transfer | 距离变换
// this is distance_transform.cpp

#include"distance_transfer.h"
#include<array>

void imageprocess::DistanceTransform(const cv::Mat& src_image, cv::Mat& dst_image)
{
	//step1: check the input parameters: 检查输入参数
	assert(!src_image.empty());
	assert(src_image.channels() == 1);

	//step2: initialize dst_image : 初始化目标图像
	cv::threshold(src_image, dst_image, 100, 255, cv::THRESH_BINARY);


	//step3: pass throuth from top to bottom, left to right: 从上到下,从做到右遍历
	for (size_t i = 1; i < dst_image.rows - 1; i++)
	{
		for (size_t j = 1; j < dst_image.cols; j++)
		{
			//AL  AL	
			//AL  P
			//AL
			std::array<cv::Point, 4> AL;
			AL.at(0) =  cv::Point( i - 1, j - 1 );
			AL.at(1) =  cv::Point( i - 1, j );
			AL.at(2) =  cv::Point( i, j - 1 );
			AL.at(3) =  cv::Point(i + 1, j - 1 );

			int Fp = dst_image.at<uchar>(i, j);

			//Fq
			std::array<int, 4> Fq = { 0 };
			Fq.at(0) = dst_image.at<uchar>(i - 1, j - 1);
			Fq.at(1) = dst_image.at<uchar>(i - 1, j);
			Fq.at(2) = dst_image.at<uchar>(i, j - 1);
			Fq.at(3) = dst_image.at<uchar>(i + 1, j - 1);

			std::array<int, 4> Dpq = { 0 };
			std::array<int, 4> DpqAddFq = { 0 };
		
			for (size_t k = 0; k < 4; k++)
			{
				//D(p, q)
				Dpq.at(k) = D4(i, AL.at(k).x, j, AL.at(k).y);
				//D(p,q) + F(q)
				DpqAddFq.at(k) = Dpq.at(k) + Fq.at(k);
			}
			//F(p) = min[F(p), D(p,q) + F(q)]
			std::sort(DpqAddFq.begin(), DpqAddFq.end());
			
			auto min = DpqAddFq.at(0);
			Fp = std::min(Fp, min);

			dst_image.at<uchar>(i, j) = Fp;

		}
	}

	
	//step4: pass throuth from bottom to top, right to left: 从下到上,从右到左遍历

	for (int i = dst_image.rows - 2; i > 0; i--)
	{
		for (int j = dst_image.cols -  2; j >= 0 ; j--)
		{
			//	    BR
			//  P   BR
			//  BR  BR
			std::array<cv::Point, 4> BR;
			BR.at(0) = cv::Point( i - 1, j + 1 );
			BR.at(1) = cv::Point( i , j + 1 );
			BR.at(2) = cv::Point( i + 1, j + 1 );
			BR.at(3) = cv::Point( i + 1, j );

			int Fp = dst_image.at<uchar>(i, j);

			//Fq
			std::array<int, 4> Fq = { 0 };
			Fq.at(0) = dst_image.at<uchar>(i - 1, j + 1);
			Fq.at(1) = dst_image.at<uchar>(i, j + 1);
			Fq.at(2) = dst_image.at<uchar>(i + 1, j + 1);
			Fq.at(3) = dst_image.at<uchar>(i + 1, j);

			std::array<int, 4> Dpq = { 0 };
			std::array<int, 4> DpqAddFq = { 0 };

			for (size_t k = 0; k < 4; k++)
			{
				//D(p, q)
				Dpq.at(k) = D4(i, BR.at(k).x, j, BR.at(k).y);
				//D(p,q) + F(q)
				DpqAddFq.at(k) = Dpq.at(k) + Fq.at(k);
			}

			//F(p) = min[F(p), D(p,q) + F(q)]
			std::sort(DpqAddFq.begin(), DpqAddFq.end());

			auto min = DpqAddFq.at(0);
			Fp = std::min(Fp, min);

			dst_image.at<uchar>(i, j) = static_cast<uchar>(Fp);

		}
	}

	
}


int imageprocess::D4(const int& x1, const int& x2, const int& y1, const int& y2)
{
	return abs(x1 - x2) + abs(y1 - y2);
}

int imageprocess::D8(const int& x1, const int& x2, const int& y1, const int& y2)
{
	return cv::max(abs(x1 - x2), (y1 - y2));
}
  • 测试
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Copyright https://mangoroom.cn
// License(MIT)
// Author:mango
// image proccess algorithm  | 图像处理算法
// this is main.cpp

#include"distance_transfer.h"



int main()
{
	cv::Mat src = cv::Mat::zeros(cv::Size(600, 400), CV_8UC1);

	for (size_t i = 100; i < 180; i++)
	{
		for (size_t j = 200; j < 400; j++)
		{
			src.at<uchar>(i, j) = 255;
		}
	}

	
	cv::Mat dst = src.clone();
	imageprocess::DistanceTransform(src, dst);
	normalize(dst, dst, 0, 255, cv::NORM_MINMAX);
	
	// opencv 
	/*cv::threshold(src, src, 100, 255, cv::THRESH_BINARY);
	cv::distanceTransform(src, dst, cv::DIST_L1, cv::DIST_MASK_PRECISE);
	normalize(dst, dst, 0, 1, cv::NORM_MINMAX);*/


	cv::imshow("src", src);
	cv::imshow("dst", dst);

	cv::imwrite("dst.jpg", dst);
	cv::waitKey(0);

	return 0;
}

执行结果

运行代码结果.png


本文由芒果浩明发布,转载请注明来源。 本文链接:https://blog.mangoeffect.net/opencv/distance-transfer.html


微信公众号