问题3 图像的空间域处理问题(掩膜)ps:opnecv上不同类型的矩阵转化时,默认会有saturate_cast在 如float图转化为uchar图,大鱼255的是255 小于0的 是0 直接一个等号就可以
相信这个问题大家并不陌生,但是具体操作起来其实会涉及到很多问题,opencv提供了filter2D函数,可以将我们的疑团解开.这个函数一共有7个参数,第一个源,第二个目标
源与目标类型要一致,我们希望都是float的,目标和源支持相同,第三个参数一般就是src.depth(),他是要得到目标或者源的数据类型,不包裹通道信息,第四个参数是卷积核,我们希望它是float的。第5个是卷积核的中心,我们选择哪一个。默认是中点。用Point(1,1)这样去设置点,这个不能超过核边界,第6个参数是一个double型的,表示对结果最后加上一个值。第七个参数是处理遇到边界时的做法:大概是有5种具体见http://www.linuxidc.com/Linux/2012-12/75965.htm;filter2D函数中默认使用的是BORDER_REFLECT_101这种类型,所以它是对每一个像素点都进行处理的。当然增加的边界太大显然是不合理的,所以核不应该过大。对于边界的具体探求在copyMakeBorder问题中我们会详细测试。filter2D对于多维度的矩阵处理则是对每一维都进行同样的操作。对了 图像滤波后的点并不是实时更新的,迭代产生的点不参与迭代计算,这点要注意一下。小结就是,遇到边界,则扩展边界,利用核进行计算得到每个点的滤波值,利用float类型,可以方便的进行小数操作,要显示的话在转化为uchar类型即可。空间滤波还是比较简单的。注意核不能过大,边界毕竟是人为加的信息了。
问题4:图像的求和
利用 addWeighted( src1, alpha, src2, beta, 0.0, dst);函数,2个src与dst的类型其实可以不同,但是我们计算性质的一般还是都选择float类型的(有时后统计的选择uchar类型)。 0.0是最终叠加后的附加值,alpha与beta分别是比列,它们的和可以不为1. ps opencv 中的saturate_cast是有四舍五入功能的,还有利用mat_读取图片时,如果使用float则若 文件打不开,则直接报错,用<Vec3b>则课进行后续的empty()的判断,不错直接弹出(VS2010中测试)。这些细节还是要注意的,防止程序宕! 问题5:改变图像的对比度和亮度(这其实是对像素的操作而已)对图像的元素进行遍历,求结果,当然也有集成的函数,src.convertTo(dst,dst.depth(),alpha,beta)我们没希望src与dst都是浮点的 这样便于计算,支持从自己到自己,若src与dst名字不同,则指向不同的空间(一般的opencv函数的结果都是这样的,指向不同的空间)。Mat_<Vec3b> x; Mat_<Vec3f> f; Mat_<Vec3f> f1; string name1="../Lena.jpg"; f=imread(name1,CV_LOAD_IMAGE_COLOR); if (f.empty()) { cout<<"open failed"<<endl; return; } f.convertTo(f1,f.depth(),0.2,50.1); x=f1; namedWindow("1",CV_WINDOW_AUTOSIZE); imshow("1",x); waitKey(0); cout<<f(1,1)[1]<<endl; cout<<f1(1,1)[1]<<endl; cout<<(int)x(1,1)[1]<<endl; }。 问题5:基本绘图(我们希望在图像上划出我们自己的线 (1):画直线(起点和终点),Scalar color(255,0,0); line(f,Point(100,100),Point(200,200),color,5);这里我们只需要定义前面5个参数,后面的一般用不到,一般也少定义Point与Scalar对象。没必要。 (2):画圆(中心与半径),circle(f,Point(200,250),100,Scalar(255,0,0),-1);前5个参数:第二个是中心点,第三个是半径(要求为int),第5个是-1表示将圆填充,可选其他厚度 圆的半径点计算可能有点取舍(因为有些地方肯定不是半径整数值,有一个像素内的偏差,应该看不出来,人类的肉眼的分辨率还是很低的)。 (3):画矩形(两个对角点),rectangle(f,Point(100,100),Point(200,250),Scalar(255,0,0),-1); 其他的图形还有椭圆,多边形等等,可以自行查找参数和函数 这时我们有个问题,这里画的彩色的线,那万一我们读取的是灰度的图,opencv会怎么处理呢; 实验证明,灰度图是按一维的float读取后,颜色参数只有第一个有效,255是白,0是黑 ,可以进行处理 若灰度图按三维读取(这是后三维上的值是完全相同的,在彩色空间上是颜色方块的对角线上,看起来是灰度,其实是彩图),画出来的是彩色的颜色。 对于一维与多维,opencv一般是这样的,多维就是一维的扩展性处理,掩膜等对多维进行相同的操作,而对于维度间的计算是很少的。 对于图像的视觉处理,由于人眼的分辨率很低,所以像素的略微相差人们是开不出来的。而识别(计算机对于像素点间的精确计算)应该把他们识别成一类 ,这就需要一个相差的范围,特征对于变化的东西根据人类视觉的特点,要得出相似的量,这是一门大学问,要求具体问题具体分析。 问题6:随机数与绘文字 随机数: RNG rng; cout<<rng.uniform(1,50)<<endl; cout<<rng.uniform(1,50)<<endl; cout<<rng.uniform(1,50)<<endl; cout<<rng.uniform(1,50)<<endl; cout<<rng.uniform(1,50)<<endl; cout<<rng.uniform(1,50)<<endl; 这个随机数有特点。(总感觉不是很随机);可能不是很好用吧。http://blog.csdn.net/yang_xian521/article/details/6931385这文章可以看看 绘文:这个很有意思:putText(f,xia,Point(200,250),rng.uniform(0,8),1.5,Scalar(255,0,0),rng.uniform(0,10));总感觉这个随机数不是真正的随机数,Point指的是 文字左下角的坐标(由于文字的类型,可能会超出这个坐标),反正这个写字还是挺有意思的。 问题6:通道的分割与合并 核心是空间的合并与分割(合并前,子空间需要定义了大小,分割前,母空间需要定义大小) (类型相同才能合并)合并我们使用merge()函数,它的基本形式是2个参数的形式,前面是一个vector<Mat_<float> > 要求类型都相同,维度也相同,并且都初始化了大小(不初始化 就会产生多余的指针),第二个参数是 最后合并的结果,它可以不初始化大小,但是类型必须和前面对应,既维度(相加)和数据类型(相同),最后的合并结果指向了新的空间 分割:分割我们用bing.push_back(x);其实直接定义个2的长度就可以了! bing.push_back(x1); bing.push_back(x2); split(f,bing); cout<<f(2,3)[0]<<endl; cout<<f(2,3)[1]<<endl; cout<<f(2,3)[2]<<endl; x=bing[0]; cout<<x(2,3)<<endl; cout<<bing[1](2,3)<<endl; cout<<bing[2](2,3)<<endl; 各个位面 不需要初始化大小,但特别的是,我们不能直接用x(2,3)去访问了,必须有x=bing【0】的过程!这可以这样来解释,由于push_back是这样的,之前的mat有定义了空间,则它们 是指向同一个空间的,但是倘若push_back前没有指向空间,在建立空间后,它们是指向不同空间的,因为它们间的关系不是引用的关系,是等号的关系。前面指向同一空间是因为智能 指针的作用结果。所以前面的东西也就可以解释了!多维的可以分成一维的 然后在合并,这样可以合并不同维度类型的矩阵 如2维与3维 合并成一个5维的。但是他们的数据类型 还是应该相同的。至于分割,完了之后我们还可以通过等号与智能指针的特性,将vector中指向的空间,分给不同的名字,直接等号就可以了! 问题7离散傅里叶变换(相信很多同学这个东西都没有学明白) 首先我们扩展边界,这个扩展是没有问题的,因为最后转换到空间域后,我们会将图像扩展的给消去。在图像的处理上应该可行。但得到的复频域的值已经和原来的完全不同了。首先制作 比较好的图像,扩展一下边界copyMakeBorder(f,f1,0,900-f.rows,0,900-f.cols,BORDER_CONSTANT,Scalar(255));定义个是原图,第二个是目的,第三个和第5个可以看成是左与 上的偏离(既对原点的偏离),第四个与第6个则是需要填充的行数与列数,这里的目的图像是不要初始化化大小的。后一个是边界的类型,若选为固定,则最后一个为值。 下面给上一个比较粗糙的代码,我们按照代码来分析:void main() { int i,j; Mat_<float> f; Mat_<float> x; Mat_<float> f1; Mat_<float> f2; Mat_<Vec2f> result; vector<Mat_<float> > bing; string name1="../e.jpg"; f=imread(name1,CV_LOAD_IMAGE_GRAYSCALE); if (f.empty()) { cout<<"open failed"<<endl; return; } i=getOptimalDFTSize(f.rows); j=getOptimalDFTSize(f.cols); copyMakeBorder(f,f1,0,i-f.rows,0,j-f.cols,BORDER_CONSTANT,Scalar(0)); f2.create(i,j); for(i=0;i<f2.rows;i++) for (j=0;j<f2.cols;j++) { f2(i,j)=0; } bing.push_back(f1); bing.push_back(f2); merge(bing,result); dft(result,result); split(result,bing); magnitude(bing[0],bing[1],x); x=x+Scalar::all(1); log(x,x); namedWindow("1",1); int cx = x.cols/2; int cy = x.rows/2; Mat q0(x, Rect(0, 0, cx, cy)); // Top-Left - 为每一个象限创建ROI Mat q1(x, Rect(cx, 0, cx, cy)); // Top-Right Mat q2(x, Rect(0, cy, cx, cy)); // Bottom-Left Mat q3(x, Rect(cx, cy, cx, cy)); // Bottom-Right Mat tmp; // 交换象限 (Top-Left with Bottom-Right) q0.copyTo(tmp); q3.copyTo(q0); tmp.copyTo(q3); q1.copyTo(tmp); // 交换象限 (Top-Right with Bottom-Left) q2.copyTo(q1); tmp.copyTo(q2); normalize(x,x,0,1,CV_MINMAX); imshow("1",x); waitKey(0); } 先来说说图像的ROI;因为在这个问题中,我们使用了一些ROI; ROI是图像中人们感兴趣的部分,非常形象。 mat_的等于好时刻中,如果类型不同,则必定指向新的空间,不与原来所有的相同; opencv的log函数(源,目标)已自然对数为底,支持对多维数据的处理; 目标无需初始化,初始化了也没什么用。 normalize(源,目标,底线,最高限,CV_MINMAX)最大最小的比列缩放法,对目标进行映射。原理也比较简单,最大最小是对多维矩阵的所有数据而言的。 对于ROI有以下的代码测试 Mat_<Vec2f> fe; Mat_<Vec2f> fe1; Mat_<Vec2f> fe3; fe3.create(1,2); fe.create(2,2); fe(0,0)[0]=1; fe(0,1)[0]=2; fe(1,0)[0]=3; fe(1,1)[0]=4; fe(0,0)[1]=5; fe(0,1)[1]=6; fe(1,0)[1]=7; fe(1,1)[1]=8; fe1=fe(Rect(0,0,2,1)); fe3.copyTo(fe1); cout<<fe<<endl; 非常的简洁,Rect表示0,0点开始(这里列写在前面),取2列,去1行(自己算1行)作为ROI,这样fe1的访问是相对访问,可以看成指向了一个区域。若自己是自己的roi则 以后指向的就是ROI了,其他数据由于无指向,从而丢失了!有意思的是,这里fe1指向了空间中的部分,那么我们若将fe1定义为其他的clone()则他就指向了其他的区域! 但我们可以用copyTo的方法,这里要求fe3的大小与fe3完全相同,否则无效。这样分析代码就非常的明朗了! dft可以看成只有2个参数(输入,输出),要求都是2维的,目标不需要初始化。虚部与实部!magnitude(bing[0],bing[1],x);将计算进行虚数的模计算,int的cx与cy保证了后面的块对应(可能有边界没有处理 但不碍事,1个像素的量级而已)。最后在将块进行对换(刚好的),其他的函数上面也有讲解。但这些都是为了让人们看到变换的结果而做了。实际的图像处理是要变换会空间域的。 那么,我们只要对变换后的数据进行处理,然后在进行反变换就可以了!用idft(源,目标)进行反变换。但是反变换之后,虚部由于计算误差,一般不为0,但是和实不相比,非常的小 实部的值会非常大,但我们用前面的方法进行归一化后,实部的小数形式与原始图的小数形式是相同的。这里指的是normalize(bing[0],bing[0],0,1,CV_MINMAX);的归一化方法。 所以要知道原图的最大值与最小值,我们可以将反变换的图像写成uchar的形式。 关于dft后的模值对称性,其实不是完全对称的,第0行与第0列为:去除原点,然后各自关于自己的行或列中心对称,然后剩下的部分则成反向对角对称,若剩下的部分点数为 奇数,则会存在一个中心。测试代码如下,#include <string> #include <cstdio> #include <opencv2\opencv.hpp> #include <vector> using namespace cv; using namespace std; void main() { /*int i,j; Mat_<float> f; Mat_<float> x; Mat_<float> f1; Mat_<float> f2; Mat_<Vec2f> result; Mat_<float> result1; vector<Mat_<float> > bing; string name1="../e.jpg"; f=imread(name1,CV_LOAD_IMAGE_GRAYSCALE); if (f.empty()) { cout<<"open failed"<<endl; return; } i=getOptimalDFTSize(f.rows); j=getOptimalDFTSize(f.cols); cout<<f.rows<<endl; cout<<f.cols<<endl; copyMakeBorder(f,f1,0,i-f.rows,0,j-f.cols,BORDER_CONSTANT,Scalar(0)); f2.create(i,j); for(i=0;i<f2.rows;i++) for (j=0;j<f2.cols;j++) { f2(i,j)=0; } //normalize(f,f,0,1,CV_MINMAX); //cout<<f(1,2)<<endl; bing.push_back(f1); bing.push_back(f2); merge(bing,result); dft(result,result); cout<<result(0,0)[0]<<endl; cout<<result(0,0)[1]<<endl; cout<<result(result.rows-1,result.cols-1)[0]<<endl; cout<<result(result.rows-1,result.cols-1)[1]<<endl; cout<<result.rows<<endl; cout<<result.cols<<endl; cout<<"~~~~~~~~~~~"<<endl; split(result,bing); magnitude(bing[0], bing[1], bing[0]); cout<<bing[0](1,1)<<endl; cout<<bing[0](bing[0].rows-1,bing[0].cols-1)<<endl; cout<<bing[0].rows<<endl; cout<<bing[0].cols<<endl;*/ /*idft(result,result); split(result,bing); cout<<bing[0](1,2)<<endl; cout<<bing[1](1,2)<<endl; normalize(bing[0],bing[0],0,1,CV_MINMAX); cout<<bing[0](1,2)<<endl; imshow("2",bing[0]); waitKey(0);*/ int i,j; RNG rng; Mat_<Vec2f> test; Mat_<Vec2f> test1; vector<Mat_<float> > result; result.resize(2); test.create(5,5); for(i=0;i<test.rows;i++) for(j=0;j<test.cols;j++) { test(i,j)[0]=rng.uniform(1.0,50.0); test(i,j)[1]=0; } test1=test.clone(); split(test1,result); normalize(result[0],result[0],0,1,CV_MINMAX); merge(result,test1); cout<<result[0]<<endl<<"xxxxxxxxxxxxx"<<endl; dft(test1,test1); cout<<"*******"<<endl<<test1<<endl; split(test1,result); magnitude(result[0],result[1],result[0]); cout<<"WWWWWWWWWWWWWW"<<endl<<result[0]<<endl; idft(test1,test1); split(test1,result); normalize(result[0],result[0],0,1,CV_MINMAX); cout<<result[0]<<endl; } 所以我们对于频域移位后的图像其实不是完全对称的,在频域滤波处理上,也不是完全对称的,但是大概效果一般还行。 对于dft输入图像,我们可以先归一化在输入,这样反变换结果的归一化,与这个应该是相同的。频域滤波的代码 #include <string> #include <cstdio> #include <cmath> #include <opencv2\opencv.hpp> #include <vector> using namespace cv; using namespace std; void main() { double xx(4); int i,j; Mat_<float> f; Mat_<float> f1; Mat_<Vec2f> result; vector<Mat_<float> > bing; string name1="../21.bmp"; f=imread(name1,CV_LOAD_IMAGE_GRAYSCALE); if (f.empty()) { cout<<"open failed"<<endl; return; } i=getOptimalDFTSize(f.rows); j=getOptimalDFTSize(f.cols); copyMakeBorder(f,f,0,i-f.rows,0,j-f.cols,BORDER_CONSTANT,Scalar(0));//建立border,多余值设为0; normalize(f,f,0,1,CV_MINMAX); f1.create(i,j); for(i=0;i<f1.rows;i++) for (j=0;j<f1.cols;j++) { f1(i,j)=0; } bing.push_back(f); bing.push_back(f1); merge(bing,result); dft(result,result); for(i=0;i<result.rows;i++) for (j=0;j<result.cols;j++) { if (!((sqrt((double)(i^2+j^2))<xx)||(sqrt((double)(i^2+(result.cols-1-j)^2))<xx)||(sqrt((double)((result.rows-1-i)^2+j^2))<xx)||(sqrt((double)((result.rows-1-i)^2+(result.cols-1-j)^2))<xx))) { result(i,j)[0]=0; result(i,j)[1]=0; } } idft(result,result); namedWindow("1",1); imshow("1",f); split(result,bing); normalize(bing[0],bing[0],0,1,CV_MINMAX); namedWindow("2",1); imshow("2",bing[0]); waitKey(0); } 关于spilt与merge还要说明的是关联性的问题; 对于merge 因为不改变原先的指向,所以一般无疑问。 而对于spilt改变了原先的指向,若mat_数组里原先有指向的空间,且空间大小与欲分割的母矩阵一维上的相同,那么就会空间内容的替代。不是这种情况则会指向新的空间。 Mat_<uchar> a; Mat_<uchar> b; vector<Mat_<uchar> > bing; Mat_<Vec2b> mother; mother.create(3,4); a.create(3,3); b.create(4,4); a=a+Scalar(1); b=b+Scalar(2); cout<<a<<endl<<b<<endl<<mother<<endl; bing.push_back(a); bing.push_back(b); split(mother,bing); cout<<bing[0]<<endl<<bing[1]<<endl<<a<<endl<<b<<endl;