2017-12-31 11:59:48 coming_is_winter 阅读数 2219
图像处理名词解释(一)之链码

       链码(又称为freeman码)是用曲线起始点的坐标和边界点方向代码来描述曲线或边界的方法,常被用来在图像处理、计算机图形学、模式识别等领域中表示曲线和区域边界。它是一种边界的编码表示法,用边界方向作为编码依据,为简化边界的描述,一般描述的是边界点集。它将线状地物或区域边界,由起点和一系列在基本方向上的单位矢量,给出每个后续点相对其前继点的方向编码表示。



      如上图所示,在寻找链码时,制定一个起始点(黑点),沿着逆时针方向,寻找一圈直到闭合,链码为:300301121232,下一步的工作是将其归一化,这里归一化的具体指链码寻找具有旋转不变性和起始点选择随意性,,简单点的例子是001,010,100是起始点选择不同的一个链码的三种不同表现形式,这里统一将其归一化为代表的最小整数:001(四进制)。推广到上面的链码,归一化为:003011212323,集体左移一位。。下一步求导数:前一位链码与后一位的差分(这里指逆时针转动90°的倍数(四链接))。
      
2019-11-01 20:34:22 weixin_44225182 阅读数 650

题目

计算下面图像 边界阶数为20的形状数及其相应的近似多边形
在这里插入图片描述

概念

形状数:链码的最小一阶差分码
简单说来求形状数就是:先求出图像的链码 ,再求其一阶差分码,最后找一阶差分码的最小值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
链码:用曲线(或折线)起始点的坐标和边界点方向代码来描述曲线或边界的方法
有4方向、8方向链码之分。
在这里插入图片描述
那么具体怎么计算链码呢?
在这里插入图片描述
拿上图8方向链码举例(右图):
假设起始点为1,则链码为 1 0 1 3 6 6 6 6 6…

这里说一下怎么求一阶差分链码:
假设4方向链码为 0 2 1 3 1 3 0
那么一阶差分链码为: 2 3 2 2 2 1 0
具体怎么来的呢?
在这里插入图片描述
在这里插入图片描述
解释:
从0—>2,按照逆时针旋转规则,0旋转到2,需要走2步:0–1—2
从2–>1,需要3步:2–3--0–1
依次类推
注意最后一个(0),其实就是起始点,形成闭合

C++算法(以4方向链码为例)

  • 计算第一个数与第二个数差值
  • 比较二者大小
  • 若第一个数大,则一阶差分为 差值相反数
  • 若第二个数大,则一阶差分为 4-差值

整理计算链码算法

  1. 对图像进行预处理 ,比如去除噪声、边缘化等 依照不同图片采取不同措施
  2. 进行重取样(重取样原理在下面代码中)
  3. 随便找一个起始点,记录起始位置
  4. 利用循环 or 递归 求链码(类似C++中的走迷宫)

实验代码

注:这里图片记得使用题目所给图像,否则需要对下面代码进行修改
该代码仅为 测试算法 还未封装为function


% write by 海轰
%该程序针对特定图片其作用 
%为测试程序

t=im2bw(imread('homework1.png'));

%过滤边缘白点 这里仅针对这幅图像
t(:,1)=0;
t(140:145,:)=0;

imshow(t),title('原图');

[m,n]=size(t);

%行扫描 找出图像白色区域最左边的端点
for i=1:m
x=0;y=141;
tem=t(i,:);
h=sum(tem);
if h~=0
for j=1:n
if tem(j)==1
x=j;
break
end
end

%行扫描 反转 寻找白点区域最右边的端点
tem=flip(tem);
for j=1:n
if tem(j)==1
y=n-j+1;
break;
end
end

%对区域进去填充 便于找到边界
for j=x+1:y-1
t(i,j)=1;
end
end
end

figure,imshow(t),title('填充区域后');
t=bwperim(t,8);
figure,imshow(t),title('找到边界');


a=t;
rt=zeros(145,141);

%对图像进行重取样
%假设原图像为100*100 一点为(62,73)
%重取样模板为100*100 但是间隔为10 (分成10*10)
%先对(62,73)/10=(6.2,7.3)
%再取整 (6,7)
%再还原 10*(6,7)=(60,70)
%这样重取样后得到(60,70)
for i=1:m
for j=1:n
if t(i,j)==1
if round(j/15)==0
rt(15*round(i/15),15*(round(j/15)+1))=1;
else
rt(15*round(i/15),15*round(j/15))=1;
end
end
end
end
figure,imshow(rt),title('重取样后');

stack=[0 0];%保存起点
code=[];%保存链码
points=zeros(25,2);%保存端点
k=0;
t=rt;

%随便寻找起点
for i=1:m
for j=1:n
if t(i,j)==1
stack=[i j];
break;
end
end
end

s1=stack(1);
s2=stack(2)+1;

%while循环求链码
while (s1~=stack(1)||s2~=stack(2))&&k<500
k=k+1;

if k==1
s2=s2-1;
end

if s2+15<=141
if t(s1,s2+15)==1
code(k)=0;
points(k,1)=s1;
points(k,2)=s2;
t(s1,s2)=0;
s1=s1;s2=s2+15;
continue;
end
end

if s1-15>0&&s2+15<=141
if t(s1-15,s2+15)==1
code(k)=1;
points(k,1)=s1;
points(k,2)=s2;
t(s1,s2)=0;
s1=s1-15;
s2=s2+15;
continue;
end
end

if s1-15>0
if t(s1-15,s2)==1
code(k)=2;
points(k,1)=s1;
points(k,2)=s2;
t(s1,s2)=0;
s1=s1-15;s2=s2;
continue;
end
end

if s1-15>0&&s2-15>0
if t(s1-15,s2-15)==1
code(k)=3;
points(k,1)=s1;
points(k,2)=s2;
t(s1,s2)=0;
s1=s1-15;s2=s2-15;
continue;
end
end

if s2-15>0
if t(s1,s2-15)==1
code(k)=4;
points(k,1)=s1;
points(k,2)=s2;
t(s1,s2)=0;
s1=s1;s2=s2-15;
continue;
end
end

if s2-15>0
if t(s1+15,s2-15)==1
code(k)=5;
points(k,1)=s1;
points(k,2)=s2;
t(s1,s2)=0;
s1=s1+15;
s2=s2-15;
continue;
end
end

if t(s1+15,s2)==1
code(k)=6;
points(k,1)=s1;
points(k,2)=s2;
t(s1,s2)=0;
s1=s1+15;s2=s2;
continue;
end

if s2+15<=141
if t(s1+15,s2+15)==1
code(k)=7;
points(k,1)=s1;
points(k,2)=s2;
t(s1,s2)=0;
s1=s1+15;
s2=s2+15;
continue;
end
end
end

xt=rt-t;
figure,imshow(xt),title('相似多边形'),hold on;
for i=1:25
if i==25
plot([points(25,2);points(1,2)],[points(25,1);points(1,1)]);
else
plot(points(i:i+1,2),points(i:i+1,1));
end
end

结果图

在这里插入图片描述
在这里插入图片描述

更多

获取更多资料、代码,微信公众号:海轰Pro
回复 海轰 即可

2012-02-11 15:58:00 weixin_30379531 阅读数 45
链码在图像提取的后期即模式识别是一个很重要的特征,比如进行数字识别或者文字识别都会用到链码的特征,而链码的提取则可以借助于边界跟踪算法获取边界序列,注意是边界序列而不是边界,边界很容易获取,但是要想把边界的点按照一定的顺序输出则要费些功夫。下面采用边界跟踪算法获取边界,并存储在堆栈中,(这里的堆栈实际是C++容器类,是虚拟堆栈)。


利用点的八邻域信息,选择下一个点作为边界点,这个算法需要选择一个开始点,可以选择图像上是目标点,在最上,最左的点。然后查看它的八邻域的点,从右下方45°的位置开始寻找,如果是目标点,将沿顺时针90°作为下一次寻找的方向,如果不是,则逆时针45°继续寻找,一旦找到重复上面的过程。


具体的步骤在算法中有讲解。

/************************************************************************/
/* 查找物体的边界,输出已排序的边界序列 适应于单一区域        */
/************************************************************************/
//若能够输出边界点的序列则是比较有用的
#include<cv.h>
#include <highgui.h>
#include <iostream>
#include <stack>
using namespace std;



int main(){ 
	IplImage * image,*image2;
	image = cvLoadImage("E:\\image\\mapleleaf.tif",0);
	cvNamedWindow("image",1);
	cvShowImage("image",image);

	image2 = cvCreateImage(cvSize(image->width, image->height),image->depth,1);
	cvZero(image2);//image2 赋值为0
	//寻找区域的左上角点
	CvPoint startPoint = cvPoint(0,0);
	bool bFindStartpoint = false;
	int i ,j;
	unsigned char * ptr,*dst;
	stack<int> board;//奇数位存储x坐标,偶数位存储y坐标

	//当前扫描点
	CvPoint currentPoint = cvPoint(0,0);
	//邻域的8个点的方向
	int directions[8][2] = {{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1}}; 
	int beginDirection = 0;
	bool bFindBoardpoint = false;//寻找到邻域的边界点的判定
	for (i = 0 ; i< image->height && bFindStartpoint == false; i++)
	{
		for (j = 0 ; j< image->width && bFindStartpoint == false; j++)
		{
			ptr = (unsigned char *)(image->imageData + i*image->widthStep + j);
			if (*ptr == 255)
			{
				startPoint.x = j;
				startPoint.y = i;
				bFindStartpoint = true;
				//cout<<"x:  " << j <<"y :  " <<i <<endl;  
			}
		}
	}

	//进行边界跟踪 每次搜索8个方向的点 找到了即停止
	currentPoint = startPoint;
    bFindStartpoint = false;
	beginDirection = 0;
	board.push(startPoint.x);
	board.push(startPoint.y);
	while (!bFindStartpoint)
	{
		bFindBoardpoint = false;
		//在8个方向寻找符合条件的边界点
		while (!bFindBoardpoint)
		{   
			//进行出界判定  不对啊 这张图不可能出界啊
			ptr = (unsigned char *)(image->imageData + (currentPoint.y + directions[beginDirection][1])* image->widthStep + currentPoint.x + directions[beginDirection][0]);
			if (*ptr == 255)
			{
				bFindBoardpoint = true;
				currentPoint.x +=  directions[beginDirection][0];
				currentPoint.y  += directions[beginDirection][1];
				/************************************************************************/
				/*  此处添加序列存储的代码                    */
				/************************************************************************/
				//一、将边界存储到图片中
				dst  = (unsigned char *)image2->imageData + currentPoint.y * image2->widthStep + currentPoint.x;
				*dst = 255;

				//二、将边界点的序列存储到一个堆栈中
				board.push(currentPoint.x);
				board.push(currentPoint.y);

				if (currentPoint.x == startPoint.x  && currentPoint.y == startPoint.y )
				{
					bFindStartpoint = true;
				}
				//改变下次首先开始扫描的方向
				beginDirection -= 2;
				if (beginDirection < 0)
				{
					beginDirection += 8;
				}
				
				
				
			}
			else
			{
				beginDirection ++;
				beginDirection = beginDirection%8;
			}
		}
		//cout<<"currentPoint    "<<currentPoint.x <<"     "<< currentPoint.y<<endl;
	}
	cvNamedWindow("image2",1);
	cvShowImage("image2",image2);
	

	//显示堆栈中的数据 顺时针存储,逆时针显示
	//注意:显示时候堆栈中已经没有数据了
/*	int x,y;
	while(!board.empty())
	{
		y = board.top();
		board.pop();
		x = board.top();
		board.pop();
		cout<<"x   "<<x<<"    y    "<<y<<endl;
	}
*/
	cvWaitKey(0);
	return 0;
}

/************************************************************************/
/* 轮廓跟踪算法获取物体的轮廓序列 生成边界链码   */
/************************************************************************/
#include<cv.h>
#include <highgui.h>
#include <iostream>
#include <stack>
using namespace std;



int main(){ 
	IplImage * image,*image2,*image3;
	image = cvLoadImage("E:\\image\\bottle2.tif",0);
	cvNamedWindow("image",1);
	cvShowImage("image",image);

	image2 = cvCreateImage(cvSize(image->width, image->height),image->depth,1);
	image3 = cvCreateImage(cvSize(image->width, image->height),image->depth,1);
	cvZero(image2);//image2 赋值为0
	cvZero(image3);
	//寻找区域的左上角点
	CvPoint startPoint = cvPoint(0,0);
	bool bFindStartpoint = false;
	int i ,j;
	unsigned char * ptr,*dst;
	stack<int> board;//奇数位存储x坐标,偶数位存储y坐标

	//当前扫描点
	CvPoint currentPoint = cvPoint(0,0);
	//邻域的8个点的方向
	int directions[8][2] = {{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1}}; 
	int beginDirection = 0;
	bool bFindBoardpoint = false;//寻找到邻域的边界点的判定
	for (i = 0 ; i< image->height && bFindStartpoint == false; i++)
	{
		for (j = 0 ; j< image->width && bFindStartpoint == false; j++)
		{
			ptr = (unsigned char *)(image->imageData + i*image->widthStep + j);
			if (*ptr == 255)
			{
				startPoint.x = j;
				startPoint.y = i;
				bFindStartpoint = true;
				//cout<<"x:  " << j <<"y :  " <<i <<endl;  
			}
		}
	}

	//进行边界跟踪 每次搜索8个方向的点 找到了即停止
	currentPoint = startPoint;
    bFindStartpoint = false;
	beginDirection = 0;
	board.push(startPoint.x);
	board.push(startPoint.y);
	while (!bFindStartpoint)
	{
		bFindBoardpoint = false;
		//在8个方向寻找符合条件的边界点
		while (!bFindBoardpoint)
		{   
			//进行出界判定  不对啊 这张图不可能出界啊
			ptr = (unsigned char *)(image->imageData + (currentPoint.y + directions[beginDirection][1])* image->widthStep + currentPoint.x + directions[beginDirection][0]);
			if (*ptr == 255)
			{
				bFindBoardpoint = true;
				currentPoint.x +=  directions[beginDirection][0];
				currentPoint.y  += directions[beginDirection][1];
				/************************************************************************/
				/*  此处添加序列存储的代码                    */
				/************************************************************************/
				//一、将边界存储到图片中
				dst  = (unsigned char *)image2->imageData + currentPoint.y * image2->widthStep + currentPoint.x;
				*dst = 255;

				//二、将边界点的序列存储到一个堆栈中
				board.push(currentPoint.x);
				board.push(currentPoint.y);

				if (currentPoint.x == startPoint.x  && currentPoint.y == startPoint.y )
				{
					bFindStartpoint = true;
				}
				//改变下次首先开始扫描的方向
				beginDirection -= 2;
				if (beginDirection < 0)
				{
					beginDirection += 8;
				}
				
				
				
			}
			else
			{
				beginDirection ++;
				beginDirection = beginDirection%8;
			}
		}
		//cout<<"currentPoint    "<<currentPoint.x <<"     "<< currentPoint.y<<endl;
	}
	cvNamedWindow("image2",1);
	cvShowImage("image2",image2);
	

	//显示堆栈中的数据 顺时针存储,逆时针显示
	//注意:显示时候堆栈中已经没有数据了
/*	int x,y;
	while(!board.empty())
	{
		y = board.top();
		board.pop();
		x = board.top();
		board.pop();
		cout<<"x   "<<x<<"    y    "<<y<<endl;
	}
*/

	//Board中存储着边界的序列 转化为8邻域链码,每隔10个点取样 显示

	int lianmaLength = (board.size()+5)/10;
    int* lianma = new int[lianmaLength];

	for (i = 0 ; i< lianmaLength  && !board.empty();i += 2)
	{
		lianma[i+1] = board.top();
		board.pop();
		lianma[i] = board.top();
		board.pop();

		for (j = 0; j< 18 && !board.empty();j++)
		{
			board.pop();
		}
	}
	//将数据在image3中显示
	int t;
	for ( t = 0; t < lianmaLength;t += 2)
	{
		 i = lianma[t+1];
		 j = lianma[t];
		 ptr = (unsigned char *)image3->imageData + i*image->widthStep + j;
		 *ptr = 255;
		
	}
    cvNamedWindow("image3",1);
	cvSaveImage("E:\\image\\bottle2lianma.bmp",image3);
	cvShowImage("image3",image3);


	cvWaitKey(0);
	return 0;
}





转载于:https://www.cnblogs.com/libing64/archive/2012/02/11/2878733.html

2018-08-17 09:15:30 zcg1942 阅读数 1135

虽然不是专门研究图像分割的(峰兄才是),但多少接触了一点,并且图像分割是图像处理中的最为复杂的,通过图像分割可以很好地认识图像处理的好多方法。今天看的是边界追踪和链码的表示。网易计算机视觉工程师的第一道选择题就考察了这个,其实冈萨雷斯的书第11章就有讲,还是要多看书啊。

书中是接着图像分割讲的。我们分割完成后总要表示它,那么就有两种思路,一种是选择外部特征,表示分割部分的边界,一种是选择内部特征,找出分割主体的像素。之所以说思路,还没有上升到方法,是因为具体的方法要求描述子要对大小、旋转、平移不敏感。

边界追踪就是一种找边界的方法,它的输出是排序过的边界点的序列。追踪方法有Moore边界追踪方法和Square跟踪算法。Moore算法是在当前点的8邻域(摩尔邻域)内顺时针找黑色像素,找到则递归进行下一步。Square则没有利于邻域进行搜索,是一个if-then规则,根据当前像素的情况进行移动方向的选择,如果当前像素是黑色,则左移一个像素;如果当前是白色,则右移。这种方法无法追踪得到只具有八邻接性而不具有四邻接性图形的边界。

边界追踪算法找到了图形边界,我们要对它进行描述和表示,其实就是进行编码。书中主要介绍了Freeman链码,基于Moore边界追踪,对下一个像素相对于当前像素的位置进行编码(有8个方向)。对8个方向依次用0~7的数字表示。就像特战队队员之间通报敌人在几点钟方向一样,链码表示了下一个边界点的方向(远近就是一个像素块的距离,像素的对角线或者边长)。

                                 3

                                2

                                1

                                 4

 

                                0

                                 5

                                 6

                                7

Freeman链码显然与初始点的选取有关,我们还需要使编码与初始锚点无关,即无论选择哪一个点做起始点,最后的编码都相同。注意到如果依次使用不同的点做初始点,编码之间的关系是循环移位,于是我们可以对它们进行归一化,选择其中最小的最为最终的编码(把编码看作自然数进行大小的比较)。

刚才提到了,描述子应该具有旋转、平移、缩放不变性。平移不变性很明显是满足的,缩放不变性通过重采样也可以满足。旋转不变性要通过差分编码来解决,同时减少编码长度。需要注意的是计算差值的时候不是对原始的Freeman链码的相邻两个数做减法,而是要满足逆时针关系的条件下做减法,因为在8邻域内编码是按照逆时针的顺序编码的。比如643的差分码。6到4按照逆时针的走法要6步,4到3要7步,所以差分码是67.但是这也只能满足旋转角度是45度的整数倍时的不变性。

链码所能编码的边界应该是离散的,在保持边界特征的前提下尽量短。这就是多边形近似。MPP最小周长多边形近似利用了边界区域像素内外墙之间的凹顶点和凸顶点,利用橡皮条原理完成近似。此外还有聚合技术和分裂技术。

除了链码,表示边界和形状的方法还有标记图和边界线段、骨架。标记图是边界的一维表示,描述质心到边界的距离与角度的关系。为了实现旋转不变性,可以将距离质心最远的点选为起始点,或者在本征轴上选择距离质心最远的点。注意是起始点的意思是角度轴以起始点的角度开始变化。边界线段利用了凸壳和凸缺的概念,从一个凸缺进入另一个凸缺的转折点就是分割点。骨架可以用中轴变换MAT来定义,组成骨架的点是边界上与该点有多个最近邻点的点,可以用燎原之火来形象化:大火从边界开始向中心燃烧,它们集合的地方就是骨架。

边界描述子

讲完表示,就要说到真正的边界描述子了。最简单的是描述边界的长度,偏心率,曲率。长度可以通过链码求出,垂直和水平方向长度是1,对角是根号2.偏心率是长轴和短轴之比,长轴短轴相互垂直,长轴就是直径,是其端点是边界上距离最远的点。曲率描述斜率的变化率。斜率变化非负处的点是凸线段的一部分(其实就是二阶导)。

描述子还有形状数。同样基于链码。形状数的阶n定义为链码的长度(数字个数)。差分链码也可以归一化,得到的就是形状编号。

傅里叶描绘子,将二维平面的点看作复平面的点,坐标看作是一个复数,从而将二维问题简化成一维问题。对它求傅里叶变换,然后就可以重构原来的边界信号。重构过程中可以选择部分分量(舍去高频细节)。同时借助傅里叶变换可以很好地表示选择平移缩放变换。将二维问题转换为一维问题的方法还有统计矩。矩其实是变量与均值的插值的幂的期望。我们可以将曲线旋转作为直方图,这样无论是哪个角度的曲线,都通过将断点连线转至水平方向,即实现了旋转不变性,进一步将直方图归一化为单位面积,直方图转为概率密度图,实现了尺寸的归一化。矩的实现简单,而且矩包含了对边界形状的物理解释。二阶矩是曲线关于均值的扩展程度,三阶矩度量曲线关于均值的对称性。

区域描述子

以上是边界的描述子,对于区域也有对应的一些描述子。比如使用周长和面积表示区域的致密性。圆度率也可以描述致密性,是该区域的面积与同样周长的圆的面积之比。拓扑描绘子,通过孔洞数量H和连通分量的数量C可以定义欧拉数E=C-H.欧拉数同时还可以简单地解释由直线线段表示的区域(多边形网络)。V-Q+F=C-H,V表示顶点数,Q是边数,F是面数。使用连通分量可以在已分割图像中提取最大特征。

区域的另一个特征是纹理。纹理的表示方法又有三种,统计方法、结构方法和频谱方法。

统计方法的对象是灰度直方图,方差表示度量对比度,三阶矩度量直方图偏斜度(负数表示直方图对称尺度向左偏)。直方图中无法体现像素的位置信息,我们通过共生矩阵可以表示灰度值之间的相对位置。共生矩阵其实也是一种统计,只不过统计的是当前像素右边紧邻的像素的灰度值。矩阵的元素m34表示灰度值为3的像素右边第一个元素灰度值是4的情况个数是m。

结构方法的思想是找到一些基本的纹理单元,通过一些规则可以生成复杂的纹理。

频谱方法当然是利用傅里叶频谱,进一步使用极坐标,并且分别对r和角度theta积分,得到两个一维函数。

不变矩

除了纹理,还可以使用不变矩描述区域。大小为MxN的图像的二维(p+q)阶矩定义为:

中心矩还要在求幂之前与均值做差。由二阶矩和三阶矩可以推出7个不变矩组,书中列出了7种不变矩在旋转缩放镜像平移下的表现。

使用主分量进行描绘

有时候我们可以得到多光谱下的图像,即便是单幅图像也可以分成三通道下的三个分量图像。每个像素对应一个多维的向量,于是MxN大小的图像可以得到MxN个向量,向量维数由图像的个数决定。用比较饶但是简洁的说法就是图像的大小决定向量的个数,图像的个数决定向量的大小(维数)。借助霍特林变换(离散KL变换)

A由x的协方差矩阵的特征向量构成。y的协方差矩阵是对角矩阵,主对角线的特征值就是x的协方差矩阵的特征值。可以通过y重构x,当不使用所有的特征值和其对应的特征向量重构时,误差就是剩余没有使用的特征值的和,所以我们霍特林变换是均方误差最小的,因为A的行向量对应的特征值是递减的。霍特林变换也叫主分量变换。

协方差矩阵的最大特征值对应的特征向量指向了总体方差最大的方向,第二个特征向量与第一个垂直,这样就可以找到质心,同时使用主分量变换可以对图像尺度、平移和旋转归一化。

Reference:

  1. Square:https://blog.csdn.net/kksc1099054857/article/details/74937731
  2. Freeman:https://blog.csdn.net/yang6464158/article/details/39801381
2018-05-04 23:15:46 ok460695972ko 阅读数 3087

       最近在做一些关于图像的东西,在用边界跟踪提取图像的边界之后要用链码编码之后才能进行下一步处理,所以查资料学习了一下标题里面提到的东西。在这里就不说这些东西的背景了,主要是通俗地解释一下这些东西的原理。

      在解释这些链码之前,首先解释一下一些需要了解的基础知识

4连通和8连通

       在计算机图形学里面,像素是数字图像的基本元素。一张图是由有限个像素点组成的。大家平时经常在网上看到的1080*1920的图其实就是指这张图每一列有1080个像素点,每一行有1920个像素点,整张图一共由1080*1920个像素点构成。根据中心像素点的邻接方向个数的不同,可以分为4连通和8连通(还有其他的情况,不过这里就只介绍4连通和8连通)。从中心像素点正右边开始,逆时针定义方向数,具体如下图:


     假如我们从图片里面提取了一个物体的轮廓如下图,红色的像素点代表我们编码的起始点。接下来以下图来讲解一下标题上的几种链码。

freeman链码

      在边界曲线上选取一点作为起点,记录起点的坐标,然后按照顺时针的方向,以8连通的方式从编码为1的方向(45度)开始搜索下一个边界像素点。找到之后记录方向编码,然后从找到的像素点开始重复以上步骤,得出一组链码,这个就是freeman链码。如果是闭合的边界,则编码完成后回到起点,可以省去起点坐标。上图的freeman码是(1,1)21176644或者211766444。

       关于搜索下一个边界像素点的要从哪一个方向开始,我查资料的时候看到不同的人有不同的做法,个人感觉自己定一套规则就好,往后的其他边界都按这套规则编码就没问题。

归一化链码

      对于闭合的边界,无论我们平移它,得到的链码都是一样的。但是如果选取的起点不同,那么得到的链码也会有所不同。这时候我们就要将它归一化。原理就是把我们得到的链码看成是一个自然数,将链码循环写下去,选取构成的自然数最小的那组链码。这个就是归一化链码。上图的链码循环:211766444211766444211766444……归一化链码为117664442。

一阶差分链码

       归一化链码解决了因为起点坐标不同而编码不同的问题,但仍有不足。如果我们将边界旋转,那么它的归一化链码也会发生变化。如下图


链码为666001334(按未旋转前链码编码的顺序),归一化链码为001334666,可看出旋转后链码编码发生了改变,那么当然归一化链码也会改变。

这时候我们就可以用一阶差分链码。原理就是计算相邻两个元素方向变化(按逆时针方向)的次数。例如

5→5变化了0步,记为0,4→6变化了两步,记为2。这样就可以得到一阶差分链码。根据归链码计算,上图旋转前后的一阶差分链码都为706706006。


归一化一阶差分链码

    其实就是对一阶差分链码进行归一化处理,得出的链码具有平移不变性和旋转不变性。上图的归一化一阶差分链码为006706706。


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