图像处理几何运算

2017-12-12 17:07:11 yinghuan0206 阅读数 1218

图像几何变换又称空间变换,指图像平移,镜像,旋转等操作。它是将一幅图的坐标位置映射到另一幅图像中的新坐标(f(x0,y0)→g(x1,y1)),关键是确定映射关系和变换参数。几何变换不改变图像像素值,只在图像平面进行像素的重新安排。

  图像几何变换分为两部分:(1)进行空间变换运算。(2)使用灰度插值算法处理映射时坐标非整数情况和坐标不对齐情况(如放大,缩小)。

1、图像平移

  变换公式:(x1  y1   1)=(x0  y0  1) ×[ 1   0   0; 0   1   0;Tx   Ty   1 ]=(x0+Tx    y0+Ty   1);

矩阵表示:

2、图像镜像

水平镜像   [x1  y1  1]=[Width-x0    y0    1]

垂直镜像   [x1  y1  1]=[x0    Height-y0    1]   

MATLAB实现,Imtransform()函数用来实现一般的二维空间变换。形式为:B= imstransform(A,TFORM,method);

Tform 为变换的具体类型,Tform由maketform()函数和cp2tform()函数创建。 T=maketform(transformtype,Matrix);transformtype指定变换类型,常见‘affine’是二维或多维仿射变换。Matrix即仿射变换矩阵,上述表达式的矩阵形式。cp2tform更多用于生成两图像配准的仿射变换,是根据控制每一对输入图像和待配准图像的基准点对儿坐标以及选择变换类型来进行数据拟合。形如:T=cp2tform(input_points,base_points,'affine');

method为选择的插值方法(有三次插值bicubic,双线性插值bilinerar,最近邻插值nearest三种)

3、图像转置

图像转置即将图像像素的x坐标与y坐标互换。

变换公式: (x1   y1   1)=(x0   y0   1)× [ 0    1    0; 1    0    0;0   0   1 ] =(y0  x0  1).

转置MATLAB实现同镜像都是使用Imtransform()函数和maketform()函数。

4、图像缩放 

	图像缩放即按照比例将图像放大或缩小。

变换公式: (x1   Y1   1) = (X0 × Sx     y0 × Sy    1)。

直接根据缩放公式进行计算得到的目标图像中某些映射坐标可能不是整数,从而找不到对应像素位置。因此必须对其进行近似操作,即插值。

MATLAB工具:除了使用imtransform()函数实现外(形式同上),还可使用专门的图像缩放函数imresize()。调用形式如下:

B=imresize(A,Scale,method);// Scale为缩放比例;method为插值方法,默认最近邻插值,也可选择双线性插值和三次插值。

5、图像旋转

图像旋转一般指将图像围绕某一定点旋转一定的角度,可以将转出显示区域的图像截去,也可以改变输出图像大小来扩展显示范围。

5.1 以原点为中心的图像旋转

数学表达:(x1,y1,1)= (x0,y0,1)×[ cosθ sinθ 0;-sinθ cosθ 0;0 0 1]
MATLAB工具:同上所有能计算出变换结构TFORM的图像操作都可用Imtransform()函数实现。此外imromote()函数可以专门用来实现
图像绕中心逆时针旋转。调用形式:B=imromote(A,angle,method,'crop');// crop选项裁剪旋转后超出显示范围部分。

5.2 以任意点为中心的图像旋转

将平移和旋转操作结合可以使图像围绕任意点进行旋转操作。即先进行坐标系平移,再以新的坐标原点为中心旋转,之后将新原点平移回原来的坐标系的原点。具体步骤如下:

	(1) 将坐标系1变为坐标系2.
	(2)将该点旋转θ度。
	(3)将坐标系2变回坐标系1。

例:围绕图像中心旋转实现

坐标系1(x1,y1)以图像左上角为原点,向右和向下为x,y的正方向。坐标系2(x2,y2)原点为图像中心,向右和向上分别为x,y的正方向。

第(1)步 数学形式(x2   y2    1)=(x1-0.5w   0.5h-y1   1)=(x1   y1   1)×[1   0   0;0   -1   0;-0.5w   0.5h   1]

第(2)步数学形式同上5.1     (x',y',1)= (x,y,1)×[ cosθ sinθ 0;-sinθ cosθ 0;0 0 1]

第(3)步 数学形式(x1   y1    1)=(x2+0.5w'   0.5h'-y1   1)=(x1   y1   1)×[1   0   0;0   -1   0;0.5w'   0.5h'   1]

将3次变换所用的矩阵依次相乘可得算法整体数学表达式。


























2015-12-02 17:00:40 GarfieldEr007 阅读数 2883

第六章 几何运算

目录

  1. 引言
  2. 灰度级插值
  3. 空间变换

3.1 仿射变换

3.2 透视变换

  1. 几何校正
  2. 图象卷绕(Image Warping)
  3. 图象变形(Image Morphing)

作业


1.引言

几何运算与点运算不同,它可改变图象中物体(象素)之间的空间关系。这种运算可以看成将各象素在图象内移动的过程。其定义为:

g(x,y)=f(x',y')=f[a(x,y),b(x,y)] 其中,f(x,y)表示输入图象,g(x,y)表示输出图象,a(x,y)和b(x,y)表示空间变换,若它们是连续的,则将保持图象中的连通关系。

几何运算中灰度级插值是必不可少的组成部分,因为图象一般用整数位置处的象素来定义,而几何变换中,g(x,y)的灰度值一般由处在非整数坐标上的f(x,y)的值来确定,即g中的一个象素一般对应于f中的几个象素之间的位置。反过来看也是一样,即f中的一个象素往往被映射到g中的几个象素之间的位置。

实现几何运算有两种方法,其一为前向映射法,即:将输入象素的灰度一个个地转移到输出图象中,如果一个输入象素被映射到四个输出象素之间的位置,则其灰度值就按插值法在四个输出象素之间进行分配;其二为后向映射法(象素填充法),这时将输出象素逐个地映射回输入图象中,若输出象素被映射到四个输入象素之间的位置,则其灰度由它们的插值来确定。在实际中,通常采用后向映射法。

几何变换常用于摄象机的几何校正过程,这对于利用图象进行几何测量的工作是十分重要的。

仿射变换(Affine Transformation)和图象卷绕(Image Warping)是两类常见的几何运算。前者属于射影几何变换,多用于图象配准(ImageRegistration)作为比较或匹配的预处理过程;后者用控制点及插值过程来定义,将一幅图象逐渐变化到另一幅图象的图象变形(Morphing)过程是其典型的应用,多见于影视特技及广告的制作。

2.灰度级插值

最简单的插值方法是最近邻插值,即选择离它所映射到的位置最近的输入象素的灰度值为插值结果。复杂一点的方法是双线性插值,如下图所示:

假设输出图象的宽度为W,高度为H,输入图象的宽度为w高度为h,要将输入图象的尺度拉伸或压缩变换至输出图象的尺度。按照线形插值的方法,将输入图象的宽度方向分为W等份,高度方向分为H等份,那么输出图象中任意一点(x,y)的灰度值就应该由输入图象中四点(a,b)、(a+1,b)、(a,b+1)和(a+1,b+1)的灰度值来确定(如图1.)。其中a和b的值分别为:

 
 

 

(x,y)点的灰度值f(x, y)应为:

 
 

 

其中

 
 

 

此外,还有多种其它插值方法,如三次样条等。图象处理软件一般都提供若干种插值方法供用户自己选择。

3.空间变换

空间变换包括可用数学函数表达的简单变换(如:平移、拉伸等仿射变换)和依赖实际图象而不易用函数形式描述的复杂变换(如对存在几何畸变的摄象机所拍摄的图象进行校正,需要实际拍摄栅格图象,根据栅格的实际扭曲数据建立空间变换;再如通过指定图象中一些控制点的位移及插值方法来描述的空间变换)。

3.1 仿射变换(affine transformation)

 
 

其中 A 是变形矩阵,b是平移矢量。在2维空间,A可以按如下的四个步骤分解:尺度、伸缩、扭曲、旋转

(1)尺度

 
 

(2)伸缩

 
 

(3)扭曲

 
 

(4)旋转

 
 

即:

图2 人脸图象和掩膜图象

3.2 透视变换(Perspective Transformation)

透视变换是中心投影的射影变换,在用非齐次射影坐标表达时是平面的分式线性变换,具有如下的形式:

 
 

(参见:丘维生,解析几何,北京大学出版社,1996。)

透视变换常用于图象的校正,例如在移动机器人视觉导航研究中,由于摄象机与地面之间有一倾斜角,而不是直接垂直朝下(正投影),有时希望将图象校正成正投影的形式,就需要利用透视变换。

 重投影变换的应用实例(透视变形的校正)

(根据孙健同学的硕士论文摘录改编,祥见:孙健,面向室外移动机器人THMR-III导航的路标识别算法的研究,硕士学位论文,清华大学计算机系,1997.5)

如果将摄取到的图象映射到平行于地面的平面上,其效果相当于摄影机把姿态改变为垂直于地面向下看,就会在这个新的平面上得到物体真实形状。由于这种映射相当于将原图象重新投影到另一个成象面上,故称之为重投影变换,并将映射后得到的图象称为重投影图象。

图3. 重投影示意图

在两个坐标平面之间的投影变换的数学形式如下:

设(x, y)是坐标平面XOY中的一点,(x’,y’)是(x,y)在坐标平面X’OY’中对应的象点,则有

其中a,b,c,d,e,f,u,v都是常量。

确定两个平面之间的投影变换只需要在两个平面上找到四对“物点和象点”,有这四对点的对应关系就可以确定a,b,c,d,e,f,u,v。具体而言,用一个规范化坐标系(平面)作为重投影平面,首先建立从规范化坐标(NormalCoordinate)到原始图象坐标(Oringinal Coordinate)的变换,其形式为

在地面上取定一个矩形(不一定是正方形),要求它的边平行或垂直于车体轴线,这样的矩形在原始图象中显示为一个等腰梯形,如图4.所示。

图4. 规范化坐标系和原始图象坐标系

设A点的坐标为(x1,y1),C点坐标为(x2,y2),则B、D的坐标分别是(φ-x1,y1)和(φ-x2,y2)。经过投影,ABCD四个点依此与规范化坐标系中的(0,0),(1,0),(0,1),(1,1)四个点相对应。把此对应关系代入上式,得

 

 

可以求得如下的各待定常数:

以上变换的逆变换OrgToNormal的形式是

把四对点的对应关系代入上式,得到待定系数的计算方法为:

 其中

公式中只需确定(x1,y1)和(x2,y2),确定这几个参数的方法如图5.所示:

图5. 几何关系参数示意图

(注:“重投影变换”源于:朱志刚,林学闫,“重投影变换在智能移动机器人视觉系统的应用”,模式识别与人工智能,1990,Vol.5,No.2)

4.几何校正

 几何校正通过由已知的控制点(栅格)建立起来的几何变换(.\download_IPCVPR\GeometricsTransformation\GeometricTransformations.htm)进行。

 

左图:走廊的原图象;中图:透视变换将图象中的地砖校正成正方形;右图:透视变换将门的四角(原图最右侧的门,有把手的门)校正成矩形。(图片来源:Andrew Zisserman,Robotics Research Group, University ofOxford, http://www.dai.ed.ac.uk/CVonline/LOCAL_COPIES/EPSRC_SSAZ/node10.html)

5.图象卷绕(Image Warping)

 图象卷绕是通过指定一系列控制点的位移来定义空间变换的图象变形处理。非控制点的位移根据控制点进行插值来确定。有时利用多项式函数来拟合控制点之间的对应关系,这时称为多项式卷绕(Polynomial Warping)。一般情况下,由控制点将图象分成许多多变形区域,对每个变形区域使用双线性插值函数来填充非控制点。

  =>

6.图象变形(Image Morphing)

 变形是使图象中的一个物体逐渐变形为另外一个物体的过程。从一起始图象出发,利用渐隐(dissolve)技术,使起始图象逐渐“淡出(fadeout)”,而目标图象则逐渐“淡入(fade in)”,同时以对应物体为转换控制对象,通过选择控制点及控制线来建立插值过程,让物体上的点从它们的起始位置逐渐移向对应的终止位置。

 

演示播放(..\Demos\chapter06\morph\ahzmorph.avi)

作业

  1. 使用UleadMorph应用,选择控制点或控制线及网格,观察图象变形,说明原因。
  2. 编制实现透视及仿射变换的程序,通过交互输入参数观察效果。
  3. 有两幅人像摄影照片(例如,一幅是明星的剧照,另一幅是你自己的照片),试设计一个程序,通过少量的交互选择若干重要的控制点,通过图象卷绕将其中一幅照片中的人脸部分用另一个人的脸部替换

返回主目录 返回本章目录

清华大学计算机系 艾海舟

最近修改时间:2001年7月18日

出处:http://media.cs.tsinghua.edu.cn/~ahz/digitalimageprocess/CourseImageProcess.html

2016-07-14 19:03:02 zhongriqianqian2076 阅读数 2152

几何变换

几何变换可以看成图像中物体(或像素)空间位置改变,或者说是像素的移动。

几何运算需要空间变换和灰度级差值两个步骤的算法,像素通过变换映射到新的坐标位置,新的位置可能是在几个像素之间,即不一定为整数坐标。这时就需要灰度级差值将映射的新坐标匹配到输出像素之间。最简单的插值方法是最近邻插值,就是令输出像素的灰度值等于映射最近的位置像素,该方法可能会产生锯齿。这种方法也叫零阶插值,相应比较复杂的还有一阶和高阶插值。

插值算法感觉只要了解就可以了,图像处理中比较需要理解的还是空间变换。

空间变换

空间变换对应矩阵的仿射变换。一个坐标通过函数变换的新的坐标位置:


所以在程序中我们可以使用一个2*3的数组结构来存储变换矩阵:


以最简单的平移变换为例,平移(b1,b2)坐标可以表示为:


因此,平移变换的变换矩阵及逆矩阵记为:


缩放变换:将图像横坐标放大(或缩小)sx倍,纵坐标放大(或缩小)sy倍,变换矩阵及逆矩阵为:


选择变换:图像绕原点逆时针旋转a角,其变换矩阵及逆矩阵(顺时针选择)为:



OpenCV中的图像变换函数

基本的放射变换函数:

[cpp] view plain copy
  1. void cvWarpAffine(   
  2.     const CvArr* src,//输入图像  
  3.     CvArr* dst, //输出图像  
  4.     const CvMat* map_matrix,   //2*3的变换矩阵  
  5.     int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,   //插值方法的组合  
  6.     CvScalar fillval=cvScalarAll(0)   //用来填充边界外的值  
  7. );  
另外一个比较类似的函数是cvGetQuadrangleSubPix:

[cpp] view plain copy
  1. void cvGetQuadrangleSubPix(   
  2.        const CvArr* src,  //输入图像   
  3.        CvArr* dst,   // 提取的四边形  
  4.        const CvMat* map_matrix //2*3的变换矩阵  
  5. );  
这个函数用以提取输入图像中的四边形,并通过map_matrix变换存储到dst中,与WarpAffine变换意义相同,

即对应每个点的变换:


WarpAffine与 GetQuadrangleSubPix 不同的在于cvWarpAffine 要求输入和输出图像具有同样的数据类型,有更大的资源开销(因此对小图像不太合适)而且输出图像的部分可以保留不变。而 cvGetQuadrangleSubPix 可以精确地从8位图像中提取四边形到浮点数缓存区中,具有比较小的系统开销,而且总是全部改变输出图像的内容。


实践:图像旋转变换(原尺寸)

首先用cvWarpAffine实验将图像逆时针旋转degree角度。
[cpp] view plain copy
  1. //逆时针旋转图像degree角度(原尺寸)  
  2. void rotateImage(IplImage* img, IplImage *img_rotate,int degree)  
  3. {  
  4.     //旋转中心为图像中心  
  5.     CvPoint2D32f center;    
  6.     center.x=float (img->width/2.0+0.5);  
  7.     center.y=float (img->height/2.0+0.5);  
  8.     //计算二维旋转的仿射变换矩阵  
  9.     float m[6];              
  10.     CvMat M = cvMat( 2, 3, CV_32F, m );  
  11.     cv2DRotationMatrix( center, degree,1, &M);  
  12.     //变换图像,并用黑色填充其余值  
  13.     cvWarpAffine(img,img_rotate, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );  
  14. }  
逆时针旋转30度结果:
这里我们将新的图像还保留原来的图像尺寸。这样的效果显然不太好,我们通过计算相应放大图像尺寸。

实践:图像旋转变换(保留原图内容,放大尺寸)

需要计算新图的尺寸,示意图如下:

所以新图size为(width*cos(a)+height*sin(a), height*cos(a)+width*sin(a))
[cpp] view plain copy
  1. //旋转图像内容不变,尺寸相应变大  
  2. IplImage* rotateImage1(IplImage* img,int degree){  
  3.     double angle = degree  * CV_PI / 180.; // 弧度    
  4.     double a = sin(angle), b = cos(angle);   
  5.     int width = img->width;    
  6.     int height = img->height;    
  7.     int width_rotate= int(height * fabs(a) + width * fabs(b));    
  8.     int height_rotate=int(width * fabs(a) + height * fabs(b));    
  9.     //旋转数组map  
  10.     // [ m0  m1  m2 ] ===>  [ A11  A12   b1 ]  
  11.     // [ m3  m4  m5 ] ===>  [ A21  A22   b2 ]  
  12.     float map[6];  
  13.     CvMat map_matrix = cvMat(2, 3, CV_32F, map);    
  14.     // 旋转中心  
  15.     CvPoint2D32f center = cvPoint2D32f(width / 2, height / 2);    
  16.     cv2DRotationMatrix(center, degree, 1.0, &map_matrix);    
  17.     map[2] += (width_rotate - width) / 2;    
  18.     map[5] += (height_rotate - height) / 2;    
  19.     IplImage* img_rotate = cvCreateImage(cvSize(width_rotate, height_rotate), 8, 3);   
  20.     //对图像做仿射变换  
  21.     //CV_WARP_FILL_OUTLIERS - 填充所有输出图像的象素。  
  22.     //如果部分象素落在输入图像的边界外,那么它们的值设定为 fillval.  
  23.     //CV_WARP_INVERSE_MAP - 指定 map_matrix 是输出图像到输入图像的反变换,  
  24.     cvWarpAffine( img,img_rotate, &map_matrix, CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS, cvScalarAll(0));    
  25.     return img_rotate;  
  26. }  





可以用matlab来实现   out=imrotate(W2,30,'nearest','crop'); 旋转30度,整体图像大小不变。 


实践:图像旋转变换(保留原图内容,放大尺寸)-2

试一下用cvGetQuadrangleSubPix函数:
[cpp] view plain copy
  1. //旋转图像内容不变,尺寸相应变大  
  2. IplImage* rotateImage2(IplImage* img, int degree)    
  3. {    
  4.     double angle = degree  * CV_PI / 180.;   
  5.     double a = sin(angle), b = cos(angle);   
  6.     int width=img->width, height=img->height;  
  7.     //旋转后的新图尺寸  
  8.     int width_rotate= int(height * fabs(a) + width * fabs(b));    
  9.     int height_rotate=int(width * fabs(a) + height * fabs(b));    
  10.     IplImage* img_rotate = cvCreateImage(cvSize(width_rotate, height_rotate), img->depth, img->nChannels);    
  11.     cvZero(img_rotate);    
  12.     //保证原图可以任意角度旋转的最小尺寸  
  13.     int tempLength = sqrt((double)width * width + (double)height *height) + 10;    
  14.     int tempX = (tempLength + 1) / 2 - width / 2;    
  15.     int tempY = (tempLength + 1) / 2 - height / 2;    
  16.     IplImage* temp = cvCreateImage(cvSize(tempLength, tempLength), img->depth, img->nChannels);    
  17.     cvZero(temp);    
  18.     //将原图复制到临时图像tmp中心  
  19.     cvSetImageROI(temp, cvRect(tempX, tempY, width, height));    
  20.     cvCopy(img, temp, NULL);    
  21.     cvResetImageROI(temp);    
  22.     //旋转数组map  
  23.     // [ m0  m1  m2 ] ===>  [ A11  A12   b1 ]  
  24.     // [ m3  m4  m5 ] ===>  [ A21  A22   b2 ]  
  25.     float m[6];    
  26.     int w = temp->width;    
  27.     int h = temp->height;    
  28.     m[0] = b;    
  29.     m[1] = a;    
  30.     m[3] = -m[1];    
  31.     m[4] = m[0];    
  32.     // 将旋转中心移至图像中间    
  33.     m[2] = w * 0.5f;    
  34.     m[5] = h * 0.5f;    
  35.     CvMat M = cvMat(2, 3, CV_32F, m);    
  36.     cvGetQuadrangleSubPix(temp, img_rotate, &M);    
  37.     cvReleaseImage(&temp);    
  38.     return img_rotate;  
  39. }    






可以用matlab实现 out=imrotate(I,30,'nearest','loose');旋转30度,图像整体变大



实践:图像放射变换(通过三点确定变换矩阵)

在OpenCV 2.3的参考手册中《opencv_tutorials》介绍了另一种确定变换矩阵的方法,通过三个点变换的几何关系映射实现变换。
变换示意图如下:

即通过三个点就可以确定一个变换矩阵。(矩形变换后一定为平行四边形)
以下是基于OpenCV 2.3的代码(需至少2.0以上版本的支持)
[cpp] view plain copy
  1. int main( )  
  2. {  
  3.     Point2f srcTri[3];  
  4.     Point2f dstTri[3];  
  5.     Mat rot_mat( 2, 3, CV_32FC1 );  
  6.     Mat warp_mat( 2, 3, CV_32FC1 );  
  7.     Mat src, warp_dst, warp_rotate_dst;  
  8.     //读入图像  
  9.     src = imread( "baboon.jpg", 1 );  
  10.     warp_dst = Mat::zeros( src.rows, src.cols, src.type() );  
  11.     // 用3个点确定A仿射变换  
  12.     srcTri[0] = Point2f( 0,0 );  
  13.     srcTri[1] = Point2f( src.cols - 1, 0 );  
  14.     srcTri[2] = Point2f( 0, src.rows - 1 );  
  15.     dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 );  
  16.     dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 );  
  17.     dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 );  
  18.     warp_mat = getAffineTransform( srcTri, dstTri );  
  19.     warpAffine( src, warp_dst, warp_mat, warp_dst.size() );  
  20.     /// 旋转矩阵  
  21.     Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );  
  22.     double angle = -50.0;  
  23.     double scale = 0.6;  
  24.     rot_mat = getRotationMatrix2D( center, angle, scale );  
  25.     warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );  
  26.     ////OpenCV 1.0的形式  
  27.     //IplImage * img=cvLoadImage("baboon.jpg");  
  28.     //IplImage *img_rotate=cvCloneImage(img);  
  29.     //CvMat M =warp_mat;  
  30.     //cvWarpAffine(img,img_rotate, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );  
  31.     //cvShowImage("Wrap2",img_rotate);  
  32.   
  33.     namedWindow( "Source", CV_WINDOW_AUTOSIZE );  
  34.     imshow( "Source", src );  
  35.     namedWindow( "Wrap", CV_WINDOW_AUTOSIZE );  
  36.     imshow( "Wrap", warp_dst );  
  37.     namedWindow("Wrap+Rotate", CV_WINDOW_AUTOSIZE );  
  38.     imshow( "Wrap+Rotate", warp_rotate_dst );  
  39.     waitKey(0);  
  40.     return 0;  
  41. }  
变换结果:

直方图均衡化   W2=histeq(W2);

噪声(高斯)函数 out=imnoise(W2,'gaussian',0,0.001);

按列翻转   out=flipdim(W2,2);

转载请注明出处:http://blog.csdn.net/xiaowei_cqu/article/details/7616044
实验代码下载:http://download.csdn.net/detail/xiaowei_cqu/4339856

写在最后的一点点闲话
之前一直用的2.1的版本,后来装了2.3,只是听说2.3很强大,但我刚开始学,用的也基础,完全没感觉出不同。直到今天忽然看到了2.3的手册,才发现从2.0开始函数和基本结构都有了很大的改变,而我一直还是用的1.0风格的函数(比如cvMat,cvLoadImage)。我的两个学习工具《Learnning OpenCV》和《OpenCV中文参考手册》都是基于1.0的,这也是我到今天才看到Mat,然后直接被惊艳到了。
别人总结出来的东西能帮助我们在一开始迅速入门,但要学深,学精,终归还是要自己去努力挖的。

上面是从小薇学姐那抄来的,哈哈,好好学习了一下。

2018-06-07 19:48:58 hjxu2016 阅读数 12532

本文为参考这位https://blog.csdn.net/eastmount/article/details/46345299所做的一些笔记,文字部分复制粘贴,代码部分有所改进,增加了绕中心点旋转等

本篇博客是自己的理解,如有错误,欢迎指正,图像数据下载地址

https://download.csdn.net/download/hjxu2016/10436834

  点运算对单幅图像做处理,不改变像素的空间位置;代数运算对多幅图像做处理,也不改变像素的空间位置;几何运算对单幅图像做处理,改变像素的空间位置,几何运算包括两个独立的算法:空间变换算法和灰度级插值算法。
        空间变换操作包括简单空间变换、多项式卷绕和几何校正、控制栅格插值和图像卷绕,这里主要讲述简单的空间变换,如图像平移、镜像、缩放和旋转。主要是通过线性代数中的齐次坐标变换。
        图像平移坐标变换如下:

        运行效果如下图所示,其中BMP图片(0,0)像素点为左下角。
  第一步:在ResourceView资源视图中,添加Menu子菜单如下:(注意ID号)

        第二步:设置平移对话框。将试图切换到ResourceView界面--选中Dialog,右键鼠标新建一个Dialog,并新建一个名为IDD_DIALOG_PY。编辑框(X)IDC_EDIT_PYX 和 (Y)IDC_EDIT_PYY,确定为默认按钮。设置成下图对话框:

        第三步:在对话框资源模板空白区域双击鼠标—Create a new class创建一个新类--命名为CImagePYDlg。会自动生成它的.h和.cpp文件。打开类向导(Ctrl W),选择类名:CImagePYDlg添加成员变量如下图所示,同时在Message Maps中生成ID_JHBH_PY实现函数。
 
        第四步:在CImageProcessingView.cpp中添加头文件#include "ImagePYDlg.h",并实现平移。

void CImageProcessingView::OnJhbhPy()
{
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	CImagePYDlg dlg;

	int num;//记录每一行需要填充的字节
	if (m_nWidth * 3 % 4 != 0)
	{
		num = 4 - m_nWidth * 3 % 4;
	}
	else 
	{
		num = 0;
	}

	if (dlg.DoModal() == IDOK)
	{
		if (dlg.m_xPY > m_nWidth || dlg.m_yPY > m_nHeight)
		{
			AfxMessageBox("图像平移不能超过原始长度:", MB_OK, 0);
			return;
		}
		AfxMessageBox("图像空间变换-平移", MB_OK, 0);

		FILE *fpo = fopen(BmpName, "rb");
		FILE *fpw = fopen(BmpNameLin, "wb+");

		fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
		fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
		fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
		fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
		fread(m_pImage, m_nImage, 1, fpo);

		unsigned char *ImageSize;
		ImageSize = new unsigned char[m_nImage];  //new和delete有效的进行动态内存的分配和释放  
		unsigned char black;          //填充黑色='0'   
		int x, y;

		for (y = 0; y < m_nHeight; y++)
		{
			if (y < dlg.m_yPY) /*第一部分:到平移后像素位置前面的所有像素点赋值为黑色*/
			{
				for (x = 0; x < m_nWidth; x++)
				{
					ImageSize[(y*m_nWidth + x) * 3 + y*num] = black;
					ImageSize[(y*m_nWidth + x) * 3 + 1 + y*num] = black;
					ImageSize[(y*m_nWidth + x) * 3 + 2 + y*num] = black;
				}
			}
			else if (y >= dlg.m_yPY)
			{
				for (x = 0; x < m_nWidth; x++)
				{
					if (x < dlg.m_xPY)
					{
						ImageSize[(y*m_nWidth + x) * 3 + y*num] = black;
						ImageSize[(y*m_nWidth + x) * 3 + 1 + y*num] = black;
						ImageSize[(y*m_nWidth + x) * 3 + 2 + y*num] = black;
					}
					else if (x >= dlg.m_xPY)
					{
						ImageSize[(y*m_nWidth + x) * 3 + y*num] = m_pImage[((y-dlg.m_yPY)*m_nWidth + x-dlg.m_xPY) * 3 + y*num];
						ImageSize[(y*m_nWidth + x) * 3 + 1 + y*num] = m_pImage[((y - dlg.m_yPY)*m_nWidth + x - dlg.m_xPY) * 3+1 + y*num];
						ImageSize[(y*m_nWidth + x) * 3 + 2 + y*num] = m_pImage[((y - dlg.m_yPY)*m_nWidth + x - dlg.m_xPY) * 3+1 + y*num];
					}
				}
			}
		}
		fwrite(ImageSize, m_nImage, 1, fpw);
		fclose(fpo);
		fclose(fpw);
		numPicture = 2;
		level = 400;       
		Invalidate();
	}

}

二. 图像镜像

1.水平镜像翻转
        其设原图像宽度为lWidth,高度为lHeight,源图像中(x0,y0)经过水平镜像后坐标为


打开类向导,在CImageProcessingView中添加IDs为ID_JHBH_FZ,生成函数,代码如下:


                        

void CImageProcessingView::OnJhbhSpjx()
{
	// TODO: 在此添加命令处理程序代码
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("水平镜像!", MB_OK, 0);
	int num;//记录每一行需要填充的字节
	if (m_nWidth * 3 % 4 != 0)
	{
		num = 4 - m_nWidth * 3 % 4;
	}
	else
	{
		num = 0;
	}

	//打开临时的图片  
	FILE *fpo = fopen(BmpName, "rb");
	FILE *fpw = fopen(BmpNameLin, "wb+");
	fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
	fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
	fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
	fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
	fread(m_pImage, m_nImage, 1, fpo);


	/*new和delete有效的进行动态内存的分配和释放*/
	unsigned char *ImageSize;
	ImageSize = new unsigned char[m_nImage];

	int x, y;
	for (y = 0; y < m_nHeight; y++)
	{
		for (x = 0; x < m_nWidth; x++)
		{
			ImageSize[(y*m_nWidth + x)*3 + y*num] = m_pImage[(y*m_nWidth + m_nWidth - x)*3 + y*num];
			ImageSize[(y*m_nWidth + x) * 3 + y*num + 1] = m_pImage[(y*m_nWidth + m_nWidth - x)*3 + y*num  + 1];
			ImageSize[(y*m_nWidth + x) * 3 + y*num + 2] = m_pImage[(y*m_nWidth + m_nWidth - x)*3 + y*num + 2];
		}
	}
	

	fwrite(ImageSize, m_nImage, 1, fpw);
	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level = 400;
	Invalidate();
}
2.垂直镜像倒转
        其中变换矩阵如下:
                                      X=X0
                                      Y=height-Y0-1   (height为图像高度)
        它相当于把原图的像素矩阵的最后一行像素值赋值给第一行,首先找到(0,0)对应的(height-1,0)像素值,然后依次赋值该行的像素数据;最后当前行赋值结束,依次下一行。重点是找到每行的第一个像素点即可。
        代码中引用两个变量:Place=(m_nWidth*3)*(m_nHeight-1-1)即是(height-1,0)最后一行的第一个像素点;然后是循环中Place=(m_nWidth*3)*(m_nHeight-number-1)找到每行的第一个像素点。

        同样通过类向导生成函数void CImageProcessingView::OnJhbhDz(),代码如下:

void CImageProcessingView::OnJhbhCzjx()
{
	// TODO: 在此添加命令处理程序代码
	// TODO: 在此添加命令处理程序代码
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("垂直镜像!", MB_OK, 0);

	int num;//记录每一行需要填充的字节
	if (m_nWidth * 3 % 4 != 0)
	{
		num = 4 - m_nWidth * 3 % 4;
	}
	else
	{
		num = 0;
	}

	//打开临时的图片  
	FILE *fpo = fopen(BmpName, "rb");
	FILE *fpw = fopen(BmpNameLin, "wb+");
	fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
	fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
	fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
	fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
	fread(m_pImage, m_nImage, 1, fpo);


	/*new和delete有效的进行动态内存的分配和释放*/
	unsigned char *ImageSize;
	ImageSize = new unsigned char[m_nImage];

	int x, y;
	for (y = 0; y < m_nHeight; y++)
	{
		for (x = 0; x < m_nWidth; x++)
		{
			ImageSize[(y*m_nWidth + x) * 3 + y*num] = m_pImage[((m_nHeight - y)*m_nWidth + x) * 3 + (m_nHeight - y)*num];
			ImageSize[(y*m_nWidth + x) * 3 + y*num + 1] = m_pImage[((m_nHeight - y)*m_nWidth + x) * 3 + (m_nHeight - y)*num + 1];
			ImageSize[(y*m_nWidth + x) * 3 + y*num + 2] = m_pImage[((m_nHeight - y)*m_nWidth + x) * 3 + (m_nHeight - y)*num + 2];
		}
	}


	fwrite(ImageSize, m_nImage, 1, fpw);
	fclose(fpo);
	fclose(fpw); 
	numPicture = 2;
	level = 400;
	Invalidate();
}

三. 图像旋转-绕左下角旋转

        图像饶原点旋转顺时针theta角矩阵变换如下:注意BMP图像(0,0)左下角

先来个绕左下角旋转

新建Dialog如下图所示,设置ID_DIALOG_XZ和变量:


        再点击空白处创建CImageXZDlg类(旋转),它会自动生成.h和.cpp文件。打开类向导生成CImageXZDlg类的成员变量m_xzds(旋转度数),并设置其为int型(最大值360 最小值0)。
        在类向导(Ctrl+W)选择类CImageProcessingView,为ID_JHBH_TXXZ(图像旋转)添加函数,同时添加头文件#include "ImageXZDlg.h"

void CImageProcessingView::OnXzzxj()
{
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("左下角旋转!", MB_OK, 0);
	CImageXZ dlg;

	if (dlg.DoModal() == IDOK)
	{
		int num;//记录每一行需要填充的字节
		if (m_nWidth * 3 % 4 != 0)
		{
			num = 4 - m_nWidth * 3 % 4;
		}
		else
		{
			num = 0;
		}
		double PI = asin(0.5) * 6;
		double degree = 1.0 * dlg.m_xXZJZ * PI / 180;
		CString str;
		str.Format("转换后的角度=%d", dlg.m_xXZJZ);
		AfxMessageBox(str);

		//打开临时的图片  
		FILE *fpo = fopen(BmpName, "rb");
		FILE *fpw = fopen(BmpNameLin, "wb+");
		fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
		fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
		fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
		fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
		fread(m_pImage, m_nImage, 1, fpo);


		/*new和delete有效的进行动态内存的分配和释放*/
		unsigned char *ImageSize;
		ImageSize = new unsigned char[m_nImage];
		unsigned char black;
		int x, y;
		int xPlace, yPlace;
	
		for (y = 0; y < m_nHeight; y++)//高对应着行
		{
			for (x = 0; x < m_nWidth; x++)//宽对应着列
			{
				xPlace = (int)(x*cos(degree) - y*sin(degree));//代表原图上xplace的x坐标
				yPlace = (int)(x*sin(degree) + y*cos(degree));//代表原图上yplace的y坐标
				if (xPlace <= m_nWidth && yPlace <= m_nHeight && xPlace>=0 && yPlace>=0)
				{
				ImageSize[(y*m_nWidth + x) * 3 + y*num] = m_pImage[(yPlace*m_nWidth+xPlace)*3 + yPlace * num];
				ImageSize[(y*m_nWidth + x) * 3 + y*num + 1] = m_pImage[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 1];
				ImageSize[(y*m_nWidth + x) * 3 + y*num + 2] = m_pImage[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 2];

				}
				else if (xPlace > m_nWidth || yPlace > m_nHeight || xPlace < 0 || yPlace < 0)
				{
					ImageSize[(y*m_nWidth + x) * 3 + y*num] = black;
					ImageSize[(y*m_nWidth + x) * 3 + y*num + 1] = black;
					ImageSize[(y*m_nWidth + x) * 3 + y*num + 2] = black;

				}
			}
		}
		fwrite(ImageSize, m_nImage, 1, fpw);
		fclose(fpo);
		fclose(fpw);
		numPicture = 2;
		level = 400;        //几何变换                
		Invalidate();
	}
}
 运行效果如下图所示,中心旋转太难了!找到中心那个位置就不太容易,我做不下去了,fuck~同时旋转过程中,由于是饶左下角(0,0)实现,故有的角度会到界面外显示全黑。下图分别旋转15度和355度。


四. 图像旋转-绕中心点旋转(有点问题的版本)

这理论部分就有点复杂,还要找中心点坐标啥的,首先我干脆不找中心点了,默认中心点就是(width/2,height/2)


代码如下

void CImageProcessingView::OnJhzhZxxz()
{
	// TODO: 在此添加命令处理程序代码
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("中心点逆时针旋转!", MB_OK, 0);
	CImageXZDlg dlg;

	if (dlg.DoModal() == IDOK)
	{
		int num;//记录每一行需要填充的字节
		if (m_nWidth * 3 % 4 != 0)
		{
			num = 4 - m_nWidth * 3 % 4;
		}
		else
		{
			num = 0;
		}
		double PI = asin(0.5) * 6;//定义pi
		double degree = 1.0 * dlg.m_nZXJZ * PI / 180;//计算角度
		CString str;
		str.Format("转换后的角度=%d", dlg.m_nZXJZ);
		AfxMessageBox(str);

		//打开临时的图片  
		FILE *fpo = fopen(BmpName, "rb");
		FILE *fpw = fopen(BmpNameLin, "wb+");
		fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
		fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
		fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
		fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
		fread(m_pImage, m_nImage, 1, fpo);


		/*new和delete有效的进行动态内存的分配和释放*/
		unsigned char *ImageSize;
		ImageSize = new unsigned char[m_nImage];
		unsigned char black;
		int x, y;    //旧图像点的坐标
		int xPlace, yPlace;    //注意,这里xplace和之前的不同,这里是新图像点的坐标
		float a0 = (float)(m_nWidth - 1) *0.5;//计算中心点,a0是横坐标,b0是纵坐标
		float b0 = (float)(m_nHeight - 1)*0.5;

		float varx = -a0*cos(degree) + b0*sin(degree) + a0;//计算两个敞亮
		float vary = -a0*sin(degree) - b0*cos(degree) + b0;
		for (y = 0; y < m_nHeight; y++)
		{
			for (x = 0; x < m_nWidth; x++)
			{
				xPlace = (int)(x*cos(degree) - y*sin(degree) + varx + 0.5);//算出新图像点的坐标
				yPlace = (int)(x*sin(degree) + y*cos(degree) + vary + 0.5);

				if (xPlace <= m_nWidth && yPlace <= m_nHeight && xPlace >= 0 && yPlace >= 0)
				{//替换

					ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num] = m_pImage[(y*m_nWidth + x) * 3 + y*num];
					ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 1] = m_pImage[(y*m_nWidth + x) * 3 + y*num + 1];
					ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 2] = m_pImage[(y*m_nWidth + x) * 3 + y*num + 2];

				}
				/*else if (xPlace > m_nWidth || yPlace > m_nHeight || xPlace < 0 || yPlace < 0)
				{
					ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num] = black;
					ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 1] = black;
					ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 2] = black;

				}*/
			}
		}
		fwrite(ImageSize, m_nImage, 1, fpw);
		fclose(fpo);
		fclose(fpw);
		numPicture = 2;
		level = 400;        //几何变换                
		Invalidate();
	}
}

结果很奇怪,旋转90度就不会出现这个问题。后来又写了一个版本的旋转,参考

https://www.cnblogs.com/tingshuo/archive/2011/05/15/2047016.html可以说是复制粘贴哈哈

五. 图像旋转-绕中心旋转(新版本)

图像旋转算法与实现

好吧,先下个定义,图像旋转是指图像以某一点为中心旋转一定的角度,形成一幅新的图像的过程。当然这个点通常就是图像的中心。既然是按照中心旋转,自然会有这样一个属性:旋转前和旋转后的点离中心的位置不变.

根据这个属性,我们可以得到旋转后的点的坐标与原坐标的对应关系。由于原图像的坐标是以左上角为原点的,所以我们先把坐标转换为以图像中心为原点。假设原图像的宽为w,高为h,(x0,y0)为原坐标内的一点,转换坐标后的点为(x1,y1)。那么不难得到:

x1 = x0 - w/2; y1 = -y0 + h/2;

在新的坐标系下,假设点(x0,y0)距离原点的距离为r,点与原点之间的连线与x轴的夹角为b,旋转的角度为a,旋转后的点为(x1,y1), 如下图所示。

那么有以下结论:

x0=rcosb;y0=rsinb

x1 = rcos(b-a) = rcosbcosa+rsinbsina=x0cosa+y0sina;

y1=rsin(b-a)=rsinbcosa-rcosbsina=-x0sina+y0cosa;

得到了转换后的坐标,我们只需要把这些坐标再转换为原坐标系即可。这里还有一点要注意,旋转后的图像的长和宽会发生变化,因此要计算新图像的长和宽。

代码如下

void CImageProcessingView::OnJhbhGjxz()
{
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("中心点逆时针旋转-该进版本!", MB_OK, 0);
	CImageXZDlg dlg;

	if (dlg.DoModal() == IDOK)
	{
		int num;//记录每一行需要填充的字节
		if (m_nWidth * 3 % 4 != 0)
		{
			num = 4 - m_nWidth * 3 % 4;
		}
		else
		{
			num = 0;
		}
		double PI = asin(0.5) * 6;
		double degree = 1.0 * dlg.m_nZXJZ * PI / 180;
		CString str;
		str.Format("转换后的角度=%d", dlg.m_nZXJZ);
		AfxMessageBox(str);

		//打开临时的图片  
		FILE *fpo = fopen(BmpName, "rb");
		FILE *fpw = fopen(BmpNameLin, "wb+");
		fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
		fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
		fread(m_pImage, m_nImage, 1, fpo);

		//以图像中心为原点左上角,右上角,左下角和右下角的坐标,用于计算旋转后的图像的宽和高
		POINT pLT, pRT, pLB, pRB;//分别对应左上点、右上点、左下角、右下角

		pLT.x = -m_nWidth / 2; pLT.y = m_nHeight / 2;
		pRT.x = m_nWidth / 2; pRT.y = m_nHeight / 2;
		pLB.x = -m_nWidth / 2; pLB.y = -m_nHeight / 2;
		pRB.x = m_nWidth / 2; pRB.y = -m_nHeight / 2;

		//旋转之后的坐标
		POINT pLTN, pRTN, pLBN, pRBN;
		double sina = sin(degree);
		double cosa = cos(degree);
		pLTN.x = pLT.x*cosa + pLT.y*sina;
		pLTN.y = -pLT.x*sina + pLT.y*cosa;
		pRTN.x = pRT.x*cosa + pRT.y*sina;
		pRTN.y = -pRT.x*sina + pRT.y*cosa;
		pLBN.x = pLB.x*cosa + pLB.y*sina;
		pLBN.y = -pLB.x*sina + pLB.y*cosa;
		pRBN.x = pRB.x*cosa + pRB.y*sina;
		pRBN.y = -pRB.x*sina + pRB.y*cosa;
		  //旋转后图像宽和高
		int desWidth = max(abs(pRBN.x - pLTN.x), abs(pRTN.x - pLBN.x));
		int desHeight = max(abs(pRBN.y - pLTN.y), abs(pRTN.y - pLBN.y));
		     //分配旋转后图像的缓存
		int desBufSize = ((desWidth * m_nBitCount + 31) / 32) * 4 * desHeight;
		/*new和delete有效的进行动态内存的分配和释放*/
		unsigned char *ImageSize;
		ImageSize = new unsigned char[desBufSize];

		BITMAPFILEHEADER nbmfHeader;
		nbmfHeader.bfType = 0x4D42;
		nbmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)+ desWidth * desHeight * m_nBitCount / 8;
		nbmfHeader.bfReserved1 = 0;
		nbmfHeader.bfReserved2 = 0;
		nbmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
		   //Bitmap头信息
		BITMAPINFOHEADER   bmi;
		bmi.biSize = sizeof(BITMAPINFOHEADER);
		bmi.biWidth = desWidth;
		bmi.biHeight = desHeight;
		bmi.biPlanes = 1;
		bmi.biBitCount = m_nBitCount;
		bmi.biCompression = BI_RGB;
		bmi.biSizeImage = 0;
		bmi.biXPelsPerMeter = 0;
		bmi.biYPelsPerMeter = 0;
		bmi.biClrUsed = 0;
		bmi.biClrImportant = 0;
		fwrite(&nbmfHeader, sizeof(BITMAPFILEHEADER), 1, fpw);
		fwrite(&bmi, sizeof(BITMAPINFOHEADER), 1, fpw);

		int num1;//记录每一行需要填充的字节
		if (desWidth * 3 % 4 != 0)
		{
			num1 = 4 - desWidth * 3 % 4;
		}
		else
		{
			num1 = 0;
		}

		for (int i = 0; i < desHeight; i++)
			  {
			       for (int j = 0; j < desWidth; j++)
				        {
				           //转换到以图像为中心的坐标系,并进行逆旋转
					            int tX = (j - desWidth / 2)*cos(2*PI - degree) + (-i + desHeight / 2)*sin(2 * PI - degree);
								 int tY = -(j - desWidth / 2)*sin(2 * PI - degree) + (-i + desHeight / 2)*cos(2 * PI - degree);
				            //如果这个坐标不在原图像内,则不赋值
				            if (tX > m_nWidth/ 2 || tX < -m_nWidth/ 2 || tY > m_nHeight / 2 || tY < -m_nHeight / 2)
					            {
				                continue;
				           }
			            //再转换到原坐标系下
				             int tXN = tX + m_nWidth/ 2; int tYN = abs(tY - m_nHeight / 2);
			           //值拷贝
							 ImageSize[(i*desWidth + j) * 3 + i * num1] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num];
							 ImageSize[(i*desWidth + j) * 3 + i * num1 + 1] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num + 1];
							 ImageSize[(i*desWidth + j) * 3 + i * num1 + 2] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num + 2];
			        }
			    }


		fwrite(ImageSize, desBufSize, 1, fpw);
		fclose(fpo);
		fclose(fpw);
		numPicture = 2;
		level = 400;        //几何变换                
		Invalidate();
	}
}

看新的结果


六. 图像缩放

下面是采用最近邻插值法的过程,注意BMP图缩放还需修改头文件信息。
        第一步:在资源视图中添加“图像缩放”Dialog

        第二步:点击空白处创建对话框的类CImageSFDlg,同时打开类向导为其添加成员变量m_sfbs(缩放倍数),其为int型在0-200之间。



        第三步:打开类向导为其添加成员函数void CImageProcessingView::OnJhbhSf() 并实现缩放。同时添加头文件#include "ImageSFDlg.h"。

void CImageProcessingView::OnJhbhTxsfZjl()
{
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("图像缩放-最近邻!", MB_OK, 0);
	CImageSFdlg dlg;

	if (dlg.DoModal() == IDOK)
	{
		int num;//记录每一行需要填充的字节
		if (m_nWidth * 3 % 4 != 0)
		{
			num = 4 - m_nWidth * 3 % 4;
		}
		else
		{
			num = 0;
		}

		//打开临时的图片  
		FILE *fpo = fopen(BmpName, "rb");
		FILE *fpw = fopen(BmpNameLin, "wb+");
		fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
		fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
		fread(m_pImage, m_nImage, 1, fpo);

		float SFX = dlg.SFX;
		float SFY = dlg.SFY;

		//计算缩放后的宽高
		int desWidth = int(m_nWidth * SFX);
		int desHeight = int(m_nHeight * SFY);

		//记录缩放后每一行需要填充的字节
		int num1;
		if (desWidth * 3 % 4 != 0)
		{
			num1 = 4 - m_nWidth * 3 % 4;
		}
		else
		{
			num1 = 0;
		}

		//分配旋转后图像的缓存
		int desBufSize = ((desWidth * m_nBitCount + 31) / 32) * 4 * desHeight;

		unsigned char *ImageSize;
		ImageSize = new unsigned char[desBufSize];

		BITMAPFILEHEADER nbmfHeader;
		nbmfHeader.bfType = 0x4D42;
		nbmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + desWidth * desHeight * m_nBitCount / 8;
		nbmfHeader.bfReserved1 = 0;
		nbmfHeader.bfReserved2 = 0;
		nbmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
		//Bitmap头信息
		BITMAPINFOHEADER   bmi;
		bmi.biSize = sizeof(BITMAPINFOHEADER);
		bmi.biWidth = desWidth;
		bmi.biHeight = desHeight;
		bmi.biPlanes = 1;
		bmi.biBitCount = m_nBitCount;
		bmi.biCompression = BI_RGB;
		bmi.biSizeImage = 0;
		bmi.biXPelsPerMeter = 0;
		bmi.biYPelsPerMeter = 0;
		bmi.biClrUsed = 0;
		bmi.biClrImportant = 0;
		fwrite(&nbmfHeader, sizeof(BITMAPFILEHEADER), 1, fpw);
		fwrite(&bmi, sizeof(BITMAPINFOHEADER), 1, fpw);

		for (int i = 0; i < desHeight; i++)
		{
			for (int j = 0; j < desWidth; j++)
			{
				
				int tXN = int(j / SFX);
				int tYN = int (i / SFY);
	 
				
				//值拷贝
				ImageSize[(i*desWidth + j) * 3 + i * num1] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num];
				ImageSize[(i*desWidth + j) * 3 + i * num1 + 1] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num + 1];
				ImageSize[(i*desWidth + j) * 3 + i * num1 + 2] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num + 2];
			}
		}
		fwrite(ImageSize, desBufSize, 1, fpw);
		fclose(fpo);
		fclose(fpw);
		numPicture = 2;
		flagSF = 1;
		m_nDrawWidthSF = desWidth;
		m_nDrawHeightSF = desHeight;
		level = 500;        //几何变换                
		Invalidate();

	}
}
第四步:因为图像缩放修改BMP图片头信息,所以需要修改ShowBitmap中的显示第二张图片时的部分代码。如下所示:添加变量flagSF、m_nDrawWidthSF和m_nDrawHeightSF。
  1. /*定义显示图像缩放时的长宽与标记*/  
  2. int flagSF=0;          //图像几何变换缩放变换  
  3. int m_nDrawWidthSF=0;  //图像显示宽度缩放后  
  4. int m_nDrawHeightSF=0; //图像显示高度缩放后  
  5.   
  6. //****************显示BMP格式图片****************//  
  7. void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName)  
  8. {  
  9.         ......  
  10.         else        //图像几何变换  
  11.         if(level=200)  
  12.         {  
  13.             m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,  
  14.                 LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);  
  15.         }  
  16.   
  17.   
  18.         if( m_bitmap.m_hObject ) {  
  19.             m_bitmap.Detach();            //m_bitmap为创建的位图对象  
  20.         }  
  21.         m_bitmap.Attach(m_hBitmapChange);  
  22.         //定义并创建一个内存设备环境  
  23.         CDC dcBmp;  
  24.         if( !dcBmp.CreateCompatibleDC(pDC) )   //创建兼容性的DC  
  25.             return;  
  26.         BITMAP m_bmp;                          //临时bmp图片变量  
  27.         m_bitmap.GetBitmap(&m_bmp);            //将图片载入位图中  
  28.         CBitmap *pbmpOld = NULL;  
  29.         dcBmp.SelectObject(&m_bitmap);         //将位图选入临时内存设备环境  
  30.   
  31.         //图片显示调用函数StretchBlt   
  32.         if(flagSF==1)  
  33.         {  
  34.             CString str;  
  35.             str.Format("缩放长=%d 宽%d 原图长=%d 宽=%d",m_nDrawWidthSF,  
  36.                         m_nDrawHeightSF,m_nWidth,m_nHeight);  
  37.             AfxMessageBox(str);  
  38.             flagSF=0;  
  39.             //m_nDrawWidthSF缩放此存见函数最近邻插值法中赋值  
  40.             if(m_nDrawWidthSF<650 && m_nDrawHeightSF<650)     
  41.                 pDC->StretchBlt(m_nWindowWidth-m_nDrawWidthSF,0,  
  42.                     m_nDrawWidthSF,m_nDrawHeightSF,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);  
  43.             else  
  44.                 pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,  
  45.                     m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);  //显示大小为640*640  
  46.         }  
  47.         else {  
  48.             //如果图片太大显示大小为固定640*640 否则显示原图大小  
  49.             if(m_nDrawWidth<650 && m_nDrawHeight<650)  
  50.                 pDC->StretchBlt(m_nWindowWidth-m_nDrawWidth,0,  
  51.                     m_nDrawWidth,m_nDrawHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);  
  52.             else  
  53.                 pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,  
  54.                     m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);   
  55.         }  
  56.         //恢复临时DC的位图  
  57.         dcBmp.SelectObject(pbmpOld);      
  58. }  

        运行效果如下图所示,采用最近邻插值法缩放大了会出现失帧。


双线性插值有点难,后面有时间会写一下

2018-09-25 16:09:34 qq_35654080 阅读数 778
%利用interp2函数对图像通过各种插值法进行放大
I=imread('lean.jpg');
I2=imresize(I,0.125);
Z1=interp2(double(I2),2,'nearest'); %最近邻法插值
Z1=uint8(Z1);
subplot(221);imshow(Z1);
title('最近邻插值');
Z2=interp2(double(I2),2,'linear'); %线性法插值
Z2=uint8(Z2);
subplot(222);imshow(Z2);
title('线性邻插值');
Z3=interp2(double(I2),2,'spline'); %三次样条法插值
Z3=uint8(Z3);
subplot(223);imshow(Z3);
title('三次样条法插值');
Z4=interp2(double(I2),2,'cubic'); %立方法插值
Z4=uint8(Z4);
subplot(224);imshow(Z4);
title('立方法插值');

 自定义translation

function J=translation(I,a,b)
%I为输入图像,a和b描述I图像沿着x轴和y轴移动的距离
%不考虑溢出情况
[M,N,G]=size(I);
I=im2double(I);
J=ones(M,N,G);
for i=1:M
    for j=1:N
        if ((i+a)>=1&&(j+b>=1)&&(j+b<=N));%判断平移后行列坐标是否超出范围
            J(i+a,j+b,:)=I(i,j,:);%图像平移
        end
    end
end




%对图像实现平移操作
I=imread('office_1.jpg');
a=90;b=90; %设置平移坐标
J1=translation(I,a,b);
subplot(221);imshow(J1);axis on;
title('右下平移图像');
a=-90;b=-90;
J2=translation(I,a,b);
subplot(222);imshow(J2);axis on;
title('左上平移图像');
a=-90;b=90;
J3=translation(I,a,b);
subplot(223);imshow(J3);axis on;
title('右上平移图像');
a=90;b=-90;
J2=translation(I,a,b);
subplot(224);imshow(J4);axis on;
title('左下平移图像');

结果:

 

考虑溢出情况:

function J=translation1(I,a,b)
%I为输入图像,a和b描述I图像沿着x轴和y轴移动的距离
%考虑溢出情况,采用扩大显示区域的方法
[M,N,G]=size(I);
I=im2double(I);
J=ones(M+abs(a),N+abs(b),G);
for i=1:M
for j=1:N
    if (a<0&&b<0); %如果进行右下移动,对新图像矩阵进行赋值
        J(i,j,:)=I(i,j,:);
    else if(a>0&&b>0);
        J(i+a,j+b,:)=I(i,j,:);
    else if(a>0&&b<0);
        J(i+a,j,:)=I(i,j,:);
    else
        J(i,j+b,:)=I(i,j,:);
        end
       end
     end
   end
end
%对图像实现平移操作
I=imread('office_1.jpg');
a=90;b=90; %设置平移坐标
J1=translation_T(I,a,b);
subplot(221);imshow(J1);axis on;
title('右下平移图像');
a=-90;b=-90;
J2=translation_T(I,a,b);
subplot(222);imshow(J2);axis on;
title('左上平移图像');
a=-90;b=90;
J3=translation_T(I,a,b);
subplot(223);imshow(J3);axis on;
title('右上平移图像');
a=90;b=-90;
J4=translation_T(I,a,b);
subplot(224);imshow(J4);axis on;
title('左下平移图像');

结果:

 

imrotate 旋转处理

 

%利用imrotate函数对图像进行旋转处理
A=imread('20151130101641521.jpg');
J1=imrotate(A,60);%设置旋转角度,实现旋转并显示
J2=imrotate(A,-60);
J3=imrotate(A,60,'bicubic','crop');%设置输出图像的大小,实现旋转图像并显示
J4=imrotate(A,30,'bicubic','loose');
figure;
subplot(221);imshow(J1);
title('逆时针旋转60度')
subplot(222);imshow(J2);
title('逆时针旋转30度')
subplot(223);imshow(J3);
title('裁剪旋转')
subplot(224);imshow(J4);
title('不裁剪旋转')

结果:

 

 imresize实现对图像缩放

%利用imresize函数对图像实现缩放
I=imread('20151130101641521.jpg');
J=imresize(I,0.2);
subplot(2,2,1);imshow(I);
title('原始图像');
disp('图像放大,最近邻插值法运算时间:');
tic
J1=imresize(J,8,'nearest');
toc
subplot(2,2,2);imshow(J1);
title('图像放大,最近邻插值')
disp('图像放大,双线性插值法时间:');
tic
J2=imresize(J,8,'bilinear');
toc
subplot(2,2,3);imshow(J2);
title('图像放大,双线性插值')
disp('图像放大,双立方插值法时间:')
tic
J3=imresize(J,8,'bicubic');
toc
subplot(2,2,4);imshow(J3);
title('图像放大,双立方插值')

结果:

 

imcrop实现图像裁剪 

%利用imcrop图像的裁剪
I=imread('20151130101641521.jpg');
I2=imcrop(I,[50 80 80 112]);
figure;imshow(I),
title('原图像');
figure();imshow(I2)
title('剪切图像')

结果:

 

用手动实现图像的裁剪

%用手动实现图像的裁剪
[I,map]=imread('20151130101641521.jpg');
figure;imshow(I,map);
I2=imcrop(I,map);
figure;imshow(I2);

结果:

 

matlab实现图像错切

%错切
im=(imread('robot.jpg'));
im1=rgb2gray(im);
figure
subplot(131),imshow(im1);
[row,col]=size(im1); %获取行数 和 列数

%图像的水平错切
G=zeros(row,col);
a=pi/6; %水平错切30度
b=tan(a);
for m=1:row
for n=1:col
G(round(m+b*n),n)=im1(m,n);
end
end
subplot(132),imshow(uint8(G));
 
%图像的垂直错切
G=zeros(row,col);
a=pi/6; %水平错切30度
b=tan(a);
for m=1:row
for n=1:col
G(n,round(m+b*n))=im1(m,n);
end
end
subplot(133),imshow(uint8(G));

结果:

 

图像镜像变换

图像的镜像变换不改变的图像的形状。图像的镜像变换分为三种:水平镜像,垂直镜像和对角镜像。

%利用matlab实现图像的水平,垂直及对角镜像变换
I1=imread('robot.jpg');
I1=double(I1);
subplot(2,2,1);imshow(uint8(I1));
title('原始图像');
H=size(I1);
I2(1:H(1),1:H(2),1:H(3))=I1(H(1):-1:1,1:H(2),1:H(3));
subplot(2,2,2);imshow(uint8(I2));
title('垂直镜像');
I3(1:H(1),1:H(2),1:H(3))=I1(1:H(1),H(2):-1:1,1:H(3));
subplot(2,2,3);imshow(uint8(I3));
title('水平镜像');
I4(1:H(1),1:H(2),1:H(3))=I1(H(1):-1:1,H(2):-1:1,1:H(3));
subplot(2,2,4);imshow(uint8(I4));
title('对角镜像');

结果:

 
领域处理 

滑动领域处理

%利用nlfilter函数实现滑动邻域操作
A=imread('cameraman.tif');
A=im2double(A);
fun=@(x)median(x(:));
B=nlfilter(A,[3 3],fun);
subplot(121);imshow(A);
title('原始图像');
subplot(122);imshow(B);
title('领域操作');

结果: