2017-07-25 15:51:23 qq_35954333 阅读数 1944

在图像分割领域中,区域生长是一个很有用的算法,它往往可以从局部区域中逐渐分将图像分割成具有不同相似性的几部分。区域生长算法的关键部分在于种子点的选取和相似性准则的判定,这直接影响到图像分割效果,种子点可以通过人工方法(前景或背景)选取,也可以通过图像预处理算法自动获取。

实现区域生长算法过程如下(以灰度图为例,判定准则为相邻像素间灰度差):

1.种子点获取。

2.种子点入栈,考虑种子点周围八领域灰度值,若邻域像素满足生长条件,则该点入栈。

3.获取栈顶元素,考虑其邻域像素,若满足则入栈,不满则舍弃,重复该过程直至栈为空。

4.获取下一个种子点,重复2,3直到所有种子点已全部完成生长,程序结束。

class ImageGrow	//区域生长类
{
public:
	cv::Mat Image;	//原图像
	cv::Mat Image_Grow;	//标记图像
	std::vector<cv::Point>	seed;	//待生长种子点
	std::vector<cv::Point>	region_grow_point;	//生长后像素点集


public:

	void Region_Grow(int);		//生长算法
	void Show_Image();	//显示生长后图像
	

};

void ImageGrow::Region_Grow(int thre)
{
	cv::Mat flagmat=cv::Mat::zeros(Image.size(),CV_8U);	//用于标记区域生长情况
	std::stack<cv::Point>	pointstack;		//用于存放像素点的栈
	std::vector<cv::Point2i>	direction(8);	//8邻域编码,用于遍历生长点周围像素
	direction[0]=cv::Point2i(-1,-1);
	direction[1]=cv::Point2i(-1,0);
	direction[2]=cv::Point2i(-1,1);
	direction[3]=cv::Point2i(0,-1);
	direction[4]=cv::Point2i(0,1);
	direction[5]=cv::Point2i(1,-1);
	direction[6]=cv::Point2i(1,0);
	direction[7]=cv::Point2i(1,1);

	cv::Point2i src,test_src;
	int src_value;

	for(int i=0;i<seed.size();++i)	//种子点入栈
	{
		src=seed[i];
		pointstack.push(src);


		flagmat.at<uchar>(src.y,src.x)=255;				

		src_value=Image.at<uchar>(src.y,src.x);
		

		while(!pointstack.empty())	//栈非空则一直执行生长算法
		{
			src=pointstack.top();	//提取栈顶像素点


			region_grow_point.push_back(src);


			pointstack.pop();

			for(int j=0;j<direction.size();++j)	//8邻域判别
			{
				test_src.x=src.x+direction[j].x;
				test_src.y=src.y+direction[j].y;

				if(test_src.x<0 || test_src.y<0 || test_src.x>(Image.cols-1) || test_src.y>(Image.rows-1))
					continue;
						//检查像素点是否越界
				if(!flagmat.at<uchar>(test_src.y,test_src.x))//若该像素点之前没有被标记过则对该像素点进行判别
				{
					int cur_value=Image.at<uchar>(test_src.y,test_src.x);
					if(abs(src_value-cur_value)<thre)	//符合生长条件,像素点入栈
					{
						
						flagmat.at<uchar>(test_src.y,test_src.x)=255;
						pointstack.push(test_src);
					}
				}
			}
		}

	}
	Image_Grow=flagmat.clone();

}
void ImageGrow::Show_Image()		//显示图像
{
	cv::Mat	Image_Color(Image.size(),CV_8UC3,cv::Scalar(0,0,0));//创建三通道图像
	std::vector<cv::Mat>	channels;
	cv::split(Image_Color,channels);
	channels[0]=Image.clone();
	channels[1]=Image.clone();
	channels[2]=Image.clone();
	cv::RNG	rng;
	int a=rng.uniform(0,255),b=rng.uniform(0,255),c=rng.uniform(0,255);	//获取随机数,用于显示生长区域

	for(int i=0;i<region_grow_point.size();++i)		//对符合条件的生长点赋值
	{
		

			channels[0].at<uchar>(region_grow_point[i].y,region_grow_point[i].x)=a;
			channels[1].at<uchar>(region_grow_point[i].y,region_grow_point[i].x)=b;
			channels[2].at<uchar>(region_grow_point[i].y,region_grow_point[i].x)=c;

		
	}
	cv::merge(channels,Image_Color);


	cv::namedWindow("Image2",CV_WINDOW_NORMAL);
	cv::imshow("Image2",Image_Color);

	cv::imwrite("the grow Image.jpg",Image_Color);
}
int main()
{
	ImageGrow Test;		//创建区域生长类对象
	Test.Image=cv::imread("F:/biaoding.jpg",0);	//以灰度图方式读取图像
	
	Test.seed.push_back(cv::Point2i(600,500));//设置生长点,这边采用手动标记方式

	Test.Region_Grow(100);						//区域生长,设置生长阈值,可根据实际情况设定

	cv::namedWindow("Image1",CV_WINDOW_NORMAL);
	cv::imshow("Image1",Test.Image);

	Test.Show_Image();							//显示图像
	cv::imwrite("the origion Image.jpg",Test.Image);
	

	cv::waitKey(-1);

	

	return 0;
}
最后,上测试图

原图:










生长图(种子点选在标定板矩形框空白处)


2019-09-20 16:52:49 OpenSceneGraph 阅读数 477

1.图像增强:对于噪声图像、模糊图像等对图像信息增强以突出有用信息。

  • 高通平滑、低通锐化;平滑模糊、锐化突出图像细节
  • 滤波器还有带通、带阻等形式
  • 根据噪声(椒盐噪声、高斯噪声....)的不同,选用不同的滤波
  • 邻域有4-邻域、对角邻域、8-邻域,相对应的有邻接,即空间上相邻、像素灰度相似
  • 图像边缘处理:忽略不处理、补充、循环使用
  • 目前尚未图像处理大多基于灰度图像
% %低通滤波器,filtertype-滤波器的种类,n-滤波器的阶数,image-待滤波的原图像
% function H=lpfilter(filtertype,D0,n,image)
% im=imread(image);
% [DIM1,DIM2,K]=size(im);%确定原始图像大小
% if K==3
%     im1=rgb2hsv(im);
%     im2=double(im1(:,:,3));
% else
%     im2=double(im);
% end
% u=0:(DIM1-1);
% v=0:(DIM2-1);
% idx=find(u>DIM1/2);
% u(idx)=u(idx)-DIM1;
% idy=find(v>DIM2/2);
% v(idy)=v(idy)-DIM2;
% [V,U]=meshgrid(v,u);
% D=sqrt(U.^2+V.^2);%计算坐标到频域中心的距离
% 
% switch filtertype
%     case 'ideal'%理想
%         H=double(D<=D0);
%     case 'butterworth'%巴特沃斯
%         H=1./(1+(D./D0).^(2*n));
%     case 'guassian'%高斯
%         H=exp(-(D.^2)./(2*(D0^2)));
%     case 'exponent'%指数低通
%         H=exp(-D./(2*D0)./(2*D0));
%     case 'trapezoid'%梯形低通
%         if D<=D0
%             H=1.0;
%         else if D>100
%                 H=0.0;
%             else
%                 H=(D-100)./(D0-100);
%             end
%         end
%     otherwise
%         error('Unkown filter type.');
% end
% F=(fft2(im2));
% G=H.*F;
% g=real((ifft2(G)));
% figure;
% subplot(1,2,1);
% imshow(im2,[]);
% title('Original Image');%原始图像灰度部分
% subplot(1,2,2);
% imshow(g,[]);
% title('Lowpass Filtered Image');%低通效果
%H=lpfilter('ideal',14,1,'cat.jpg');

%低通滤波器,filtertype-滤波器的种类,n-滤波器的阶数,image-待滤波的原图像
function H=lpfilter(filtertype,D0,n,image)
im=imread(image);
[DIM1,DIM2,K]=size(im);%确定原始图像大小
if K==3
    im1=rgb2hsv(im);
    im2=double(im1(:,:,3));
else
    im2=double(im);
end
u=0:(DIM1-1);
v=0:(DIM2-1);
idx=find(u>DIM1/2);
u(idx)=u(idx)-DIM1;
idy=find(v>DIM2/2);
v(idy)=v(idy)-DIM2;
[V,U]=meshgrid(v,u);
D=sqrt(U.^2+V.^2);%计算坐标到频域中心的距离

switch filtertype
    case 'ideal'%理想
        Hlp=double(D<=D0);
    case 'butterworth'%巴特沃斯
        if nargin==4
            n=1;
        end
        Hlp=1./(1+(D./D0).^(2*n));
    case 'guassian'%高斯
        Hlp=exp(-(D.^2)./(2*(D0^2)));
    otherwise
        error('Unkown filter type.');
end
H=1-Hlp;
F=(fft2(im2));
G=H.*F;
g=real((ifft2(G)));
figure;
subplot(1,2,1);
imshow(im2,[]);
title('Original Image');%原始图像灰度部分
subplot(1,2,2);
imshow(g,[]);
title('Lowpass Filtered Image');%低通效果

2.图像复原:改善图像质量(模糊、失真、噪声)

常见噪声:高斯、瑞利、伽马(爱尔兰)、指数、均匀、脉冲(椒盐)等

根据图像不同采用不同滤波器进行复原,重要的是复原的步骤及各个方法的特点

  • 逆滤波法无噪声下可精确复原
  • 维纳滤波法需要较多先验知识,实现难度大
  • 最大熵恢复法是非线性算法,耗时
  • 卡尔曼滤波法要根据前一个估计值和最近一个观测数据,计算量大
  • 传播波方程恢复法利用物理知识复原,但对运动方向敏感
  • 逆滤波复原形式简单,适合极高信噪比条件,但计算量大
  • 维纳滤波对不同区域误差不敏感,不能处理非平稳信号和噪声
  • 算术平均滤波器可消噪,平滑但模糊
  • 几何平均滤波器平滑效果更好,丢失细节少
  • 谐波均值滤波器处理高斯、盐噪声效果好,不适应椒噪声
  • 谐逆波滤波器不同情况下适用椒、盐噪声
  • 中值滤波器适用椒盐噪声
  • 最大/小值滤波器适用椒/盐噪声
  • 中点滤波器适用高斯、均匀噪声
% %逆滤波复原
% I=imread('cat.jpg');
% L=10;
% T=10;
% PSF=fspecial('motion',L,T);
% Blurred=imfilter(I,PSF,'circular','conv');
% subplot(2,2,1);imshow(I);
% subplot(2,2,2);imshow(Blurred);
% R=deconvwnr(Blurred,PSF);
% subplot(2,2,3);imshow(R);

3.图像分割

 注重方法的实现:阈值分割、基于区域、基于能量及有些方法的改进。

%Hough变换
clc;
clear all;
I=imread('cat.jpg');
I=rgb2gray(I);
subplot(1,2,1);imshow(I);
rotI=imrotate(I,33,'crop');%imrotate(A,angle),将图像A围绕其中心点进行angle角度的逆时针旋转
BW=edge(rotI,'canny');%采用灰度或一个二值化图像I作为它的输入,并返回一个与I相同大小的二值化图像BW,在函数检测到边缘的地方为1,其他地方为0。
[H,T,R]=hough(BW);
subplot(1,2,2);imshow(H,[],'XData',T,'YData',R,'InitialMagnification','fit');
% %均值迭代分割
% I=imread('cat.jpg');
% [w,h]=size(I);
% %1--初始化
% avg=0.0;%图像的平均值
% for y=1:h
% for x=1:w
%     avg=avg+double(I(x,y));
% end
% end
% Thresh=avg./w./h;%选择一个初始化的阈值T(通常取灰度值的平均值)
% %2--将图像像素分为两类:G1:灰度值>T,G2:灰度值<T
% %计算G1中所有像素的均值u1,以及G2中所有的均值u2
% curThd=Thresh;
% preThd=curThd;
% subthd=2;
% while (subthd>1.0)
%     preThd=curThd;
%     u1=0,u2=0;
%     num_u1=0,num_u2=0;
%     for y=1:h
%     for x=1:w
%         if double(I(x,y))<preThd
%             u1=u1+double(I(x,y));
%             num_u1=num_u1+1;
%         else
%             u2=u2+double(I(x,y));
%             num_u2=num_u2+1;
%         end
%     end
%     end
%     curThd=(u1./num_u1+u2./num_u2)./2;
%     subthd=abs(preThd-curThd);
% end
% for y=1:h
%     for x=1:w
%         if double(I(x,y))<curThd
%             I(x,y)=0;
%         else
%             I(x,y)=255;
%         end
%     end
% end
% R=I;
% figure,imshow(R);

%最大类间方差分割法
I=imread('cat.jpg');
level=graythresh(I);%:使用最大类间方差法找到图片的一个合适的阈值
R=im2bw(I,level);%将灰度图像转换为二值图像时,需要设定一个阈值,这个函数可以帮助我们获得一个合适的阈值
figure,imshow(R);

 

2015-07-31 15:55:36 Ben_Ben_Niao 阅读数 884

一、基本概念

两种方法:基于边缘检测的方法分割、基于区域生长方法的分割。

  1. 基于边缘检测:检测局部特性的不连续性,链接成边界,然后分割出不同的区域。包括边缘家的测图像分割、阈值选取的图像分割。
  2. 区域生长:检测相邻区域的相似性,相似则是同一个区域,然后合并,不想似,则是不同的区域。包括:区域生长,分裂-合并分割方法。
  3. 这两种方法可以结合一起使用。

二、基于灰度的基本策略

不连续性——区域之间;相似性——区域内部;

思路:先确定点、线(宽度为1)、边(不定宽度),再确定区域;


三、间断检测(点、线、边)

基本思路:用形态学的模板,点和线的模板如下:

点:     线: 


  1. 边缘检测:梯度,canny,sobel算子(前面文章已经分析过了)
  2. 局部链接算法:领域内两个边缘点,当边缘梯度方向相似度超过阈值则链接边界;

四、霍夫变换

算法原理:如果是直线,则满足方程:y=ax+b;那么构造a_o_b坐标系,同一条直线在a_o_b坐标系中对应同一个点(a,b),所以对于不同的边缘点,如果共线,则必定在a_o_b坐标系中相交:


那么对于图像上的一个边缘点,怎么求霍夫变换呢?

  1. 边缘点+梯度,则可以求出具体的直线方程y=ax+b,然后将方程转换到极坐标,统计极坐标下(seta,p)的个数。后续过程如图:
  2. 求出直线后,怎么求线段呢?(个人认为)求出直线后,直线的端点(x,y)在a_o_b平面肯定对应特殊的(seta,p)曲线,找出这个,然后反算到图像坐标。


同样,对于圆方程:(x-c1)^2 + (y-c2)^2 = c3;需要三个空间参数(空间直线相交)。

五、阈值分割

阈值来源:直方图统计,自动阈值,手动选取阈值

根据边界梯度特性,统计直方图,选取阈值,步骤如下:


区域生长:需要选取种子点,然后重复再邻域内找相似度高的(需要满足一些条件,如区域闭合,完备等)


2005-03-14 19:44:00 jasonme 阅读数 3305

3.4 图像模型
一个自然的视频图像包括一格采样值.自然图像在它们原始的状态下通常很难于压缩,这是因为相邻图像采样值之间的很强的联系造成的.我们可以从一幅图像的自相关函数图中得到不同图像之间的相似程度.在中点处的最顶点表示图像未经移动时的图像.当空域移动拷贝被从原始图像的任一个方向移除的时候,这个函数值就会急骤下降,就这说明了一个图像采样值的邻域内是高度相关的.

一个运动补偿的剩余图象的自相关函数在当空域移动增加的时候快速衰减,表明了相邻的采样点是弱相关的.有效的运动补偿在剩余帧中减少了本地相关性,这样就让图象比原始状态下的图象更易于压缩.图像模型的作用就是把图像或剩余图像的相关性去除掉,并把它转变为一种可以被熵编码器有效编码的数据形式.实际图像模型一般有三个主要的组成部分:变换(去相关和简化数据),量化(减小转换数据的相关性)和重组(组合数据来把大值分组到一起)。

3.4.1 预测图像编码
运动补偿是预测编码的一个实例,在这种补偿下,编码器基于上一帧创造了对于当前帧的一个区域的预测并从当前域中减去这个预测来形成一个剩余帧。如果预测是成功的,那和在剩余帧中的能量是比原始帧中的能量少得多的,而且剩余帧可以用更少的位来表示。

与之相似的是,同一个图像采样或区域的预测可以由前面传输的同样图象或帧中组成.预测性编码被用作是早期的压缩算法的基础,也是H.264的一个很重要的组成部分.帧内编码(应用在变换域中,见后).空域预测有时被描述为"差分脉冲编码调制"(DPCM)--从通信系统中引入的一个差分的PCM编码方法.


 B C
A X

图中假设一个像素X是被编码的像素点,如果在光栅顺序下处理这帧的话,那么点A,B和C(在当前和之前行的邻近像素)在编码和解码器中都是可得的(因为他们已经在X之前被解码).编码器在根据一些在之前编码的像素的组合来得到对于X的预测,从X中减掉这个预测,然后编码剩余帧频(做差之后的结果).解压器形成相同的预测,然后把解码的剩余帧加进去来重建像素值.如果编码过程是有损的(比如说剩余帧被量化了)那么解码的像素值A,B,C也许与原始的A,B,C是不同的(因为编码过程的损失),这样的话上述的过程可能在编码器和解码器上引起累积的不匹配.这种情况下,编码器应该解码剩余帧中的R(X),并重建像素.

比如说:
编码器预测: P(X) = (2A + B + C) / 4
剩余帧  R(X) = X - P(X) 被编码并传输
解码器解码R(X)并组成同样的预测: P(X) = (2A + B + C) / 4
重建像素 X = R(X) + P(X)

编码器使用解码的像素值A,B,C来构成重建,比如说,P(X)=(2A+B+C)/4.这样的话,编码器和解码器使用同样的P(X),这样就可以避免错位的发生.

这种方法的压缩的效率依赖于对于P(X)预测的准确性.如果预测是准确的(P(X)与X的值是相近的),那么剩余帧的能量就非常小了.然而,并不是经常都能选择一个对复杂图像来说那么理想的预测器的.编码器就有必要指出对解码端选择预测器,这样就有一个预测和需要的多余位来表示信号和预测器的一个折衷考虑了.

3.4.2 变换编码
3.4.2.1 总述
图像中或视频CODEC中的变换阶段是把图像或运动补偿剩余值转到另一个域中(变换域中)。变换的选择依赖于以下几个分类
1. 在转换域中的数据是去相关性的(转换域中的数据的大多数能量都会聚集到很小的值中)
2. 转换应该是可逆的.
3. 转换的计算过程应该是易于处理的,

对图像和视频压缩以及很多的变换都建议使用以下两类方式:基于块的变换和基于图像的变换。基于块的变换的例子比如Karhunen-Loeve变换(KLT),单值分解(SVD)和余弦变换(DCT)。每一种变换都是对于N*N的图像块或剩余样本来说的,这样图像就被用块的单元来处理。块变换有很低的内存需求而且很适于基于块的运动补偿剩余帧的压缩,但是受边界区域的影响比较严重。基于图像的变换对于整个图像或帧来进行操作(或一个图像的一大段进行操作)。最常用的图像变换是离散小波变换(DWT或直接说小波变换)。像DWT这样的变换对于表态图像的压缩来说已经被证明是很有效的了,但是它们对内存的消耗都比较大(因为整个图像或段都以单个单元来进行处理)而且不适用于基于块的运动补偿方法。DCT和DWT在MPEG4视频部分中都得到了应用(而且一个DCT的变种在H.264中被使用)。它们将在下面几节被讨论。

3.4.2.2
余弦变换在X中进行运算(X是一个N*N的采样块,通常是指在预测之后的图像采样或剩余帧值)来构造Y,一个N*M的系数块。DCT的操作(及它的反变换IDCT),可以用变换矩阵A来描述。一个N*N采样块的DCT变换由如下式子得到:Y=AXA(T),反DCT变换:Y=AXA(T),A(T)表示A的对称矩阵

X是一个采样的矩阵,Y是一个系统的矩阵,A是一个N*N的变换矩阵。A的元素是:

A(i,j) = C(i) * cos [(2j+1)*i*PI]/2N
C(i)=   N^(-1/2) i=0
 (2/N)^(-1/2) i>0

一个二维DCT的输出是一组N*N的系数,它表示了图像在DCT域中的块数据,这些系数可以被想成是一组标准基的”权“值。任何图像块都可以由编合所有的N*N标准基来重组,通过这些基乘以相应的权因素值(系数)。

例2 图像块的DCT系数
一个选定的4*4的块,以及DCT系数。这种用DCT域表示块的方法的优点不是很明显的,因为并没有减少存储的空间,原来我们存16个像素值,我们现在成了需要存16个DCT系数。DCT的实用性当块被从系数的一个子集中构建出来的时候表现了出来:

把除去一些很大的值之外的所有的系数设为0,执行IDCT之后的结果如下图:在IDCT之前加入更多的系数可以形成渐近的更加准确的对原图的重建。这样,就可能从从一个子集的系数集中近似于原始的图像。删除系数中的小系数值(比如说通过量化)可以让图像数据在更少的系数表示位中表示出来,尽管这样做会影响图像的质量。


3.4.2.3 小波
流行的"小波变换"要(基于一系列系数与离散小波函数相等的滤波器来进行图像压缩的一种广泛使用的方法).一个应用于有N个采样结果的离散信号的离散小波变换的基本运行过程如下.一对滤波器被用来把信号分解为一个低频部分(L)和一个高频部分(H).每个波段由因子2被子采样化,这样这两个频率波都含有N/2个采样点。如果正确选择了滤波器的话,这个操作过程是可逆的。

这种方法可以被扩展应用到一个二维的信号上,比如说灰度图中。一个2D图的每一行都被经过低通和高通的滤波(Lx和Hx),每个滤波器的输出被下采样用来制造中间图像L和H。L是原始图象的低通滤波,并通过在x方向进行下采样的结果.H是原图像的高通滤波并在x方向的下采样结果。接下来,这些新图的每一列都使用低和高通的滤波器(Ly和Hy),并经过下采样来制造出四个子图象(LL,LH,HL,HH)。这四个子图象可以被组合为与原图的采样数相同的图象。LL是原图通过在水平和垂直方向经过低通,并用因素2进行子采样的结果。HL是通过在垂直方向经过高通滤波并包含了剩余垂直频率的结果。LH是通过在水平方向高通滤波并包含了剩余的水平频的结果,而HH是通过在水平和垂直两个方向进行高通滤波的结果。它们之间,四个图像包含了所有的原始图像的信息,但是LH,HL,HH的稀疏性使它们更加容易压缩。

在图像压缩程序中,二维的小波分解被继续应用到LL图中,来组成四个新的子图。得到的新的低通图象继续被得到子图象。很多高频的采样结果都是接近0的,它就可以通过把小值来移除来达到更优化的传输。在解码端,原图像通过上采样,滤波和加值被重建。

3.4.3 量化
量化器把一个在值域X的信号量化减为到值域Y中。使用更小的位来表示量化后的信号是可行的,因为转换后的值域比原来更小了。一个标量的量化器把输入的信号映射到一个量化的输出值域中,而一个向量的量化器把一组输入采样值映射到一组量化值中。

3.4.3.1 标量量化
标量量化的一个简单的例子就是把小数近似到最近的整数上,比如,从R到Z的映射。这个过程是有损的(不可逆的)因为它是无法从被近似后的结果来恢复出原来的小数的。

一个量化的更加通用的例子就是:
FQ = round (X/QP)
Y=FQ * QP

这里QP是量化的步长。量化输出级间隔单一的QP间断值。

在图像或视频的CODEC中,量化操作通常由两部分构成:编码端的前向量化FQ,和解码端的反量化(IQ).(事实上量化是不可逆的,这样用一个更准确的叫法的话:标量器和重标量器。在两个标量值之间的QP步长是一个重要的参数。如果步长很长的话,那么量化值的范围就很小,这样的话就可以在传输过程中被更有效地被表示(高压缩比地压缩).但是重量化值是原信号值的近似。如果步长很小的话,重量化值与原始信号更加匹配,但是量化值就落到一个更大的范围之内,就降低了压缩的效率。


量化可以被用来在像DCT或小波变换之后,除去了小系数之后降低图像数据的精确度。一个图像或视频的前向量化器设计为映射小系数值到0而保留一部分大系数值。前向量化器的输出通常是一个稀疏的量化系数的数组,大都为0。

3.4.3.2 向量量化
一个向量量化器把一系列的输入数据(比如一块图像采样)映射到一个单值(codeword),而在解码端,每个单值(codeword)都映射到一个对于原始输入数据的近似。这组向量保存在编码和解码端,被用来当作一个编码表。在图像压缩中一个典型的向量量化程序如下:

1. 把原始图像分区(比如M × N像素块)
2. 从编码表中选择一个与当前区域最相近的向量
3. 把选定的向量的序号传给解码器
4. 在解码端,用选定的向量来重建一个原始图像的拷贝。

量化是被用在空间域中(比如说用向量量化过的图像采样组),但是它也可以被用在运动补偿或变换数据中.向量量化设计中的关键问题包含编码表的设计和在编码表中有效都查找最优化的向量的问题。

3.4.4 重排列和零编码
量化过的变换系数需要被仔细编码来进行存储和传输。在基于变换的图像或视频编码器上,量化器的输出是一个稀疏的数组,它包含了一些非0的系数和很多的0值系数。重排(把非0系数进行重组)和有效地表示0系数要在熵编码前进行操作。这些过程在DCT和小波变换中被提到了。

3.4.4.1 DCT
系数分布
一块图像或剩余采样的大的DCT系数通常都是在DC(0,0)系数“低频”部分。DCT系数的非0系数都聚在上顶部系数,而分布在水平和垂直方向是大约对称的。对于剩余区域来说,聚集在DC位置的系数是歪斜的,比如说,更多的非0系数在左手边出现。这是因为区域图片在垂直方向有很强的高频成分(因为在垂直方向的子采样),这样因为垂直频率的原因得到了更大的DCT系数。

扫描
在量化之后,块的DCT系数被重排以把非0系数进行组合,以使对于剩余0值的表示方法更加有效。优化的重组方法(扫描方法)依赖于非0DCT系数的分布情况。对于经典的一帧来说,合适的扫描方式应该是zigzag法,它是从左上角的DC系数开始的.从DC系数开始,每一个量化的系数被拷贝到一维数组里面。非0系数在重排数列的最前端被重组,而在之后是很长序列的0值。

zigzag扫描扫描方式对于一个域块可能并不是理想的,因为系数是歪斜分布的,那么一个修改后的扫描顺序可会是有效的,比如说左边的系数在右边系数扫描之前被扫描。


运行级编码
重排过程的输出是一个通常在开始端包含一个或多个簇的非0系数,后面的是一串0值系数。这些0值被编码而实现更优化的表示方法,比如说,通过表示一串0里面的数量来缩减表示位数等等。

例如:
输入数组:16,0,0,-3,5,6,0,0,0,0,-7, . . .
输出值:  (0,16),(2,-3),(0,5),(0,6),(4,-7). . .

DCT系数高频部分常被量化为0,这样一个重排的块通常以一串0结尾。一个特殊情况是需要指出一个块中的最后一个非0系数。在所谓的二维运行级编码被使用。每一个运行级对被用上面的方式进行编码,而一个单独的编码符号"last",用来指出最后一个0值的位置。如果三维的运行级编码被使用的话,每个符号编码要编三个量化值:运行度,级数以及最后非0值。如上例就可以写为:
(0, 16, 0), (2,-3, 0), (0, 5, 0), (0, 6, 0), (4,-7, 1)

最后一个码中的1表示这是这个块中最后一个非0值。

3.4.4.2 小波
系数分布
很多在高子波中的系数(右下方的系数值)都是接近于0值的,可以被量化到0而不损失图像的质量。非0系数对应于图像的结构,比如说,在violin图中,弓就有很清晰的垂直结构。当低频中的系数是非0时,有很大的可能性在高频相应的系数也是0。这们设想一个非0的量化系数树,以在低频中根部开始。在第一层的一个单个LL的系数在其他的第一层有相应的系数值。第一层系数位置映射到四个相应的子段的同一位置的系数位置.

零树编码
在熵编码之前越好地编码非0小波系数就越好.达到这一点的一个有效的方法就是从最低层开始编码每个非0系数树.最低层的系数需要被编码,接着是它在高层的子系数,如此反复.编码过程在树达到0值系数的时候一直运行.之后的的全0值的层的子层值都为0.在解码端,重建过程从树根开始,非0系数被解码并重建,当遇到零树的时候,所有的剩余的子结点都被设为0.这嵌入零树(EZW)的小波系数编码方法.编码过程包含一种其他的可能性,0值系数可能跟着(a)一个零树(就像原来那样),或(b)一个非零的子系数阵.(b)不常发生,但是如果考虑到这些不常发生的情况的时候,重建图像的质量会被轻微地提高.

2017-10-14 18:58:07 JNingWei 阅读数 12791

原理

超像素概念是2003年Xiaofeng Ren提出和发展起来的图像分割技术,是指具有相似纹理、颜色、亮度等特征的相邻像素构成的有一定视觉意义的不规则像素块。它利用像素之间特征的相似性将像素分组,用少量的超像素代替大量的像素来表达图片特征,很大程度上降低了图像后处理的复杂度,所以通常作为分割算法的预处理步骤。

常见的超像素分割方法包括: Graph-basedNCutTurbopixelQuick-shiftGraph-cut aGraph-cut b 以及 SLIC

其中,SLIC(simple linear iterativeclustering),即 简单线性迭代聚类
它是2010年提出的一种思想简单、实现方便的算法,将彩色图像转化为CIELAB颜色空间和XY坐标下的5维特征向量,然后对5维特征向量构造距离度量标准,对图像像素进行局部聚类的过程。

SLIC主要优点如下:

  1. 生成的超像素如同细胞一般紧凑整齐,邻域特征比较容易表达。这样基于像素的方法可以比较容易的改造为基于超像素的方法。
  2. 不仅可以分割彩色图,也可以兼容分割灰度图。
  3. 需要设置的参数非常少,默认情况下只需要设置一个预分割的超像素的数量。
  4. 相比其他的超像素分割方法,SLIC在运行速度、生成超像素的紧凑度、轮廓保持方面都比较理想。

效果图

经过观察发现,在迭代至第10轮后,分割效果基本不再发生变化。

原图

这里写图片描述


K=64 时

第1轮迭代,效果图:
这里写图片描述

第20轮迭代,效果图:
这里写图片描述

K=128 时

第1轮迭代,效果图:
这里写图片描述

第20轮迭代,效果图:
这里写图片描述

K=256 时

第1轮迭代,效果图:
这里写图片描述

第20轮迭代,效果图:
这里写图片描述

K=1024 时

第1轮迭代,效果图:
这里写图片描述

第20轮迭代,效果图:
这里写图片描述

实现代码

代码是我上网找来的,稍微改动了一丢丢。

原代码出处:SLIC算法分割超像素原理及Python实现

import math
from skimage import io, color
import numpy as np
from tqdm import trange


class Cluster(object):
    cluster_index = 1

    def __init__(self, h, w, l=0, a=0, b=0):
        self.update(h, w, l, a, b)
        self.pixels = []
        self.no = self.cluster_index
        self.cluster_index += 1

    def update(self, h, w, l, a, b):
        self.h = h
        self.w = w
        self.l = l
        self.a = a
        self.b = b

    def __str__(self):
        return "{},{}:{} {} {} ".format(self.h, self.w, self.l, self.a, self.b)

    def __repr__(self):
        return self.__str__()


class SLICProcessor(object):
    @staticmethod
    def open_image(path):
        """
        Return:
            3D array, row col [LAB]
        """
        rgb = io.imread(path)
        lab_arr = color.rgb2lab(rgb)
        return lab_arr

    @staticmethod
    def save_lab_image(path, lab_arr):
        """
        Convert the array to RBG, then save the image
        """
        rgb_arr = color.lab2rgb(lab_arr)
        io.imsave(path, rgb_arr)

    def make_cluster(self, h, w):
        return Cluster(h, w,
                       self.data[h][w][0],
                       self.data[h][w][1],
                       self.data[h][w][2])

    def __init__(self, filename, K, M):
        self.K = K
        self.M = M

        self.data = self.open_image(filename)
        self.image_height = self.data.shape[0]
        self.image_width = self.data.shape[1]
        self.N = self.image_height * self.image_width
        self.S = int(math.sqrt(self.N / self.K))

        self.clusters = []
        self.label = {}
        self.dis = np.full((self.image_height, self.image_width), np.inf)

    def init_clusters(self):
        h = self.S / 2
        w = self.S / 2
        while h < self.image_height:
            while w < self.image_width:
                self.clusters.append(self.make_cluster(h, w))
                w += self.S
            w = self.S / 2
            h += self.S

    def get_gradient(self, h, w):
        if w + 1 >= self.image_width:
            w = self.image_width - 2
        if h + 1 >= self.image_height:
            h = self.image_height - 2

        gradient = self.data[w + 1][h + 1][0] - self.data[w][h][0] + \
                   self.data[w + 1][h + 1][1] - self.data[w][h][1] + \
                   self.data[w + 1][h + 1][2] - self.data[w][h][2]
        return gradient

    def move_clusters(self):
        for cluster in self.clusters:
            cluster_gradient = self.get_gradient(cluster.h, cluster.w)
            for dh in range(-1, 2):
                for dw in range(-1, 2):
                    _h = cluster.h + dh
                    _w = cluster.w + dw
                    new_gradient = self.get_gradient(_h, _w)
                    if new_gradient < cluster_gradient:
                        cluster.update(_h, _w, self.data[_h][_w][0], self.data[_h][_w][1], self.data[_h][_w][2])
                        cluster_gradient = new_gradient

    def assignment(self):
        for cluster in self.clusters:
            for h in range(cluster.h - 2 * self.S, cluster.h + 2 * self.S):
                if h < 0 or h >= self.image_height: continue
                for w in range(cluster.w - 2 * self.S, cluster.w + 2 * self.S):
                    if w < 0 or w >= self.image_width: continue
                    L, A, B = self.data[h][w]
                    Dc = math.sqrt(
                        math.pow(L - cluster.l, 2) +
                        math.pow(A - cluster.a, 2) +
                        math.pow(B - cluster.b, 2))
                    Ds = math.sqrt(
                        math.pow(h - cluster.h, 2) +
                        math.pow(w - cluster.w, 2))
                    D = math.sqrt(math.pow(Dc / self.M, 2) + math.pow(Ds / self.S, 2))
                    if D < self.dis[h][w]:
                        if (h, w) not in self.label:
                            self.label[(h, w)] = cluster
                            cluster.pixels.append((h, w))
                        else:
                            self.label[(h, w)].pixels.remove((h, w))
                            self.label[(h, w)] = cluster
                            cluster.pixels.append((h, w))
                        self.dis[h][w] = D

    def update_cluster(self):
        for cluster in self.clusters:
            sum_h = sum_w = number = 0
            for p in cluster.pixels:
                sum_h += p[0]
                sum_w += p[1]
                number += 1
                _h = sum_h / number
                _w = sum_w / number
                cluster.update(_h, _w, self.data[_h][_w][0], self.data[_h][_w][1], self.data[_h][_w][2])

    def save_current_image(self, name):
        image_arr = np.copy(self.data)
        for cluster in self.clusters:
            for p in cluster.pixels:
                image_arr[p[0]][p[1]][0] = cluster.l
                image_arr[p[0]][p[1]][1] = cluster.a
                image_arr[p[0]][p[1]][2] = cluster.b
            image_arr[cluster.h][cluster.w][0] = 0
            image_arr[cluster.h][cluster.w][1] = 0
            image_arr[cluster.h][cluster.w][2] = 0
        self.save_lab_image(name, image_arr)

    def iterate_10times(self):
        self.init_clusters()
        self.move_clusters()
        for i in trange(20):
            self.assignment()
            self.update_cluster()
            name = 'Elegent_Girl_M{m}_K{k}_loop{loop}.jpg'.format(loop=i, m=self.M, k=self.K)
            self.save_current_image(name)


if __name__ == '__main__':
    for k in [64, 128, 256, 1024]:
        p = SLICProcessor('800.jpg', k, 30)
        p.iterate_10times()


打印结果:

  0%|          | 0/20 [00:00<?, ?it/s]/home/user/anaconda2/lib/python2.7/site-packages/skimage/util/dtype.py:111: UserWarning: Possible precision loss when converting from float64 to uint8
  "%s to %s" % (dtypeobj_in, dtypeobj))
100%|##########| 20/20 [32:36<00:00, 97.83s/it] 
100%|##########| 20/20 [24:37<00:00, 73.88s/it]
100%|##########| 20/20 [21:30<00:00, 64.55s/it]
100%|##########| 20/20 [18:49<00:00, 56.46s/it]

Process finished with exit code 0


超像素—学习笔记

阅读数 1459

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