No2. 图像几何变换


主要内容

  1. 完成图像旋转、平移、比例缩放。
  2. 设计实现用户UI交互方式下的图像几何变换。
  3. 实现图像的透视畸变矫正。

一、图像平移

平移操作是将图像的所有像素坐标经行水平或垂直方向移动,平移变换分为两种类型:图像大小变化和图像大小不变。前者图像的信息不会丢失,后者可能丢失部分图像。
在这里插入图片描述
因此可以得到变换逆矩阵为:
在这里插入图片描述
示例代码如下:

1. 平移,图像大小不变

cv::Mat imageTranslation1(cv::Mat& srcImage, int xOffset, int yOffset) {
	     int nRows = srcImage.rows;
	     int nCols = srcImage.cols;
	     cv::Mat resultImage(srcImage.size(), srcImage.type());
	     //遍历图像
	     for (int i = 0; i < nRows; ++i) {
		 for (int j = 0; j < nCols; ++j) {
			//映射变换
			int x = j - xOffset;
			int y = i - yOffset;
			//边界判断
			if (x >= 0 && y >= 0 && x < nCols && y < nRows)
				resultImage.at< cv::Vec3b >(i, j) = srcImage.ptr<cv::Vec3b>(y)[x];
		    }
	     }
	    return resultImage;
}

2. 平移,图像大小改变

cv::Mat imageTranslation2(cv::Mat & srcImage, int xOffset, int yOffset) {
//设置平移尺寸
int nRows = srcImage.rows + abs(yOffset);
int nCols = srcImage.cols + abs(xOffset);
cv::Mat resultImage(nRows, nCols, srcImage.type());
//图像遍历
for (int i = 0; i < nRows; ++i) {
	for (int j = 0; j < nCols; ++j) {
		//映射变换
		int x = j - xOffset;
		int y = i - yOffset;
		//边界判断
		if (x >= 0 && y >= 0 && x <= nCols && y < nRows)
			resultImage.at<cv::Vec3b>(i, j) = srcImage.ptr<cv::Vec3b>(y)[x];
	}	
}
return resultImage;
}

二、图像旋转

图像旋转是指图像按照某个位置转动一定角度的过程,旋转中心图像任保持着原始尺寸。设图像旋转中心坐标为(Cx,Cy),源图像数据坐标为(x,y) ,旋转后目标图像坐标为(x’,y’) ,先将坐标原点由图像左上角变换到旋转中心,坐标转换矩阵形式为:
在这里插入图片描述
将其转换为图像矩阵表达式:
在这里插入图片描述
设旋转后图像的最左边点的横坐标,最右边点的横坐标,最高点的纵坐标,最低点的纵坐标非别为:lxmin,lxmax,lymin,lymax
最后需要将坐标原点变换到旋转后的图像的左上角坐标,转换矩阵表示如下:
在这里插入图片描述
示例代码如下:

cv::Mat angleRotate(cv::Mat& src, int angle) {
	//角度转换
	float alpha = angle * CV_PI / 180;
	//构造旋转矩阵
	float rotateMat[3][3] = {
		{cos(alpha),-sin(alpha),0},
		{sin(alpha),cos(alpha),0},
		{0,0,1} };
	int nSrcRows = src.rows;
	int nSrcCols = src.cols;
	//计算旋转后图像矩阵各个顶点的位置
	float a1 = nSrcCols * rotateMat[0][0];
	float b1 = nSrcCols * rotateMat[1][0];
	float a2 = nSrcCols * rotateMat[0][0] + nSrcRows * rotateMat[0][1];
	float b2 = nSrcCols * rotateMat[1][0] + nSrcRows * rotateMat[1][1];
	float a3 = nSrcRows * rotateMat[0][1];
	float b3 = nSrcRows * rotateMat[1][1];
	//计算出极值点
	float  kxMin = min(min(min(0.0f, a1), a2), a3);	
	float  kxMax = max(max(max(0.0f, a1), a2), a3);
	float  kyMin = min(min(min(0.0f, b1), b2), b3);
	float  kyMax = max(max(max(0.0f, b1), b2), b3);
	//计算输出矩阵的尺寸
	int nRows = abs(kxMax - kxMin);
	int nCols = abs(kyMax - kyMin);
	cv::Mat dst(nRows, nCols, src.type(), cv::Scalar::all(0));
	for (int i = 0; i < nRows; ++i)
	{
		for (int j = 0; j < nCols; ++j) 
		{
			//旋转坐标转换
			int x = (j + kxMin) * rotateMat[0][0] - (i + kyMin) * rotateMat[0][1];
			int y = -(j + kxMin) * rotateMat[1][0] + (i + kyMin) * rotateMat[1][1];
			//区域旋转
			if ((x >= 0) && (x < nSrcCols) && (y >= 0) && (y < nSrcCols) && (y >= 0) && (y < nSrcRows))
			{
				dst.at<cv::Vec3b>(i, j) = src.at<cv::Vec3b>(y, x);
			}
		}
	}
	return dst;
}

三、图像缩放

图像缩放是将图像的尺寸变小或者变大的过程,也就是减少或增加源图像数据像素的个数。对于源图像数据 f(x,y) ,分辨率为M×N,如果将其分辨率改为m×n,则比例因子为:k=M/N
则将 f(x, y) 变换成 g(x’, y’) 的矩阵变换关系如下:
在这里插入图片描述
示例代码如下:

//基于等间隔提取图像缩放
cv::Mat imageReduction(cv::Mat &srcImage, float kx, float ky) {
	//获取输出图像分辨率
	int nRows = cvRound(srcImage.rows * kx);
	int nCols = cvRound(srcImage.cols * ky);
	cv::Mat resultImage(nRows, nCols, srcImage.type());
	for (int i = 0; i < nRows; ++i) {
		for (int j = 0; j < nCols; ++j) {
			//根据水平因子计算坐标
			int x = static_cast<int>((i + 1) / kx + 0.5) - 1;
			//更具垂直因子计算坐标
			int y = static_cast<int>((j + 1) / ky + 0.5) - 1;
			resultImage.at<cv::Vec3b>(i, j) = srcImage.at<cv::Vec3b>(x, y);
		}
	}
	return resultImage;
}

四、用户UI交互设计

UI设计主要依靠MFC来实现,是基于对话框设计的程序。界面如下图所示:
在这里插入图片描述
图像一显示的原图片,图像二显示的是处理后的图像。
在MFC中,为了实现图像的显示,采用了CImage类的方式,显示图片是通过函数ShowImg()实现,其具体代码如下:

void  CimageProcessDlg::ShowImg(cv::Mat mat, UINT ID)
{//显示mat在picture控件中 
	Mat temp;
	//图片基本信息
	int width = mat.cols;
	int height = mat.rows;
	int channels = mat.channels();
	int depth = mat.depth();
	int dims = mat.dims;
	CWnd* m_pMyWnd = GetDlgItem(ID);
	CDC *m_pDC = m_pMyWnd->GetDC();//获取窗口所拥有的设备上下文,用于显示图像

	CLearwindow(ID);
	m_pMyWnd->UpdateWindow();
	ATL::CImage cImage;
	cImage.Destroy();//这一步是防止重复利用造成内存问题
	cImage.Create(width, height, 8 * channels);
	// 如果是1个通道的图像(灰度图像) DIB格式才需要对调色板设置  。 CImage中内置了调色板,我们要对他进行赋值:
	if (1 == channels)
	{
		RGBQUAD* ColorTable;
		int MaxColors = 256;
		//这里可以通过CI.GetMaxColorTableEntries()得到大小(如果你是CI.Load读入图像的话)  
		ColorTable = new RGBQUAD[MaxColors];
		cImage.GetColorTable(0, MaxColors, ColorTable);//这里是取得指针  
		for (int i = 0; i < MaxColors; i++)
		{
			ColorTable[i].rgbBlue = (BYTE)i;
			ColorTable[i].rgbGreen = (BYTE)i;
			ColorTable[i].rgbRed = (BYTE)i;
		}
		cImage.SetColorTable(0, MaxColors, ColorTable);
		delete[]ColorTable;
	}

	if (depth == 0) {//CV_8U
		mat.copyTo(temp);
	}
	else if (depth == 1) {//CV_8S
		mat.convertTo(temp, CV_8U, 255.0, 0);
	}
	else if (depth == 2) {//CV_16U
		mat.convertTo(temp, CV_8U, 255.0, 0);
	}
	else if (depth == 3) {//CV_16S
		mat.convertTo(temp, CV_8U, 255.0, 0);
	}
	else if (depth == 4) {//CV_32S
		mat.convertTo(temp, CV_8U, 255.0, 0);
	}
	else if (depth == 5) {// CV_32F
		mat.convertTo(temp, CV_8U, 255.0, 0);
	}
	else if (depth == 6) {//CV_64F
		mat.convertTo(temp, CV_8U, 255.0, 0);
	}
	else if (depth == 7) {
		AfxMessageBox(L"Format Error    depth==7    Type = CV_USRTYPE1");
		return;
	}
	else {
		AfxMessageBox(L"Format Error. Type = Unknown");
		return;
	}
	uchar* ps;
	uchar* pimg = (uchar*)cImage.GetBits(); //获取CImage的像素存贮区的指针
	int step = cImage.GetPitch();//每行的字节数,注意这个返回值有正有负
	for (int i = 0; i < height; i++)
	{
		ps = temp.ptr<uchar>(i);
		for (int j = 0; j < width; j++)
		{
			if (1 == channels)
			{
				*(pimg + i * step + j) = ps[j];
			}
			else if (3 == channels)
			{
				*(pimg + i * step + j * 3) = ps[j * 3];
				*(pimg + i * step + j * 3 + 1) = ps[j * 3 + 1];
				*(pimg + i * step + j * 3 + 2) = ps[j * 3 + 2];
			}
		}
	}
	CRect rc;
	m_pMyWnd->GetWindowRect(&rc);
	/*InvalidateRect(m_pMyWnd->m_hWnd,&rc,true);*/
	int nwidth = rc.Width();
	int nheight = rc.Height();

	int fixed_width = min(cImage.GetWidth(), nwidth);
	int fixed_height = min(cImage.GetHeight(), nheight);

	double ratio_w = fixed_width / (double)cImage.GetWidth();
	double ratio_h = fixed_height / (double)cImage.GetHeight();
	double ratio = min(ratio_w, ratio_h);int show_width = (int)(cImage.GetWidth() * ratio);
	int show_height = (int)(cImage.GetHeight() * ratio);

	int offsetx = (nwidth - show_width) / 2;
	int offsety = (nheight - show_height) / 2;

	::SetStretchBltMode(m_pDC->GetSafeHdc(), COLORONCOLOR);//设置位图的伸缩模式
	cImage.StretchBlt(m_pDC->GetSafeHdc(), offsetx, offsety, show_width, show_height,
		0, 0, cImage.GetWidth(), cImage.GetHeight(), SRCCOPY);
}

其余平移,旋转,缩放功能的代码和上述示例近似。

五、图像的透视畸变矫正

在图像处理中,因为镜头角度等原因,容易导致图像出现倾斜、变形等情况,为了方便后续处理,常常需要进行图像矫正。
在opencv中,提供了一种可行的方法,即使用cv::perspectiveTransform()函数。输入原始图像和变换之后的图像的4组对应点,便可以得到变换矩阵。通过设置鼠标事件回调函数,来获取对应的四个点4个点。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;
const int N = 400;
const int M = 220;
Mat img;
Point p[5];
int flag = 1;
int cnt = 0;
static void mouse_callback(int event, int x, int y, int, void *) {
	//当鼠标左键按下时,记录其坐标
	if (event == EVENT_LBUTTONDOWN) {
		p[cnt].x = x * 1.0; p[cnt++].y = y * 1.0;
		cout << "p" << cnt << " is recorded at " << p[cnt - 1] << endl;
	}

	if (cnt == 4) {
		cnt = 0;
		//变换前图像四个点
		vector<Point2f>src(4);
		src[0] = p[0];
		src[1] = p[1];
		src[2] = p[2];
		src[3] = p[3];
		//变换后
		vector<Point2f>dst(4);
		dst[0] = Point2f(0, 0);//左上
		dst[1] = Point2f(N, 0);//右上
		dst[2] = Point2f(0, M);//左下
		dst[3] = Point2f(N, M);//右下
		//获取透视变换矩阵

		Mat m = getPerspectiveTransform(src, dst);
		Mat res;

		warpPerspective(img, res, m, Size(N, M), INTER_LINEAR);//实现图像透视变换
		namedWindow("PIC2", 1);
		imshow("PIC2", res);
		waitKey(0);
	}

}
int main() {
	img = imread("sample.jpg");
	if (!img.data) { cout << "read image file wrong!" << endl; getchar(); return 0; }
	cout << "height = " << img.size().height << ",width = " << img.size().width << endl;
	getchar();

	namedWindow("PIC");
	imshow("PIC", img);

	setMouseCallback("PIC", mouse_callback);//设置鼠标事件回调函数

	while (char(waitKey(1)) != 'q') {}

	return 0;
}

六、结果分析

1. 图像平移结果截图及分析

在主函数中分别调用平移处理函数,代码如下:

//图像左移不改变大小
	cv::Mat resultImage1 = imageTranslation1(srcImage, 50, 80);
	cv::imshow("resultImage1", resultImage1);
	//图像左移改变大小
	cv::Mat resultImage2 = imageTranslation2(srcImage, 50, 80);
	cv::imshow("resultImage2", resultImage2);
	//图像右移不改变大小
	cv::Mat resultImage3 = imageTranslation1(srcImage, -50, -80);

结果如下图所示:

图像左移不改变大小
在这里插入图片描述
图像左移改变大小
在这里插入图片描述

图像右移不改变大小
在这里插入图片描述
平移操作是将图像的所有像素坐标经行水平或垂直方向移动,平移变换分为两种类型:图像大小变化和图像大小不变。前者图像的信息不会丢失,后者可能丢失部分图像。

2. 图像旋转截图及分析

在主函数中调用旋转函数,代码如下:

	int angle = 30;
	cv::Mat resultImage = angleRotate(srcImage, angle);
	imshow("resultImage", resultImage);

结果如下图所示:

图像顺时针旋转30°
在这里插入图片描述
图像旋转是指图像按照某个位置转动一定角度的过程,旋转中心图像任保持着原始尺寸。但旋转过后要相应改变显示框的尺寸,避免使图像信息丢失。

3. 图像缩放截图及分析

在主函数中调用缩放函数,代码如下:

cv::Mat resultImage4 = imageReduction(srcImage, 0.5, 0.5);
	cv::imshow("resultImage4", resultImage4);

结果如下图所示:

图像纵横变为原来的0.5倍
在这里插入图片描述
图像缩放是将图像的尺寸变小或者变大的过程,也就是减少或增加源图像数据像素的个数。缩放在一定程度上会造成信息的丢失,在此过程中如何进行图像插值和采样是一个非常重要的问题。

4. 人机交互设计截图及分析

在MFC界面,分别带年纪button按钮,可以得到不同的处理结果,如下图所示:
平移处理结果图
在这里插入图片描述
旋转处理结果图
在这里插入图片描述
缩放处理结果图
在这里插入图片描述
该程序还有很多方面需要进一步完善,可以加入自定义位移量,旋转角度,缩放比例的功能,使界面交互更加的人性化。

5. 透视畸变矫正截图及分析

在控制台运行程序,在先前存入的图片下选择四个点作为矫正的依据,如图所示,四个的坐标分别为[152,143],[417,142],[93,311],[451,344]。这些坐标通过鼠标事件来获得,在通过调用opencv处理畸变的函数来实现。处理结果如窗口PIC2所示
在这里插入图片描述

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值