数字图像处理(五)几何变换之图像平移、镜像、绕中心点旋转、缩放等

本文为参考这位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;

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

那么有以下结论:

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。
[cpp]  view plain  copy
  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. }  

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


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

猜你喜欢

转载自blog.csdn.net/hjxu2016/article/details/80614147