图像平移 数字图像处理

2019-09-07 17:05:40 weixin_44225182 阅读数 7544

图像平移

对于图像的平移,MATLAB中是可以利用膨胀函数平移图像。
代码:

I = imread('a1.jpg');
se = translate(strel(1), [50 140]);%将一个平面结构化元素分别向下和向右移动30个位置
J = imdilate(I,se);%利用膨胀函数平移图像
subplot(121);imshow(I), title('原图')
subplot(122), imshow(J), title('移动后的图像');

效果图:
在这里插入图片描述
这里我重点说平移的基本原理及代码实现。

首先,我们必须要知道图像是怎么平移的?图像可以看成二维矩阵,由很多的像素点组成,假设一个像素点的坐标为(1,2),我们向下平移2个像素,向右平移3个像素,那么目标坐标就是(3,5)。这个在二维坐标中很好得出结果,无非就是横纵坐标加减偏移地址罢了,但是在矩阵中怎么算呢?其实这里有几种方法,但思维都是一样的。看原理:
假设(x0,y0)是原图像的点,Tx是x方向偏移量,Ty是y方向偏移量(图像处理一般向下为x轴,向右为y轴)。那么(x1,y1)可以由下图矩阵计算。

在这里插入图片描述
通过上面的矩阵运算,我们对原图像的中的每一个像素点进行该变换(仅仅是位置变换),得到新的坐标,然后在新坐标下显示原图像。

代码(这个是copy别人的,她用的矩阵变换和我的不一样,但是原理相同):

I=rgb2gray(imread('a1.jpg'));%读入图片并转化为灰度图
subplot(1,2,1),imshow(I),title('原图');                   %建立窗口,显示灰度图I
[r,c]=size(I);                      %计算灰度图的大小,r表示行,c表示列,即通过size函数将灰度图I的行数存在矩阵的r中,列数存在矩阵的c中,这样就知道灰度图的大小是r×c
dst=zeros(r,c);                     %建立r×c的0矩阵(平移结果矩阵),初始化为零(黑色)
dx=50;                              %平移的x方向的距离,这里是竖直方向
dy=80;                              %平移的y方向的距离,这里是水平方向
tras=[1 0 dx;0 1 dy;0 0 1];         %平移变换矩阵
for i=1:r
    for j=1:c
        temp=[i;j;1];               %灰度图I要平移变换的点,这里用矩阵表示
        temp=tras*temp;             %矩阵相乘,得到三行一列的矩阵temp,即平移后的矩阵
        x=temp(1,1);                %把矩阵temp的第一行第一列的元素给x   
        y=temp(2,1);                %把矩阵temp的第二行第一列的元素给y 
        if(x>=1&&x<=r)&&(y>=1&&y<=c)%判断所变换后得到的点是否越界
            dst(x,y)=I(i,j);        %得到平移结果矩阵,点(x,y)是由点(i,j)平移而来的,有对应关系 
        end
    end
end
subplot(1,2,2),imshow(uint8(dst)),title('平移后');          %建立窗口,显示平移后的图

效果图:
在这里插入图片描述
效果虽然达到了,但是,她的这种写法好像都变成了灰色图片。那么怎么得到和膨胀函数的效果一样呢?
这个也简单,我们只需要对原图像的三个通道分别平移,再合成就ok了(就是这么简单,哈哈)。
代码(这个才是我写的):

 t=imread('a1.jpg');

%分别提取三通道的矩阵
t_1=t(:,:,1)
t_2=t(:,:,2)
t_3=t(:,:,3)

%这里不能是[m,n]=size(t),我们是用三个二维矩阵合成一个三维图片(这里图片看出三维,毕竟有RGB)
[m,n,z]=size(t);

% 定义偏移量
dx=50;
dy=140;

%定义新矩阵 ,存储新坐标
r_1=zeros(m,n);
r_2=zeros(m,n);
r_3=zeros(m,n);

%martix 变换用的矩阵
martix=[1,0,dx;0,1,dy;0,0,1];

%坐标变换
for i=1:m
for j=1:n
tem=[i;j;1];
tem=martix*tem;
x=tem(1,1);
y=tem(2,1);
if(x>=1&&x<=m)&&(y>=1&&y<=n)
r_1(x,y)=t_1(i,j);
r_2(x,y)=t_2(i,j);
r_3(x,y)=t_3(i,j);
end
end
end

%得到三个新矩阵合成一张图片
rt=t
rt(:,:,1)=r_1;
rt(:,:,2)=r_2;
rt(:,:,3)=r_3;

%显示
subplot(1,2,1),imshow(t),title('原图');
subplot(1,2,2),imshow(rt),title('平移后');

效果图:
在这里插入图片描述

更多

获取更多资料、代码,微信公众号:海轰Pro
回复 海轰 即可

2015-06-04 17:28:35 Eastmount 阅读数 24793
       本文主要讲述基于VC++6.0 MFC图像处理的应用知识,主要结合自己大三所学课程《数字图像处理》及课件进行讲解,主要通过MFC单文档视图实现显示BMP图片空间几何变换,包括图像平移、图形旋转、图像反转倒置镜像和图像缩放的知识。同时文章比较详细基础,没有采用GDI+获取矩阵,而是通过读取BMP图片信息头和矩阵像素实现变换,希望该篇文章对你有所帮助,尤其是初学者和学习图像处理的学生。
       【数字图像处理】一.MFC详解显示BMP格式图片
       【数字图像处理】二.MFC单文档分割窗口显示图片
       【数字图像处理】三.MFC实现图像灰度、采样和量化功能详解
       【数字图像处理】四.MFC对话框绘制灰度直方图
       【数字图像处理】五.MFC图像点运算之灰度线性变化、灰度非线性变化、阈值化和均衡化处理详解
        
免费资源下载地址:
        http://download.csdn.net/detail/eastmount/8772951


一. 图像平移

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

        运行效果如下图所示,其中BMP图片(0,0)像素点为左下角。


        其代码核心算法:
        1.在对话框中输入平移坐标(x,y) m_xPY=x,m_yPY=y
        2.定义Place=dlg.m_yPY*m_nWidth*3 表示当前m_yPY行需要填充为黑色
        3.新建一个像素矩阵 ImageSize=new unsigned char[m_nImage]
        4.循环整个像素矩阵处理 
             for(int i=0 ; i<m_nImage ; i++ ){
                   if(i<Place) {ImageSize[i]=black; continue;}
//黑色填充底部 从小往上绘图
                   else if(i>=Place && countWidth<dlg.m_xPY*3) {//黑色填充左部分
                         ImageSize[i]=black; countWidth++;  continue;
                   }
                   else if(i>=Place && countWidth>=dlg.m_xPY*3) {//图像像素平移区域
                        ImageSize[i]=m_pImage[m_pImagePlace];//原(0,0)像素赋值过去
                        m_pImagePlace++; countWidth++;
                        if(countWidth==m_nWidth*3) { //一行填满 m_pImagePlace走到(0,1)
                              number++; m_pImagePlace=number*m_nWidth*3;
                        }
                   }
             }
         5.写文件绘图fwrite(ImageSize,m_nImage,1,fpw)

        第一步:在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",并实现平移。
/********************************************************/
/* 图像空间几何变换:图像平移 ID_JHBH_PY(几何变换-平移) 
/* 使用平移对话框:CImagePYDlg dlg                     
/* 算法:f(x,y)=f(x+x0,y+y0)图像所有点平移,空的补黑'0' 
/* 注意该图像平移方法只是从左上角(0,0)处开始平移        
/* 其他方向原理相同 自己去实现                           
/********************************************************/

void CImageProcessingView::OnJhbhPy() 
{
	if(numPicture==0) {
		AfxMessageBox("载入图片后才能空间平移!",MB_OK,0);
		return;
	}
	//定义采样对话框也是用来空间变换平移的坐标
	CImagePYDlg dlg;     
	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);

		/************************************************************/
		/* 图片空间变换-平移                                        
		/* 坐标(dlg.m_xPY,dlg.m_yPY)表示图像平移的坐标        
		/* 先用Plave计算出平移后的起始坐标,其他的坐标赋值为'0'黑色 
		/* 然后依次平移坐标,空的赋为黑色,否则填充                 
		/************************************************************/
		
		/******************************************************************/
		/* 严重错误1:数组变量赋值相等                                    
		/* 在View.h中定义变量 BYTE *m_pImage 读入图片数据后的指针         
		/* 建立临时变量数组,让它平移变换 unsigned char *ImageSize         
		/* ImageSize=m_pImage(错误)                                       
		/* 会导致ImageSize赋值变换时m_pImage也产生了变换,所以输出全为黑色 
		/*     因为它俩指向了相同的数组地址                               
		/* 解决方法:使用下面C++的new方法动态分配或for循环i=m_nImage赋值  
		/******************************************************************/

		/*临时变量存储的像素与m_pImage相同,便于处理图像*/
		unsigned char *ImageSize;      
	    ImageSize=new unsigned char[m_nImage];  //new和delete有效的进行动态内存的分配和释放

		int Place;                    //建立临时坐标 记录起始坐标(0,0)平移过来的位置
		int m_pImagePlace;            //原始图像平移为(0,0) 图像把它平移到Place位置
		unsigned char black;          //填充黑色='0' 

		/************************************************************/
		/* for(int i=0 ; i<m_nHeight ; i++ )                        
		/* for(int j=0 ; j<m_nWidth ; j++ )                         
		/* 不能使用的上面的因为可能图像的最后一行没有完整的一行像素 
		/* 这样会出现exe报错,使用m_nImage读写所有像素比较正确       
		/************************************************************/

		Place=dlg.m_yPY*m_nWidth*3;   //前m_yPY行都要填充为黑色          
		black=0;                       //颜色为黑色
		m_pImagePlace=0;               //图像处事位置为(0,0),把该点像素平移过去 
		int countWidth=0;              //记录每行的像素个数,满行时变回0
		int number=0;                  //数字记录使用的像素行数,平移时使用

		for(int i=0 ; i<m_nImage ; i++ )
		{
			/*如果每行的像素填满时清为0*/
			if(countWidth==m_nWidth*3) {
				countWidth=0;
			}
			
			/*第一部分:到平移后像素位置前面的所有像素点赋值为黑色*/
			if(i<Place) {
				ImageSize[i]=black;     //赋值为黑色
				continue;
			}
			
			/*第二部分:平移区域的左边部分赋值为黑色*/
			else if(i>=Place && countWidth<dlg.m_xPY*3) { //RGB乘3
				ImageSize[i]=black;     //赋值为黑色
				countWidth++;
				continue;
			}
	
			/****************************/
			/* 各部分如图所示:          
			/* 000000000000000000000000 
			/* 000000000000000000000000 
			/* 0000000................. 
			/* 0000000.................
			/* 0000000................. 
			/* 0000000.................
			/* 点表示像素部分,0为黑色   
			/****************************/

			/* 重点错误提示:由于bmp图像显示是从左下角开始存储(0,0)点所以输出图像为 */
			/* bmp图像是从左下角到右上角排列的 */

			/****************************/
			/* 各部分如图所示:          
			/* 0000000................. 
			/* 0000000................. 
			/* 0000000.................
			/* 0000000................. 
			/* 000000000000000000000000 
			/* 000000000000000000000000 
			/* 点表示像素部分,0为黑色   
			/****************************/
		
			/*第三部分:图像像素平移区域*/
			else if(i>=Place && countWidth>=dlg.m_xPY*3)
			{
				ImageSize[i]=m_pImage[m_pImagePlace];     
				m_pImagePlace++;
				countWidth++;
				if(countWidth==m_nWidth*3)
				{
					number++;
					m_pImagePlace=number*m_nWidth*3;
				}
			}
		}
		
		fwrite(ImageSize,m_nImage,1,fpw);  
		fclose(fpo);
		fclose(fpw);
		numPicture = 2;
		level=200;        //200表示几何变换
		Invalidate();
	}	
}
        同时在ShowBitmap中添加level标记重新绘制图片,代码如下:
else        //图像几何变换
if(level=200)
{
	m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
       运行时需要注意一点:BMP图像在处理过程中可能会出现一些斜线,而平移(40,60)位移量时可能出现如下。他是因为BMP格式有个非常重要的规定,要求每一扫描的字节数据必须能被4整除,也就是Dword对齐(长度4字节),如果图像的一行字节数不能被4整除,就需要在每行末尾不起0达到标准。
        例如一行像素为97字节,我们就需要补3个字节吗,数值可以是0,但是我们在BMP格式的信息头里说明了其宽度,所以补齐后对我们没有影响,所以后面补若干个字节的0即可直到被4整除。

 
        通过后面的图像缩放后,我从学做了一遍这个补齐的缩放。代码如下,能够实现完美平移。nice啊~
void CImageProcessingView::OnJhbhPy() 
{
	if(numPicture==0) {
		AfxMessageBox("载入图片后才能空间平移!",MB_OK,0);
		return;
	}
	//定义采样对话框也是用来空间变换平移的坐标
	CImagePYDlg dlg;     
	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);

		int num;            //记录每行多余的图像素数个数
		int sfSize;         //补齐后的图像大小
		//重点:图像的每行像素都必须是4的倍数:1*1的图像为 r g b 00H 
		if(m_nWidth*3%4!=0)
		{
			num=(4-m_nWidth*3%4);
			sfSize=(m_nWidth*3+num)*m_nHeight; //每行多number个
		}
		else
		{
			num=0;
			sfSize=m_nWidth*m_nHeight*3;
		}
		//注意:假如最后一行像素不足,我默认处理为完整的一行,不足补00H
		//总之处理后的图像总是m*n且为4倍数,每行都完整存在

		/*更改文件头信息 定义临时文件头结构变量*/
		BITMAPFILEHEADER bfhsf;
		BITMAPINFOHEADER bihsf;       
		bfhsf=bfh;
		bihsf=bih;
		bfhsf.bfSize=sfSize+54;		
		fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw);
		fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw);
		fread(m_pImage,m_nImage,1,fpo);

		CString str;
		str.Format("补齐=%d",num);
		AfxMessageBox(str);

		/*临时变量存储的像素与sfSize相同 new和delete有效的进行动态内存的分配和释放*/
		unsigned char *ImageSize;      
	    ImageSize=new unsigned char[sfSize];  

		int Place;                    //建立临时坐标 记录起始坐标(0,0)平移过来的位置  
        int m_pImagePlace;            //原始图像平移为(0,0) 图像把它平移到Place位置  
        unsigned char black=0;        //填充黑色='0'  
		unsigned char other=0;        //补码00H='\0'

		Place=dlg.m_yPY*(m_nWidth*3+num); //前m_yPY行都要填充为黑色          
		m_pImagePlace=0;                  //图像处事位置为(0,0),把该点像素平移过去 
		int countWidth=0;                 //记录每行的像素个数,满行时变回0
		int number=0;                     //数字记录使用的像素行数,平移时使用

		for(int i=0 ; i<sfSize ; i++ )  
        {  
            /*第一部分:到平移后像素位置前面的所有像素点赋值为黑色*/  
            if(i<Place) 
			{  
                ImageSize[i]=black;     //赋值为黑色  
                continue;  
            }  
              
            /*第二部分:平移区域的左边部分赋值为黑色*/  
            else if(i>=Place && countWidth<dlg.m_xPY*3)  //RGB乘3
			{   
                ImageSize[i]=black;     //赋值为黑色  
                countWidth++;  
                continue;  
            }  
          
            /*第三部分:图像像素平移区域*/  
            else if(i>=Place && countWidth>=dlg.m_xPY*3)  
            {  
                ImageSize[i]=m_pImage[m_pImagePlace];       
                m_pImagePlace++;  
                countWidth++;  
                if(countWidth==m_nWidth*3)  
                {  
					if(num==0)
					{
						countWidth=0;
						number++;  
						m_pImagePlace=number*m_nWidth*3; 
					}
					else //num为补0
					{
						for(int j=0;j<num;j++)
						{
							i++;
							ImageSize[i]=other;
						}
						countWidth=0;
						number++;  
						m_pImagePlace=number*(m_nWidth*3+num); //重点:添加Num
					}
                }  
            }  
        }  
          
        fwrite(ImageSize,sfSize,1,fpw);    
        fclose(fpo);  
        fclose(fpw);  
        numPicture = 2;  
        level=200;        //200表示几何变换  
        Invalidate();  
    }     
}
        运行效果如下图所示,完美平移,其他算法遇到斜线问题类似补齐即可。




二. 图像镜像

1.水平镜像翻转
        其变换矩阵如下:
                                 X=width-X0-1   (width为图像宽度)
                                 Y=Y0

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

/* 几何变换 图像翻转:自己对这个功能比较感兴趣,做个图像反转 */
void CImageProcessingView::OnJhbhFz() 
{
	if(numPicture==0) {
		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);
	
	/*new和delete有效的进行动态内存的分配和释放*/
	unsigned char *ImageSize;      
	ImageSize=new unsigned char[m_nImage];    
	int countWidth=0;        //记录每行的像素个数,满行时变回0
	int Place;               //记录图像每行的位置,便于图像反转
	int number=0;            //数字记录使用的像素行数
	Place=m_nWidth*3-1;

	//翻转矩阵: y=y0 x=width-x0-1
	for(int i=0 ; i<m_nImage ; i++ )
	{
		if(countWidth==m_nWidth*3)
		{
			countWidth=0;
		}
		ImageSize[i]=m_pImage[Place]; //(0,0)赋值(0,width*3-1)像素
		Place--;
		countWidth++;
		if(countWidth==m_nWidth*3)
		{
			number++;
			Place=number*m_nWidth*3-1;
		}
	}
		
	fwrite(ImageSize,m_nImage,1,fpw);  
	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level=200;                      
	Invalidate();
}
        运行效果如下图所示,其中还是存在一些小BUG,如前面的BMP图补0凑齐4整数倍宽度或颜色失帧。





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::OnJhbhDz() 
{
	if(numPicture==0) {
		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);
	
	/*new和delete有效的进行动态内存的分配和释放*/
	unsigned char *ImageSize;      
	ImageSize=new unsigned char[m_nImage];    
	int countWidth=0;                   //记录每行像素个数,满行时变回0
	int Place;                          //每列位置
	int number=0;                       //像素行数
	Place=(m_nWidth*3)*(m_nHeight-1-1); //0行存储

	//翻转矩阵: x=x0 y=height-y0-1 
	for(int i=0 ; i<m_nImage ; i++ )
	{
		ImageSize[i]=m_pImage[Place]; //(0,0)赋值(0,0)像素
		Place++;
		countWidth++;
		if(countWidth==m_nWidth*3)
		{
			countWidth=0;
			number++;
			Place=(m_nWidth*3)*(m_nHeight-number-1);
		}
	}
		
	fwrite(ImageSize,m_nImage,1,fpw);  
	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level=200;                      
	Invalidate();
}
        运行结果如下图所示,第二张图颜色没有失帧或变灰,这完全可以怀疑在翻转过程中RGB像素编程BGR后导致的结果,最终实现了翻转图像,但灰度存在一定;所以如果改为RBG顺序不变化即可原图颜色显示。






三. 图像旋转

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


        写到这里真心觉得写底层的代码非常困难啊!尤其是以为像素转换二维像素,同时也觉得当时的自己算法部分还是很强大的,也感觉到如果采用GDI+操作像素矩阵Matrix或ColorMatrix是多么的方便,因为它定义好了X和Y向量,这就是为什么Android前面写的图像处理要容易得多。但是效率高~
        好像利用GDI+旋转通过几句代码即可:
        matrix.Rotate(15); //矩阵旋转15度
        graph.SetTransform(&matrix);
        graph.DrawImage(&image,points,3);
        下面这部分代码是实现Android旋转的:参考我的博客
//旋转图片  
private void TurnPicture() {  
    Matrix matrix = new Matrix();  
    turnRotate=turnRotate+15;  
    //选择角度 饶(0,0)点选择 正数顺时针 负数逆时针 中心旋转  
    matrix.setRotate(turnRotate,bmp.getWidth()/2,bmp.getHeight()/2);   
    Bitmap createBmp = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());  
    Canvas canvas = new Canvas(createBmp);   
    Paint paint = new Paint();   
    canvas.drawBitmap(bmp, matrix, paint);  
    imageCreate.setBackgroundColor(Color.RED);  
    imageCreate.setImageBitmap(createBmp);  
    textview2.setVisibility(View.VISIBLE);  
}
        实现效果如下图所示:


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

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

/**********************************************************/
/* 几何变换:图片旋转                                 
/* 先添加对话框:IDD_JHBH_TXXZ(图像旋转),创建新类CImageXZDlg  
/* 创建输入度数的:m_xzds Member variables 为int 0-360间 
/**********************************************************/

void CImageProcessingView::OnJhbhTxxz() 
{
	if(numPicture==0) {
		AfxMessageBox("载入图片后才能空间旋转!",MB_OK,0);
		return;
	}
	
	//定义对话框并调用对话框
	CImageXZDlg dlg;    
	if( dlg.DoModal()==IDOK ) //显示对话框
	{
		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);
		
		/*new和delete有效的进行动态内存的分配和释放*/
		unsigned char *ImageSize;      
		ImageSize=new unsigned char[m_nImage];    
		int Place;          //记录图像每行的位置,便于图像旋转

		/*定义PA=3.14时使用的方法是arcsin(1.0/2)*6即为π*/
		double PA;
		PA=asin(0.5)*6;
		
		/*把输入的0-360的正整数度数转换为角度,30度=π/6*/
		double degree; 
		degree=PA*dlg.m_xzds/180;   //调用dlg.m_xzds(旋转度数)
		
		//对应的二维矩阵 注意图像矩阵从左下角开始处理 它最终要转换成一维存储
		int X,Y;               //图像变换前通过一维矩阵转换为二维
		int XPlace,YPlace;

		//输出转换为的角度
		CString str;
		str.Format("转换后的角度=%f",degree);
		AfxMessageBox(str);

		//图像旋转处理
		for(int i=0 ; i<m_nImage ; i++ )
		{
			//原图:一维矩阵转换为二维矩阵
			X=(i/3)%m_nWidth;
			Y=(i/3)/m_nWidth;
			//注意错误:X=i/m_nHeight Y=i%m_nWidth; 只输出最后1/3

			//图像旋转为:a(x,y)=x*cos-y*sin b(x,y)=x*sin+y*cos
			XPlace=(int)(X*cos(degree)-Y*sin(degree));
			YPlace=(int)(X*sin(degree)+Y*cos(degree));
			
			//在转换为一维图想输出
			if( (XPlace>=0 && XPlace<=m_nWidth) && (YPlace>=0 && YPlace<=m_nHeight) )
			{
				Place=YPlace*m_nWidth*3+XPlace*3;
				//在图像范围内赋值为该像素
				if(Place+2<m_nImage) 
				{
					ImageSize[i]=m_pImage[Place];
					i++;
					ImageSize[i]=m_pImage[Place+1];
					i++;
					ImageSize[i]=m_pImage[Place+2];
				}
				//否则赋值为黑色
				else 
				{
					ImageSize[i]=0; 
					i++;
					ImageSize[i]=0;
					i++;
					ImageSize[i]=0;
				}
			}
			//否则赋值为黑色
			else
			{
				ImageSize[i]=0;
				i++;
				ImageSize[i]=0;
				i++;
				ImageSize[i]=0;
			}
		}
	
		fwrite(ImageSize,m_nImage,1,fpw);  
		fclose(fpo);
		fclose(fpw);
		numPicture = 2;
		level=200;        //几何变换              
		Invalidate();
	}
}
        运行效果如下图所示,中心旋转太难了!找到中心那个位置就不太容易,我做不下去了,fuck~同时旋转过程中,由于是饶左下角(0,0)实现,故有的角度会到界面外显示全黑。下图分别旋转15度和355度。






四. 图像缩放

        图像缩放主要有两种方法:
        1.最近邻插值:
向后映射时,输出图像的灰度等于离它所映射位置最近的输入图像的灰度值。其中向前映射和向后映射如下:

 

        对于向前映射每个输出图像的灰度要经过多次运算,对于向后映射,每个输出图像的灰度只经过一次运算。在实际应用中,更多的是采用向后映射法,其中根据四个相邻像素灰度值计算某个位置的像素灰度值即为灰度级插值。
        2.双线性插值:四点确定一个平面函数,属于过约束问题。即单位正方形顶点已知,求正方形内任一点的f(x,y)值。



        换个通熟的说法,如下图所示。采用最近邻插值法就是P(x,y)像素值采用四舍五入等于离它最近的输入图像像素值。分别计算它到四个顶点之间的距离,但是这样会造成图像的马赛克、锯齿等现象。而采用双线性插值法,主要通过该坐标周围的四个像素值,按照比例混合计算器近似值。比例混合的依据是离哪个像素近,哪个像素的比例越大。


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

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



        第三步:打开类向导为其添加成员函数void CImageProcessingView::OnJhbhSf() 并实现缩放。同时添加头文件#include "ImageSFDlg.h"。
/*******************************************************************/
/* ID_JHBH_SF: 几何运算-缩放-最近邻插值算法               
/* 算法思想:输出图像的灰度等于离它所映射位置最近的输入图像的灰度值 
/* 先计算出放大缩小后的长宽,根据它计算找原图中的点灰度,四舍五入  
/*******************************************************************/

void CImageProcessingView::OnJhbhSf() 
{
	if(numPicture==0) {
		AfxMessageBox("载入图片后才能几何缩放图像!",MB_OK,0);
		return;
	}

	CImageSFDlg dlg;           //定义缩放对话框
	if( dlg.DoModal()==IDOK )
	{
		//采样坐标最初为图片的自身像素  m_sfbs(缩放倍数)
		if( dlg.m_sfbs==0 ) {
			AfxMessageBox("输入图片缩放倍数不能为0!",MB_OK,0);
			return;
		}
		
		FILE *fpo = fopen(BmpName,"rb");
		FILE *fpw = fopen(BmpNameLin,"wb+");
		fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
		fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
		
		/*先求缩放后的长宽*/
		int sfWidth,sfHeight;                            //缩放后的长宽
		int sfSize;                                      //缩放后的图像大小
		sfWidth=(int)(m_nWidth*(dlg.m_sfbs*1.0)/100);    //24位图像RGB必须是3倍数 循环读取时为RGB
		sfHeight=(int)(m_nHeight*(dlg.m_sfbs*1.0)/100);
		int number;                                      //记录每行多余的图像素数个数

		//重点:图像的每行像素都必须是4的倍数:1*1的图像为 r g b 00H 
		if(sfWidth*3%4!=0) {
			number=(4-sfWidth*3%4);
			sfSize=(sfWidth*3+(4-sfWidth*3%4))*sfHeight;
		}
		else {
			number=0;
			sfSize=sfWidth*sfHeight*3;
		}
		//注意:假如最后一行像素不足,我默认处理为完整的一行,不足补00H
		//总之处理后的图像总是m*n且为4倍数,每行都完整存在
	
		/*更改文件头信息 定义临时文件头结构变量*/
		BITMAPFILEHEADER bfhsf;
		BITMAPINFOHEADER bihsf;                //缩放(sf)
		bfhsf=bfh;
		bihsf=bih;

		bfhsf.bfSize=sfSize+54;		
		bihsf.biWidth=sfWidth;
		bihsf.biHeight=sfHeight;

		//显示部分m_nDrawWidth<650显示原图,否则显示
		flagSF=1;                         //图像缩放为1标识变量
		m_nDrawWidthSF=sfWidth;
		m_nDrawHeightSF=sfHeight;

		fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw);
		fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw);

		fread(m_pImage,m_nImage,1,fpo);	
		
		unsigned char red,green,blue;
		unsigned char other=0;                       //补码00H='\0'
		int placeX;                                  //记录在原图中的第几行的位置
		int placeY;									 //记录在原图中的位置(x,y)
		int placeBH;                                 //记录变换后在变换图中的位置

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

		/*读取文件像素信息 缩放注意:1.找最近灰度 2.四舍五入法(算法+0.5)*/
		for(int i=0; i<sfHeight ; i++ )                  //行
		{
			placeX=(int)(i/(dlg.m_sfbs*1.0/100)+0.5)*bih.biWidth*3;
			for(int j=0; j<sfWidth ; j++ )               //列
			{
				red=green=blue=0;
				//放大倍数为(dlg.m_sfbs*1.0/100)
				placeY=placeX+(int)(j/(dlg.m_sfbs*1.0/100)+0.5)*3;    
				//重点是:number*i补充00H,如果是numer图像会被切成2块
				placeBH=(i*sfWidth*3+number*i)+j*3;                  
				if(placeY+2<m_nImage)
				{
					ImageSize[placeBH]=m_pImage[placeY];
					ImageSize[placeBH+1]=m_pImage[placeY+1];
					ImageSize[placeBH+2]=m_pImage[placeY+2];
				}
				else
				{
					ImageSize[placeBH]=0;
					ImageSize[placeBH+1]=0;
					ImageSize[placeBH+2]=0;
				}
			}
		}
		
		fwrite(ImageSize,sfSize,1,fpw);
		fclose(fpo);
		fclose(fpw);
		numPicture = 2;
		level=200;
		Invalidate();
	}
}
        第四步:因为图像缩放修改BMP图片头信息,所以需要修改ShowBitmap中的显示第二张图片时的部分代码。如下所示:添加变量flagSF、m_nDrawWidthSF和m_nDrawHeightSF。
/*定义显示图像缩放时的长宽与标记*/
int flagSF=0;          //图像几何变换缩放变换
int m_nDrawWidthSF=0;  //图像显示宽度缩放后
int	m_nDrawHeightSF=0; //图像显示高度缩放后

//****************显示BMP格式图片****************//
void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName)
{
        ......
		else        //图像几何变换
		if(level=200)
		{
			m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
				LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
		}


		if( m_bitmap.m_hObject ) {
			m_bitmap.Detach();            //m_bitmap为创建的位图对象
		}
		m_bitmap.Attach(m_hBitmapChange);
		//定义并创建一个内存设备环境
		CDC dcBmp;
		if( !dcBmp.CreateCompatibleDC(pDC) )   //创建兼容性的DC
			return;
		BITMAP m_bmp;                          //临时bmp图片变量
		m_bitmap.GetBitmap(&m_bmp);            //将图片载入位图中
		CBitmap *pbmpOld = NULL;
		dcBmp.SelectObject(&m_bitmap);         //将位图选入临时内存设备环境

		//图片显示调用函数StretchBlt 
		if(flagSF==1)
		{
			CString str;
			str.Format("缩放长=%d 宽%d 原图长=%d 宽=%d",m_nDrawWidthSF,
                        m_nDrawHeightSF,m_nWidth,m_nHeight);
			AfxMessageBox(str);
			flagSF=0;
			//m_nDrawWidthSF缩放此存见函数最近邻插值法中赋值
			if(m_nDrawWidthSF<650 && m_nDrawHeightSF<650)   
				pDC->StretchBlt(m_nWindowWidth-m_nDrawWidthSF,0,
					m_nDrawWidthSF,m_nDrawHeightSF,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
			else
				pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,
					m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);  //显示大小为640*640
		}
		else {
			//如果图片太大显示大小为固定640*640 否则显示原图大小
			if(m_nDrawWidth<650 && m_nDrawHeight<650)
				pDC->StretchBlt(m_nWindowWidth-m_nDrawWidth,0,
					m_nDrawWidth,m_nDrawHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
			else
				pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,
					m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY); 
		}
		//恢复临时DC的位图
		dcBmp.SelectObject(pbmpOld);    
}
        运行效果如下图所示,采用最近邻插值法缩放大了会出现失帧。




        但是同时当图片缩小是总是报错,图片缩放确实有点难,因为像素需要补齐4整数倍,同时需要修改消息头,同时像素矩阵的变换都非常复杂。


        最后还是希望文章对你有所帮助,如果文章有不足或错误之处,请海涵。自己给自己点个赞,挺不容易的,但还会继续写完~
      (By:Eastmount 2015-06-04 下午5点   http://blog.csdn.net/eastmount/

2019-07-09 20:11:27 wujuxKkoolerter 阅读数 379

图像几何变换-平移变换

平移变换是将图像上所有像素点按照给定的偏移量在水平方向沿x轴,在垂直方向沿y轴移动。对于图像中任意一点p(x0,y0)p(x_0,y_0)平移变换的公式如下:
{x=x0+xy=y0+y \begin{cases} x = x_0 + \triangle x \\ y = y_0 + \triangle y \end{cases}
其中,x\triangle x表示水平x方向的偏移量,y\triangle y为y方向的偏移量。

利用齐次坐标表示如下:

[xy1]=[10x01y001]×[x0y01] \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} = \begin{bmatrix} 1 &amp; 0 &amp; \triangle x \\ 0 &amp; 1 &amp; \triangle y \\ 0 &amp; 0 &amp; 1 \end{bmatrix} \times \begin{bmatrix} x_0 \\ y_0 \\ 1 \end{bmatrix}
表示图像上任意点p(x0,y0)p(x_0,y_0)偏移(x,y)(\triangle x,\triangle y)后的新坐标p(x,y)p(x,y)

Python实现的代码如下:

def transform_xy(src,deltax,deltay):
    rows,cols = src.shape[0],src.shape[1]

    assert deltax < cols
    assert deltay < rows

    dst = np.zeros_like(src)
    if deltax >= 0 and deltay >= 0:
        dst[deltay:rows,deltax:cols] = src[0:rows-deltay,0:cols-deltax]
    elif deltax >=0 and deltay <= 0:
        dst[0:rows+deltay,deltax:cols] = src[np.abs(deltay):rows,0:cols-deltax]
    elif deltax <=0 and deltay >= 0:
        dst[deltay:rows,0:cols+deltax] = src[0:rows-deltay,np.abs(deltax):cols]
    else:
        dst[0:rows+deltay,0:cols+deltax] = src[np.abs(deltay):rows,np.abs(deltax):cols]
    return dst

程序运行的结果如下:
在这里插入图片描述

2020-01-17 09:28:22 Trent1985 阅读数 5781

        本篇作为新年到来前的最后一篇,提前祝大家新年快乐!

        图像几何变换又叫做图像基本变换,主要包括图像平移、图像缩放和图像旋转几个部分,当然还有图像镜像等简单的内容。图像基本变换是图像处理的基本内容,是学习以后复杂的仿射变换、透视变换以及更高级的MLS网格变形等内容的基础,意义重大。本篇将从平移、缩放和旋转三个方面来讲解如何单纯使用C语言来轻松实现这三个算法。

图像平移变换

[定义与算法]

        图像平移变换可以表示为水平方向和垂直方向的位移,如果把图像坐标系的原点(0,0)点平移到(x0,y0),则图像内任意一点(x,y)平移后坐标(x’,y’)用公式表示如下:

 

        我们对测试图进行水平和垂直正方向平移100像素,效果图如图Fig.1所示。

        注意,黑色区域是我们默认填充的颜色,平移变换会出现图像跑到原图画布外面的情况,此时,原来的区域可以填充任意颜色,图像平移变换就这么简单。

[绘制与代码]

        我们用C语言来实现图像平移变换算法,定义f_Transform.h文件,在文件中定义如下接口:

/*********************************************************
*Function:图像平移变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          xoffset:水平方向平移量
*          yoffset:垂直方向平移量
*Return:  0-成功,其他失败
*********************************************************/
int f_XYOfffset(unsigned char* srcData, int width, int height, int stride, int xoffset, int yofffset);

        在这个接口中,定义了两个参数xoffset和yoffset分别用来表示水平和垂直的偏移向量,注意,如果xoffset和yoffset都为正数,则表示的是向水平和垂直的正方向偏移了xoffset和yoffset个像素距离,表现在结果图中,即图像向左偏移,反之,图像向右边偏移。

        完整接口代码如下:

/*********************************************************
*Function:图像平移变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          xoffset:水平方向平移向量
*          yoffset:垂直方向平移向量
*Return:  0-成功,其他失败
*********************************************************/
int f_XYOfffset(unsigned char* srcData, int width, int height, int stride, int xoffset, int yoffset)
{
	int ret = 0;
	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * height * stride);
	memcpy(tempData, srcData, sizeof(unsigned char) * height * stride);
	unsigned char* pSrc = srcData;
	for(int j = 0; j < height; j++)
	{
		for(int i = 0; i < width; i++)
		{
			int cx = i + xoffset;
			int cy = j + yoffset;
			if(cx >= 0 && cx < width && cy >= 0 && cy < height)
			{
				int pos = cx * 4 + cy * stride;
			    pSrc[0] = tempData[pos];
				pSrc[1] = tempData[pos + 1];
				pSrc[2] = tempData[pos + 2];
			}
			else
			{
				pSrc[0] = 0;
				pSrc[1] = 0;
				pSrc[2] = 0;
			}
			pSrc += 4;			
		}
	}
	free(tempData);
	return ret;
};

        接口到这里就写完了,仅仅30行左右,下面我们来写个测试代码:

#include "stdafx.h"
#include"imgRW\f_SF_ImgBase_RW.h"
#include"f_Transform.h"
int _tmain(int argc, _TCHAR* argv[])
{
	//定义输入图像路径
	char* inputImgPath = "Test.png";
	//定义输出图像路径
	char* outputImgPath = "res_offset.jpg";
	//定义图像宽高信息
	int width = 0, height = 0, component = 0, stride = 0;
	//图像读取(得到32位bgra格式图像数据)
	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
	stride = width * 4;
	int ret = 0;
	//其他图像处理操作(这里以32位彩色图像灰度化为例)
	//////////////////////////IMAGE PROCESS/////////////////////////////
	//图像平移
	int xoffset = -100;//图像向右平移
	int yoffset = -100;//图像向左平移
     //调用图像平移变换接口
	ret = f_XYOfffset(bgraData, width, height, stride,xoffset, yoffset);
     //保存平移变换结果图
	ret = Trent_ImgBase_ImageSave(outputImgPath, width, height, bgraData, JPG);
	printf("f_XYOffset is finished!");
////////////////////////////////////////////////////////////////////
	free(bgraData);
}
	return 0;

        可以看到,在这段代码中,我们使用的都是C语言标准库,对于初学者而言,非常方便,通俗易懂。

图像缩放变换

[定义与算法]

        图像缩放即图像缩小与放大,是图像处理中最常用的操作,可以说图像处理离不开图像缩放,好的图像缩放算法可以高清还原图像信息,对于各种复杂的图像应用而言意义重大。

        假设图像中任意一点(x,y),按照水平方向缩放比例a和垂直方向缩放比例b进行缩放,则缩放后点坐标(x’,y’)的计算如下:

                                                                                            x{}'=ax

                                                                                            y{}'=by

        当a和b小于1时,表现为缩小,大于1时表现为图像放大,等于1时不缩放;既然有了放大与缩小,就存在图像信息的删除与填充,如何进行精确计算缩放后的坐标位置?这里引入一个必需要讲解的内容---图像插值算法。

        图像插值算法有很多,从最邻近插值,到二次插值、三次插值、卷积插值等等以及超分辨率算法等高级插值,可以说,图像插值是一门学问,单独成书的也很多很多。这里,我们讲解两种最常用也是最基础的插值算法:最邻近插值和双线性二次插值。

        最邻近插值不明思义就是用距离它最近的点来代替它。如下图Fig.2所示,有A和B两个像素点,A的值为100,B的值为20,要计算AB之间的C点像素值,其中,C点距离A的距离为0.4,距离B的距离为0.6,那么,C点距离A点像素最近,则C=100,这就是最邻近插值。

        最邻近插值计算量最小,但效果较差,往往会出现锯齿问题,即缩放后的图像边缘会出现锯齿毛刺,如图Fig.3所示,左边为原图,右边为最邻近插值放大两倍的结果,可以看到字母的边缘出现了锯齿状,非常不平滑。

 

        双线性二次插值也称为一阶插值,这个算法说复杂也复杂,说简单也简单,如果你要从数学上讲明白,那要从拉格朗日插值多项式说起。

        拉格朗日插值:

        对于给定的n+1个节点,x0,x1,..xn,如果能够找到n+1个多项式l1(x),l2(x),...ln(x),满足如下条件:,

        那么,拉格朗日插值多项式P(x)表示如下:

        其中,l(x)被称作插值基函数。

        上述内容可以参考张铁所著《数值分析》136页部分,上述内容与二次插值有什么关系呢?这里我们一道例题来说明,该例题也来自张铁《数值分析》。

        对于x0和x1,它的一阶拉格朗日插值多项式L(x)求取如上所示,其中l0(x)和l1(x)分别表示对应权重,如果要计算x0到x1之间的任意一点x,那么计算L1(x)即可,看到这里我们明白,如果给定两个点,计算两点之间的任意一点插值,那么,我们只要可以使用上述1阶拉格朗日插值多项式即可,而这个方法就是二次插值的基础。

        对于双线性二次插值,我们假设要计算的插值点(x,y)的值为f(x,y),在它的附近有有f(i,j),f(i+1,j),f(i,j+1)和f(i+1,j+1)四个点,如下图Fig.4所示,f(x,y)的计算方法如下:

        ①我们使用一阶拉格朗日插值计算点(i,j)和(i+1,j)之间的点(x,j)处的插值f(x,j);

        ②我们使用一阶拉格朗日插值计算点(i,j+1)和(i+1,j+1)之间的点(x,j+1)处的插值f(x,j+1);

        ③我们使用一阶拉格朗日插值计算(x,j)和(x,j+1)之间点(x,y)处的插值f(x,y);

        这个公式就是双线性插值公式。我们用这个公式,对比最邻近插值效果,如下图Fig.5所示。

[绘制与代码]

        有了上述算法的解析,下面我们通过C语言来实现,首先定义接口如下:

/*********************************************************
*Function:图像缩放变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          scaleX:水平方向缩放比例,[0,]
*          scaleY:垂直方向缩放比例,[0,]
*          outW:缩放结果图宽度
*          outH:缩放结果图高度
*          outStride:缩放结果图Stride
*          interpolation:插值方式,0-最邻近插值,1-双线性插值
*Return:  缩放图像bgra数据指针
*********************************************************/
unsigned char* f_Zoom(unsigned char* srcData, int width, int height, int stride, float scaleX, float scaleY, int* outW, int* outH, int * outStride, int interpolation);

        我们定义了f_Zoom的接口,这个接口中,由于缩放会改变图像大小,因此,我们返回一个缩放后的图像数据指针,同时,返回缩放后的图像宽高信息outW、outH和outStride;由于缩放包含水平和垂直方向的缩放因子,所以,添加水平缩放参数scaleX和垂直缩放参数scaleY,当scaleX小于1时表示水平缩小,等于1表示水平不缩放,大于1表示水平放大,垂直方向亦如此;最后,由于我们可以使用最邻近插值和双线性插值两种方式进行缩放,因此,添加了interpolation插值参数;

        有了接口,我们给出完整的接口实现代码如下:

/*********************************************************
*Function:图像缩放变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          scaleX:水平方向缩放比例,[0,]
*          scaleY:垂直方向缩放比例,[0,]
*          outW:缩放结果图宽度
*          outH:缩放结果图高度
*          outStride:缩放结果图Stride
*          interpolation:插值方式,0-最邻近插值,1-双线性插值
*Return:  缩放图像bgra数据指针
*********************************************************/
unsigned char* f_Zoom(unsigned char* srcData, int width, int height, int stride, float scaleX, float scaleY, int* outW, int* outH, int * outStride, int interpolation)
{
	int w = width * scaleX;
	int h = height * scaleY;
	int s = w * 4;
	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * s * h);
	memset(tempData, 255, sizeof(unsigned char) * s * h);
	unsigned char* pTemp = tempData;
	//最邻近插值
	if(interpolation == 0)
	{
	    for(int j = 0; j < h; j++)
	    {
	    	for(int i = 0; i < w; i++)
	    	{
	    		int cx = CLIP3(i * width / w, 0, width - 1);
	    		int cy = CLIP3(j * height / h, 0, height - 1);
	    		int pos = cx * 4 + cy * stride;
	    		pTemp[0] = srcData[pos];
	    		pTemp[1] = srcData[pos + 1];
	    		pTemp[2] = srcData[pos + 2];
	    		pTemp[3] = srcData[pos + 3];
	    		pTemp += 4;			
	    	}
	    }
	}
	else//双线性插值
	{
		for(int j = 0; j < h; j++)
	    {
	    	for(int i = 0; i < w; i++)
	    	{
	    		float cx = CLIP3((float)i * width / w, 1, width - 2);
	    		float cy = CLIP3((float)j * height / h, 1, height - 2);
				int tx = (int)cx;
				int ty = (int)cy;
				float p = abs(cx - tx);
				float q = abs(cy - ty);
	    		int pos = tx * 4 + ty * stride;
				int p1 = pos;
				int p2 = pos + stride;
				int p3 = pos + 4;
				int p4 = pos + 4 + stride;
				float a = (1.0f - p) * (1.0f - q);
				float b = (1.0f - p) * q;
				float c = p * (1.0f - q);
				float d = p * q;
	    		pTemp[0] = CLIP3((a * srcData[p1 + 0] + b * srcData[p2 + 0] + c * srcData[p3 + 0] + d * srcData[p4 + 0]), 0, 255);
	    		pTemp[1] = CLIP3((a * srcData[p1 + 1] + b * srcData[p2 + 1] + c * srcData[p3 + 1] + d * srcData[p4 + 1]), 0, 255);
	    		pTemp[2] = CLIP3((a * srcData[p1 + 2] + b * srcData[p2 + 2] + c * srcData[p3 + 2] + d * srcData[p4 + 2]), 0, 255);
	    		pTemp[3] = CLIP3((a * srcData[p1 + 3] + b * srcData[p2 + 3] + c * srcData[p3 + 3] + d * srcData[p4 + 3]), 0, 255);
	    		pTemp += 4;			
	    	}
	    }
	}
	*outW = w;
	*outH = h;
	*outStride = s;
	return tempData;
};

        代码不足60行,通俗易懂,最后给出接口的调用代码:

#include "stdafx.h"
#include"imgRW\f_SF_ImgBase_RW.h"
#include"f_Transform.h"
int _tmain(int argc, _TCHAR* argv[])
{
	//定义输入图像路径
	char* inputImgPath = "t150.png";
	//定义图像宽高信息
	int width = 0, height = 0, component = 0, stride = 0;
	//图像读取(得到32位bgra格式图像数据)
	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
	stride = width * 4;
	int ret = 0;
	//其他图像处理操作(这里以32位彩色图像为例)
	//////////////////////////IMAGE PROCESS/////////////////////////////
	//图像缩放
	int outW, outH, outS;
	float scaleX = 5;
	float scaleY = 5;
	int interpolation = INTERPOLATE_BILINEAR;//INTERPOLATE_NEAREST;
	char* outZoomImgPath = "res_zoom.png";
	unsigned char* pResZoom = f_Zoom(bgraData, width, height, stride, scaleX, scaleY, &outW, &outH, &outS, interpolation);
	ret = Trent_ImgBase_ImageSave(outZoomImgPath, outW, outH, pResZoom, PNG);
	free(pResZoom);
	printf("f_zoom is finished!");
	////////////////////////////////////////////////////////////////////
	free(bgraData);
	return 0;
}

        效果测试如下图Fig.6所示:

图像旋转变换

[定义与算法]

        图像旋转即将图像按照某个原点顺时针或者逆时针旋转某个角度。

        如果平面上所有点(x,y)绕原点O旋转\Theta角度,旋转后的点为(x’,y’),则两者正向和逆向计算公式如下:

        公式的推导我们以正向变换为例,在极坐标系中,假设(x,y)到原点O距离为r,注意,这里O点表示上文平移后的图像中心点,(x,y)和原点的连线与x轴方向的夹角为b,旋转角度为a,旋转后坐标为(x’,y’),如下图Fig.7所示,则按照极坐标公式有:

        上述便是正向推导过程,即由(x,y)到旋转后的点(x’,y’)。

        在实际中,图像平面中的原点一般为左上角,也就是左上角为(0,0)点,垂直方向向下为正方向,与正常的笛卡尔坐标系不同。我们想要的往往是图像围绕图像中心点进行角度旋转,这个时候,我们需要把点(x,y)先转换为以图像中心为原点的坐标,也就是进行一定的坐标平移,然后再进行旋转变换。同时,为例避免孔洞现象,我们一般在计算的过程中,是按照逆向变换,根据目标图像素位置(x’,y’)计算它在原图中的位置(x,y),一次完成旋转变换的。

        假设旋转后图像的宽为W’,高为H’,点(x’,y’)映射到原图中的坐标为(x,y),则计算过程如下:

        ①按照平移逆变换将(x’,y’)进行平移:

        ②按照旋转逆变换公式将(x,y)进行变换:

        ③将坐标原点由图像中心平移至左上角:

        ④根据(x,y)位置选择插值算法插值得到最终旋转后的像素值;

        上面的过程就是完整的图像旋转变换,下面我们将动手实践一下。

[绘制与代码]

        我们首先定义一个图像旋转的接口,如下:

/*********************************************************
*Function:图像旋转变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          angle:图像旋转角度
*          outW:旋转结果图宽度
*          outH:旋转结果图高度
*          outStride:旋转结果图Stride
*          interpolation:插值方式,0-最邻近插值,1-双线性插值
*Return:  旋转图像bgra数据指针
*********************************************************/
unsigned char* f_Rotate(unsigned char* srcData, int width, int height, int stride, int angle, int* outW, int* outH, int* outStride, int interpolation);

        图像旋转变换中,图像的大小发生了变化,因此这里我们的接口返回一个变换后的图像数据指针,与缩放接口类似,添加新图像宽高输出参数outW,outH和outStride;由于旋转变换需要角度信息,因此这里添加了角度输入参数angle,范围为0到360度,同时,设置插值算法参数interpolation;

        按照前文的旋转变换算法公式,我们给出C代码如下:

/*********************************************************
*Function:图像旋转变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          angle:图像旋转角度
*          outW:旋转结果图宽度
*          outH:旋转结果图高度
*          outStride:旋转结果图Stride
*          interpolation:插值方式,0-最邻近插值,1-双线性插值
*Return:  旋转图像bgra数据指针
*********************************************************/
unsigned char* f_Rotate(unsigned char* srcData, int width, int height, int stride, int angle, int* outW, int* outH, int* outStride, int interpolation)
{
	float degree = angle * PI / 180.0f;
	float cx = 0, cy = 0, Cos = 0, Sin = 0;
	Cos = cos(degree);
	Sin = sin(degree);
	//计算新图像的宽高
    int w = width * Cos + height * Sin;
	int h = height * Cos + width * Sin;
	int s = w * 4;
	*outW = w;
	*outH = h;
	*outStride = s;
	//常量计算,用来优化速度
	cx = -w / 2.0f * Cos - h / 2.0f * Sin + width / 2.0f;
	cy = w / 2.0f * Sin - h / 2.0f * Cos + height / 2.0f;
	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * s * h);
	memset(tempData, 255, sizeof(unsigned char) * s * h);
	unsigned char* pTemp = tempData;
	//最邻近插值
	if(interpolation == 0)
	{
	    for(int j = 0; j < h; j++)
	    {
	    	for(int i = 0; i < w; i++)
	    	{
				//这里实际上就是按照公式计算,进行了优化,把一些常量计算放到了外面的cx和cy中
	    		int tx = i * Cos + j * Sin + cx;
	    		int ty = j * Cos - i * Sin + cy;
				if(tx >= 0 && tx < width && ty >= 0 && ty < height)
				{
	    		    int pos = tx * 4 + ty * stride;
	    		    pTemp[0] = srcData[pos];
	    		    pTemp[1] = srcData[pos + 1];
	    		    pTemp[2] = srcData[pos + 2];
	    		    pTemp[3] = srcData[pos + 3];
				}
				else
				{
					pTemp[0] = 0;
	    		    pTemp[1] = 0;
	    		    pTemp[2] = 0;
	    		    pTemp[3] = 255;
				}
	    		pTemp += 4;			
	    	}
	    }
	}
	else//双线性插值
	{
		for(int j = 0; j < h; j++)
	    {
	    	for(int i = 0; i < w; i++)
	    	{
				//这里实际上就是按照公式计算,进行了优化,把一些常量计算放到了外面的cx和cy中
                float mx = i * Cos + j * Sin + cx;
	    		float my = j * Cos - i * Sin + cy;
				if(mx >= 0 && mx < width && my >= 0 && my < height)
				{
				    int tx = (int)mx;
				    int ty = (int)my;
				    float p = abs(mx - tx);
				    float q = abs(my - ty);
	    		    int pos = tx * 4 + ty * stride;
				    int p1 = pos;
				    int p2 = pos + stride;
				    int p3 = pos + 4;
				    int p4 = pos + 4 + stride;
				    float a = (1.0f - p) * (1.0f - q);
				    float b = (1.0f - p) * q;
				    float c = p * (1.0f - q);
				    float d = p * q;
	    		    pTemp[0] = CLIP3((a * srcData[p1 + 0] + b * srcData[p2 + 0] + c * srcData[p3 + 0] + d * srcData[p4 + 0]), 0, 255);
	    		    pTemp[1] = CLIP3((a * srcData[p1 + 1] + b * srcData[p2 + 1] + c * srcData[p3 + 1] + d * srcData[p4 + 1]), 0, 255);
	    		    pTemp[2] = CLIP3((a * srcData[p1 + 2] + b * srcData[p2 + 2] + c * srcData[p3 + 2] + d * srcData[p4 + 2]), 0, 255);
	    		    pTemp[3] = CLIP3((a * srcData[p1 + 3] + b * srcData[p2 + 3] + c * srcData[p3 + 3] + d * srcData[p4 + 3]), 0, 255);
				}
				else
				{
					pTemp[0] = 0;
	    		    pTemp[1] = 0;
	    		    pTemp[2] = 0;
	    		    pTemp[3] = 255;
				}
				pTemp += 4;
	    	}
	    }
	}
	return tempData;
};

        本文的代码可以看到,基本都是将插值的interpolation条件判断放到了循环外面,导致代码段较长,实际上这样做是为了优化速度,增强代码可读性,大家可以体会一下。

        我们对上面接口进行调用测试如下:

#include "stdafx.h"
#include"imgRW\f_SF_ImgBase_RW.h"
#include"f_Transform.h"
int _tmain(int argc, _TCHAR* argv[])
{
	//定义输入图像路径
	char* inputImgPath = "Test.png";
	//定义图像宽高信息
	int width = 0, height = 0, component = 0, stride = 0;
	//图像读取(得到32位bgra格式图像数据)
	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
	stride = width * 4;
	int ret = 0;
	//其他图像处理操作(这里以32位彩色图像为例)
	//////////////////////////IMAGE PROCESS/////////////////////////////
	//图像旋转
	int outW, outH, outS;
	int angle = 80;
	int interpolation = INTERPOLATE_NEAREST;//INTERPOLATE_BILINEAR;//INTERPOLATE_NEAREST;
	char* outRotateImgPath = "res_rotate_nearest.jpg";
	unsigned char* pResRotate = f_Rotate(bgraData, width, height, stride, angle, &outW, &outH, &outS, interpolation);
	ret = Trent_ImgBase_ImageSave(outRotateImgPath, outW, outH, pResRotate, JPG);
	free(pResRotate);
	printf("f_Rotate is finished!");
	////////////////////////////////////////////////////////////////////
	free(bgraData);
	return 0;
}

        大家可以看到,调用非常方便,甚至比opencv更加通俗易懂。最后我们给出对应的测试效果,如图Fig.8所示。

        本节完整的代码工程关注本人公众号“SF图像算法”有相关下载链接即可免费下载。

[知识扩展]

        在本文中,我们详细了解了图像平移、图像缩放和图像旋转三种图像几何变换,也是最常用的图像基本变换。在这个过程中我们是单个一一讲解的,为的是让新手同学们能够各个击破,单独理解。而实际中,这三个变换可以通过一个仿射变换矩阵来统一表达,放这边还矩阵如下:

        其中,s表示缩放比例,tx和ty表示平移量,theta表示角度;

        按照这个公式,我们可以一次计算出三种变换后对应的结果(x’,y’),然后再进行插值计算即可,大家可以自己尝试一下;

        对于图像变形和插值算法,多少年来,有无数相关的论文研究,近几年来,随着深度学习的飞速发展,基于卷积神经网络的超分辨率,图像复原以及图像变形的算法也是层出不穷,可见其意义之大!

        最后,学海无涯,共勉!

 

 

2019-04-26 21:26:00 zaishuiyifangxym 阅读数 2080

目录

1 图像缩放- resize()

2 图像旋转- getRotationMatrix2D(), warpAffine()

3 图像翻转- flip()

4 图像平移- warpAffine()

参考资料


图像几何变换有图像缩放、图像旋转、图像翻转和图像平移等。

 

1 图像缩放- resize()

图像缩放主要调用 resize() 函数实现,具体如下:

result = cv2.resize(src, dsize[, result[. fx[, fy[, interpolation]]]])

其中,参数

src 表示原始图像;

dsize 表示缩放大小;

fx和fy 也可以表示缩放大小倍数,他们两个(dsize或fx/fy)设置一个即可实现图像缩放。例如:

(1)result = cv2.resize(src, (160,160))

(2)result = cv2.resize(src, None, fx=0.5, fy=0.5)

图像缩放:设({x_0},{y_0})是缩放后的坐标,(x,y)是缩放前的坐标,{s_x} 和 {s_y} 为缩放因子,则公式如下:

                                                                       [{x_0}\text{ }{\text{ }}{y_0}\text{ }{\text{ }}1] = [x\text{ }{\text{ }}y\text{ }{\text{ }}1]{\text{ }}\left[ \begin{gathered} {s_x}\text{ }{\text{ }}0\text{ }\text{ }{\text{ 0}} \hfill \\ 0{\text{ }}\text{ }\text{ }{s_y}\text{ }{\text{ 0}} \hfill \\ {\text{0 }}\text{ }\text{ }0\text{ }\text{ }{\text{ 1}} \hfill \\ \end{gathered} \right]

 

(1) cv2.resize(src, (200,100)) 设置的dsize是列数为200,行数为100

result = cv2.resize(src, (200,100))

代码如下:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)

# 图像缩放
result = cv2.resize(src, (200,100))
print (result.shape)

# 显示图像
cv2.imshow("src", src)
cv2.imshow("result", result)

# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

 

运行结果如下图所示:

 

(2)可以获取 原始图像像素\times乘以缩放系数 进行图像变换;

result = cv2.resize(src, (int(cols * 0.6), int(rows * 1.2)))

 

代码如下所示:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
rows, cols = src.shape[:2]
print
rows, cols

# 图像缩放 dsize(列,行)
result = cv2.resize(src, (int(cols * 0.6), int(rows * 1.2)))

# 显示图像
cv2.imshow("src", src)
cv2.imshow("result", result)

# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

 

运行结果如下图所示:

 

(3)(fx,fy) 缩放倍数的方法对图像进行放大或缩小。

result = cv2.resize(src, None, fx=0.3, fy=0.3)

 

代码如下所示:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src =cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
rows, cols = src.shape[:2]
print
rows, cols

# 图像缩放
result = cv2.resize(src, None, fx=0.3, fy=0.3)

# 显示图像
cv2.imshow("src", src)
cv2.imshow("result", result)

# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

 

运行结果如下图所示:(按例比 0.3 \times 0.3 缩小)


 

2 图像旋转- getRotationMatrix2D(), warpAffine()

图像旋转:设 ({x_0},{y_0}) 是旋转后的坐标,({x},{y}) 是旋转前的坐标,({ m},{n}) 是旋转中心,a 是旋转的角度,({ left},{top}) 是旋转后图像的左上角坐标,则公式如下:

                                     [{x_0}\text{ }{\text{ }}{y_0}\text{ }{\text{ }}1] = [x\text{ }{\text{ }}y\text{ }{\text{ }}1]{\text{ }}\left[ \begin{gathered} \text{ }\text{ } 1\text{ }\text{ }\text{ }{\text{ }}0\text{ }\text{ }{\text{ 0}} \hfill \\ \text{ }\text{ }0{\text{ }} - 1\text{ }{\text{ 0}} \hfill \\ {\text{ - }}m\text{ }{\text{ }}n\text{ }{\text{ 1}} \hfill \\ \end{gathered} \right]{\text{ }}\left[ \begin{gathered} \cos a{\text{ }} - \sin a{\text{ }}0 \hfill \\ \sin a\text{ }\text{ }{\text{ }}\cos a\text{ }{\text{ }}0 \hfill \\ \text{ }\text{ }{\text{ }}0{\text{ }}\text{ }\text{ }\text{ }\text{ }\text{ }\text{ }0\text{ }\text{ }\text{ }{\text{ }}1 \hfill \\ \end{gathered} \right]{\text{ }}\left[ \begin{gathered} \text{ }\text{ }1\text{ }\text{ }\text{ }\text{ }{\text{ }}0\text{ }\text{ }{\text{ }}0 \hfill \\ \text{ }\text{ }0{\text{ }} \text{ }- 1\text{ }{\text{ }}0 \hfill \\ left\text{ }{\text{ }}top{\text{ }}\text{ }1 \hfill \\ \end{gathered} \right]

 

图像旋转主要调用getRotationMatrix2D() 函数和 warpAffine() 函数实现,绕图像的中心旋转,具体如下:

M = cv2.getRotationMatrix2D((cols/2, rows/2), 30, 1)

其中,参数分别为:旋转中心、旋转度数、scale

rotated = cv2.warpAffine(src, M, (cols, rows))

其中,参数分别为:原始图像、旋转参数 和 原始图像宽高

 

(1)旋转30度

代码如下:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src =  cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)

# 原图的高、宽 以及通道数
rows, cols, channel = src.shape

# 绕图像的中心旋转
# 参数:旋转中心 旋转度数 scale
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 30, 1)
# 参数:原始图像 旋转参数 元素图像宽高
rotated = cv2.warpAffine(src, M, (cols, rows))

# 显示图像
cv2.imshow("src", src)
cv2.imshow("rotated", rotated)

 

运行结果如下图所示:

 

(2)旋转90度

代码如下:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src =  cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)

# 原图的高、宽 以及通道数
rows, cols, channel = src.shape

# 绕图像的中心旋转
# 参数:旋转中心 旋转度数 scale
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 90, 1)
# 参数:原始图像 旋转参数 元素图像宽高
rotated = cv2.warpAffine(src, M, (cols, rows))

# 显示图像
cv2.imshow("src", src)
cv2.imshow("rotated", rotated)

 

运行结果如下图所示:

 

(3)旋转180度

代码如下:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src =  cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)

# 原图的高、宽 以及通道数
rows, cols, channel = src.shape

# 绕图像的中心旋转
# 参数:旋转中心 旋转度数 scale
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 180, 1)
# 参数:原始图像 旋转参数 元素图像宽高
rotated = cv2.warpAffine(src, M, (cols, rows))

# 显示图像
cv2.imshow("src", src)
cv2.imshow("rotated", rotated)

 

运行结果如下图所示:


 

3 图像翻转- flip()

图像翻转在OpenCV中调用函数 flip() 实现,函数用法如下:

dst = cv2.flip(src, flipCode)

其中,参数:

src 表示原始图像;

flipCode 表示翻转方向,如果flipCode为0,则以X轴为对称轴翻转,如果fliipCode>0则以Y轴为对称轴翻转,如果flipCode<0则在X轴、Y轴方向同时翻转。

 

代码如下:(注意一个窗口多张图像的用法

# encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图片
img = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
src = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 图像翻转
# 0以X轴为对称轴翻转 >0以Y轴为对称轴翻转 <0X轴Y轴翻转
img1 = cv2.flip(src, 0)
img2 = cv2.flip(src, 1)
img3 = cv2.flip(src, -1)

# 显示图形 (注意一个窗口多张图像的用法)
titles = ['Source', 'Image1', 'Image2', 'Image3']
images = [src, img1, img2, img3]
for i in range(4):
    plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

 

运行结果如下图所示:(注意一个窗口多张图像的用法


 

4 图像平移- warpAffine()

图像平移:设 ({x_0},{y_0}) 是缩放后的坐标,({x},{y}) 是缩放前的坐标,{d_x},{d_y} 为偏移量,则公式如下:

                                                             [{x_0}\text{ }{\text{ }}{y_0}\text{ }{\text{ }}1] = [x\text{ }{\text{ }}\text{ }y{\text{ }}\text{ }1]{\text{ }}\left[ \begin{gathered} \text{ }1{\text{ }}\text{ }\text{ }0\text{ }\text{ }{\text{ }}0 \hfill \\ \text{ }0\text{ }\text{ }{\text{ }}1\text{ }\text{ }{\text{ }}0 \hfill \\ {d_x}{\text{ }}\text{ }{d_y}{\text{ }}\text{ }1 \hfill \\ \end{gathered} \right]

 

图像平移首先定义平移矩阵M,再调用 warpAffine() 函数实现平移,函数用法如下:

M = np.float32([[1, 0, x], [0, 1, y]])

shifted = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

 

代码如下:

# encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图片
img = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 图像平移 下、上、右、左平移
M = np.float32([[1, 0, 0], [0, 1, 100]])
img1 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

M = np.float32([[1, 0, 0], [0, 1, -100]])
img2 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

M = np.float32([[1, 0, 100], [0, 1, 0]])
img3 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

M = np.float32([[1, 0, -100], [0, 1, 0]])
img4 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

# 显示图形
titles = ['Image1', 'Image2', 'Image3', 'Image4']
images = [img1, img2, img3, img4]
for i in range(4):
    plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

 

运行结果如下图所示:


 

参考资料

[1] https://blog.csdn.net/Eastmount/article/details/82454335

[2] Python+OpenCV图像处理

图像平移变换

阅读数 2844