图像处理对图像剪切的概念

2017-04-15 19:34:01 m0_37264397 阅读数 61093
  • 程序打包安装运行

    1、掌握Raspberry Pi的使用; 2、具备树莓派的网站建站技术; 3、具备基于树莓派的物联网开发技术; 4、基于Raspberry的人脸识别与检测实践;

    14人学习 张晨光
    免费试看

    傅立叶变换在图像处理中有非常重要的作用。因为不仅傅立叶分析涉及图像处理很多方面,傅立 叶改进算法,比如离散余弦变换,gabor与小波在图像处理中也有重要的分量。傅立叶变换在图像处理的重要作用:

   1.图像增强与图像去噪

      绝 大部分噪音都是图像的高频分量,通过低通滤波器来滤除高频——噪声; 边缘也是图像的高频分量,可以通过添加高频分量来增强原始图像的边缘;

  2.图像分割之边缘检测

     提 取图像高频分量

  3.图像特征提取:

     形状特征:傅里叶描述子

     纹 理特征:直接通过傅里叶系数来计算纹理特征

     其他特征:将提取的特征值进行傅里叶变 换来使特征具有平移、伸缩、旋转不变性

  4.图像压缩

     可以直接通过傅里叶系数来压缩数据;常用的离散余弦变换是傅立叶变换的实变换;傅立叶变换。

    傅里叶变换是将 时域信号分解为不同频率的正弦信号或余弦函数叠加之和。连续情况下要求原始信号在一个周期内满足绝对可积条件。离散情况下,傅里叶变换一定存在。冈萨雷斯 版<图像处理>里面的解释非常形象:一个恰当的比喻是将傅里叶变换比作一个玻璃棱镜。棱镜是可以将光分解为不同颜色的物理仪器,每个成分的颜 色由波长(或频率)来决定。傅里叶变换可以看作是数学上的棱镜,将函数基于频率分解为不同的成分。当我们考虑光时,讨论它的光谱或频率谱。同样,傅立叶变 换使我们能通过频率成分来分析一个函数。

    傅立叶变换有很多优良的性质。

    如线性, 对称性(可以用在计算信号的傅里叶变换里面);

    时移性:函数在时域中的时移,对 应于其在频率域中附加产生的相移,而幅度频谱则保持不变;

    频移性:函数在时域中乘 以e^jwt,可以使整个频谱搬移w。这个也叫调制定理,通讯里面信号的频分复用需要用到这个特性(将不同的信号调制到不同的频段上同时传输);

   卷积定理:时域卷积等于频域乘积;时域乘积等于频域卷积(附加一个系数)。(图像处理里面 这个是个重点)。

   信号在频率域的表现。

    在频域中,频率越大说明原始信号变化速度越快;频率越小说明原始信号越平缓。当频率为0时,表示直 流信号,没有变化。因此,频率的大小反应了信号的变化快慢。高频分量解释信号的突变部分,而低频分量决定信号的整体形象。

    在图像处理中,频域反应了图像在空域灰度变化剧烈程度,也就是图像灰度的变化速度,也就是图像的梯 度大小。对图像而言,图像的边缘部分是突变部分,变化较快,因此反应在频域上是高频分量;图像的噪声大部分情况下是高频部分;图像平缓变化部分则为低频分 量。也就是说,傅立叶变换提供另外一个角度来观察图像,可以将图像从灰度分布转化到频率分布上来观察图像的特征。书面一点说就是,傅里叶变换提供了一条从 空域到频率自由转换的途径。对图像处理而言,以下概念非常的重要:

    图像高频分量: 图像突变部分;在某些情况下指图像边缘信息,某些情况下指噪声,更多是两者的混合;

    低 频分量:图像变化平缓的部分,也就是图像轮廓信息

    高通滤波器:让图像使低频分量抑 制,高频分量通过

    低通滤波器:与高通相反,让图像使高频分量抑制,低频分量通过

    带通滤波器:使图像在某一部分的频率信息通过,其他过低或过高都抑制

    带阻滤波器,是带通的反。

模板运算与卷积定理

    在时域内做模板运算,实际上就是对图像进行卷积。 模板运算是图像处理一个很重要的处理过程,很多图像处理过程,比如增强/去噪(这两个分不清楚),边缘检测中普遍用到。根据卷积定理,时域卷积等价与频域 乘积。因此,在时域内对图像做模板运算就等效于在频域内对图像做滤波处理。

比如说 一个均值模板,其频域响应为一个低通滤波器;在时域内对图像作均值滤波就等效于在频域内对图像用均值模板的频域响应对图像的频域响应作一个低通滤波。

    图像去噪

    图像去噪 就是压制图像的噪音部分。因此,如果噪音是高频额,从频域的角度来看,就是需要用一个低通滤波器对图像进行处理。通过低通滤波器可以抑制图像的高频分量。 但是这种情况下常常会造成边缘信息的抑制。常见的去噪模板有均值模板,高斯模板等。这两种滤波器都是在局部区域抑制图像的高频分量,模糊图像边缘的同时也 抑制了噪声。还有一种非线性滤波-中值滤波器。中值滤波器对脉冲型噪声有很好的去掉。因为脉冲点都是突变的点,排序以后输出中值,那么那些最大点和最小点 就可以去掉了。中值滤波对高斯噪音效果较差。

    椒盐噪声:对于椒盐采用中值滤波可以 很好的去除。用均值也可以取得一定的效果,但是会引起边缘的模糊。

高斯白噪声:白 噪音在整个频域的都有分布,好像比较困难。

冈萨雷斯版图像处理P185:算术均值 滤波器和几何均值滤波器(尤其是后者)更适合于处理高斯或者均匀的随机噪声。谐波均值滤波器更适合于处理脉冲噪声。

    图像增强

有时候感觉图像增 强与图像去噪是一对矛盾的过程,图像增强经常是需要增强图像的边缘,以获得更好的显示效果,这就需要增加图像的高频分量。而图像去噪是为了消除图像的噪 音,也就是需要抑制高频分量。有时候这两个又是指类似的事情。比如说,消除噪音的同时图像的显示效果显著的提升了,那么,这时候就是同样的意思了。

    常见的图像增强方法有对比度拉伸,直方图均衡化,图像锐化等。前面两个是在空域进行基于像 素点的变换,后面一个是在频域处理。我理解的锐化就是直接在图像上加上图像高通滤波后的分量,也就是图像的边缘效果。对比度拉伸和直方图均衡化都是为了提 高图像的对比度,也就是使图像看起来差异更明显一些,我想,经过这样的处理以后,图像也应该增强了图像的高频分量,使得图像的细节上差异更大。同时也引入 了一些噪音。

    对图像二维傅立叶变换的意义

众所周至,傅立叶变换可以将连续或离散的函数序列从空域映射到频域上,因此,傅立叶变换是信息与信号学中不可获缺的强大工具。但是,由于傅立 叶变换在学习时是以一大堆公式的形式给出的,因此很多人(包括我在内)往往在做了一大堆习题掌握了变换的数学表示却对其变换后的物理意义一无所知,尤其是 自学的时候更是晕头转向。

     这里假设大家对傅立叶变换的数学表示已经很熟悉了,撇开傅立叶变换本身和其在其他领域的应用不谈,只谈图像傅立叶变换前后的对应关系。我们知道傅立叶变换 以前,图像(未压缩的位图)是由对在连续空间(现实空间)上的采样得到一系列点的集合,我们习惯用一个二维矩阵表示空间上各点,则图像可由 z=f(x,y)来表示。由于空间是三维的,图像是二维的,因此空间中物体在另一个维度上的关系就由梯度来表示,这样我们可以通过观察图像得知物体在三维 空间中的对应关系。为什么要提梯度?因为实际上对图像进行二维傅立叶变换得到频谱图,就是图像梯度的分布图,当然频谱图上的各点与图像上各点并不存在一一 对应的关系,即使在不移频的情况下也是没有。傅立叶频谱图上我们看到的明暗不一的亮点,实际上图像上某一点与邻域点差异的强弱,即梯度的大小,也即该点的 频率的大小(可以这么理解,图像中的低频部分指低梯度的点,高频部分相反)。一般来讲,梯度大则该点的亮度强,否则该点亮度弱。这样通过观察傅立叶变换后 的频谱图,也叫功率图(看看频谱图的各点的计算公式就知道为什么叫功率图了:)),我们首先就可以看出,图像的能量分布,如果频谱图中暗的点数更多,那么 实际图像是比较柔和的(因为各点与邻域差异都不大,梯度相对较小),反之,如果频谱图中亮的点数多,那么实际图像一定是尖锐的,边界分明且边界两边像素差 异较大的。对频谱移频到原点以后,可以看出图像的频率分布是以原点为圆心,对称分布的。将频谱移频到圆心除了可以清晰地看出图像频率分布以外,还有一个好 处,它可以分离出有周期性规律的干扰信号,比如正玄(sin的正玄,找不到这个字,郁闷)干扰,一副带有正玄干扰,移频到原点的频谱图上可以看出除了中心 以外还存在以某一点为中心,对称分布的亮点集合,这个集合就是干扰噪音产生的,这时可以很直观的通过在该位置放置带阻滤波器消除干扰。



数学公式:

1维的离散序列的DFT变换公式为:

 

2维的离散矩阵的DFT变换公式为:

1.使用模板处理图像相关概念:     

      模板:矩阵方块,其数学含义是一种卷积运算。
      卷积运算:可看作是加权求和的过程,使用到的图像区域中的每个像素分别于卷积核(权矩阵)的每个元素对应相
                乘,所有乘积之和作为区域中心像素的新值。
      卷积核:卷积时使用到的权用一个矩阵表示,该矩阵与使用的图像区域大小相同,其行、列都是奇数,
              是一个权矩阵。
      卷积示例:
              3 * 3 的像素区域R与卷积核G的卷积运算:
              R5(中心像素)=R1G1 + R2G2 + R3G3 + R4G4 + R5G5 + R6G6 + R7G7 + R8G8 + R9G9
            

2.使用模板处理图像的问题:
       边界问题:当处理图像边界像素时,卷积核与图像使用区域不能匹配,卷积核的中心与边界像素点对应,
                 卷积运算将出现问题。
       处理办法:
              A. 忽略边界像素,即处理后的图像将丢掉这些像素。
              B. 保留原边界像素,即copy边界像素到处理后的图像。

3.常用模板:



例子1.:

//【1】以灰度模式读取原始图像并显示
	Mat srcImage = imread("1.jpg", 0);
	if(!srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; } 
	imshow("原始图像" , srcImage);   

	//【2】将输入图像延扩到最佳的尺寸,边界用0补充
	int m = getOptimalDFTSize( srcImage.rows );
	int n = getOptimalDFTSize( srcImage.cols ); 
	//将添加的像素初始化为0.
	Mat padded;  
	copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0));

	//【3】为傅立叶变换的结果(实部和虚部)分配存储空间。
	//将planes数组组合合并成一个多通道的数组complexI
	Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
	Mat complexI;
	merge(planes, 2, complexI);         

	//【4】进行就地离散傅里叶变换
	dft(complexI, complexI);           

	//【5】将复数转换为幅值,即=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
	split(complexI, planes); // 将多通道数组complexI分离成几个单通道数组,planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
	magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude  
	Mat magnitudeImage = planes[0];

	//【6】进行对数尺度(logarithmic scale)缩放
	magnitudeImage += Scalar::all(1);
	log(magnitudeImage, magnitudeImage);//求自然对数

	//【7】剪切和重分布幅度图象限
	//若有奇数行或奇数列,进行频谱裁剪      
	magnitudeImage = magnitudeImage(Rect(0, 0, magnitudeImage.cols & -2, magnitudeImage.rows & -2));
	//重新排列傅立叶图像中的象限,使得原点位于图像中心  
	int cx = magnitudeImage.cols/2;
	int cy = magnitudeImage.rows/2;
	Mat q0(magnitudeImage, Rect(0, 0, cx, cy));   // ROI区域的左上
	Mat q1(magnitudeImage, Rect(cx, 0, cx, cy));  // ROI区域的右上
	Mat q2(magnitudeImage, Rect(0, cy, cx, cy));  // ROI区域的左下
	Mat q3(magnitudeImage, Rect(cx, cy, cx, cy)); // ROI区域的右下
	//交换象限(左上与右下进行交换)
	Mat tmp;                           
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);
	//交换象限(右上与左下进行交换)
	q1.copyTo(tmp);                 
	q2.copyTo(q1);
	tmp.copyTo(q2);

	//【8】归一化,用0到1之间的浮点值将矩阵变换为可视的图像格式
	//此句代码的OpenCV2版为:
	//normalize(magnitudeImage, magnitudeImage, 0, 1, CV_MINMAX); 
	//此句代码的OpenCV3版为:
	normalize(magnitudeImage, magnitudeImage, 0, 1, NORM_MINMAX); 

	//【9】显示效果图
	imshow("频谱幅值", magnitudeImage); 

函数解读:

C++: intgetOptimalDFTSize(int vecsize)

源码解读;

copyMakeBorder

C++: void copyMakeBorder(InputArraysrc, OutputArray dst, int top, int bottom,                                                        int left,int right, intborderType, const Scalar& value=Scalar())

src: 源图像

dst: 目标图像,和源图像有相同的类型,dst.cols=src.cols+left+right; dst.rows=src.rows+dst.top+dst.bottom

top:

bottom:

left:

right: 以上四个参数指定了在src图像周围附加的像素个数。

borderType: 边框类型

value: 当borderType==BORDER_CONSTANT时需要指定该值。


例子2.

  1. int cv::getOptimalDFTSizeint size0 )  
  2. {  
  3.    int a = 0, b = sizeof(optimalDFTSizeTab)/sizeof(optimalDFTSizeTab[0]) -1;  
  4.    if( (unsigned)size0 >= (unsigned)optimalDFTSizeTab[b] )  
  5.        return -1;  
  6.    
  7.    while( a < b )//二分查找合适的size  
  8.     {  
  9.        int c = (a + b) >> 1;  
  10.        if( size0 <= optimalDFTSizeTab[c] )  
  11.            b = c;  
  12.        else  
  13.            a = c+1;  
  14.     }  
  15.    
  16.     returnoptimalDFTSizeTab[b];  
  17. }</span>  

optimalDFTSizeTab定义在namespace cv中,里边的数值为2^x*3^y*5^z

static const int optimalDFTSizeTab[] = {1,2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16,…,                                                                                       2123366400, 2125764000};

 

 


示例代码:

  1. <span style="font-size:18px;">#include <opencv2/core/core.hpp>  
  2. #include<opencv2/highgui/highgui.hpp>  
  3. #include<opencv2/imgproc/imgproc.hpp>  
  4. #include <iostream>  
  5.    
  6. using namespace cv;  
  7. using namespace std;  
  8.    
  9. int main(){  
  10.        Matsrc = imread("fruits.jpg");  
  11.        if(src.empty())  
  12.        {  
  13.               return-1;  
  14.        }  
  15.    
  16.        Matsrc_gray;  
  17.        cvtColor(src,src_gray,CV_RGB2GRAY);//灰度图像做傅里叶变换  
  18.    
  19.        intm = getOptimalDFTSize(src_gray.rows);//2,3,5的倍数有更高效率的傅里叶转换  
  20.        intn = getOptimalDFTSize(src_gray.cols);  
  21.    
  22.        Matdst;  
  23.        ///把灰度图像放在左上角,在右边和下边扩展图像,扩展部分填充为0;  
  24.        copyMakeBorder(src_gray,dst,0,m-src_gray.rows,0,n-src_gray.cols,BORDER_CONSTANT,Scalar::all(0));  
  25.        cout<<dst.size()<<endl;  
  26.    
  27.        //新建一个两页的array,其中第一页用扩展后的图像初始化,第二页初始化为0  
  28.        Matplanes[] = {Mat_<float>(dst), Mat::zeros(dst.size(), CV_32F)};  
  29.        Mat  completeI;  
  30.        merge(planes,2,completeI);//把两页合成一个2通道的mat  
  31.    
  32.        //对上边合成的mat进行傅里叶变换,支持原地操作,傅里叶变换结果为复数。通道1存的是实部,通道2存的是虚部。  
  33.        dft(completeI,completeI);  
  34.    
  35.        split(completeI,planes);//把变换后的结果分割到各个数组的两页中,方便后续操作  
  36.        magnitude(planes[0],planes[1],planes[0]);//求傅里叶变换各频率的幅值,幅值放在第一页中。  
  37.    
  38.        MatmagI = planes[0];  
  39.        //傅立叶变换的幅度值范围大到不适合在屏幕上显示。高值在屏幕上显示为白点,  
  40.        //而低值为黑点,高低值的变化无法有效分辨。为了在屏幕上凸显出高低变化的连续性,我们可以用对数尺度来替换线性尺度:  
  41.        magI+= 1;  
  42.        log(magI,magI);//取对数  
  43.        magI= magI(Rect(0,0,src_gray.cols,src_gray.rows));//前边对原始图像进行了扩展,这里把对原始图像傅里叶变换取出,剔除扩展部分。  
  44.    
  45.    
  46.        //这一步的目的仍然是为了显示。 现在我们有了重分布后的幅度图,  
  47.        //但是幅度值仍然超过可显示范围[0,1] 。我们使用 normalize() 函数将幅度归一化到可显示范围。  
  48.        normalize(magI,magI,0,1,CV_MINMAX);//傅里叶图像进行归一化。  
  49.    
  50.    
  51.        //重新分配象限,使(0,0)移动到图像中心,  
  52.        //在《数字图像处理》中,傅里叶变换之前要对源图像乘以(-1)^(x+y)进行中心化。  
  53.        //这是是对傅里叶变换结果进行中心化  
  54.        intcx = magI.cols/2;  
  55.        intcy = magI.rows/2;  
  56.    
  57.        Mattmp;  
  58.        Matq0(magI,Rect(0,0,cx,cy));  
  59.        Matq1(magI,Rect(cx,0,cx,cy));  
  60.        Matq2(magI,Rect(0,cy,cx,cy));  
  61.        Matq3(magI,Rect(cx,cy,cx,cy));  
  62.    
  63.         
  64.        q0.copyTo(tmp);  
  65.        q3.copyTo(q0);  
  66.        tmp.copyTo(q3);  
  67.    
  68.        q1.copyTo(tmp);  
  69.        q2.copyTo(q1);  
  70.        tmp.copyTo(q2);  
  71.    
  72.         
  73.    
  74.        namedWindow("InputImage");  
  75.        imshow("InputImage",src);  
  76.    
  77.        namedWindow("SpectrumImage");  
  78.        imshow("SpectrumImage",magI);  
  79.    
  80.        waitKey();  
  81.        return0;  
  82. }</span>  


图像卷积原理:

图像处理-线性滤波-1 基础(相关算子、卷积算子、边缘效应)

这里讨论利用输入图像中像素的小邻域来产生输出图像的方法,在信号处理中这种方法称为滤波(filtering)。其中,最常用的是线性滤波:输出像素是输入邻域像素的加权和。

 

1.相关算子(Correlation Operator)

       定义:image image ,其中h称为相关核(Kernel).

        

  步骤:

        1)滑动核,使其中心位于输入图像g的(i,j)像素上

        2)利用上式求和,得到输出图像的(i,j)像素值

        3)充分上面操纵,直到求出输出图像的所有像素值

 

  例:

A = [17  24      15            h = [8     6
     23      14  16                 3     7
         13  20  22                 4     2]
     10  12  19  21             
     11  18  25     9]

计算输出图像的(2,4)元素=image

image

Matlab 函数:imfilter(A,h)

 

2.卷积算子(Convolution)

定义:image image ,其中

   步骤:

        1)将围绕中心旋转180度

        2)滑动核,使其中心位于输入图像g的(i,j)像素上

        3)利用上式求和,得到输出图像的(i,j)像素值

        4)充分上面操纵,直到求出输出图像的所有像素值

       例:计算输出图像的(2,4)元素=image

       image

Matlab 函数:Matlab 函数:imfilter(A,h,'conv')% imfilter默认是相关算子,因此当进行卷积计算时需要传入参数'conv'

3.边缘效应

当对图像边缘的进行滤波时,核的一部分会位于图像边缘外面。

image

常用的策略包括:

1)使用常数填充:imfilter默认用0填充,这会造成处理后的图像边缘是黑色的。

2)复制边缘像素:I3 = imfilter(I,h,'replicate');

image

   

4.常用滤波

fspecial函数可以生成几种定义好的滤波器的相关算子的核。

例:unsharp masking 滤波

1
2
3
4
5
I = imread('moon.tif');
h = fspecial('unsharp');
I2 = imfilter(I,h);
imshow(I), title('Original Image')
figure, imshow(I2), title('Filtered Image')
 
 

图像处理-线性滤波-2 图像微分(1、2阶导数和拉普拉斯算子)

更复杂些的滤波算子一般是先利用高斯滤波来平滑,然后计算其1阶和2阶微分。由于它们滤除高频和低频,因此称为带通滤波器(band-pass filters)。

在介绍具体的带通滤波器前,先介绍必备的图像微分知识。

1 一阶导数

连续函数,其微分可表达为image ,或image                         (1.1)

对于离散情况(图像),其导数必须用差分方差来近似,有

                                   image,前向差分 forward differencing                  (1.2)

                                   image ,中心差分 central differencing                     (1.3)

1)前向差分的Matlab实现

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
function dimg = mipforwarddiff(img,direction)
% MIPFORWARDDIFF     Finite difference calculations 
%
  DIMG = MIPFORWARDDIFF(IMG,DIRECTION)
%
 Calculates the forward-difference for a given direction
 IMG       : input image
 DIRECTION : 'dx' or 'dy'
 DIMG      : resultant image
%
  See also MIPCENTRALDIFF MIPBACKWARDDIFF MIPSECONDDERIV
  MIPSECONDPARTIALDERIV
  
  Omer Demirkaya, Musa Asyali, Prasana Shaoo, ... 9/1/06
  Medical Image Processing Toolbox
  
imgPad = padarray(img,[1 1],'symmetric','both');%将原图像的边界扩展
[row,col] = size(imgPad);
dimg = zeros(row,col);
switch (direction)   
case 'dx',
   dimg(:,1:col-1) = imgPad(:,2:col)-imgPad(:,1:col-1);%x方向差分计算,
case 'dy',
   dimg(1:row-1,:) = imgPad(2:row,:)-imgPad(1:row-1,:); 
otherwise, disp('Direction is unknown');
end;
dimg = dimg(2:end-1,2:end-1);

2)中心差分的Matlab实现

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
function dimg = mipcentraldiff(img,direction)
% MIPCENTRALDIFF     Finite difference calculations 
%
  DIMG = MIPCENTRALDIFF(IMG,DIRECTION)
%
 Calculates the central-difference for a given direction
 IMG       : input image
 DIRECTION : 'dx' or 'dy'
 DIMG      : resultant image
%
  See also MIPFORWARDDIFF MIPBACKWARDDIFF MIPSECONDDERIV
  MIPSECONDPARTIALDERIV
  
  Omer Demirkaya, Musa Asyali, Prasana Shaoo, ... 9/1/06
  Medical Image Processing Toolbox
  
img = padarray(img,[1 1],'symmetric','both');
[row,col] = size(img);
dimg = zeros(row,col);
switch (direction)
    case 'dx',
        dimg(:,2:col-1) = (img(:,3:col)-img(:,1:col-2))/2;
    case 'dy',
        dimg(2:row-1,:) = (img(3:row,:)-img(1:row-2,:))/2;
    otherwise,
        disp('Direction is unknown');
end
dimg = dimg(2:end-1,2:end-1);
1   

实例:技术图像x方向导数

1
2
I = imread('coins.png'); figure; imshow(I);
Id = mipforwarddiff(I,'dx'); figure, imshow(Id);

      image image

    原图像                           x方向1阶导数

 

2 图像梯度(Image Gradient)

图像I的梯度定义为image  ,其幅值为image 。出于计算性能考虑,幅值也可用image 来近似。

Matlab函数

1)gradient:梯度计算

2)quiver:以箭头形状绘制梯度。注意放大下面最右侧图可看到箭头,由于这里计算横竖两个方向的梯度,因此箭头方向都是水平或垂直的。

实例:仍采用上面的原始图像

1
2
3
4
5
I = double(imread('coins.png'));
[dx,dy]=gradient(I);
magnitudeI=sqrt(dx.^2+dy.^2);
figure;imagesc(magnitudeI);colormap(gray);%梯度幅值
hold on;quiver(dx,dy);%叠加梯度方向

        image image

                         梯度幅值          梯度幅值+梯度方向

 

3 二阶导数

对于一维函数,其二阶导数image ,即image 。它的差分函数为

                                 image                  (3.1)

 

3.1 普拉斯算子(laplacian operator)

3.1.2 概念

拉普拉斯算子是n维欧式空间的一个二阶微分算子。它定义为两个梯度向量算子的内积

                          image       (3.2)

其在二维空间上的公式为:    image                (3.3)

 

对于1维离散情况,其二阶导数变为二阶差分

1)首先,其一阶差分为image

2)因此,二阶差分为

          image

3)因此,1维拉普拉斯运算可以通过1维卷积核image 实现

 

对于2维离散情况(图像),拉普拉斯算子是2个维上二阶差分的和(见式3.3),其公式为:

image   (3.4)

上式对应的卷积核为

                       image

常用的拉普拉斯核有:

                      image

3.1.2 应用

拉普拉斯算子会突出像素值快速变化的区域,因此常用于边缘检测。

 

 

Matlab里有两个函数

1)del2

计算公式:image image  

2)fspecial:图像处理中一般利用Matlab函数fspecial

h = fspecial('laplacian', alpha) returns a 3-by-3 filter approximating the shape of the two-dimensional Laplacian operator.
The parameter alpha controls the shape of the Laplacian and must be in the range 0.0 to 1.0. The default value for alpha is 0.2.

 

3.1.3 资源

http://fourier.eng.hmc.edu/e161/lectures/gradient/node8.html (非常清晰的Laplacian Operator介绍,本文的主要参考)

http://homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm

 

 
 
 
 

sift算法

 

尺度不变特征转换(Scale-invariant feature transform 或 SIFT)是一种电脑视觉的算法用来侦测与描述影像中的局部性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量,此算法由 David Lowe 在1999年所发表,2004年完善总结。

Sift算法就是用不同尺度(标准差)的高斯函数对图像进行平滑,然后比较平滑后图像的差别,
差别大的像素就是特征明显的点。

sift可以同时处理亮度,平移,旋转,尺度的变化,利用特征点来提取特征描述符,最后在特征描述符之间寻找匹配


五个步骤

1构建尺度空间,检测极值点,获得尺度不变性

2特征点过滤并进行经确定位,剔除不稳定的特征点

3 在特征点处提取特征描述符,为特征点分配方向直

4声称特征描述子,利用特征描述符寻找匹配点

5计算变换参数

当2幅图像的sift特征向量生成以后,下一步就可以采用关键点特征向量的欧式距离来作为2幅图像中关键点的相似性判定量度


尺度空间:

尺度就是受delta这个参数控制的表示

而不同的L(x,y,delta)就构成了尺度空间,实际上具体计算的时候即使连续的高斯函数,都要被离散为矩阵来和数字图像进行卷积操作

L(x,y,delta)=G(x,y,e)*i(x,y)

尺度空间=原始图像(卷积)一个可变尺度的2维高斯函数G(x,y,e)


G(x,y,e) = [1/2*pi*e^2] * exp[ -(x^2 + y^2)/2e^2] 


为了更有效的在尺度空间检测到稳定的关键点,提出了高斯差分尺度空间,利用不同尺度的高斯差分核与原始图像i(x,y)卷积生成

D(x,y,e)=(G(x,y,ke)-G(x,y,e))*i(x,y)

=L(x,y,ke)-L(x,y,e)

(为避免遍历每个像素点)


高斯卷积:

在组建一组尺度空间后,再组建下一组尺度空间,对上一组尺度空间的最后一幅图像进行二分之一采样,得到下一组尺度空间的第一幅图像,然后进行像建立第一组尺度空间那样的操作,得到第二组尺度空间,公式定义为
         L(x,y,e) = G(x,y,e)*I(x,y)

    图像金字塔的构建:图像金字塔共O组,每组有S层,下一组的图像由上一组图像降采样得到、

高斯差分

    在尺度空间建立完毕后,为了能够找到稳定的关键点,采用高斯差分的方法来检测那些在局部位置的极值点,即采用俩个相邻的尺度中的图像相减,即公式定义为:
        D(x,y,e) = ((G(x,y,ke) - G(x,y,e)) * I(x,y) 
                 = L(x,y,ke) - L(x,y,e)
 咱们再来具体阐述下构造D(x,y,e)的详细步骤:
    1、首先采用不同尺度因子的高斯核对图像进行卷积以得到图像的不同尺度空间,将这一组图像作为金子塔图像的第一层。
    2、接着对第一层图像中的2倍尺度图像(相对于该层第一幅图像的2倍尺度)以2倍像素距离进行下采样来得到金子塔图像的第二层中的第一幅图像,对该图像采用不同尺度因子的高斯核进行卷积,以获得金字塔图像中第二层的一组图像。
    3、再以金字塔图像中第二层中的2倍尺度图像(相对于该层第一幅图像的2倍尺度)以2倍像素距离进行下采样来得到金字塔图像的第三层中的第一幅图像,对该图像采用不同尺度因子的高斯核进行卷积,以获得金字塔图像中第三层的一组图像。这样依次类推,从而获得了金字塔图像的每一层中的一组图像,
 4、对上图得到的每一层相邻的高斯图像相减,就得到了高斯差分图像,如下述第一幅图所示。下述第二幅图中的右列显示了将每组中相邻图像相减所生成的高斯差分图像的结果,限于篇幅,图中只给出了第一层和第二层高斯差分图像的计算
sift算法
 

 

图像处理之卷积概念

 

我们来看一下一维卷积的概念.
连续空间的卷积定义是 f(x)与g(x)的卷积是 f(t-x)g(x) 在t从负无穷到正无穷的积分值.t-x要在f(x)定义域内,所以看上去很大的积分实际上还是在一定范围的.
实际的过程就是f(x) 先做一个Y轴的反转,然后再沿X轴平移t就是f(t-x),然后再把g(x)拿来,两者乘积的值再积分.想象一下如果g(x)或者f(x)是个单位的阶越函数. 那么就是f(t-x)与g(x)相交部分的面积.这就是卷积了.
把积分符号换成求和就是离散空间的卷积定义了.

 

么在图像中卷积卷积地是什么意思呢,就是图像f(x),模板g(x),然后将模版g(x)在模版中移动,每到一个位置,就把f(x)与g(x)的定义域相交的元素进行乘积并且求和,得出新的图像一点,就是被卷积后的图像. 模版又称为卷积核.卷积核做一个矩阵的形状.


卷积定义上是线性系统分析经常用到的.线性系统就是一个系统的输入和输出的关系是线性关系.就是说整个系统可以分解成N多的无关独立变化,整个系统就是这些变化的累加.
如 x1->y1, x2->y2; 那么A*x1 + B*x2 -> A*y1 + B*y2 这就是线性系统. 表示一个线性系统可以用积分的形式 如 Y = Sf(t,x)g(x)dt S表示积分符号,就是f(t,x)表示的是A B之类的线性系数.
看上去很像卷积呀,,对如果f(t,x) = F(t-x) 不就是了吗.从f(t,x)变成F(t-x)实际上是说明f(t,x)是个线性移不变,就是说 变量的差不变化的时候,那么函数的值不变化. 实际上说明一个事情就是说线性移不变系统的输出可以通过输入和表示系统线性特征的函数卷积得到.


http://dept.wyu.edu.cn/dip/DIPPPT2005/����������ϵͳ.ppt


谈起卷积分当然要先说说冲击函数—-这个倒立的小蝌蚪,卷积其实就是为它诞生的。”冲击函数”是狄拉克为了解决一些瞬间作用的物理现象而提出的符号。
古人曰:”说一堆大道理不如举一个好例子”,冲量这一物理现象很能说明”冲击函数”。在t时间内对一物体作用F的力,我们可以让作用时间t很小,作用力F很大,但让Ft的乘积不变,即冲量不变。于是在用t做横坐标、F做纵坐标的坐标系中,就如同一个面积不变的长方形,底边被挤的窄窄的,高度被挤的高高的,在数学中它可以被挤到无限高,但即使它无限瘦、无限高、但它仍然保持面积不变(它没有被挤没!),为了证实它的存在,可以对它进行积分,积分就是求面积嘛!于是”卷积” 这个数学怪物就这样诞生了。说它是数学怪物是因为追求完美的数学家始终在头脑中转不过来弯,一个能瘦到无限小的家伙,竟能在积分中占有一席之地,必须将这个细高挑清除数学界。但物理学家、工程师们确非常喜欢它,因为它解决了很多当时数学家解决不了的实际问题。最终追求完美的数学家终于想通了,数学是来源于实际的,并最终服务于实际才是真。于是,他们为它量身定做了一套运作规律。于是,妈呀!你我都感觉眩晕的卷积分产生了。
例子:
有一个七品县令,喜欢用打板子来惩戒那些市井无赖,而且有个惯例:如果没犯大罪,只打一板,释放回家,以示爱民如子。
有一个无赖,想出人头地却没啥指望,心想:既然扬不了善名,出恶名也成啊。怎么出恶名?炒作呗!怎么炒作?找名人呀!他自然想到了他的行政长官——县令。
无赖于是光天化日之下,站在县衙门前撒了一泡尿,后果是可想而知地,自然被请进大堂挨了一板子,然后昂首挺胸回家,躺了一天,嘿!身上啥事也没有!第二天如法炮制,全然不顾行政长管的仁慈和衙门的体面,第三天、第四天……每天去县衙门领一个板子回来,还喜气洋洋地,坚持一个月之久!这无赖的名气已经和衙门口的臭气一样,传遍八方了!
县令大人噤着鼻子,呆呆地盯着案子上的惊堂木,拧着眉头思考一个问题:这三十个大板子怎么不好使捏?……想当初,本老爷金榜题名时,数学可是得了满分,今天好歹要解决这个问题:
——人(系统!)挨板子(脉冲!)以后,会有什么表现(输出!)?
——费话,疼呗!
——我问的是:会有什么表现?
——看疼到啥程度。像这无赖的体格,每天挨一个板子啥事都不会有,连哼一下都不可能,你也看到他那得意洋洋的嘴脸了(输出0);如果一次连揍他十个板子,他可能会皱皱眉头,咬咬牙,硬挺着不哼
(输出1);揍到二十个板子,他会疼得脸部扭曲,象猪似地哼哼(输出3);揍到三十个板子,他可能会象驴似地嚎叫,一把鼻涕一把泪地求你饶他一命(输出5);揍到四十个板子,他会大小便失禁,勉
强哼出声来(输出1);揍到五十个板子,他连哼一下都不可能(输出0)——死啦!
县令铺开坐标纸,以打板子的个数作为X轴,以哼哼的程度(输出)为Y轴,绘制了一条曲线:
——呜呼呀!这曲线象一座高山,弄不懂弄不懂。为啥那个无赖连挨了三十天大板却不喊绕命呀?
—— 呵呵,你打一次的时间间隔(Δτ=24小时)太长了,所以那个无赖承受的痛苦程度一天一利索,没有叠加,始终是一个常数;如果缩短打板子的时间间隔(建议 Δτ=0.5秒),那他的痛苦程度可就迅速叠加了;等到这无赖挨三十个大板(t=30)时,痛苦程度达到了他能喊叫的极限,会收到最好的惩戒效果,再多打就显示不出您的仁慈了。
——还是不太明白,时间间隔小,为什么痛苦程度会叠加呢?
——这与人(线性时不变系统)对板子(脉冲、输入、激励)的响应有关。什么是响应?人挨一个板子后,疼痛的感觉会在一天(假设的,因人而异)内慢慢消失(衰减),而不可能突然消失。这样一来,只要打板子的时间间隔很小,每一个板子引起的疼痛都来不及完全衰减,都会对最终的痛苦程度有不同的贡献:
t个大板子造成的痛苦程度=Σ(第τ个大板子引起的痛苦*衰减系数)
[衰减系数是(t-τ)的函数,仔细品味]
数学表达为:y(t)=∫T(τ)H(t-τ)
——拿人的痛苦来说卷积的事,太残忍了。除了人以外,其他事物也符合这条规律吗?
——呵呵,县令大人毕竟仁慈。其实除人之外,很多事情也遵循此道。好好想一想,铁丝为什么弯曲一次不折,快速弯曲多次却会轻易折掉呢?
——恩,一时还弄不清,容本官慢慢想来——但有一点是明确地——来人啊,将撒尿的那个无赖抓来,狠打40大板!
卷积及拉普拉斯变换的通俗解释–对于我这类没学过信号系统的人来说太需要了
卷积(convolution, 另一个通用名称是德文的Faltung)的名称由来,是在于当初定义它时,定义成 integ(f1(v)*f2(t-v))dv,积分区间在0到t之间。举个简单的例子,大家可以看到,为什么叫”卷积”了。比方说在(0,100)间积分,用简单的辛普生积分公式,积分区间分成100等分,那么看到的是f1(0)和f2(100)相乘,f1(1)和f2(99)相乘,f1(2)和f2 (98)相乘,……… 等等等等,就象是在坐标轴上回卷一样。所以人们就叫它”回卷积分”,或者”卷积”了。
为了理解”卷积”的物理意义,不妨将那个问题”相当于它的时域的信号与系统的单位脉冲响应的卷积”略作变化。这个变化纯粹是为了方便表达和理解,不影响任何其它方面。将这个问题表述成这样一个问题:一个信号通过一个系统,系统的响应是频率响应或波谱响应,且看如何理解卷积的物理意义。
假设信号函数为f, 响应函数为g。f不仅是时间的函数(信号时有时无),还是频率的函数(就算在某一固定时刻,还有的地方大有的地方小);g也是时间的函数(有时候有反应,有时候没反应),同时也是频率的函数(不同的波长其响应程度不一样)。那我们要看某一时刻 t 的响应信号,该怎么办呢?
这就需要卷积了。
要看某一时刻 t 的响应信号,自然是看下面两点:
1。你信号来的时候正赶上人家”系统”的响应时间段吗?
2。就算赶上系统响应时间段,响应有多少?
响 应不响应主要是看 f 和 g 两个函数有没有交叠;响应强度的大小不仅取决于所给的信号的强弱,还取决于在某频率处对单位强度响应率。响应强度是信号强弱和对单位强度信号响应率的乘积。”交叠”体现在f(t1)和g(t-t1)上,g之所以是”(t-t1)”就是看两个函数错开多少。
由于 f 和 g 两个函数都有一定的带宽分布(假若不用开头提到的”表述变化”就是都有一定的时间带宽分布),这个信号响应是在一定”范围”内广泛响应的。算总的响应信号,当然要把所有可能的响应加起来,实际上就是对所有可能t1积分了。积分范围虽然一般在负无穷到正无穷之间;但在没有信号或者没有响应的地方,积也是白积,结果是0,所以往往积分范围可以缩减。
这就是卷积及其物理意义啊。并成一句话来说,就是看一个时有时无(当然作为特例也可以永恒存在)的信号,跟一个响应函数在某一时刻有多大交叠。
*********拉普拉斯*********
拉普拉斯(1729-1827) 是法国数学家,天文学家,物理学家。他提出拉普拉斯变换(Laplace Transform) 的目的是想要解决他当时研究的牛顿引力场和太阳系的问题中涉及的积分微分方程。
拉普拉斯变换其实是一个数学上的简便算法;想要了解其”物理”意义 — 如果有的话 — 请看我举这样一个例子:
问题:请计算十万乘以一千万。
对于没学过指数的人,就只会直接相乘;对于学过指数的人,知道不过是把乘数和被乘数表达成指数形式后,两个指数相加就行了;如果要问究竟是多少,把指数转回来就是。
“拉 普拉斯变换” 就相当于上述例子中把数转换成”指数” 的过程;进行了拉普拉斯变换之后,复杂的微分方程(对应于上例中”复杂”的乘法) 就变成了简单的代数方程,就象上例中”复杂”的乘法变成了简单的加减法。再把简单的代数方程的解反变换回去(就象把指数重新转换会一般的数一样),就解决了原来那个复杂的微分方程。
所以要说拉普拉斯变换真有” 物理意义”的话,其物理意义就相当于人们把一般的有理数用指数形式表达一样。
另外说两句题外话:
1 。拉普拉斯变换之所以现在在电路中广泛应有,根本原因是电路中也广泛涉及了微分方程。
2。拉普拉斯变换与Z变换当然有紧密联系;其本质区别在于拉氏变换处理的是时间上连续的问题,Z变换处理的是时间上分立的问题。
Signals, Linear Systems, and Convolution
Download from here
 
我们都知道卷积公式,但是它有什么物理意义呢?平时我们用卷积做过很多事情,信号处理时,输出函数是输入函数和系统函数的卷积;在图像处理时,两组幅分辨率不同的图卷积之后得到的互相平滑的图像可以方便处理。卷积甚至可以用在考试作弊中,为了让照片同时像两个人,只要把两人的图像卷积处理即可,这就是一种平滑的过程,可是我们怎么才能真正把公式和实际建立起一种联系呢?生活中就有实例:
     比如说你的老板命令你干活,你却到楼下打台球去了,后来被老板发现,他非常气愤,扇了你一巴掌(注意,这就是输入信号,脉冲),于是你的脸上会渐渐地(贱贱地)鼓起来一个包,你的脸就是一个系统,而鼓起来的包就是你的脸对巴掌的响应。
      好,这样就和信号系统建立起来意义对应的联系。下面还需要一些假设来保证论证的严谨:假定你的脸是线性时不变系统,也就是说,无论什么时候老板打你一巴掌,打在你脸的同一位置(这似乎要求你的脸足够光滑,如果你说你长了很多青春痘,甚至整个脸皮处处连续处处不可导,那难度太大了,我就无话可说了),你的脸上总是会在相同的时间间隔内鼓起来一个相同高度的包来,并且假定以鼓起来的包的大小作为系统输出。好了,那么,下面可以进入核心内容——卷积了!
      如果你每天都到楼下去打台球,那么老板每天都要扇你一巴掌,不过当老板打你一巴掌后,你5分钟就消肿了,所以时间长了,你甚至就适应这种生活了……如果有一天,老板忍无可忍,以0.5秒的间隔开始不间断的扇你的过程,这样问题就来了:第一次扇你鼓起来的包还没消肿,第二个巴掌就来了,你脸上的包就可能鼓起来两倍高,老板不断扇你,脉冲不断作用在你脸上,效果不断叠加了,这样这些效果就可以求和了,结果就是你脸上的包的高度岁时间变化的一个函数了(注意理解)!
      如果老板再狠一点,频率越来越高,以至于你都辨别不清时间间隔了,那么,求和就变成积分了。可以这样理解,在这个过程中的某一固定的时刻,你的脸上的包的鼓起程度和什么有关呢?和之前每次打你都有关!但是各次的贡献是不一样的,越早打的巴掌,贡献越小,这就是说,某一时刻的输出是之前很多次输入乘以各自的衰减系数之后的叠加而形成某一点的输出,然后再把不同时刻的输出点放在一起,形成一个函数,这就是卷积。卷积之后的函数就是你脸上的包的大小随时间变化的函数。本来你的包几分钟就可以消肿,可是如果连续打,几个小时也消不了肿了,这难道不是一种平滑过程么?反映到公式上,f(a)就是第a个巴掌,g(x-a)就是第a个巴掌在x时刻的作用程度,乘起来再叠加就ok了,这就是卷积!
     最后提醒各位,请勿亲身尝试……

卷积的物理意义?
在信号与系统中,两个函数所要表达的物理含义是什么?例如,一个系统,其单位冲激响应为h(t),当输入信号为f(t)时,该系统的输出为y(t)。为什么y(t)是f(t)和h(t)的卷积?(从数学推导我明白,但其物理意义不明白。)y(t)是f(t)和h(t)的卷积表达了一个什么意思?

卷积(convolution, 另一个通用名称是德文的Faltung)的名称由来,是在于当初定义它时,定义成 integ(f1(v)*f2(t-v))dv,积分区间在0到t之间。举个简单的例子,大家可以看到,为什么叫“卷积”了。比方说在(0,100)间积分,用简单的辛普生积分公式,积分区间分成100等分,那么看到的是f1(0)和f2(100)相乘,f1(1)和f2(99)相乘,f1(2)和f2(98)相乘,......... 等等等等,就象是在坐标轴上回卷一样。所以人们就叫它“回卷积分”,或者“卷积”了。
为了理解“卷积”的物理意义,不妨将那个问题“相当于它的时域的信号与系统的单位脉冲响应的卷积”略作变化。这个变化纯粹是为了方便表达和理解,不影响任何其它方面。将这个问题表述成这样一个问题:一个信号通过一个系统,系统的响应是频率响应或波谱响应,且看如何理解卷积的物理意义。
假设信号函数为f, 响应函数为g。f不仅是时间的函数(信号时有时无),还是频率的函数(就算在某一固定时刻,还有的地方大有的地方小);g也是时间的函数(有时候有反应,有时候没反应),同时也是频率的函数(不同的波长其响应程度不一样)。那我们要看某一时刻 t 的响应信号,该怎么办呢?
这就需要卷积了。
其实卷积积分应用广泛用在信号里面,一个是频域一个是时域
 

卷积是个啥?我忽然很想从本质上理解它。于是我从抽屉里翻出自己珍藏了许多年,每每下决心阅读却永远都读不完的《应用傅立叶变换》。
 
3.1 一维卷积的定义
 
函数f(x)与函数h(x)的卷积,由函参量的无穷积分

  定义。这里参量x和积分变量α皆为实数;函数f和h可实可复。
 
定义虽然找到了,但我还是一头雾水。卷积是个无穷积分吗?那它是干啥用的?再往后翻:几何说明、运算举例、基本性质,一堆的公式,就是没有说它是干啥用的。我于是坐在那呆想,忽然第二个困扰我的问题冒了出来:傅立叶变换是个啥?接着就是第三个、第四个、……、第N个问题。
 
傅立叶变换是个啥?听说能将时域上的东东变到频域上分析?哎?是变到频域上还是空间域上来着?到底啥是时域,频域,空间域?
 
上网查傅立叶变换的物理意义,没发现明确答案,倒发现了许多和我一样晕着问问题的人。结果又多出了许多名词,能量?功率谱?图像灰度域?……没办法又去翻那本教材。
 
1.1 一维傅立叶变换的定义与傅立叶积分定理
 
设f(x)是实变量x的函数,该函数可实可复,称积分

为函数f(x)的傅立叶变换。
 
吐血,啥是无穷积分来着?积分是啥来着?还能记起三角函数和差化积、积化和差公式吗?我忽然有种想把高中课本寻来重温的冲动。
 
卷积主要是为了将信号运算从时域转换为频域。
信号的时域的卷积等于频域的乘积。
利用这个性质以及特殊的δ函数可以通过抽样构造简单的调制电路
 
 
我比较赞同卷积的相关性的作用  在通信系统中的接收机部分MF匹配滤波器等就是本质上的相关
匹配滤波器最简单的形式就是原信号反转移位相乘积分得到的近似=相关
相关性越好得到的信号越强   这个我们有一次大作业做的  做地做到呕吐  呵呵
还有解调中一些东西本质就是相关
 

卷积公式  解释  卷积公式是用来求随机变量和的密度函数(pdf)的计算公式。  定义式:  z(t)=x(t)*y(t)= ∫x(m)y(t-m)dm.   已知x,y的pdf,x(t),y(t).现在要求z=x+y的pdf. 我们作变量替显,令  z=x+y,m=x. 雅可比行列式=1.那么,z,m联合密度就是f(z,m)=x(m)y(z-m)*1. 这样,就可以很容易求Z的在(z,m)中边缘分布  即fZ(z)=∫x(m)y(z-m)dm..... 由于这个公式和x(t),y(t)存在一一对应的关系。为了方便,所以记 ∫x(m)y(z-m)dm=x(t)*y(t)   长度为m的向量序列u和长度为n的向量序列v,卷积w的向量序列长度为(m+n-1),   u(n)与v(n)的卷积w(n)定义为: w(n)=u(n)@v(n)=sum(v(m)*u(n-m)),m from 负无穷到正无穷;   当m=n时w(1) = u(1)*v(1)   w(2) = u(1)*v(2)+u(2)*v(1)   w(3) = u(1)*v(3)+u(2)*v(2)+u(3)*v(1)   …   w(n) = u(1)*v(n)+u(2)*v(n-1)+ … +u(n)*v(1)   …   w(2*n-1) = u(n)*v(n)   当m≠n时,应以0补齐阶次低的向量的高位后进行计算  这是数学中常用的一个公式,在概率论中,是个重点也是一个难点。

  卷积公式是用来求随机变量和的密度函数(pdf)的计算公式。
  定义式:
  z(t)=x(t)*y(t)= ∫x(m)y(t-m)dm.
  已知x,y的pdf,x(t),y(t).现在要求z=x+y的pdf. 我们作变量替显,令
  z=x+y,m=x. 雅可比行列式=1.那么,t,m联合密度就是f(z,m)=x(m)y(z-m)*1. 这样,就可以很容易求Z的在(z,m)中边缘分布
  即fZ(z)=∫x(m)y(z-m)dm..... 由于这个公式和x(t),y(t)存在一一对应的关系。为了方便,所以记 ∫x(m)y(z-m)dm=x(t)*y(t)
 
卷积是一种线性运算,图像处理中常见的mask运算都是卷积,广泛应用于图像滤波。castlman的书对卷积讲得很详细。
高斯变换就是用高斯函数对图像进行卷积。高斯算子可以直接从离散高斯函数得到:
for(i=0; i<N; i++)
{
for(j=0; j<N; j++)
{
g[i*N+j]=exp(-((i-(N-1)/2)^2+(j-(N-1)/2)^2))/(2*delta^2));
sum += g[i*N+j];
}
}
再除以 sum 得到归一化算子
N是滤波器的大小,delta自选
首先,再提到卷积之前,必须提到卷积出现的背景。卷积是在信号与线性系统的基础上或背景中出现的,脱离这个背景单独谈卷积是没有任何意义的,除了那个所谓褶反公式上的数学意义和积分(或求和,离散情况下)。
信号与线性系统,讨论的就是信号经过一个线性系统以后发生的变化(就是输入输出和所经过的所谓系统,这三者之间的数学关系)。所谓线性系统的含义,就是,这个所谓的系统,带来的输出信号与输入信号的数学关系式之间是线性的运算关系。
因此,实际上,都是要根据我们需要待处理的信号形式,来设计所谓的系统传递函数,那么这个系统的传递函数和输入信号,在数学上的形式就是所谓的卷积关系。
卷积关系最重要的一种情况,就是在信号与线性系统或数字信号处理中的卷积定理。利用该定理,可以将时间域或空间域中的卷积运算等价为频率域的相乘运算,从而利用FFT等快速算法,实现有效的计算,节省运算代价。

参考:

http://www.cnblogs.com/a-toad/archive/2008/10/24/1318921.html

http://blog.sina.com.cn/s/blog_6d0e97bb01013op2.html

2019-04-23 16:24:29 Eastmount 阅读数 19609
  • 程序打包安装运行

    1、掌握Raspberry Pi的使用; 2、具备树莓派的网站建站技术; 3、具备基于树莓派的物联网开发技术; 4、基于Raspberry的人脸识别与检测实践;

    14人学习 张晨光
    免费试看

该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门、OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子、图像增强技术、图像分割等,后期结合深度学习研究图像识别、图像分类应用。希望文章对您有所帮助,如果有不足之处,还请海涵~

该系列在github所有源代码:https://github.com/eastmountyxz/ImageProcessing-Python
PS:请求帮忙点个Star,哈哈,第一次使用Github,以后会分享更多代码,一起加油。

同时推荐作者的C++图像系列知识:
[数字图像处理] 一.MFC详解显示BMP格式图片
[数字图像处理] 二.MFC单文档分割窗口显示图片
[数字图像处理] 三.MFC实现图像灰度、采样和量化功能详解
[数字图像处理] 四.MFC对话框绘制灰度直方图
[数字图像处理] 五.MFC图像点运算之灰度线性变化、灰度非线性变化、阈值化和均衡化处理详解
[数字图像处理] 六.MFC空间几何变换之图像平移、镜像、旋转、缩放详解
[数字图像处理] 七.MFC图像增强之图像普通平滑、高斯平滑、Laplacian、Sobel、Prewitt锐化详解

前文参考:
[Python图像处理] 一.图像处理基础知识及OpenCV入门函数
[Python图像处理] 二.OpenCV+Numpy库读取与修改像素
[Python图像处理] 三.获取图像属性、兴趣ROI区域及通道处理
[Python图像处理] 四.图像平滑之均值滤波、方框滤波、高斯滤波及中值滤波
[Python图像处理] 五.图像融合、加法运算及图像类型转换
[Python图像处理] 六.图像缩放、图像旋转、图像翻转与图像平移
[Python图像处理] 七.图像阈值化处理及算法对比
[Python图像处理] 八.图像腐蚀与图像膨胀
[Python图像处理] 九.形态学之图像开运算、闭运算、梯度运算
[Python图像处理] 十.形态学之图像顶帽运算和黑帽运算
[Python图像处理] 十一.灰度直方图概念及OpenCV绘制直方图
[Python图像处理] 十二.图像几何变换之图像仿射变换、图像透视变换和图像校正
[Python图像处理] 十三.基于灰度三维图的图像顶帽运算和黑帽运算
[Python图像处理] 十四.基于OpenCV和像素处理的图像灰度化处理
[Python图像处理] 十五.图像的灰度线性变换
[Python图像处理] 十六.图像的灰度非线性变换之对数变换、伽马变换
[Python图像处理] 十七.图像锐化与边缘检测之Roberts算子、Prewitt算子、Sobel算子和Laplacian算子
[Python图像处理] 十八.图像锐化与边缘检测之Scharr算子、Canny算子和LOG算子
[Python图像处理] 十九.图像分割之基于K-Means聚类的区域分割
[Python图像处理] 二十.图像量化处理和采样处理及局部马赛克特效
[Python图像处理] 二十一.图像金字塔之图像向下取样和向上取样

前面一篇文章我讲解了Python图像量化、采样处理及图像金字塔。本文主要讲解图像傅里叶变换的相关内容,在数字图像处理中,有两个经典的变换被广泛应用——傅里叶变换和霍夫变换。其中,傅里叶变换主要是将时间域上的信号转变为频率域上的信号,用来进行图像除噪、图像增强等处理。基础性文章,希望对你有所帮助。同时,该部分知识均为杨秀璋查阅资料撰写,转载请署名CSDN+杨秀璋及原地址出处,谢谢!!

1.图像傅里叶变换
2.Numpy实现傅里叶变换
3.Numpy实现傅里叶逆变换
4.OpenCV实现傅里叶变换
5.OpenCV实现傅里叶逆变换


PS:文章参考自己以前系列图像处理文章及OpenCV库函数,同时参考如下文献:
《数字图像处理》(第3版),冈萨雷斯著,阮秋琦译,电子工业出版社,2013年.
《数字图像处理学》(第3版),阮秋琦,电子工业出版社,2008年,北京.
《OpenCV3编程入门》,毛星云,冷雪飞,电子工业出版社,2015,北京.
百度百科-傅里叶变换
网易云课堂-高登教育 Python+OpenCV图像处理
安安zoe-图像的傅里叶变换
daduzimama-图像的傅里叶变换的迷思----频谱居中
tenderwx-数字图像处理-傅里叶变换在图像处理中的应用
小小猫钓小小鱼-深入浅出的讲解傅里叶变换(真正的通俗易懂)


一.图像傅里叶变换原理

傅里叶变换(Fourier Transform,简称FT)常用于数字信号处理,它的目的是将时间域上的信号转变为频率域上的信号。随着域的不同,对同一个事物的了解角度也随之改变,因此在时域中某些不好处理的地方,在频域就可以较为简单的处理。同时,可以从频域里发现一些原先不易察觉的特征。傅里叶定理指出“任何连续周期信号都可以表示成(或者无限逼近)一系列正弦信号的叠加。”

下面引用李老师 “Python+OpenCV图像处理” 中的一个案例,非常推荐同学们去购买学习。如下图所示,他将某饮料的制作过程的时域角度转换为频域角度。

绘制对应的时间图和频率图如下所示:

傅里叶公式如下,其中w表示频率,t表示时间,为复变函数。它将时间域的函数表示为频率域的函数f(t)的积分。

傅里叶变换认为一个周期函数(信号)包含多个频率分量,任意函数(信号)f(t)可通过多个周期函数(或基函数)相加合成。从物理角度理解,傅里叶变换是以一组特殊的函数(三角函数)为正交基,对原函数进行线性变换,物理意义便是原函数在各组基函数的投影。如下图所示,它是由三条正弦曲线组合成。

傅里叶变换可以应用于图像处理中,经过对图像进行变换得到其频谱图。从谱频图里频率高低来表征图像中灰度变化剧烈程度。图像中的边缘信号和噪声信号往往是高频信号,而图像变化频繁的图像轮廓及背景等信号往往是低频信号。这时可以有针对性的对图像进行相关操作,例如图像除噪、图像增强和锐化等。

二维图像的傅里叶变换可以用以下数学公式(15-3)表达,其中f是空间域(Spatial Domain))值,F是频域(Frequency Domain)值

对上面的傅里叶变换有了大致的了解之后,下面通过Numpy和OpenCV分别讲解图像傅里叶变换的算法及操作代码。


二.Numpy实现傅里叶变换

Numpy中的 FFT包提供了函数 np.fft.fft2()可以对信号进行快速傅里叶变换,其函数原型如下所示,该输出结果是一个复数数组(Complex Ndarry)。

fft2(a, s=None, axes=(-2, -1), norm=None)

  • a表示输入图像,阵列状的复杂数组
  • s表示整数序列,可以决定输出数组的大小。输出可选形状(每个转换轴的长度),其中s[0]表示轴0,s[1]表示轴1。对应fit(x,n)函数中的n,沿着每个轴,如果给定的形状小于输入形状,则将剪切输入。如果大于则输入将用零填充。如果未给定’s’,则使用沿’axles’指定的轴的输入形状
  • axes表示整数序列,用于计算FFT的可选轴。如果未给出,则使用最后两个轴。“axes”中的重复索引表示对该轴执行多次转换,一个元素序列意味着执行一维FFT
  • norm包括None和ortho两个选项,规范化模式(请参见numpy.fft)。默认值为无

Numpy中的fft模块有很多函数,相关函数如下:

#计算一维傅里叶变换
numpy.fft.fft(a, n=None, axis=-1, norm=None)
#计算二维的傅里叶变换
numpy.fft.fft2(a, n=None, axis=-1, norm=None)
#计算n维的傅里叶变换
numpy.fft.fftn()
#计算n维实数的傅里叶变换
numpy.fft.rfftn()
#返回傅里叶变换的采样频率
numpy.fft.fftfreq()
#将FFT输出中的直流分量移动到频谱中央
numpy.fft.shift()

下面的代码是通过Numpy库实现傅里叶变换,调用np.fft.fft2()快速傅里叶变换得到频率分布,接着调用np.fft.fftshift()函数将中心位置转移至中间,最终通过Matplotlib显示效果图。

# -*- coding: utf-8 -*-
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

#读取图像
img = cv.imread('test.png', 0)

#快速傅里叶变换算法得到频率分布
f = np.fft.fft2(img)

#默认结果中心点位置是在左上角,
#调用fftshift()函数转移到中间位置
fshift = np.fft.fftshift(f)       

#fft结果是复数, 其绝对值结果是振幅
fimg = np.log(np.abs(fshift))

#展示结果
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('Original Fourier')
plt.axis('off')
plt.subplot(122), plt.imshow(fimg, 'gray'), plt.title('Fourier Fourier')
plt.axis('off')
plt.show()

输出结果如图15-2所示,左边为原始图像,右边为频率分布图谱,其中越靠近中心位置频率越低,越亮(灰度值越高)的位置代表该频率的信号振幅越大。


三.Numpy实现傅里叶逆变换

下面介绍Numpy实现傅里叶逆变换,它是傅里叶变换的逆操作,将频谱图像转换为原始图像的过程。通过傅里叶变换将转换为频谱图,并对高频(边界)和低频(细节)部分进行处理,接着需要通过傅里叶逆变换恢复为原始效果图。频域上对图像的处理会反映在逆变换图像上,从而更好地进行图像处理。

图像傅里叶变化主要使用的函数如下所示:

#实现图像逆傅里叶变换,返回一个复数数组
numpy.fft.ifft2(a, n=None, axis=-1, norm=None)
#fftshit()函数的逆函数,它将频谱图像的中心低频部分移动至左上角
numpy.fft.fftshift()
#将复数转换为0至255范围
iimg = numpy.abs(逆傅里叶变换结果)

下面的代码分别实现了傅里叶变换和傅里叶逆变换。

# -*- coding: utf-8 -*-
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

#读取图像
img = cv.imread('Lena.png', 0)

#傅里叶变换
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
res = np.log(np.abs(fshift))

#傅里叶逆变换
ishift = np.fft.ifftshift(fshift)
iimg = np.fft.ifft2(ishift)
iimg = np.abs(iimg)

#展示结果
plt.subplot(131), plt.imshow(img, 'gray'), plt.title('Original Image')
plt.axis('off')
plt.subplot(132), plt.imshow(res, 'gray'), plt.title('Fourier Image')
plt.axis('off')
plt.subplot(133), plt.imshow(iimg, 'gray'), plt.title('Inverse Fourier Image')
plt.axis('off')
plt.show()

输出结果如图15-4所示,从左至右分别为原始图像、频谱图像、逆傅里叶变换转换图像。


四.OpenCV实现傅里叶变换

OpenCV 中相应的函数是cv2.dft()和用Numpy输出的结果一样,但是是双通道的。第一个通道是结果的实数部分,第二个通道是结果的虚数部分,并且输入图像要首先转换成 np.float32 格式。其函数原型如下所示:

dst = cv2.dft(src, dst=None, flags=None, nonzeroRows=None)

  • src表示输入图像,需要通过np.float32转换格式
  • dst表示输出图像,包括输出大小和尺寸
  • flags表示转换标记,其中DFT _INVERSE执行反向一维或二维转换,而不是默认的正向转换;DFT _SCALE表示缩放结果,由阵列元素的数量除以它;DFT _ROWS执行正向或反向变换输入矩阵的每个单独的行,该标志可以同时转换多个矢量,并可用于减少开销以执行3D和更高维度的转换等;DFT _COMPLEX_OUTPUT执行1D或2D实数组的正向转换,这是最快的选择,默认功能;DFT _REAL_OUTPUT执行一维或二维复数阵列的逆变换,结果通常是相同大小的复数数组,但如果输入数组具有共轭复数对称性,则输出为真实数组
  • nonzeroRows表示当参数不为零时,函数假定只有nonzeroRows输入数组的第一行(未设置)或者只有输出数组的第一个(设置)包含非零,因此函数可以处理其余的行更有效率,并节省一些时间;这种技术对计算阵列互相关或使用DFT卷积非常有用

注意,由于输出的频谱结果是一个复数,需要调用cv2.magnitude()函数将傅里叶变换的双通道结果转换为0到255的范围。其函数原型如下:

cv2.magnitude(x, y)

  • x表示浮点型X坐标值,即实部
  • y表示浮点型Y坐标值,即虚部
    最终输出结果为幅值,即:

完整代码如下所示:

# -*- coding: utf-8 -*-
import numpy as np
import cv2
from matplotlib import pyplot as plt

#读取图像
img = cv2.imread('Lena.png', 0)

#傅里叶变换
dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)

#将频谱低频从左上角移动至中心位置
dft_shift = np.fft.fftshift(dft)

#频谱图像双通道复数转换为0-255区间
result = 20*np.log(cv2.magnitude(dft_shift[:,:,0], dft_shift[:,:,1]))

#显示图像
plt.subplot(121), plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(result, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

输出结果如图15-5所示,左边为原始“Lena”图,右边为转换后的频谱图像,并且保证低频位于中心位置。


五.OpenCV实现傅里叶逆变换

在OpenCV 中,通过函数cv2.idft()实现傅里叶逆变换,其返回结果取决于原始图像的类型和大小,原始图像可以为实数或复数。其函数原型如下所示:

dst = cv2.idft(src[, dst[, flags[, nonzeroRows]]])

  • src表示输入图像,包括实数或复数
  • dst表示输出图像
  • flags表示转换标记
  • nonzeroRows表示要处理的dst行数,其余行的内容未定义(请参阅dft描述中的卷积示例)

完整代码如下所示:

# -*- coding: utf-8 -*-
import numpy as np
import cv2
from matplotlib import pyplot as plt

#读取图像
img = cv2.imread('Lena.png', 0)

#傅里叶变换
dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)
dftshift = np.fft.fftshift(dft)
res1= 20*np.log(cv2.magnitude(dftshift[:,:,0], dftshift[:,:,1]))

#傅里叶逆变换
ishift = np.fft.ifftshift(dftshift)
iimg = cv2.idft(ishift)
res2 = cv2.magnitude(iimg[:,:,0], iimg[:,:,1])

#显示图像
plt.subplot(131), plt.imshow(img, 'gray'), plt.title('Original Image')
plt.axis('off')
plt.subplot(132), plt.imshow(res1, 'gray'), plt.title('Fourier Image')
plt.axis('off')
plt.subplot(133), plt.imshow(res2, 'gray'), plt.title('Inverse Fourier Image')
plt.axis('off')
plt.show()

输出结果如图15-6所示,第一幅图为原始“Lena”图,第二幅图为傅里叶变换后的频谱图像,第三幅图为傅里叶逆变换,频谱图像转换为原始图像的过程。


六.总结

傅里叶变换的目的并不是为了观察图像的频率分布(至少不是最终目的),更多情况下是为了对频率进行过滤,通过修改频率以达到图像增强、图像去噪、边缘检测、特征提取、压缩加密等目的。下一篇文章,作者将结合傅里叶变换和傅里叶逆变换讲解它的应用。

时也,命也。
英语低分数线一分,些许遗憾,但不气馁,更加努力。雄关漫道真如铁,而今迈过从头越,从头越。苍山如海,残阳如血。感谢一路陪伴的人和自己。

无论成败,那段拼搏的日子都很美。结果只会让我更加努力,学好英语。下半年沉下心来好好做科研写文章,西藏之行,课程分享。同时,明天的博士考试加油,虽然裸泳,但也加油!还有春季招考开始准备。

最后补充马刺小石匠精神,当一切都看起来无济于事的时候,我去看一个石匠敲石头.他一连敲了100次,石头仍然纹丝不动。但他敲第101次的时候,石头裂为两半。可我知道,让石头裂开的不是那最后一击,而是前面的一百次敲击的结果。人生路漫漫,不可能一路一帆风顺,暂时的不顺只是磨练自己的必经之路,夜最深的时候也是距黎明最近的时刻,经历过漫漫长夜的打磨,你自身会更加强大。

最后希望这篇基础性文章对您有所帮助,如果有错误或不足之处,请海涵!

(By:Eastmount 2019-04-23 周二下午6点写于花溪 https://blog.csdn.net/Eastmount )

2018-05-08 13:29:41 dajiyi1998 阅读数 19483
  • 程序打包安装运行

    1、掌握Raspberry Pi的使用; 2、具备树莓派的网站建站技术; 3、具备基于树莓派的物联网开发技术; 4、基于Raspberry的人脸识别与检测实践;

    14人学习 张晨光
    免费试看

1.直方图:一幅图像由不同灰度值的像素组成,图像中灰度的分布情况是该图像的一个重要特征。图像的灰度直方图就描述了图像中灰度分布情况,能够很直观的展示出图像中各个灰度级所占的多少。图像的灰度直方图是灰度级的函数,描述的是图像中具有该灰度级的像素的个数:其中,横坐标是灰度级,纵坐标是该灰度级出现的频率。

数字图像在范围[0,G]内总共有L个灰度级,直方图为h(rK)=nk

rK是去见[0,G]内的第K级亮度,nk是灰度级为rK的像素数。

归一化直方图:

1.1直方图对比:

方法描述:有两幅图像patch(当然也可是整幅图像),分别计算两幅图像的直方图,并将直方图进行归一化,然后按照某种距离度量的标准进行相似度的测量。

方法的思想:基于简单的向量相似度来对图像相似度进行度量。

优点:直方图能够很好的归一化,比如256个bin条,那么即使是不同分辨率的图像都可以直接通过其直方图来计算相似度,计算量适中。比较适合描述难以自动分割的图像。

缺点:直方图反应的是图像灰度值得概率分布,并没有图像的空间位置信息在里面,因此,常常出现误判;从信息论来讲,通过直方图转换,信息丢失量较大,因此单一的通过直方图进行匹配显得有点力不从心。

 

矩阵分解的方法

方法描述:将图像patch做矩阵分解,比如SVD奇异值分解和NMF非负矩阵分解等,然后再做相似度的计算。

方法思想:因为图像本身来讲就是一个矩阵,可以依靠矩阵分解获取一些更加鲁棒的特征来对图像进行相似度的计算。

基于SVD分解的方法优点:奇异值的稳定性,比例不变性,旋转不变性和压缩性。即奇异值分解是基于整体的表示,不但具有正交变换、旋转、位移、镜像映射等代数和几何上的不变性,而且具有良好的稳定性和抗噪性,广泛应用于模式识别与图像分析中。对图像进行奇异值分解的目的是得到唯一、稳定的特征描述,降低特征空间的维度,提高抗干扰能力。

基于SVD分解的方法缺点是:奇异值分解得到的奇异矢量中有负数存在,不能很好的解释其物理意义。

基于NMF分解的方法:将非负矩阵分解为可以体现图像主要信息的基矩阵与系数矩阵,并且可以对基矩阵赋予很好的解释,比如对人脸的分割,得到的基向量就是人的“眼睛”、“鼻子”等主要概念特征,源图像表示为基矩阵的加权组合,所以,NMF在人脸识别场合发挥着巨大的作用。

基于矩阵特征值计算的方法还有很多,比如Trace变换,不变矩计算等。

基于特征点方法

方法描述:统计两个图像patch中匹配的特征点数,如果相似的特征点数比例最大,则认为最相似,最匹配

方法思想:图像可以中特征点来描述,比如sift特征点,LK光流法中的角点等等。这样相似度的测量就转变为特征点的匹配了。

以前做过一些实验,关于特征点匹配的,对一幅图像进行仿射变换,然后匹配两者之间的特征点,选取的特征点有sift和快速的sift变形版本surf等。

方法优点:能被选作特征点的大致要满足不变性,尺度不变性,旋转不变等。这样图像的相似度计算也就具备了这些不变性。

方法缺点:特征点的匹配计算速度比较慢,同时特征点也有可能出现错误匹配的现象。

基于峰值信噪比(PSNR)的方法

当我们想检查压缩视频带来的细微差异的时候,就需要构建一个能够逐帧比较差视频差异的系统。最

常用的比较算法是PSNR( Peak signal-to-noise ratio)。这是个使用“局部均值误差”来判断差异的最简单的方法,假设有这两幅图像:I1和I2,它们的行列数分别是i,j,有c个通道。每个像素的每个通道的值占用一个字节,值域[0,255]。注意当两幅图像的相同的话,MSE的值会变成0。这样会导致PSNR的公式会除以0而变得没有意义。所以我们需要单独的处理这样的特殊情况。此外由于像素的动态范围很广,在处理时会使用对数变换来缩小范围。

            

              

基于结构相似性(SSIM,structural similarity (SSIM) index measurement)的方法

结构相似性理论认为,自然图像信号是高度结构化的,即像素间有很强的相关性,特别是空域中最接近的像素,这种相关性蕴含着视觉场景中物体结构的重要信息;HVS的主要功能是从视野中提取结构信息,可以用对结构信息的度量作为图像感知质量的近似。结构相似性理论是一种不同于以往模拟HVS低阶的组成结构的全新思想,与基于HVS特性的方法相比,最大的区别是自顶向下与自底向上的区别。这一新思想的关键是从对感知误差度量到对感知结构失真度量的转变。它没有试图通过累加与心理物理学简单认知模式有关的误差来估计图像质量,而是直接估计两个复杂结构信号的结构改变,从而在某种程度上绕开了自然图像内容复杂性及多通道去相关的问题.作为结构相似性理论的实现,结构相似度指数从图像组成的角度将结构信息定义为独立于亮度、对比度的,反映场景中物体结构的属性,并将失真建模为亮度、对比度和结构三个不同因素的组合。用均值作为亮度的估计,标准差作为对比度的估计,协方差作为结构相似程度的度量。

图像模板匹配:一般而言,源图像与模板图像patch尺寸一样的话,可以直接使用上面介绍的图像相似度测量的方法;如果源图像与模板图像尺寸不一样,通常需要进行滑动匹配窗口,扫面个整幅图像获得最好的匹配patch。

模板匹配:是一种在源图像中寻找与图像patch最相似的技术,常常用来进行目标的识别、跟踪与检测。其中最相似肯定是基于某种相似度准则来讲的,也就是需要进行相似度的测量。另外,寻找就需要在图像上进行逐行、逐列的patch窗口扫描,当然也不一定需要逐行逐列的扫描,当几个像素的误差比计算速度来的不重要时就可以设置扫描的行列步进值,以加快扫描和计算的时间消耗。下面就对相似度测量和模板匹配进行介绍(所有的图像都假定是灰度图)。

         

         

1.2反向投影:一种记录给定图像中像素点如何适应直方图模型像素分布方式的一种方法,也就是说首先计算某一种特征的直方图模板,然后使用模板在去寻找图像中存在的该特征的方法。  作用:反向投影用于在输入图像(通常较大)中查找特定图像(通常较小或者仅1个像素,以下将其称为模板图像)最匹配的点或者区域,也就是定位模板图像出现在输入图像的位置。

反向投影如何查找(工作)?

查找的方式就是不断的在输入图像中切割跟模板图像大小一致的图像块,并用直方图对比的方式与模板图像进行比较。

反向投影的结果是什么?

反向投影的结果包含了:以每个输入图像像素点为起点的直方图对比结果。可以把它看成是一个二维的浮点型数组,二维矩阵,或者单通道的浮点型图像。

假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:

1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;

2)生成临时图像的直方图;

3)用临时图像的直方图和模板图像的直方图对比,对比结果记为c;

4)直方图对比结果c,就是结果图像(0,0)处的像素值;

5)切割输入图像从(0,1)至(10,11)的临时图像,对比直方图,并记录到结果图像;

6)重复(1)~(5)步直到输入图像的右下角。

1.3.直方图均衡化:如果一副图像的像素占有很多的灰度级而且分布均匀,那么这样的图像往往有高对比度和多变的灰度色调。直方图均衡化就是一种能仅靠输入图像直方图信息自动达到这种效果的变换函数。它的基本思想是对图像中像素个数多的灰度级进行展宽,而对图像中像素个数少的灰度进行压缩,从而扩展像元取值的动态范围,提高了对比度和灰度色调的变化,使图像更加清晰。直方图均衡化是图像处理领域中利用图像直方图对对比度进行调整的方法。这种方法通常用来增加许多图像的局部对比度,尤其是当图像的有用数据的对比度相当接近的时候。通过这种方法,亮度可以更好地在直方图上分布。这样就可以用于增强局部的对比度而不影响整体的对比度,直方图均衡化通过有效地扩展常用的亮度来实现这种功能。

直方图均衡化处理的“中心思想”是把原始图像的灰度直方图从比较集中的某个灰度区间变成在全部灰度范围内的均匀分布。直方图均衡化就是对图像进行非线性拉伸,重新分配图像像素值,使一定灰度范围内的像素数量大致相同。直方图均衡化就是把给定图像的直方图分布改变成“均匀”分布直方图分布。

直方图均衡化的基本思想是把原始图的直方图变换为均匀分布的形式,这样就增加了象素灰度值的动态范围从而可达到增强图像整体对比度的效果。设原始图像在(x,y)处的灰度为f,而改变后的图像为g,则对图像增强的方法可表述为将在(x,y)处的灰度f映射为g。在灰度直方图均衡化处理中对图像的映射函数可定义为:g = EQ (f),这个映射函数EQ(f)必须满足两个条件(其中L为图像的灰度级数):

(1)EQ(f)在0≤f≤L-1范围内是一个单值单增函数。这是为了保证增强处理没有打乱原始图像的灰度排列次序,原图各灰度级在变换后仍保持从黑到白(或从白到黑)的排列。

(2)对于0≤f≤L-1有0≤g≤L-1,这个条件保证了变换前后灰度值动态范围的一致性。

这种方法对于背景和前景都太亮或者太暗的图像非常有用,这种方法尤其是可以带来X光图像中更好的骨骼结构显示以及曝光过度或者曝光不足照片中更好的细节。这种方法的一个主要优势是它是一个相当直观的技术并且是可逆操作,如果已知均衡化函数,那么就可以恢复原始的直方图,并且计算量也不大。这种方法的一个缺点是它对处理的数据不加选择,它可能会增加背景杂讯的对比度并且降低有用信号的对比度;变换后图像的灰度级减少,某些细节消失;某些图像,如直方图有高峰,经处理后对比度不自然的过分增强。


2.滤波:图像滤波,即在尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,是图像预处理中不可缺少的操作,其处理效果的好坏将直接影响到后续图像处理和分析的有效性和可靠性。目的:1、消除图像中混入的噪声;2、为图像识别抽取出图像特。要求:1、不能损坏图像轮廓及边缘 ;2、图像视觉效果应当更好。

 

滤波和模糊的区别:拿高斯滤波来举例:滤波一般可以分为高通滤波和低通滤波,对于高斯低通滤波就会产生模糊效果,如果对于高斯高通滤波就会产生锐化的效果。所以通常是:高斯滤波就是指使用高斯函数进行滤波;高斯模糊就是指低通滤波。

高通:边缘增强、边缘提取 ;低通:钝化图像、去除噪音 ;带通:删除特定频率、增强中很少用

2.1.线性滤波器:在图像处理中,对邻域中的像素的计算为线性运算时,如利用窗口函数进行平滑加权求和的运算,或者某种卷积运算,都可以称为线性滤波。常见的线性滤波有:均值滤波、高斯滤波、盒子滤波、拉普拉斯滤波等等,通常线性滤波器之间只是模版系数不同。

均值滤波(normalized box filter):用其像素点周围像素的平均值代替元像素值,在滤除噪声的同时也会滤掉图像的边缘信息。在OpenCV中,可以使用boxFilter和blur函数进行均值滤波。均值滤波的核为

 

高斯滤波(Gaussian filter):高斯滤波为最常用的滤波器,具有可分离性质,可以把二维高斯运算转换为一维高斯运算,其本质上为一个低通滤波器。在OpenCV中可通过函数GaussianBlur进行操作。

 

2.2. 非线性滤波:非线性滤波利用原始图像跟模版之间的一种逻辑关系得到结果,如最值滤波器,中值滤波器。比较常用的有中值滤波器和双边滤波器。

中值滤波(median filter):中值滤波用测试像素周围邻域像素集中的中值代替原像素。中值滤波去除椒盐噪声和斑块噪声时,效果非常明显。在OpenCV中,可以使用函数medianBlur进行操作。

 

双边滤波(bilateral filter):双边滤波在平滑图像时能够很好的保留边缘特性,但是其运算速度比较慢。在OpenCV中,可以使用函数bilateralFilter进行操作。

w(x,y)为加权系数,取决于定义域核和值域核的乘积。

3边缘检测:Edge Detection 我们要找水平的边缘:需要注意的是,这里矩阵的元素和是0,所以滤波后的图像会很暗,只有边缘的地方是有亮度的。边缘是图像中灰度发生急剧变化的区域边界。图像灰度的变化情况可以用图像灰度分布的梯度来表示,数字图像中求导是利用差分近似微分来进行的,实际上常用空域微分算子通过卷积来完成。

一般图像边缘检测方法主要有如下四个步骤:

1)图像滤波:传统边缘检测算法主要是基于图像强度的一阶和二阶导数,但导数的计算对噪声很敏感,因此必须使用滤波器来改善与噪声有关的边缘检测器的性能。需要指出的是,大多数滤波器在降低噪声的同时也造成了了边缘强度的损失,因此,在增强边缘和降低噪声之间需要一个折衷的选择。

2)图像增强:增强边缘的基础是确定图像各点邻域强度的变化值。增强算法可以将邻域(或局部)强度值有显著变化的点突显出来。边缘增强一般是通过计算梯度的幅值来完成的。

3)图像检测:在图像中有许多点的梯度幅值比较大,而这些点在特定的应用领域中并不都是边缘,所以应该用某种方法来确定哪些点是边缘点。最简单的边缘检测判断依据是梯度幅值。

4)图像定位:如果某一应用场合要求确定边缘位置,则边缘的位置可在子像素分辨率上来估计,边缘的方位也可以被估计出来。近20多年来提出了许多边缘检测算子。

一阶导数算子

1)Roberts算子:是一种斜向偏差分的梯度计算方法,梯度的大小代表边缘的强度,梯度的方向与边缘的走向垂直。Roberts操作实际上是求旋转45度两个方向上微分值的和。定位精度高,在水平和垂直方向的效果好,但对噪声敏感。两个卷积核Gx、Gy分别为

                      

采用1范数衡量梯度的幅度为=如果梯度G大于某一阀值 则认为该点(x,y)为边缘点。

2)Sobel算子:是一组方向算子,从不同的方向检测边缘。Sobel算子不是简单的求平均再差分,而是加强了中心像素上下左右4个方向像素的权值,运算结果是一副边缘图。Sobel算子通常对灰度渐变和噪声较多的图像处理的比较好。两个卷积核Gx、Gy分别为:

                               

采用范数衡量梯度的幅度为如果梯度G大于某一阀值 则认为该点(x,y)为边缘点。

3)Prewitt算子:是一种边缘样板算子,利用像素点上下左右邻点灰度差,在边缘处达到极值检测边缘,对噪声具有平滑的作用。由于边缘点像素的灰度值与其邻域点的灰度值显著不同,在实际应用中通常采用微分算子和模板匹配的方法检测图像的边缘。Prewitt算子不仅能检测边缘点,而且能抑制噪声的影响,因此对灰度和噪声较多的图像处理得比较好。两个卷积核Gx、Gy分别为:

                     

采用范数衡量梯度的幅度为:如果梯度G大于某一阀值 则认为该点(x,y)为边缘点。

二阶导数算子也可以检测边缘,利用二阶导数算子检测阶梯状边缘需将检测算子与图像卷积并确定过零点。

1)  Laplacian算子:拉普拉斯算子是一种常用的二阶导数算子。实际中可根据二阶导数算子过零点的性质来确定边缘的位置。对于一个连续函数f(x,y),它在位置(x,y)的拉普拉斯值定义如下:

在图像中,计算函数的拉普拉斯值也可借助各种模板实现。这里对模板的基本要求是对应中心像素的系数应是正的,而对应中心像素邻近像素的系数应是负的,且它们的和应该是零。拉普拉斯算子检测方法常常产生双像素边界,而且这个检测方法对图像中的噪声相当敏感,不能检测边缘的方向,所以很少直接使用拉普拉斯算子进行边缘检测。常用的两种模板分别如图所示:

                   

2)LOG算法:

LOG算法步骤如下:1、 取样得到的高斯低通滤波器对输入图像滤波。2、 计算第一步得到图像的拉普拉斯。3、 找到步骤2所得图像的零交叉。

3)  Canny算子:Canny算子把边缘检测问题转换为检测单位函数极大值的问题来考虑。他利用高斯模型,借助图像滤波的概念指出一个好的边缘检测算子应该具有3个指标:1.低失误率,既要少将真的边缘丢弃,也要少将非边缘判为边缘;2.高位置精度,检测出的边缘应在真正的边界上;3.单像素边缘,即对每个边缘有唯一的响应,得到的边界为单像素宽。

考虑到上述三个条件,Canny提出了判定边缘检测算子的3个准则:信噪比准则、定位精度准则和单边缘响应准则。

1. 信噪比准则:信噪比越大,提取的边缘质量越高。信噪比SNR定义为

 

2.  定位精度准则边缘定位的精度L定义如下

其中,G’(x)和h’(x)分别是G(x)和h(x)的导数。L越大表明定位精度越高。

3. 单边缘响应准则:为了保证单边缘只有一个响应。检测算子的脉冲响应导数的零交叉点的平均距离D(f’)应满足

满足上述三个条件的算子称为Canny算子。Canny边缘检测算法的步骤如下:

(1) 用高斯滤波器平滑图像;(2)用一阶偏导的有限差分来计算梯度的幅值和方向;(3)对梯度幅值进行非极大值抑制;(排除非边缘像素,仅仅保留细线条)(4)用双阈值算法进行检测和链接边缘。

Robert:边缘定位精度较高,对于陡峭边缘且噪声低的图像效果较好,但没有进行平滑处理,没有抑制噪声的能力。

sobel和prewitt:进行了平滑处理,对噪声具有一定抑制能力,但容易出现多像素宽度。精度不高,边缘较粗糙。

Laplacian:对噪声较为敏感,使噪声能力成分得到加强,容易丢失部分边缘方向信息,造成一些不连续的检测边缘,抗噪声能力较差。

log:抗噪声能力较强,但会造成一些尖锐的边缘无法检测到。

canny:最优化思想的边缘检测算子,同时采用高斯函数对图像进行平滑处理,但会造成将高频边缘平滑掉,造成边缘丢失,采用双阈值算法检测和连接边缘。

 

4.形态学:在特殊领域运算形式——结构元素(Sturcture Element),在每个像素位置上与二值图像对应的区域进行特定的逻辑运算。运算结构是输出图像的相应像素。运算效果取决于结构元素大小内容以及逻辑运算性质。形态学,即数学形态学(mathematical Morphology),是图像处理中应用最为广泛的技术之一,主要用于从图像中提取对表达和描绘区域形状有意义的图像分量,使后续的识别工作能够抓住目标对象最为本质〈最具区分能力-most discriminative)的形状特征,如边界和连通区域等。同时像细化、像素化和修剪毛刺等技术也常应用于图像的预处理和后处理中,成为图像增强技术的有力补充。

膨胀、腐蚀、开、闭运算是数学形态学最基本的变换。

结构元素:简单地定义为像素的结构(形状)以及一个原点(又称为锚点),使用形态学滤波涉及对图像的每个像素应用这个结构元素,当结构元素的原点与给定的像素对齐时,它与图像相交部分定义了一组进行形态学运算的像素。原则上,结构元素可以是任何形状,但通常使用简单的形状,比如方形、圆形和菱形,而原点位于中心位置(基于效率的考虑)。设有两幅图像A, S。若A是被处理的对象, 而S是用来处理A的, 则称S为结构元素。结构元素通常都是一些比较小的图像, A与S的关系类似于滤波中图像和模板的关系.

腐蚀和膨胀两个滤波操作也运算在每个像素周围像素集合上(邻域),这是由结构元素定义的。当应用到一个给定的像素时,结构元素的锚点与该像素的位置对齐,而所有与他相交的像素都被包括在当前像素集合中。腐蚀替换当前像素为像素集合中找到的最小的像素值,而膨胀则替换为像素集合中找到的最大像素值。当然,对于二值图像,每个像素只能被替换为白色像素或黑色像素

腐蚀和膨胀的主要功能:1)消除噪声(2)分割出独立的图像元素,在图像中连接相邻的元素(3)寻找图像中的极大值或者极小值区域(4)求出图像的梯度。

4.1、膨胀(dilate):膨胀就是求局部最大值的操作。从数学角度来说,就是将图像与核进行卷积,计算核B覆盖区域的像素点的最大值,并把这个最大值赋值给参考点指定的元素。这样就会使图像中的高亮区域逐渐增长。模板和输入图像对应位置的元素只要有一个与的结果不为0,则结果不为0.给图像中的对象边界添加元素。用3x3的结构元素,扫描二值图像中的每一个像素,用结构元素与其覆盖的二值图像做与运算,如果都为0,则结果图像中值为0,否则为1。结果:输入图像中的前景对象扩大一圈。

膨胀的作用和腐蚀相反, 膨胀能使物体边界扩大, 具体的膨胀结果与图像本身和结构元素的形状有关。膨胀常用于将图像中原本断裂开来的同一物体桥接起来, 对图像进行二值化之后, 很容易使一个连通的物体断裂为两个部分, 而这会给后续的图像分析(如要基于连通区域的分析统计物体的个数〉造成困扰,此时就可借助膨胀桥接断裂的缝隙

4.2、腐蚀(erode):腐蚀和膨胀是相反的操作,腐蚀是求局部最小值的操作。腐蚀操作会使图像中的高亮区逐渐减小。模板和输入图像中对应位置的元素相与的结果全不为0时,结果才为0。删除对象边界的某些像素。用3x3的结构元素,扫描二值图像的每一个像素,用结构元素与其覆盖的二值图像做与运算,如果都为1,则结果图像中值为1,否则为0.结果:前景对象减小一圈。

随着腐蚀结构元素的逐步增大,小于结构元素的物体相继消失。由于腐蚀运算具有上述的特点,可以用于滤波。选择适当大小和形状的结构元素,可以滤除掉所有不能 完全包含结构元素的噪声点。然而,利用腐蚀滤除噪声有一个缺点,即在去除噪声点的同时,对图像中前景物体的形状也会有影响,但当我们只关心物体的位置或者个数时,则影响不大。

4.3、开运算:开运算是先腐蚀后膨胀。主要用于消除小物体,在纤细点处分离物体,并且在平滑较大物体的边界的同时不明显改变其面积,同时抑制比结构元小的亮细节。

4.4、闭运算:是先膨胀后腐蚀。用来填充物体内细小空洞、连接邻近物体、平滑其边界的同时并不明显改变其面积,同时抑制比结构元小的暗细节。

4.5、形态学梯度:就是将膨胀图和腐蚀图相减。对二值化图像进行这一操作可以将边缘突出来,可以使用形态学梯度来保留物体的边缘轮廓。

4.6、顶帽变换:就是用源图像减去开运算图像。因为开运算带来的结果是放大了裂缝或者局部低亮度的区域。因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围区域更明亮的区域。顶帽一般用于校正不均匀光照的影响(补充:均匀光照在从背景中提取目标的处理中扮演核心的角色)。

4.7、黑帽变换:就是用闭运算减去源图像。黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域。黑帽运算一般用来分离比邻近点暗一些的斑块。

4.8、为什么开运算可以去除目标外的孤立点?

目标外的孤立点是和目标像素值一样的点,而非背景像素点,即为1而非0(0表示选取的空洞或背景像素值)。

使用腐蚀,背景扩展,该孤立点被腐蚀掉,但是腐蚀会导致目标区域缩小一圈,因此需要再进行膨胀操作,将目标区域扩展回原来大小。所以,要使用开运算去除目标外的孤立点。

4.9、为什么闭运算可以去除目标内的孔?

目标内的孔,属于周围都是值为1,内部空洞值为0.目的是去除周围都是1的像素中间的0值。

闭运算首先进行膨胀操作,目标区域扩张一圈,将目标区域的0去除,但是目标区域同时也会向外扩张一圈,因此需要使用腐蚀操作,使得图像中的目标区域恢复到之前的大小。

 

5、漫水填充:将与种子点相连的像素相近的连通域替换成指定颜色。如果存在mask,不会填充mask的非零像素。比如边缘检测输出图像可作为mask图,操作的结果总是某个连续的区域。

作用:1、标记或分离目标区域;2、获取掩码区域,只处理掩码指定的像素点,加速处理过程。

 

6、图像金字塔:是图像中多尺度表达的一种,最主要用于图像分割,是一种以多分辨率来解释图像的有效但概念简单的结构。一幅图像的金字塔式一系列以金字塔形状排列的,分辨率逐步降低且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到到达某个终止条件才停止采样。金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。层级越高图像越小,分辨率越低。

 通常有两种类型的图像金字塔,分别是:

1)数字金字塔(Gaussuan pyramin)-用来向下采样,主要的图像金字塔,主要用来向下采样图像

2)拉普拉斯金字塔(Laplaican pyramid)-用来从金字塔底层图像重建上层未采样的还原,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用,是从金字塔底层图像中向上采样,重建一个图像。

要从金字塔的第i层生成第i+1层(将第i层表示为Gi),先要用高斯核对Gi进行卷积,然后删除所有偶数行和列,新得到的图像面积会变为源图像的1/4.按上述过程对输入图像G0执行操作就可以得到整个金字塔。

当图像金字塔的上层移动时,尺寸和分辨率会降低。在OpenCV中,从金字塔上一层图像生成下一级图像时可以使用PryDown,而通过PryUp将现有的图像在每个维度上放大两倍。

注意:PryDown和PryUp函数互逆的,PryUp不是降采样的逆操作。图像首先在每个维度上扩大为原来的两倍,新增的行(偶数行)以0填充,然后给指定的滤波器进行卷积(实际上是在每个维度都扩大为原来两倍的过滤器)去估计“丢失”像素的近似值。

6.1.高斯金字塔

高斯金字塔是通过高斯平滑和亚采样获得一些采样图像,即是第K层高斯金字塔通过平滑、亚采样就可以获得第K+1层高斯图像。高斯金字塔包括一些列的低通滤波器,其截止频率从上一层到下一层以因子2逐渐增加,所以高斯金字塔可以跨越很大的频率范围。

     a.对图像向下采样

    为了获得第G(i+1)的金字塔图像,采取如下方法:

         (1)对图像G(i)进行高斯内核卷积

         (2)将所有偶数列和偶数行去除

  得到的图像即为G(i+1)的图像,很明显G(i+1)只有源图像的四分之一,通过对输入图像G(i)(原始图像)不停的迭代上述步骤就会得到整个金字塔,即向下采样会丢失图像的信息,缩小了图像

     b.对图像的向上采样

如果想放大图像,则需要通过向上取样操作得到

       (1)将图像在每个方向上扩大为原来的两倍,新增的行和列以0填充

       (2)使用先前同样的内核(乘以4)与放大后的图像卷积,获得“新增像素”的近似值。

得到的图像即为放大后的图像,但是与源图像想必会发现比较模糊,因为在缩放中已经丢失了一些信息。如果想在缩放过程中减少信息的丢失,这些数据就形成了拉普拉斯金字塔。

6.2.拉普拉斯金字塔

  i层的拉普拉斯金字塔的数学定义为:

                   L(i) = G(i) -UP(G(i+1))&g

式中的G(i)表示第i层的图像,UP()操作是将源图像中位置为(x,y)的像素映射到目标图像的(2X+1,2Y+1)位置,即在进行向上取样,&表示卷积,g为5*5的高斯内核.

使用OpenCV中函数直接进行拉普拉斯运算: L(i) = G(i) -PryUP(G(i+1))

图像金字塔的一个重要应用就是图像分割

6.3.尺寸调整:resize()函数

     resize()函数是OpenCV中专门用来调整图像大小的函数

     此函数将源图像精确的转换为指定尺寸的目标图像。如果源图像中设置了ROI(Region Of Internet,感兴趣区域),那么resize()函数会对源图像的ROI区域进行调整图像尺寸的操作,来输出到目标图像中。若目标中已经设置了ROI区域,不难理解resize()函数将会对源图像进行尺寸调整并填充到目标图像的ROI中。

 

7二值化

7.1全局二值化:一幅图像包括目标物体、背景还有噪声,要想从多值的数字图像中直接提取出目标物体,最常用的方法就是设定一个全局的阈值T,用T将图像的数据分成两部分:大于T的像素群和小于T的像素群。将大于T的像素群的像素值设定为白色(或者黑色),小于T的像素群的像素值设定为黑色(或者白色)。

全局二值化,在表现图像细节方面存在很大缺陷。为了弥补这个缺陷,出现了局部二值化方法。

局部二值化的方法就是按照一定的规则将整幅图像划分为N个窗口,对这N个窗口中的每一个窗口再按照一个统一的阈值T将该窗口内的像素划分为两部分,进行二值化处理。

7.2局部自适应二值化:局部二值化也有一个缺陷。存在于那个统一阈值的选定。这个阈值是没有经过合理的运算得来,一般是取该窗口的平局值。这就导致在每一个窗口内仍然出现的是全局二值化的缺陷。为了解决这个问题,就出现了局部自适应二值化方法。

局部自适应二值化,该方法就是在局部二值化的基础之上,将阈值的设定更加合理化。该方法的阈值是通过对该窗口像素的平均值E,像素之间的差平方P,像素之间的均方根值Q等各种局部特征,设定一个参数方程进行阈值的计算,例如:T=a*E+b*P+c*Q,其中a,b,c是自由参数。这样得出来的二值化图像就更能表现出二值化图像中的细节。

根据阈值选取的不同,二值化的算法分为固定阈值和自适应阈值。 比较常用的二值化方法则有:双峰法、P参数法、迭代法和OTSU法等

 

8、霍夫变换:霍夫变换是图像处理中用来从图像中分离出具有某种相同特征的几何形状(通常,直线,圆等)的常用方法。经典的霍夫变换常用来检测直线,圆,椭圆等。它是利用图像全局特性而将边缘像素连接起来组成区域封闭边界的一种方法。在预先知道区域形状的条件下,利用霍夫变换可以方便地得到边界曲线而将不连续的边缘像素点连接起来。霍夫变换的主要优点是受噪声和曲线间断的影响小。利用霍夫变换还可以直接检测某些已知形状的目标。

 

9.重映射:把一个图像中一个位置的像素放置到另一个图片指定位置的过程.简单的说就是改变图片的位置(左,右,上,下,颠倒)。

为了完成映射过程, 有必要获得一些插值为非整数像素坐标,因为源图像与目标图像的像素坐标不是一一对应的.

10、仿射变换: 仿射变换(Affine Transformation)是空间直角坐标系的变换,从一个二维坐标变换到另一个二维坐标,仿射变换是一个线性变换,他保持了图像的“平行性”和“平直性”,即图像中原来的直线和平行线,变换后仍然保持原来的直线和平行线,仿射变换比较常用的特殊变换有平移(Translation)、缩放(Scale)、翻转(Flip)、旋转(Rotation)和剪切(Shear)。

 

11、图像匹配:目前图像匹配中,局部特征匹配占据了绝大部分,常用的局部特征匹配方法有Harris、SIFT、SURF、ORB等等,不同的特征点检测和匹配方法尤其独特的优势和不足;

特征点匹配经过Ransac算法优化后仍存在错误匹配点对,需要优化后的匹配结果进行量化评价;

特征点检测和匹配评价一般包括两个部分,分别为检测和匹配的评价。

1.  特征检测(feature detection)、特征提取(extraction)和匹配(matching) 这三步,可以看做是目标检测、图像配准和拼接等工作的非常重要的一步。

2.  特征检测、特征选择、特征提取、特征描述和特征匹配

       特征检测: 根据用户的需求在图像中寻找满足定义的特征,包括角点、Blob点和边缘点。检测的结果:有或没有。

       特征选择: 为了选择稳定和可靠的特征,在检测到的特征集合中,需要进一步约束,通过类似于非极大值抑制、对比度阈值约束等条件保留显著特征。选择的结果:特征子集。

       特征提取: 特征选择确定稳定可靠的特征子集后,需要提取特征的位置(Location)、方向(Orientation)和尺度(Scale)信息。方向和尺度信息主要是为支持旋转和尺度变化。

       特征描述: 结合特征(点)邻域信息,使用一定的描述规则来对特征区域进行量化并抽取能代表该特征的描述信息,为了后续的匹配,一般用特征向量(feature vector)表示。

      特征匹配: 对提取到的特征,需要通过使用一定的方法来进一步判断对应的特征是否相同(或近似),对特征向量一般使用欧式距离或最邻近距离比(NNDR)进行判定,满足一定的条件约束,则认为两个特征相近,否则剔除。一般还会通过RANSAC进一步约束剔除误匹配点。   

 

 

 

2012-04-11 16:30:26 zhao_wang 阅读数 1907
  • 程序打包安装运行

    1、掌握Raspberry Pi的使用; 2、具备树莓派的网站建站技术; 3、具备基于树莓派的物联网开发技术; 4、基于Raspberry的人脸识别与检测实践;

    14人学习 张晨光
    免费试看
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;


/// <summary>
///图片处理类
/// </summary>
public class Images
{
public Images()
{
//
//TODO: 在此处添加构造函数逻辑
//
}
  
    /// <summary>
    /// 剪切图片
    /// </summary>
    /// <param name="path_source">原始图片路径</param>
    /// <param name="path_save">目标图片路径</param>
    /// <param name="x">剪切位置的左上角x坐标</param>
    /// <param name="y">剪切位置的左上角y坐标</param>
    /// <param name="width">要剪切的宽度</param>
    /// <param name="height">要剪切的高度</param>
    public void Cut(string path_source, string path_save, int x, int y, int width, int height)
    {
        //加载底图
        Image img = Image.FromFile(path_source);
        int w = img.Width;
        int h = img.Height;
        //设置画布
        width = width >= w ? w : width;
        height = height >= h ? h : height;
        Bitmap map = new Bitmap(width, height);
        //绘图
        Graphics g = Graphics.FromImage(map);       
        g.DrawImage(img, 0, 0, new Rectangle(x,y, width, height), GraphicsUnit.Pixel);
        //保存
        map.Save(path_save);    

    }


    /// <summary>
    /// 生成缩略图
    /// </summary>
    /// <param name="path_source">原始图片路径</param>
    /// <param name="path_save">缩略图路径</param>
    /// <param name="times">缩略倍数</param>
    /// <param name="b">缩略或放大(true缩略)</param>
    public void Thumbnail(string path_source, string path_save, int times, bool b)
    {
        //加载底图
        Image img = Image.FromFile(path_source);
        int w = img.Width;
        int h = img.Height;
        //设置画布
        int width = 0;
        int height = 0;
        if (b)
        {
            width = w / times;
            height = h / times;
        }
        else
        {
            width = w * times;
            height = h * times;
        }
        Bitmap map = new Bitmap(width, height);
        //绘图
        Graphics g = Graphics.FromImage(map);
        g.DrawImage(img, 0, 0, width, height);
        //保存
        map.Save(path_save);

    }


    /// <summary>
    /// 图片合成
    /// </summary>
    /// <param name="p1">图片1</param>
    /// <param name="p2">图片2</param>
    /// <param name="path_save">新图片路径</param>
    public void Compound(string p1, string p2, string path_save)
    {
        //加载底图
        Image img1 = Image.FromFile(p1);
        Image img2 = Image.FromFile(p2);


        int w = img1.Width + img2.Width;
        int h = img1.Height > img2.Height ? img1.Height : img2.Height;
        
        //设置画布


        Bitmap map = new Bitmap(w, h);
        //绘图
        Graphics g = Graphics.FromImage(map);
        g.DrawImage(img1, 0, 0, new Rectangle(0, 0, img1.Width, img1.Height), GraphicsUnit.Pixel);
        g.DrawImage(img2, img1.Width, 0, new Rectangle(0, 0, img2.Width, img2.Height), GraphicsUnit.Pixel);
        //g.DrawImage(img, 0, 0, new Rectangle(0, 0, width, height), GraphicsUnit.Pixel);
        //保存
        map.Save(path_save);     
    }

}
2010-05-05 15:00:00 okaimee 阅读数 3547
  • 程序打包安装运行

    1、掌握Raspberry Pi的使用; 2、具备树莓派的网站建站技术; 3、具备基于树莓派的物联网开发技术; 4、基于Raspberry的人脸识别与检测实践;

    14人学习 张晨光
    免费试看

VC++图象处理编程(一)

 

基本概念

前言

数字图像处理技术与理论是计算机应用的一个重要领域,许多工程应用都涉及到图像处理,一直有一个强烈的愿望,想系统的写一个关于数字图像处理的讲座,由于工作学习很忙,时至今日才得以实现。

  “图”是物体透射光或反射光的分布,“像”是人的视觉系统对图的接收在大脑中形成的印象或认识。图像是两者的结合。人类获取外界信息是靠听觉、视觉、触觉、嗅觉、味觉等,但绝大部分(约80%左右)来自视觉所接收的图像信息。图像处理就是对图像信息进行加工处理,以满足人的视觉心理和实际应用的需要。简单的说,依靠计算机对图像进行各种目的的处理我们就称之为数字图像处理。早期的数字图像处理的目的是以人为对象,为了满足人的视觉效果而改善图像的质量,处理过程中输入的是质量差的图像,输出的是质量好的图像,常用的图像处理方法有图像增强、复原等。随着计算机技术的发展,有一类图像处理是以机器为对象,处理的目的是使机器能够自动识别目标,这称之为图像的识别,因为这其中要牵涉到一些复杂的模式识别的理论,所以我们后续的讲座只讨论其中最基本的内容。由于在许多实际应用的编程中往往都要涉及到数字图像处理,涉及到其中的一些算法,这也是许多编程爱好者感兴趣的一个内容,我们这个讲座就是讨论如何利用微软的Visual C++开发工具来实现一些常用的数字图像处理算法,论述了图像处理的理论,同时给出了VC实现的源代码。本讲座主要的内容分为基础篇、中级篇和高级篇,具体包含的主要内容有:

1. 图像文件的格式;

2. 图像编程的基础-操作调色板;

3. 图像数据的读取、存储和显示、如何获取图像的尺寸等;

4. 利用图像来美化界面;

5. 图像的基本操作:图像移动、图像旋转、图像镜像、图像的缩放、图像的剪切板操作;

6. 图像显示的各种特技效果;

7. 图像的基本处理:图像的二值化、图像的亮度和对比度的调整、图像的边缘增强、如何得到图像的直方图、图像直方图的修正、图像的平滑、图像的锐化等、图像的伪彩色、彩色图像转换为黑白图像、物体边缘的搜索等等;

8. 二值图像的处理:腐蚀、膨胀、细化、距离变换等;

9. 图像分析:直线、圆、特定物体的识别;

10.JEPG、GIF、PCX等格式文件相关操作;

11.图像文件格式的转换;

12.图像的常用变换:付利叶变换、DCT变换、沃尔什变换等;

13.AVI视频流的操作;

图像处理技术博大精深,不仅需要有很强的数学功底,还需要熟练掌握一门计算机语言,在当前流行的语言中,我个人觉的Visual C++这个开发平台是图像开发人员的首选工具。本讲座只是起到抛砖引玉的作用,希望和广大读者共同交流。

图象的文件格式

一.图象的文件格式

  要利用计算机对数字化图像进行处理,首先要对图像的文件格式要有清楚的认识,因为我们前面说过,自然界的图像以模拟信号的形式存在,在用计算机进行处理以前,首先要数字化,比如摄像头(CCD)摄取的信号在送往计算机处理前,一般情况下要经过数模转换,这个任务常常由图像采集卡完成,它的输出一般为裸图的形式;如果用户想要生成目标图像文件,必须根据文件的格式做相应的处理。随着科技的发展,数码像机、数码摄像机已经进入寻常百姓家,我们可以利用这些设备作为图像处理系统的输入设备来为后续的图像处理提供信息源。无论是什么设备,它总是提供按一定的图像文件格式来提供信息,比较常用的有BMP格式、JPEG格式、GIF格式等等,所以我们在进行图像处理以前,首先要对图像的格式要有清晰的认识,只有在此基础上才可以进行进一步的开发处理。

  在讲述图像文件格式前,先对图像作一个简单的分类。除了最简单的图像外,所有的图像都有颜色,而单色图像则是带有颜色的图像中比较简单的格式,它一般由黑色区域和白色区域组成,可以用一个比特表示一个像素,“1”表示黑色,“0”表示白色,当然也可以倒过来表示,这种图像称之为二值图像。我们也可以用8个比特(一个字节)表示一个像素,相当于把黑和白等分为256个级别,“0”表示为黑,“255”表示为白,该字节的数值表示相应像素值的灰度值或亮度值,数值越接近“0”,对应像素点越黑,相反,则对应像素点越白,此种图像我们一般称之为灰度图像。单色图像和灰度图像又统称为黑白图像,与之对应存在着彩色图像,这种图像要复杂一些,表示图像时,常用的图像彩色模式有RGB模式、CMYK模式和HIS模式,一般情况下我们只使用RGB模式,R对应红色,G对应绿色,B对应蓝色,它们统称为三基色,这三中色彩的不同搭配,就可以搭配成各种现实中的色彩,此时彩色图像的每一个像素都需要3个样本组成的一组数据表示,其中每个样本用于表示该像素的一个基本颜色。

  对于现存的所有的图像文件格式,我们在这里主要介绍BMP图像文件格式,并且文件里的图像数据是未压缩的,因为图像的数字化处理主要是对图像中的各个像素进行相应的处理,而未压缩的BMP图像中的像素数值正好与实际要处理的数字图像相对应,这种格式的文件最合适我们对之进行数字化处理。请读者记住,压缩过的图像是无法直接进行数字化处理的,如JPEG、GIF等格式的文件,此时首先要对图像文件解压缩,这就要涉及到一些比较复杂的压缩算法。后续章节中我们将针对特殊的文件格式如何转换为BMP格式的文件问题作专门的论述,经过转换,我们就可以利用得到的未压缩的BMP文件格式进行后续处理。对于JPEG、GIF等格式,由于涉及到压缩算法,这要求读者掌握一定的信息论方面的知识,如果展开的话,可以写一本书,限于篇幅原因,我们只作一般性的讲解,有兴趣的朋友可以参考相关书籍资料。

二.BMP文件结构

1 BMP文件的组成

  BMP文件由文件头、位图信息头、颜色信息和图形数据四部分组成。文件头主要包含文件的大小、文件类型、图像数据偏离文件头的长度等信息;位图信息头包含图象的尺寸信息、图像用几个比特数值来表示一个像素、图像是否压缩、图像所用的颜色数等信息。颜色信息包含图像所用到的颜色表,显示图像时需用到这个颜色表来生成调色板,但如果图像为真彩色,既图像的每个像素用24个比特来表示,文件中就没有这一块信息,也就不需要操作调色板。文件中的数据块表示图像的相应的像素值,需要注意的是:图像的像素值在文件中的存放顺序为从左到右,从下到上,也就是说,在BMP文件中首先存放的是图像的最后一行像素,最后才存储图像的第一行像素,但对与同一行的像素,则是按照先左边后右边的的顺序存储的;另外一个需要读者朋友关注的细节是:文件存储图像的每一行像素值时,如果存储该行像素值所占的字节数为4的倍数,则正常存储,否则,需要在后端补0,凑足4的倍数。

2. BMP文件头

BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息。其结构定义如下:

typedef struct tagBITMAPFILEHEADER

{

WORD bfType; // 位图文件的类型,必须为“BMP”

DWORD bfSize; // 位图文件的大小,以字节为单位

WORD bfReserved1; // 位图文件保留字,必须为0

WORD bfReserved2; // 位图文件保留字,必须为0

DWORD bfOffBits; // 位图数据的起始位置,以相对于位图文件头的偏移量表示,以字节为单位

} BITMAPFILEHEADER;该结构占据14个字节。

3. 位图信息头

BMP位图信息头数据用于说明位图的尺寸等信息。其结构如下:

typedef struct tagBITMAPINFOHEADER{

DWORD biSize; // 本结构所占用字节数

LONG biWidth; // 位图的宽度,以像素为单位

LONG biHeight; // 位图的高度,以像素为单位

WORD biPlanes; // 目标设备的平面数不清,必须为1

WORD biBitCount// 每个像素所需的位数,必须是1(双色), 4(16色),8(256色)或24(真彩色)之一

DWORD biCompression; // 位图压缩类型,必须是 0(不压缩),1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一

DWORD biSizeImage; // 位图的大小,以字节为单位

LONG biXPelsPerMeter; // 位图水平分辨率,每米像素数

LONG biYPelsPerMeter; // 位图垂直分辨率,每米像素数

DWORD biClrUsed;// 位图实际使用的颜色表中的颜色数

DWORD biClrImportant;// 位图显示过程中重要的颜色数

} BITMAPINFOHEADER;该结构占据40个字节。

注意:对于BMP文件格式,在处理单色图像和真彩色图像的时候,无论图象数据多么庞大,都不对图象数据进行任何压缩处理,一般情况下,如果位图采用压缩格式,那么16色图像采用RLE4压缩算法,256色图像采用RLE8压缩算法。

4. 颜色表

  颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下:

typedef struct tagRGBQUAD {

BYTErgbBlue;// 蓝色的亮度(值范围为0-255)

BYTErgbGreen; // 绿色的亮度(值范围为0-255)

BYTErgbRed; // 红色的亮度(值范围为0-255)

BYTErgbReserved;// 保留,必须为0

} RGBQUAD;

  颜色表中RGBQUAD结构数据的个数由BITMAPINFOHEADER 中的biBitCount项来确定,当biBitCount=1,4,8时,分别有2,16,256个颜色表项,当biBitCount=24时,图像为真彩色,图像中每个像素的颜色用三个字节表示,分别对应R、G、B值,图像文件没有颜色表项。位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:

typedef struct tagBITMAPINFO {

BITMAPINFOHEADER bmiHeader; // 位图信息头

RGBQUAD bmiColors[1]; // 颜色表

} BITMAPINFO;

  注意:RGBQUAD数据结构中,增加了一个保留字段rgbReserved,它不代表任何颜色,必须取固定的值为“0”,同时,RGBQUAD结构中定义的颜色值中,红色、绿色和蓝色的排列顺序与一般真彩色图像文件的颜色数据排列顺序恰好相反,既:若某个位图中的一个像素点的颜色的描述为“00,00,ff,00”,则表示该点为红色,而不是蓝色。

5. 位图数据

  位图数据记录了位图的每一个像素值或该对应像素的颜色表的索引值,图像记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。这种格式我们又称为Bottom_Up位图,当然与之相对的还有Up_Down形式的位图,它的记录顺序是从上到下的,对于这种形式的位图,也不存在压缩形式。位图的一个像素值所占的字节数:当biBitCount=1时,8个像素占1个字节;当biBitCount=4时,2个像素占1个字节;当biBitCount=8时,1个像素占1个字节;当biBitCount=24时,1个像素占3个字节,此时图像为真彩色图像。当图像不是为真彩色时,图像文件中包含颜色表,位图的数据表示对应像素点在颜色表中相应的索引值,当为真彩色时,每一个像素用三个字节表示图像相应像素点彩色值,每个字节分别对应R、G、B分量的值,这时候图像文件中没有颜色表。上面我已经讲过了,Windows规定图像文件中一个扫描行所占的字节数必须是4的倍数(即以字为单位),不足的以0填充,图像文件中

一个扫描行所占的字节数计算方法:

DataSizePerLine= (biWidth* biBitCount+31)/8;// 一个扫描行所占的字节数

位图数据的大小按下式计算(不压缩情况下):

DataSize= DataSizePerLine* biHeight。

上述是BMP文件格式的说明,搞清楚了以上的结构,就可以正确的操作图像文件,对它进行读或写操作了。

三.GIF图象文件格式

  GIF图象格式的全称为Graphics Interchange Format,从这个名字可以看出,这种图像格式主要是为了通过网络传输图像而设计的。GIF文件不支持24位真彩色图像,最多只能存储256色的图像或灰度图像;GIF格式文件也无法存储CMY和HIS模型的图像数据;另外,GIF图像文件的各种数据区域一般没有固定的数据长度和存储顺序,所以为了方便程序寻找数据区,将数据区中的第一个字节作为标志符;最后需要读者注意的是GIF文件存储图像数据是有二种排列顺序:顺序排列或交叉排列。交叉排列的方式适合网络传输,这样一来允许用户在不完全掌握图像数据之前,获取当前图像的轮廓数据。

  GIF文件格式分为87和89两个版本,对于87这个版本,该文件主要是有五个部分组成,它,们是按顺序出现的:文件头块、逻辑屏幕描述块、可选择的调色板块、图像数据块、最后是标志文件结束的尾块,该块总是取固定的值3BH。其中第一和第二两个块用GIF图像文件头结构描述:

GIFHEADER:{

DB Signature; //该字段占六个字节,为了用于指明图像为GIF格式,前三个字符必须为“GIF”,后三字符用于指定是哪个版本,87或89。

DW ScreenWidth;//

DW ScreenDepth;//占两个字节,以像素为单位表示图像的宽、高

DB GlobalFlagByte;//该字节的各个位用于调色版的描述

DB BackGroundColor;//代表图象的背景颜色的索引

DB AspectRatio;图像的长宽比

}

  GIF格式中的调色板有通用调色板和局部调色板之分,因为GIF格式允许一个文件中存储多个图像,因此有这两种调色板,其中通用调色板适于文件中的所有图像,而局部调色板只适用于某一个图像。格式中的数据区域一般分为四个部分,图像数据识别区域,局部调色板数据,采用压缩算法得到的图象数据区域和结束标志区域。

  在GIF89版本中,它包含七个部分,分别是文件头、通用调色板数据、图像数据区和四个补充数据区,它们主要是用于提示程序如何处理图像的.

四.JEPG图像文件

  JEPG简称为联合摄影专家小组,作为一种技术,主要用于数字化图像的标准编码,JPEG主要采用有损的压缩编码方式,它比GIF、BMP图像文件要复杂的多,这不是短短的几页篇幅可以将清楚的,万幸的是,我们可以通过一些别的方法将该格式转化为BMP格式。读者需要知道的是在对JEPG文件格式编码时,通常需要分为以下四步:颜色转化、DCT变换、量化、编码。

  以上介绍了一些常用的图像文件,对比较复杂的格式,如GIF和JEPG,仅仅作了极其浮浅的介绍,后文我们会和它们作进一步的接触。实际应用中,还有许多图像格式,文章中都没有提到,读者如果需要做进一步的研究,还需要参考一些关于图像格式方面的资料。

BMP图像的基本操作

  上一讲我们主要介绍了图像的格式,其中重点说明了BMP文件的存储格式,同时对JEPG和GIF等常用格式作了简单的介绍。本节主要讲述如何操作BMP文件,如对其读、写和显示等。

  在实现数字图象处理的过程中,主要是通过对图像中的每一个像素点运用各种图像处理算法来达到预期的效果,所以进行图像处理的第一步,也是我们最关心的问题,是如何得到图像中每一个像素点的亮度值;为了观察和验证处理的图像效果,另一个需要解决的问题是如何将处理前后的图像正确的显示出来。我们这章内容就是解决这些问题。

  随着科技的发展,图像处理技术已经渗透到人类生活的各个领域并得到越来越多的应用,但是突出的一个矛盾是图像的格式也是越来越多,目前图像处理所涉及的主要的图像格式就有很多种,如TIF、JEMP、BMP等等,一般情况下,为了处理简单方便,进行数字图像处理所采用的都是BMP格式的图像文件(有时也称为DIB格式的图像文件),并且这种格式的文件是没有压缩的。我们通过操作这种格式的文件,可以获取正确显示图像所需的调色板信息,图像的尺寸信息,图像中各个像素点的亮度信息等等,有了这些数据,开发人员就可以对图像施加各种处理算法,进行相应的处理。如果特殊情况下需要处理其它某种格式的图像,如GIF、JEMP等格式的图象文件,可以首先将该格式转换为BMP格式,然后再进行相应的处理。这一点需要读者清楚。

  BMP格式的图像文件又可以分为许多种类,如真彩色位图、256色位图,采用RLE(游程编码)压缩格式的BMP位图等等。由于在实际的工程应用和图像算法效果验证中经常要处理的是256色并且是没有压缩的BMP灰度图像,例如通过黑白采集卡采集得到的图像就是这种格式,所以我们在整个讲座中范例所处理的文件格式都是BMP灰度图像。如果读者对这种格式的位图能够作到熟练的操作,那么对于其余形式的BMP位图的操作也不会很困难。

  BMP灰度图像作为Windows环境下主要的图像格式之一,以其格式简单,适应性强而倍受欢迎。正如我们在上一讲中介绍过的那样,这种文件格式就是每一个像素用8bit表示,显示出来的图像是黑白效果,最黑的像素的灰度(也叫作亮度)值为“0”,最白的像素的灰度值为“255”,整个图像各个像素的灰度值随机的分布在“0”到“255”的区间中,越黑的像素,其灰度值越接近于“0”,越白(既越亮)的像素,其灰度值越接近于“255”;与此对应的是在该文件类型中的颜色表项的各个RGB分量值是相等的,并且颜色表项的数目是256个。

  在进行图像处理时,操作图像中的像素值就要得到图像阵列;经过处理后的图像的像素值需要存储起来;显示图像时要正确实现调色板、得到位图的尺寸信息等。结合这些问题,下面我们针对性的给出了操作灰度BMP图像时的部分函数实现代码及注释。

一.BMP位图操作

回顾:BMP位图包括位图文件头结构BITMAPFILEHEADER、位图信息头结构BITMAPINFOHEADER、位图颜色表RGBQUAD和位图像素数据四部分。处理位图时要根据文件的这些结构得到位图文件大小、位图的宽、高、实现调色板、得到位图像素值等等。这里要注意的一点是在BMP位图中,位图的每行像素值要填充到一个四字节边界,即位图每行所占的存储长度为四字节的倍数,不足时将多余位用0填充。

  有了上述知识,可以开始编写图像处理的程序了,关于在VC的开发平台上如何开发程序的问题这里不再赘述,笔者假定读者都具有一定的VC开发经验。在开发该图像处理程序的过程中,笔者没有采用面向对象的方法,虽然面向对象的方法可以将数据封装起来,保护类中的数据不受外界的干扰,提高数据的安全性,但是这种安全性是以降低程序的执行效率为代价的,为此,我们充分利用了程序的文档视图结构,在程序中直接使用了一些API函数来操作图像。在微软的MSDN中有一个名为Diblook的例子,该例子演示了如何操作Dib位图,有兴趣的读者可以参考一下,相信一定会有所收获。

  启动Visual C++,生成一个名为Dib的多文档程序,将CDibView类的基类设为CscrollView类,这样作的目的是为了在显示位图时支持滚动条,另外在处理图像应用程序的文档类(CDibDoc.h)中声明如下宏及公有变量:

#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)//计算图像每行象素所占的字节数目;

HANDLE m_hDIB;//存放位图数据的句柄;

CPalette* m_palDIB;//指向调色板Cpalette类的指针;

CSize m_sizeDoc;//初始化视图的尺寸,该尺寸为位图的尺寸;

  最后将程序的字符串表中的字符串资源IDR_DibTYPE修改为:“/nDib/nDib/nDib Files(*.bmp;*.dib)/n.bmp/nDib.Document/nDib Document”。这样作的目的是为了在程序文件对话框中可以选择BMP或DIB格式的位图文件。

1、 读取灰度BMP位图

  可以根据BMP位图文件的结构,操作BMP位图文件并读入图像数据,为此我们充分利用了VC的文档视图结构,重载了文挡类的OnOpenDocument()函数,这样用户就可以在自动生成程序的打开文件对话框中选择所要打开的位图文件,然后程序将自动调用该函数执行读取数据的操作。该函数的实现代码如下所示:

BOOL CDibDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

LOGPALETTE *pPal;//定义逻辑调色板指针;

pPal=new LOGPALETTE;//初始化该指针;

CFile file;

CFileException fe;

if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite, &fe))

{//以“读”的方式打开文件;

AfxMessageBox("图像文件打不开!");

return FALSE;

}

DeleteContents();//删除文挡;

BeginWaitCursor();

BITMAPFILEHEADER bmfHeader;//定义位图文件头结构;

LPBITMAPINFO lpbmi;

DWORD dwBitsSize;

HANDLE hDIB;

LPSTR pDIB;//指向位图数据的指针;

BITMAPINFOHEADER *bmhdr;//指向位图信息头结构的指针

dwBitsSize = file.GetLength();//得到文件长度

if (file.Read((LPSTR)&bmfHeader, sizeof(bmfHeader)) !=sizeof(bmfHeader))

return FALSE;//读取位图文件的文件头结构信息;

if (bmfHeader.bfType != 0x4d42) //检查该文件是否为BMP格式的文件;

return FALSE;

hDIB=(HANDLE) ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, dwBitsSize);

//为读取图像文件数据申请缓冲区

if (hDIB == 0)

{

return FALSE;

}

pDIB = (LPSTR) ::GlobalLock((HGLOBAL)hDIB);

//得到申请的缓冲区的指针;

if (file.ReadHuge(pDIB, dwBitsSize - sizeof(BITMAPFILEHEADER)) !=

dwBitsSize - sizeof(BITMAPFILEHEADER) )

{

::GlobalUnlock((HGLOBAL)hDIB);

hDIB=NULL;

return FALSE;

}//此时pDIB数据块中读取的数据包括位图头信息、位图颜色表、图像像素的灰度值;

bmhdr=(BITMAPINFOHEADER*)pDIB;//为指向位图信息头结构的指针赋值;

::GlobalUnlock((HGLOBAL)hDIB);

if ((*bmhdr).biBitCount!=8)//验证是否为8bit位图

{

AfxMessageBox("该文件不是灰度位图格式!");

return FALSE;

}

m_hDIB=hDIB;//将内部变量数据赋于全局变量;

//下面是记录位图的尺寸;

m_sizeDoc.x=bmhdr->biWidth;

m_sizeDoc.y=bmhdr->biHeight;

//下面是根据颜色表生成调色板;

m_palDIB=new Cpalette;

pPal->palVersion=0x300;//填充逻辑颜色表

pPal->palNumEntries=256;

lpbmi=(LPBITMAPINFO)bmhdr;

for(int i=0;i<256;i++)

{//每个颜色表项的R、G、B值相等,并且各个值从“0”到“255”序列展开;

Pal->palPalentry[i].peRed=lpbmi->bmiColors[i].rgbRed;

pPal->palPalentry[i].peGreen=lpbmi->bmiColors[i].rgbGreen;

pPal->palPalentry[i].peBlue= lpbmi->bmiColors[i].rgbBlue;;

pPal->palPalentry[i].peFlags=0;

}

m_palDIB->CreatePalette(pPal);

//根据读入的数据得到位图的宽、高、颜色表;

if(pPal)

delete pPal;

EndWaitCursor();

SetPathName(lpszPathName);//设置存储路径

SetModifiedFlag(FALSE); // 设置文件修改标志为FALSE

return TRUE;

}

  上面的方法是通过CFile类对象的操作来读取位图文件的,它需要分析位图中的文件头信息,从而确定需要读取的图像长度。这种方法相对来说有些繁琐,其实还可以以一种相对简单的方法读取位图数据,首先在程序的资源中定义DIB类型资源,然后添加位图到该类型中,将图像数据以资源的形式读取出来,这时候就可以根据所获取的数据中的位图信息结构来获取、显示图像数据了。下面的函数实现了以资源形式装载图像文件数据,该函数的实现代码如下所示:

HANDLE LoadDIB(UINT uIDS, LPCSTR lpszDibType)

{

LPCSTR lpszDibRes =MAKEINTRESOURCE(uIDS);//根据资源标志符确定资源的名字;

HINSTANCE hInst=AfxGetInstanceHandle();//得到应用程序的句柄;

HRSRC hRes=::FindResource(hInst,lpszDibRes, lpszDibType);//获取资源的句柄,这里lpszDibType为资源的名字“DIB”;

If(hRes==NULL)

return NULL

HGLOBAL hData=::LoadResource(hInst, hRes);//转载资源数据并返回该句柄;

return hData;

}

2、 灰度位图数据的存储

 为了将图像处理后所得到的像素值保存起来,我们重载了文档类的OnSaveDocument()函数,这样用户在点击Save或SaveAs子菜单后程序自动调用该函数,实现图像数据的存储。该函数的具体实现如下:

BOOL CDibDoc::OnSaveDocument(LPCTSTR lpszPathName)

{

CFile file;

CFileException fe;

BITMAPFILEHEADER bmfHdr; // 位图文件头结构;

LPBITMAPINFOHEADER lpBI;//指向位图头信息结构的指针;

DWORD dwDIBSize;;

if (!file.Open(lpszPathName, CFile::modeCreate |CFile::modeReadWrite | CFile::shareExclusive, &fe))

{

AfxMessageBox("文件打不开");

return FALSE;

}//以读写的方式打开文件;

BOOL bSuccess = FALSE;

BeginWaitCursor();

lpBI = (LPBITMAPINFOHEADER) ::GlobalLock((HGLOBAL) m_hDIB);

if (lpBI == NULL)

return FALSE;

dwDIBSize = *(LPDWORD)lpBI + 256*sizeof(RGBQUAD);

//图像的文件信息所占用的字节数;

DWORD dwBmBitsSize;//BMP文件中位图的像素所占的字节数

dwBmBitsSize=WIDTHBYTES((lpBI->biWidth)*((DWORD)lpBI->biBitCount))

*lpBI->biHeight;// 存储时位图所有像素所占的总字节数

dwDIBSize += dwBmBitsSize; //BMP文件除文件信息结构外的所有数据占用的总字节数;

lpBI->biSizeImage = dwBmBitsSize; // 位图所有像素所占的总字节数

//以下五句为文件头结构填充值

bmfHdr.bfType =0x4d42; // 文件为"BMP"类型

bmfHdr.bfSize = dwDIBSize + sizeof(BITMAPFILEHEADER);//文件总长度

bmfHdr.bfReserved1 = 0;

bmfHdr.bfReserved2 = 0;

bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + lpBI->biSize

+ 256*sizeof(RGBQUAD);

//位图数据距离文件头的偏移量;

file.Write((LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER));//向文件中写文件头信息;

file.WriteHuge(lpBI, dwDIBSize);

//将位图信息(信息头结构、颜色表、像素数据)写入文件;

::GlobalUnlock((HGLOBAL) m_hDIB);

EndWaitCursor();

SetModifiedFlag(FALSE); // 将文档设为“干净”标志,表示此后文档不需要存盘提示;

return TRUE;

}

二.调色板的操作

  通过上面的操作,我们已经可以获取图像中的数据了,现在的又一个问题是如何在窗口中显示出图像数据。灰度图像要正确显示,必须实现逻辑调色板和系统调色板。首先我们介绍一下逻辑调色板结构LOGPALETTE,该结构定义如下:

typedef struct tagLOGPALETTE

{

WORD palVersion;//调色板的板本号,应该指定该值为0x300;

WORD palNumEntries;//调色板中的表项数,对于灰度图像该值为256;

PALETEENTRY palPalEntry[1];//调色板中的颜色表项,由于该表项的数目不一定,所以这里数组长度定义为1,灰度图像对应的该数组的长度为256;

}LOGPALETTE;

颜色表项结构PALETTEENTRY定义了调色板中的每一个颜色表项的颜色和使用方式,定义如下:

typedef struct tagPALETTEENTRY

{

BYTE peRed; //R分量值;

BYTE peGreen; //G分量值;

BYTE peBlue; //B分量值;

BYTE peFlags; // 该颜色被使用的方式,一般情况下设为“0”;

}PALETTEENTRY;

  Windows系统使用调色板管理器来管理与调色板有关的操作,通常活动窗口的调色板即是当前系统调色板,所有的非活动窗口都必须按照此系统调色板来显示自己的颜色,此时调色板管理器将自动的用系统调色板中的最近似颜色来映射相应的显示颜色。如果窗口或应用程序按自己的调色板显示颜色,就必须将自己的调色板载入到系统调色板中,这种操作叫作实现调色板,实现调色板包括两个步骤,既首先将调色板选择到设备上下文中,然后在设备上下文中实现它。可以通过CDC::SelectPalette()、CDC::RealizePalette()或相应的API函数来实现上述的两个步骤。在实现调色板的过程中,通过在主框架类中处理Windows定义的消息WM_QUERYNEWPALETTE 、WM_PALETTECHANGED及视图类中处理自定义消息WM_DOREALIZE(该消息在主框架窗口定义如下:#define WM_REALIZEPAL (WM_USER+101))来实现调色板的操作。当系统需要处理调色板的变化时,将向程序的主窗口发送WM_QUERYNEWPALETTE 、WM_PALETTECHANGED,例如当某一窗口即将激活时,主框架窗口将收到WM_QUERYNEWPALETTE消息,通知该窗口将要收到输入焦点,给它一次机会实现其自身的逻辑调色板;当系统调色板改变后,主框架窗口将收到WM_PALETTECHANGED消息,通知其它窗口系统调色板已经改变,此时每一窗口都应该实现其逻辑调色板,重画客户区。

  由于上述的调色板变更消息是发往主框架窗口的,所以我们只能在主窗口中响应这两个消息,然后由主框架窗口通知各个视窗,使得程序激活时能自动装载自己的调色板。我们定义的用户消息WM_REALIZEPAL用于主框架窗口通知视窗它已经收到调色板变更消息,视窗应该协调其调色板。下面我们给出了各个消息的响应处理函数的具体实现代码和注释:

//////////////////////////////////////////////////////////////////////////                     

void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)                     

{//总实现活动视的调色板                                                                               

CMDIFrameWnd::OnPaletteChanged(pFocusWnd);                                  

CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到活动的子窗口指针;

if (pMDIChildWnd == NULL)                                                                       

return;                                                                                                       

CView* pView = pMDIChildWnd->GetActiveView();//得到视图的指针;

ASSERT(pView != NULL);

SendMessageToDescendants(WM_DOREALIZE, (WPARAM)pView->m_hWnd);

//通知所有子窗口系统调色板已改变

}

////////////////////////////////////////////////

BOOL CMainFrame::OnQueryNewPalette()//提供实现系统调色板的机会

{

// 实现活动视的调色板

CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到活动的子窗口指针;

if (pMDIChildWnd == NULL)

return FALSE;//no active MDI child frame (no new palette)

CView* pView = pMDIChildWnd->GetActiveView();//得到活动子窗口的视图指针;

ASSERT(pView != NULL);

//通知活动视图实现系统调色板

pView->SendMessage(WM_DOREALIZE, (WPARAM)pView->m_hWnd);

return TRUE;

}

/////////////////////////////////////////////////

BOOL CDibView::OnDoRealize(WPARAM wParam, LPARAM)//实现系统调色板

{

ASSERT(wParam != NULL);

CDibDoc* pDoc = GetDocument();

if (pDoc->m_hDIB == NULL)

return FALSE; // must be a new document

CPalette* pPal = pDoc->m_palDIB;

//调色板的颜色表数据在InitDIBData()函数中实现

if (pPal != NULL)

{

CMainFrame* pAppFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;//得到程序的主框架指针;

ASSERT_KINDOF(CMainFrame, pAppFrame);

CClientDC appDC(pAppFrame);//获取主框架的设备上下文;

CPalette* oldPalette = appDC.SelectPalette(pPal, ((HWND)wParam) != m_hWnd);

//只有活动视才可以设为"FALSE",即根据活动视的调色板设为"前景"调色板;

if (oldPalette != NULL)

{

UINT nColorsChanged = appDC.RealizePalette();//实现系统调色板

if (nColorsChanged > 0)

pDoc->UpdateAllViews(NULL);//更新视图

appDC.SelectPalette(oldPalette, TRUE);

//将原系统调色板置为背景调色板

}

else

{

TRACE0(“/tSelectPalette failed in”);

}

return TRUE;

}

  注:在调用API函数显示位图时,不要忘记设置逻辑调色板,即"背景"调色板,否则位图将无法正确显示,读者可以从后面的显示部分的实现看出我们在显示时实现了逻辑调色板。上述的处理相对来说比较繁琐复杂,可能对于初学者来说也比较难于理解,所以如果我们的程序仅仅限于处理灰度图象,可以采用另外一种相对简单的办法,即在文档类的初始化阶段定义一个灰度调色板,然后在设备上下文中实现它,这样作的好处是在度取灰度位图时可以不再考虑文件中的颜色表信息,提高了文件读取速度,笔者在开发一个基于机器视觉的项目时采用的就是这种方法,取的了比较满意的效果。首先定义一个指向逻辑颜色表结构LOGPALETTE的指针pPal,填充该指针,然后将该指针与调色板指针联系起来,该方法的具体实现如下:

/////////////////////////////////////////////////////////

CDibDoc::CDibDoc()

{

……………………….

LOGPALETTE *Pal;

Pal=new LOGPALETTE;

m_palDIB=new Cpalette;

pPal->palVersion=0x300;

pPal->palNumEntries=256;

for(int i=0;i<256;i++)

{//每个颜色表项的R、G、B值相等,并且各个值从“0”到“255”序列展开;

Pal->palPalentry[i].peRed=i;

pPal->palPalentry[i].peGreen=i;

pPal->palPalentry[i].peBlue=i;

pPal->palPalentry[i].peFlags=0;

}

m_palDIB->CreatePalette(pPal);

…………………..

}

三.图象的显示

 显示DIB位图数据可以通过设备上下文CDC对象的成员函数CDC::Bitblt()或CDC::StretchBlt()来实现,也可以通过API函数SetDIBBitsToDevice()或StretchDIBBits()来实现,函数中具体所用到的各个参数的意义可以参考MSDN。其中StretchDIBBits()和CDC::StretchBlt()可以将图像进行放大和缩小显示。当从文档中装入位图文件时,CDIBView类的OnInitialUpdate函数将被调用,因此可以在该函数中实现对视图尺寸的设置,用于正确的显示位图,然后就可以在视图类的OnDraw()函数中正确的显示位图了。这两个函数的具体实现代码分别如下所示:

/////////////////////////////////////////////////////////////

void CDIBView::OnInitialUpdate()

{

CscrollView::OnInitalUpdate();

CDIBDoc *pDoc=GetDocument();

If(pDoc->m_hDIB==NULL)//如果位图数据为空,设置m_sizeDoc的默认尺寸;

pDoc->m_sizeDoc.cx=pDoc->m_sizeDoc.cy=100;

SetScrollSizes(MM_TEXT,pDoc-> m_sizeDoc);

}

/////////////////////////////////////////////////////////////

void CDIBView::OnDraw(CDC *pDC)

{

BITMAPINFOHEADER *lpDIBHdr;//位图信息头结构指针;

BYTE *lpDIBBits;//指向位图像素灰度值的指针;

BOOL bSuccess=FALSE;

CPalette*OldPal=NULL;//调色板指针;

HDC hDC=pDC->GetSafeHdc();//获取当前设备上下文的句柄;

CDIBDoc *pDoc=GetDocument();//获取活动文档的指针;

If(pDoc->m_hDIB ==NULL)

{//判断图像数据是否为空;

AfxMessageBox("图像数据不能为空,请首先读取图像数据!");

return;

}

lpDIBHdr=( BITMAPINFOHEADER *)GlobalLock(pDoc->m_hDIB);//得到图像的位图头信息

lpDIBBits=lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);//获取保存图像像素值的缓冲区的指针;

if(pDoc-> m_palDIB)

{//如果存在调色板信息,实现逻辑调色板;

OldPal=pDC-> SelectPalette(pDoc-> m_palDIB,TRUE);

PDC->RealizePalette();

}

else

{

AfxMessageBox("图像的调色板数据不能为空,请首先读取调色板信息!");

return ;

}

SetStretchBltMode(hDC,COLORONCOLOR);

//显示图像

BSuccess=StretchDIBBits(hDC,0,0,pDoc-> m_sizeDoc.cx, pDoc-> m_sizeDoc.cy,

0, pDoc-> m_sizeDoc.cy,0, pDoc-> m_sizeDoc.cy,

lpDIBBits,(LPBITMAPINFO)lpDIBHdr,

DIB_RGB_COLORS,

SRCCOPY);

GlobalUnlock(pDoc->m_hDIB);

If(OldPal)//恢复调色板;

PDC->SelectPalette(OldPal,FALSE);

retrun;

}