2016-07-29 17:45:00 chuifuhuo6864 阅读数 64

图像处理之倒角距离变换

图像处理中的倒角距离变换(Chamfer Distance Transform)在对象匹配识别中经常用到,

算法基本上是基于3x3的窗口来生成每个像素的距离值,分为两步完成距离变换,第一

步从左上角开始,从左向右、从上到下移动窗口扫描每个像素,检测在中心像素x的周

围0、1、2、3四个像素,保存最小距离与位置作为结果,图示如下:


第二步从底向上、从右向左,对每个像素,检测相邻像素4、5、6、7保存最小距离与

位置作为结果,如图示所:


完成这两步以后,得到的结果输出即为倒角距离变换的结果。完整的图像倒角距离变

换代码实现可以分为如下几步:

1.      对像素数组进行初始化,所有背景颜色像素点初始距离为无穷大,前景像素点距

  离为0

2.      开始倒角距离变换中的第一步,并保存结果

3.      基于第一步结果完成倒角距离变换中的第二步

4.      根据距离变换结果显示所有不同灰度值,形成图像

最终结果显示如下(左边表示原图、右边表示CDT之后结果)


完整的二值图像倒角距离变换的源代码如下:

package com.gloomyfish.image.transform;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.Arrays;

import com.gloomyfish.filter.study.AbstractBufferedImageOp;

public class CDTFilter extends AbstractBufferedImageOp {
	private float[] dis; // nn-distances
	private int[] pos; // nn-positions, 32 bit index
	private Color bakcgroundColor;
	
	public CDTFilter(Color bgColor)
	{
		this.bakcgroundColor = bgColor;
	}

	@Override
	public BufferedImage filter(BufferedImage src, BufferedImage dest) {
		int width = src.getWidth();
		int height = src.getHeight();

		if (dest == null)
			dest = createCompatibleDestImage(src, null);

		int[] inPixels = new int[width * height];
		pos = new int[width * height];
		dis = new float[width * height];
		src.getRGB(0, 0, width, height, inPixels, 0, width);
		// 随机生成距离变换点
		int index = 0;
		Arrays.fill(dis, Float.MAX_VALUE);
		int numOfFC = 0;
		for (int row = 0; row < height; row++) {
			for (int col = 0; col < width; col++) {
				index = row * width + col;
				if (inPixels[index] != bakcgroundColor.getRGB()) {
					dis[index] = 0;
					pos[index] = index;
					numOfFC++;
				}
			}
		}
		final float d1 = 1;
		final float d2 = (float) Math.sqrt(d1 * d1 + d1 * d1);
		System.out.println(numOfFC);
		float nd, nd_tmp;
		int i, in, cols, rows, nearestPixel;

		// 1 2 3
		// 0 i 4
		// 7 6 5
		// first pass: forward -> L->R, T-B
		for (rows = 1; rows < height - 1; rows++) {
			for (cols = 1; cols < width - 1; cols++) {
				i = rows * width + cols;

				nd = dis[i];
				nearestPixel = pos[i];
				if (nd != 0) { // skip background pixels
					in = i;

					in += -1; // 0
					if ((nd_tmp = d1 + dis[in]) < nd) {
						nd = nd_tmp;
						nearestPixel = pos[in];
					}

					in += -width; // 1
					if ((nd_tmp = d2 + dis[in]) < nd) {
						nd = nd_tmp;
						nearestPixel = pos[in];
					}

					in += +1; // 2
					if ((nd_tmp = d1 + dis[in]) < nd) {
						nd = nd_tmp;
						nearestPixel = pos[in];
					}

					in += +1; // 3
					if ((nd_tmp = d2 + dis[in]) < nd) {
						nd = nd_tmp;
						nearestPixel = pos[in];
					}

					dis[i] = nd;
					pos[i] = nearestPixel;
				}
			}
		}

		// second pass: backwards -> R->L, B-T
		// exactly same as first pass, just in the reverse direction
		for (rows = height - 2; rows >= 1; rows--) {
			for (cols = width - 2; cols >= 1; cols--) {
				i = rows * width + cols;

				nd = dis[i];
				nearestPixel = pos[i];
				if (nd != 0) {
					in = i;

					in += +1; // 4
					if ((nd_tmp = d1 + dis[in]) < nd) {
						nd = nd_tmp;
						nearestPixel = pos[in];
					}

					in += +width; // 5
					if ((nd_tmp = d2 + dis[in]) < nd) {
						nd = nd_tmp;
						nearestPixel = pos[in];
					}

					in += -1; // 6
					if ((nd_tmp = d1 + dis[in]) < nd) {
						nd = nd_tmp;
						nearestPixel = pos[in];
					}

					in += -1; // 7
					if ((nd_tmp = d2 + dis[in]) < nd) {
						nd = nd_tmp;
						nearestPixel = pos[in];
					}

					dis[i] = nd;
					pos[i] = nearestPixel;

				}
			}
		}

		for (int row = 0; row < height; row++) {
			for (int col = 0; col < width; col++) {
				index = row * width + col;
				if (Float.MAX_VALUE != dis[index]) {
					int gray = clamp((int) (dis[index]));
					inPixels[index] = (255 << 24) | (gray << 16) | (gray << 8)
							| gray;
				}
			}
		}
		setRGB(dest, 0, 0, width, height, inPixels);
		return dest;
	}

	private int clamp(int i) {
		return i > 255 ? 255 : (i < 0 ? 0 : i);
	}

}
转载请注明出处!!

转载于:https://my.oschina.net/abcijkxyz/blog/721111

2019-07-26 10:22:40 weixin_43925403 阅读数 53

倒角距离变换(Chamfer Distance Transform)

在这里插入图片描述
图a,b为距离变换模板,设其值分别为a[1-5],b[5-9]。
图c为待处理的二维二值化图像,其中1代表目标区域,0代表背景区域。设其值为c[m][n]。
目标是求出目标区域中的每一个点距离背景区域的最小距离,即图d。
倒角距离变化算法思路如下:
(1)首先对原始二值矩阵进行放大得到c2,整体乘以一个膨胀因子p。p的大小会决定能算出的最大距离,即求出的最终距离矩阵中的数值上界,p越大距离越大。
(2)使用距离变换模板图a对二值化图像c2从左到右、从上到下的进行移动遍历。如果该点为0则跳过,为1则使用模板a[5]对应该点,计算该点的a[1-5]所对应的每一个点的像素值与a中每一个点的模板值之和。
如果该点为矩阵的四周上的点,即模板上部分点超出了原始矩阵的边界,则设置这些点的像素值为极大值1*p。不过为了优化算法效率,实际计算时可以仅对矩阵的非边界区域进行操作,即前向时不计算第1行,第1列和最后1列。这样做的最终效果是:只有地图的左右两列未被计算,并不影响我们在局部路径规划中的使用。
最终该点保留的值为这5个和值的最小值,得到矩阵f1具体过程以下图为例:
在这里插入图片描述
在这里插入图片描述
(3)使用模板b对前向遍历的结果矩阵f1从右到左、从下到上的进行移动遍历。操作与上一步相同,得到矩阵f2。同上,不计算矩阵的最后1行,第1列和最后1列。
(4)f2即为最终矩阵,除以3之后形成图d,效果图如下:
在这里插入图片描述
原始二值地图
在这里插入图片描述
距离地图

%对二值地图进行倒角距离变换,输出一张距离地图。
inf_factor=1200;%膨胀因子,代表像素值的上线,极大值
raw_a=zeros(1000,1000);
for i=300:700
    for j=200:300
        raw_a(i,j)=1;
    end
end
for i=300:700
    for j=700:800
        raw_a(i,j)=1;
    end
end
raw_a=double(~raw_a)*inf_factor;

figure('NumberTitle', 'off', 'Name', '原始二值地图');
imagesc(raw_a);
colormap(flipud(gray));

h=size(raw_a,1);
w=size(raw_a,2);
a_transpro=raw_a;

tic
for i=2:h%前向遍历过程,从上到下、从左到右
    for j=2:w-1
        tmp=a_transpro(i,j);%初值取目标像素点初始值
        if a_transpro(i,j)%只操作前景点,即为1的点
            tmp=min(a_transpro(i-1,j-1)+4,tmp);
            tmp=min(a_transpro(i-1,j)+3,tmp);
            tmp=min(a_transpro(i-1,j+1)+4,tmp);
            tmp=min(a_transpro(i,j-1)+3,tmp);
            a_transpro(i,j)=tmp;%最终保留几个和值的最小值作为该点距离值
        end
    end
end

for i=h-1:-1:1%从下到上、从右到左
    for j=w-1:-1:2
        tmp=a_transpro(i,j);
        if a_transpro(i,j)
            tmp=min(a_transpro(i+1,j+1)+4,tmp);
            tmp=min(a_transpro(i+1,j)+3,tmp);
            tmp=min(a_transpro(i+1,j-1)+4,tmp);
            tmp=min(a_transpro(i,j+1)+3,tmp);
            a_transpro(i,j)=tmp;
        end
    end
end

a_final=a_transpro/3;%遍历结束后除以3作为最终结果
t=toc

figure('NumberTitle', 'off', 'Name', '距离地图');
imagesc(a_final);
colormap(flipud(gray));

2019-04-11 02:10:11 qq_35306281 阅读数 262

@图像处理_距离变换_c++代码实现

距离变换:
分为:欧式距离,城区距离,棋盘距离。

算法步骤:

  1. 输入:二值图像。
  2. 从图像左上角第二行开始,从左向右、从上到下移动窗口扫描每个像素,类似滤波过程,检测在中心像素P的周围四个像素与中心像素的距离,若中心像素为0,则跳过。保存最小距离与位置作为结果。
  3. 从右下角倒数第二行开始,从右向左,从下到上扫描图像。其它同2。
  4. 输出图像。

代码如下:

/*created at 2019/4/11*/
#include <iostream>
#include <algorithm>
#include <opencv2/opencv.hpp>

void distance_transform_3x3(cv::Mat& src)
{
	int rows = src.rows;
	int cols = src.cols;
	float sum[5];
	/*第一次扫描*/
	for(int r = 1; r < rows - 1; ++r)
	{
		for(int c = 1; c < cols - 1; ++c)
		{
			if(src.at<uchar>(r,c))
			{
				sum[0] = 1.4142 + src.at<uchar>(r-1,c-1);
				sum[1] = 1      + src.at<uchar>(r-1,  c);
				sum[2] = 1.4142 + src.at<uchar>(r-1,c+1);
				sum[3] = 1	    + src.at<uchar>(r,  c-1);
				sum[4] =          src.at<uchar>(r,    c); 
				std::sort(sum, sum+5);
				src.at<uchar>(r,c) = sum[0];
			}
		}
	}
	/*第二次扫描*/
	for(int r = rows - 1; r > 0; --r)
	{
		for(int c = cols - 1; c > 0; --c)
		{
			if(src.at<uchar>(r,c))
			{
				sum[0] =          src.at<uchar>(r,    c);
				sum[1] = 1	    + src.at<uchar>(r,  c+1);
				sum[2] = 1.4142 + src.at<uchar>(r+1,c-1);
				sum[3] = 1      + src.at<uchar>(r+1,  c);
				sum[4] = 1.4142	+ src.at<uchar>(r+1,c+1);
				std::sort(sum, sum+5);
				src.at<uchar>(r,c) = sum[0];
			}
		}
	}
}

int main(int argc, char* argv[])
{
	cv::Mat src = cv::imread("test.jpg");
	distance_transform_3x3(src );
	cv::imshow("out.jpg", src );
	cv::waitKey(0);
	return 0;
}

原图:
在这里插入图片描述
输出:
在这里插入图片描述

2018-05-11 17:07:53 u013921430 阅读数 1141

 

fishing-panhttps://blog.csdn.net/u013921430转载请注明出处】

前言

       距离变换(distance transform,DT)在图像处理、计算机视觉等领域有非常多的运用,例如骨架化、目标细化、黏连目标分离等。在Matlab和OpenCV中都有进行距离变换的函数。Matlab中有bwdist函数用于二值化图像的距离变换, OpenCV中有cvDistTransform函数用于进行二值化图像的距离变换。

      除此之外,Matlab中还提供了graydist函数用于灰度加权距离变换,但是graydist居然要输入一个种子点,这让我觉得很神奇,因为在我的印象中灰度加权距离变换是不需要种子点的!自己测试运行后就发现,这是扯淡呢!算出来的结果是图像中各个点到种子点的距离加权变换。所以,我必须来讲讲我所理解的不需要种子点的灰度加权距离变换。

灰度加权距离变换

       一般的距离变换需要首先对图像进行二值化,这样的过程会损失掉图像中某些有用的信息,因为单纯的距离变换获得的是前景点到背景的最短距离,损失了灰度信息所代表的物理意义。因此在距离变换的基础上加上图像灰度信息,就有了灰度加权距离变换——GWDT(gray-weighted image distance transform),也可以称之为灰度尺度距离变换——GSDT(gray-scale image distance transform)。

       理解了距离变换,灰度加权距离变换就很好理解了,就是在距离变换的基础上加上了灰度信息。那么如何操作呢?我采用类似于区域增长的方法,在快速行进法的框架下来进行实现它,基本步骤如下;

1.    设定阈值分离前景和背景,将背景点的状态设置为ALIVE(其实所有的背景点都是seed point),将前景设为FAR,前景点的值初始化为无限大;

2.    初始化边缘点,在FAR点中选取8邻域(对二维图像进行处理)中有背景点的点,这些点就是边缘点,将边缘点的值赋予原图中的值,更改其状态为TRAIL,并将其放入一个队列Q中;

3.    在队列Q中不放回地取出点,假设取出的点记为y,计算y周围的8个点x,根据以下公式计算x,如果x的值被更新了,判断其是否已经在队列中,如果不在,那么就将其放入队列中。(在我实现的时候,考虑到效率的因素,并没有判断其是否已经在队列中,而是将其直接放入队列中,这个跟queue类和C++函数传参有关系

4.    循环第三步,直到队列Q为空。

       从上面的步骤中可以看出,实现过程与快速行进算法还是存在一定的差异的,如果按照快速行进算法的思想,那么对于点y,只会计算周围8临域中属性是FAR的点的值,不会对ALIVE的点进行更新。但是上述的过程,会对ALIVE点进行更新。我这样做是为了尽量保证每个点在变换过后值尽量小,大家可以自行修改代码来决定要不要对ALIVE点进行更新。 


       如下图所示,假设图中蓝色的点为初始化后的边缘点,他的状态为TRAIL,点A的状态为FAR;依次遍历S1-S4周围的8个点,很显然A是这四个点的共同邻域,A取四个点对其进行变换后的最小值;

 

                                                                         图 1 迭代过程示意图标题标题

 

处理结果

                                                                                        图 2 原始图像

 

 

                                                                                  图 3  处理后的图像


结果分析

       原图中我加入了一些噪声,在GWDT后,前景点特别是远离背景的前景点的灰度值相对被增强了,这样背景点就变相的被抑制了。此外,通过设定阈值可以分离区域内的黏连的大米,从而自动进行记数。等等等等,GWDT的应用还有很多。总的来说,GWDT是在传统的距离变换的基础上保留和利用了灰度信息,更加具有物理意义和价值。

代码

       为了方便大家学习,为大家提供源码;

//-------------------------
//潘正宇 ,2018.05.10
//灰度尺度距离变换,GSDT/GWDT
//-------------------------

#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <queue>
#include <string>

using namespace std;
using namespace cv;


#define INF 3.4e+38                        // 无穷大的点
enum{ ALIVE = -1, TRIAL = 0, FAR = 1 };    //设定三种状态;


bool find(queue<int> que,int value)
{
	int size = que.size();
	for (int i = 0; i <size; ++i)
	{
		int a = que.front();
		if (a == value)
		{
			return true;
		}
		que.pop();
	}
	return false;
}


bool GSDT(string &picPath)
{
	Mat Scr = imread(picPath, 0);    //以灰度图的模式读入图像

	imshow("原始图", Scr);

	Mat gsdtImg;                     //创建GSDT后的图
	gsdtImg.create(Scr.size(), CV_32FC1);

	int row = Scr.rows;
	int col = Scr.cols;

	Mat  mat_mean, mat_stddev;	
	meanStdDev(Scr, mat_mean, mat_stddev);
	double mean;
	mean = mat_mean.at<double>(0, 0);   //获取图像均值

	int *state= new int[col*row];

	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			if (Scr.at<uchar>(i, j)<(mean))
			{
				gsdtImg.at<float>(i, j) = Scr.at<uchar>(i, j);       //背景点是ALIVE的
				state[i*col + j] = ALIVE;
			}
			else
			{
				gsdtImg.at<float>(i, j) = INF;
				state[i*col + j] = FAR;
			}

		}
	}
	
	queue<int> TrailQue;                     //定义队列用于存放TRAIL的点
	for (int i = 1; i < row-1; i++)
	{
		for (int j = 1; j < col-1; j++)
		{
			if (state[i*col + j] == FAR)     //如果这个点是前景点,那么搜寻这个点周围是否有背景点,如果是那么它就是边缘点;
			{
				for (int o = -1; o <= 1; o++)
				{
					for (int p = -1; p <= 1; p++)
					{
						if (state[(i + o)*col + j + p] == ALIVE)        //找到所有的边缘点,并且将其放入队列TrailQue;
						{
							state[i*col + j] = TRIAL;                   
							gsdtImg.at<float>(i, j) = Scr.at<uchar>(i, j);
							TrailQue.push(i*col + j);
							break;
						}
					}
				}
			}
			
		}
	}
	

	while (!TrailQue.empty())
	{
		int P_row = TrailQue.front() / col;    ///获取TrailQue中点的坐标
		int P_col = TrailQue.front() % col;

		if (P_row < 1){ P_row = 1; }if (P_row>row - 2){ P_row = row - 2; }
		if (P_col < 1){ P_col = 1; }if (P_col>col - 2){ P_col = col - 2; }

		for (int o = -1; o <= 1; o++)
		{
			for (int p = -1; p <= 1; p++)
			{
				int N = (P_row + o)*col + P_col + p;

				double len = sqrt(o*o + p*p);
				float gs = gsdtImg.at<float>(P_row, P_col) + Scr.at<uchar>(P_row + o, P_col + p)*len;

				//---比较该点现有的GSDT值与P点给它的值;
				if (gsdtImg.at<float>(P_row + o, P_col + p) > gs)
				{
					state[N] = TRIAL;
					gsdtImg.at<float>(P_row + o, P_col + p) = gs;

					TrailQue.push(N);

					/*if (!find(TrailQue, N))
					{
						TrailQue.push(N);
					}*/
					
				}
					
				
			}
		}
		

		TrailQue.pop();                        //从TrailQue中删除这个点
	}

	//寻找最大值;
	float Max = 0;
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			if (gsdtImg.at<float>(i, j)>(INF / 100))
			{
				gsdtImg.at<float>(i, j) = 0;
			}
			if (gsdtImg.at<float>(i, j)>Max)//&&gsdtImg.at<float>(i, j)<(INF/10)
			{
				Max = gsdtImg.at<float>(i, j);
			}
			
		}
	}

	//将图像像素区间压缩到0-255
	Mat Dst;
	Dst = Scr.clone();
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			Dst.at<uchar>(i, j) = 255 * gsdtImg.at<float>(i, j) / (Max+1);
		}
	}

	imwrite("temp.bmp", Dst);
	
	//归一化
	gsdtImg = gsdtImg / (Max+1);
	
	
	cout << gsdtImg.at<float>(20, 206);
	imshow("GSDT",gsdtImg);
	waitKey(0);

	return true;
}


void main()
{
	string picPath = "G:\\博客\\图像处理\\灰度尺度距离变换\\rice.jpg";
	GSDT(picPath);

	system("pause");
	return;
}

 

2019-07-16 11:26:33 qq_29507011 阅读数 174

距离变换就是将距离变换为灰度,距离是矩阵的秩中任一点与秩外的距离的最小值,效果图:在这里插入图片描述
代码如下:

import numpy as np
import cv2

image = cv2.imread(‘test.jpg’)
image = cv2.resize(image,(100,100))
image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
hight,width = image.shape
image = cv2.threshold(image,127,255,cv2.THRESH_BINARY)

image = image[1]
#print(image)
img = np.zeros((hight,width))
dis_0 = []
dis_1 = []

for i in range(hight):
for j in range(width):
if image[i,j]==0:
dis_0.append((i,j))
else:
dis_1.append((i,j))

for data_1 in dis_1:
min_ = 1000
for data_0 in dis_0:
dis = pow(pow(data_1[0]-data_0[0],2)+pow(data_1[1]-data_0[1],2),.5)
if dis<min_:
min_ = dis

img[data_1[0],data_1[1]] = min_

img = img.astype(np.uint8)
cv2.imshow(‘test’,5*img)
cv2.waitKey(0)
cv2.destroyWindows()

图像处理之距离变换

阅读数 13436

没有更多推荐了,返回首页