利用opencv提取Hu不变量特征 形状匹配 机器学习识别手写数字 傅里叶变换

void GetHu(string image_path)  

  1. {  
  2.     IplImage *src_image = cvLoadImage(image_path.c_str(),0);  
  3.   
  4.     CvMoments moments;  
  5.     CvHuMoments hu_moments;  
  6.     cvMoments(src_image, &moments,0);  
  7.     cvGetHuMoments(&moments, &hu_moments);  
  8.     for(int i = 1; i < 8; ++i)  
  9.     {  
  10.         cout<<((double*)&hu_moments)[i]<<"\t";  
  11.     }  
  12. }  

另外,opencv中的cvMatchShapes()方法可以直接实现两个轮廓的相似性计算,结果越小越相似。这个函数也是基于Hu不变量的相似性匹配。

 
OpenCV中的 KNN法 即 K最近邻法 K-Nearest Neighbors
k- 最近邻是一个非常简单的机器学习算法, 并且在OpenCV 中已经实现。在这篇文章中,我们将使用免费的数据集中的手写数字,作为K 最近算法的训练集。训练之后通过它来识别数字。我们将用到一些文件操作,如:fopen,fread,etc. 如果你对文件操作不熟悉,建议先复习一下。
手写数字数据集
这个网页 http://yann.lecun.com/exdb/mnist/提供了大量的手写数据集供下载。把上面的4个文件都下载下来。这些文件包含训练图像,训练图像的标号,测试图像和测试图像对应的真实标号。文件不能通过一般的图像浏览器打开。我们必须自己写代码来读取图像和标号。
训练:加载数据集
我们使用C/C++中的标准文件操作来加载数据集。数据的在文件中的存储格式在Yann的网页中有介绍。
在图片文件中,前16个字节包含的是文件信息。其中,开头的4个字节是magic number,用来确保所读的文件正确。接下来的4个字节代表图像的数量(因为整型通常4个字节表示)。第8-12个字节代表行数,最后4个字节代表列数。之后存储的就是图像数据,逐行存储每一副图像的像素点。
在标号文件中,前4个字节同样是magic number。接下来的4个字节是标号的个数。再之后,一个标号用一个字节表示,来代表手写体对应的数字0~9中的一个。
有了对文件格式的理解,我们开始创建一个新工程,要包含OpenCV的头文件。
#include<cv.h>
#include<highgui.h>
#include<ml.h>
#include<time.h>
#include<stdio.h>
#include<stdlib.h>

using namespace cv;

int main()
{
   return 0;
}
我将会使用到OpenCV的C++接口,因此也包含了CV命名空间。首先,我们创建2个新的文件指针:
   int main()
    {
        FILE *fp = fopen("C: \\train-images.idx3-ubyte", "rb");
         FILE *fp2 = fopen("C: \\train-labels.idx1-ubyte", "rb");

        return 0;
    }
上面的语句以二进制只读的方式打开文件。然后就是异常处理语句,如果文件没有打开,我们直接返回。
if(!fp || !fp2)
   return0;
接下来,从这两个文件中读取参数:
    int magicNumber =readFlippedInteger(fp);
    int numImages =readFlippedInteger(fp);
    int numRows =readFlippedInteger(fp);
    int numCols =readFlippedInteger(fp);

fseek(fp2, 0x08, SEEK_SET);
我们一会儿会用到readFlippedInteger函数。如果你使用的是Intel的机子(以低字节优先排列)的话,就要用到这个函数。如果你使用的是高字节优先的处理器,就可以直接使用fread函数。
标号从第8个字节开始,在fp2文件中同样需要跳过开始的2个整数。
现在,我们申请空间来存储所有的图片和标号:
int size = numRows*numCols;
    CvMat *trainingVectors =cvCreateMat(numImages, size, CV_32FC1);
CvMat *trainingLabels = cvCreateMat(numImages,1, CV_32FC1);
其中“vectors”代表所有手写数字图像,即K-最近邻的输入向量,“Labels”则代表手写数据的标号,即K-最近邻的分类类别。分配好内存之后,就能从文件中读取数据了:
   BYTE *temp = new BYTE[size];
    BYTEtempClass=0;
    for(inti=0;i<numImages;i++)
    {
        fread((void*)temp,size, 1, fp);
       fread((void*)(&tempClass), sizeof(BYTE), 1, fp2);

       trainingLabels->data.fl = tempClass;

        for(intk=0;k<size;k++)
           trainingVectors->data.fl[i*size+k] = temp[k];
}
这段代码加载第i副图像到temp,并且读取他的标号到tempClass.然后,在trainningVectors和trainingLabels矩阵中依次填充元素值。为什么要这样做呢?因为矩阵的数据类型是浮点型。而文件中的数据是单个字节数据。4个字节对应一个字节
是不能匹配的。所以就采用逐个数据的方式对矩阵赋值。如果你知道更好的方法,可以告诉我。
将数据存入内存之后,我们可以将它们作为输入导入到K-最近邻算法中:
KNearest knn(trainingVectors,trainingLabels);
然后,打印出k的最大值,关闭文件。
printf("Maximum k: %d \n",knn.get_max_k());  
    fclose(fp);
fclose(fp2);
我们也可以释放矩阵,因为OpenCV中的K-最近邻算法会自己另外保存一份向量和类别的备份。
   cvReleaseMat(&trainingVectors);
cvReleaseMat(&trainingLabels);
测试:使用K-最近邻识别
训练完成之后,就要对测试图像进行测试,来看看识别的准确性。
在同一个工程下,在底部添加下面代码:
fp = fopen("C: \\t10k-images.idx3-ubyte","rb");
fp2 = fopen("C: \\t10k-labels.idx1-ubyte","rb");
然后,从文件中读取初始信息,并且设置fp2的文件指针到第8个字节:
magicNumber = readFlippedInteger(fp);
    numImages =readFlippedInteger(fp);
    numRows =readFlippedInteger(fp);
    numCols =readFlippedInteger(fp);  
fseek(fp2, 0x08, SEEK_SET);
接下来,为测试图像和图像类别(标号)分配内存:
CvMat *testVectors = cvCreateMat(numImages, size,CV_32FC1);
    CvMat*testLabels = cvCreateMat(numImages, 1, CV_32FC1);
CvMat *actualLabels = cvCreateMat(numImages, 1,CV_32FC1);
testVectors存储测试图像,testLavels存储通过算法预测的图像的标号,actualLabels存储实际的标号(也就是从fp2中读取的真实标号);
然后,创建一些临时变量:
   temp = new BYTE[size];
    tempClass=1;
    CvMat*currentTest = cvCreateMat(1, size, CV_32FC1);
    CvMat*currentLabel = cvCreateMat(1, 1, CV_32FC1);
int totalCorrect=0;
temp和tempClass存储当前图像和它真实的标号。totalCorrect对正确的预测进行跟踪。现在,我们迭代文件中的所有图像:
for(int i=0;i<numImages;i++)
    {
       fread((void*)temp, size, 1, fp);
       fread((void*)(&tempClass), sizeof(BYTE), 1, fp2);

       actualLabels->data.fl = (float)tempClass;

        for(intk=0;k<size;k++)
        {
           testVectors->data.fl[i*size+k] = temp[k];
           currentTest->data.fl[k] = temp[k];
     }
这段代码仅仅是读取一副图像并存储数据到矩阵。接下来还要在外面的for循环中添加如下代码来预测这幅图像的标号:
        knn.find_nearest(currentTest,5, currentLabel);
        testLabels->data.fl =currentLabel->data.fl[0];

       if(currentLabel->data.fl[0]==actualLabels->data.fl)
           totalCorrect++;
}
最后,打印出识别正确的数字的个数:
printf("Time: %d \nAccuracy: %f \n\n",(int)time, (double) totalCorrect*100/(double)numImages);
return 0;
整数的翻转和字节存储次序
不同的处理器有不同的字节存储次序。假设一个整数32个比特,那么就要用4个字节来存储。一些处理器将4个字节中最低位作为整数的最高有效位,另外一些则把最高位作为整数的最高有效位。这样的差异确实容易混淆,但是我们也无法控制。也许,MINST数据集的创建有非Intel的机器,所以才设计了这样的文件存储格式。以下是读取“flipped 整数”的方法:
int readFlippedInteger(FILE *fp) {     int ret = 0;     BYTE *temp;     temp = (BYTE*)(&ret);     fread(&temp[3], sizeof(BYTE), 1, fp);     fread(&temp[2], sizeof(BYTE), 1, fp);     fread(&temp[1], sizeof(BYTE), 1, fp);     fread(&temp[0], sizeof(BYTE), 1, fp);     return ret;
}
这段代码中创建了一个字节指针来指向整数ret的首地址,然后从高位挨着读取4个字节,最后返回整数ret。
结束
通过以上的方法,就实现了机器学习。当然,还有一些更好的分类方法可供选择,你也可以尝试。或者你可以改进一下K-最近邻算法(提示:也许不是图像中的所有像素点都有用)。试试吧!
 

图像处理的傅里叶变换  

傅立叶变换在图像处理中有非常非常的作用。因为不仅傅立叶分析涉及图像处理的很多方面,傅立叶的改进算法,

比如离散余弦变换,gabor与小波在图像处理中也有重要的分量。

印象中,傅立叶变换在图像处理以下几个话题都有重要作用:
1.图像增强与图像去噪
绝大部分噪音都是图像的高频分量,通过低通滤波器来滤除高频——噪声;  边缘也是图像的高频分量,可以通过添加高频分量来增强原始图像的边缘;
2.图像分割之边缘检测
提取图像高频分量
3.图像特征提取:
形状特征:傅里叶描述子
纹理特征:直接通过傅里叶系数来计算纹理特征
其他特征:将提取的特征值进行傅里叶变换来使特征具有平移、伸缩、旋转不变性
4.图像压缩
可以直接通过傅里叶系数来压缩数据;常用的离散余弦变换是傅立叶变换的实变换;

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

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

频移性:函数在时域中乘以e^jwt,可以使整个频谱搬移w。这个也叫调制定理,通讯里面信号的频分复用需要用到这个特性(将不同的信号调制到不同的频段上同时传输);
卷积定理:时域卷积等于频域乘积;时域乘积等于频域卷积(附加一个系数)。(图像处理里面这个是个重点)

 

信号在频率域的表现
在频域中,频率越大说明原始信号变化速度越快;频率越小说明原始信号越平缓。当频率为0时,表示直流信号,没有变化。因此,频率的大小反应了信号的变化快慢。高频分量解释信号的突变部分,而低频分量决定信号的整体形象。
在图像处理中,频域反应了图像在空域灰度变化剧烈程度,也就是图像灰度的变化速度,也就是图像的梯度大小。对图像而言,图像的边缘部分是突变部分,变化较快,因此反应在频域上是高频分量;图像的噪声大部分情况下是高频部分;图像平缓变化部分则为低频分量。也就是说,傅立叶变换提供另外一个角度来观察图像,可以将图像从灰度分布转化到频率分布上来观察图像的特征。书面一点说就是,傅里叶变换提供了一条从空域到频率自由转换的途径。对图像处理而言,以下概念非常的重要:

图像高频分量:图像突变部分;在某些情况下指图像边缘信息,某些情况下指噪声,更多是两者的混合;
低频分量:图像变化平缓的部分,也就是图像轮廓信息
高通滤波器:让图像使低频分量抑制,高频分量通过
低通滤波器:与高通相反,让图像使高频分量抑制,低频分量通过
带通滤波器:使图像在某一部分的频率信息通过,其他过低或过高都抑制
还有个带阻滤波器,是带通的反。


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


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

椒盐噪声:对于椒盐采用中值滤波可以很好的去除。用均值也可以取得一定的效果,但是会引起边缘的模糊。
高斯白噪声:白噪音在整个频域的都有分布,好像比较困难。
冈萨雷斯版图像处理P185:算术均值滤波器和几何均值滤波器(尤其是后者)更适合于处理高斯或者均匀的随机噪声。谐波均值滤波器更适合于处理脉冲噪声。


图像增强
有时候感觉图像增强与图像去噪是一对矛盾的过程,图像增强经常是需要增强图像的边缘,以获得更好的显示效果,这就需要增加图像的高频分量。而图像去噪是为了消除图像的噪音,也就是需要抑制高频分量。有时候这两个又是指类似的事情。比如说,消除噪音的同时图像的显示效果显著的提升了,那么,这时候就是同样的意思了。
常见的图像增强方法有对比度拉伸,直方图均衡化,图像锐化等。前面两个是在空域进行基于像素点的变换,后面一个是在频域处理。我理解的锐化就是直接在图像上加上图像高通滤波后的分量,也就是图像的边缘效果。对比度拉伸和直方图均衡化都是为了提高图像的对比度,也就是使图像看起来差异更明显一些,我想,经过这样的处理以后,图像也应该增强了图像的高频分量,使得图像的细节上差异更大。同时也引入了一些噪音。


 

冈萨雷斯版<图像处理>里面的解释非常形象:一个恰当的比喻是将傅里叶变换比作一个玻璃棱镜。棱镜是可以将光分解为不同颜色的物理仪器,每个成分的颜色由波长(或频率)来决定。 傅里叶变换可以看作是数学上的棱镜,将函数基于频率分解为不同的成分。当我们考虑光时,讨论它的光谱或频率谱。同样, 傅立叶变换使我们能通过频率成分来分析一个函数。 图像傅立叶变换的物理意义 图像的频率是表征图像中灰度变化剧烈程度的指标,是灰度在平面空间上的梯度。如:大面积的沙漠在图像中是一片灰度变化缓慢的区域,对应的频率值很低;而对于地表属性变换剧烈的边缘区域在图像中是一片灰度变化剧烈的区域,对应的频率值较高。傅立叶变换在实际中有非常明显的物理意义,设f是一个能量有限的模拟信号,则其傅立叶变换就表示f的谱。从纯粹的数学意义上看,傅立叶变换是将一个函数转换为一系列周期函数来处理的。从物理效果看,傅立叶变换是将图像从空间域转换到频率域,其逆变换是将图像从频率域转换到空间域。换句话说,傅立叶变换的物理意义是将图像的灰度分布函数变换为图像的频率分布函数,傅立叶逆变换是将图像的频率分布函数变换为灰度分布函数 傅立叶变换以前,图像(未压缩的位图)是由对在连续空间(现实空间)上的采样得到一系列点的集合,我们习惯用一个二维矩阵表示空间上各点,则图像可由z=f(x,y)来表示。由于空间是三维的,图像是二维的,因此空间中物体在另一个维度上的关系就由梯度来表示,这样我们可以通过观察图像得知物体在三维空间中的对应关系。为什么要提梯度?因为实际上对图像进行二维傅立叶变换得到频谱图,就是图像梯度的分布图,当然频谱图上的各点与图像上各点并不存在一一对应的关系,即使在不移频的情况下也是没有。傅立叶频谱图上我们看到的明暗不一的亮点,实际上图像上某一点与邻域点差异的强弱,即梯度的大小,也即该点的频率的大小(可以这么理解,图像中的低频部分指低梯度的点,高频部分相反)。一般来讲,梯度大则该点的亮度强,否则该点亮度弱。这样通过观察傅立叶变换后的频谱图,也叫功率图,我们首先就可以看出,图像的能量分布,如果频谱图中暗的点数更多,那么实际图像是比较柔和的(因为各点与邻域差异都不大,梯度相对较小),反之,如果频谱图中亮的点数多,那么实际图像一定是尖锐的,边界分明且边界两边像素差异较大的。对频谱移频到原点以后,可以看出图像的频率分布是以原点为圆心,对称分布的。将频谱移频到圆心除了可以清晰地看出图像频率分布以外,还有一个好处,它可以分离出有周期性规律的干扰信号,比如正弦干扰,一副带有正弦干扰,移频到原点的频谱图上可以看出除了中心以外还存在以某一点为中心,对称分布的亮点集合,这个集合就是干扰噪音产生的,这时可以很直观的通过在该位置放置带阻滤波器消除干扰 另外我还想说明以下几点: 1、图像经过二维傅立叶变换后,其变换系数矩阵表明: 若变换矩阵Fn原点设在中心,其频谱能量集中分布在变换系数短阵的中心附近(图中阴影区)。若所用的二维傅立叶变换矩阵Fn的原点设在左上角,那么图像信号能量将集中在系数矩阵的四个角上。这是由二维傅立叶变换本身性质决定的。同时也表明一股图像能量集中低频区域。 2 、变换之后的图像在原点平移之前四角是低频,最亮,平移之后中间部分是低频,最亮,亮度大说明低频的能量大(幅角比较大)

你可能感兴趣的:(利用opencv提取Hu不变量特征 形状匹配 机器学习识别手写数字 傅里叶变换)