欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Opencv——几何空间变换(仿射变换和投影变换)

程序员文章站 2022-03-25 17:26:37
...

【1】几何变换(空间变换)简述

图像的几何变换,又称空间变换,是图形处理的一个方面,是各种图形处理算法的基础。它将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置,其实质是改变像素的空间位置,估算新空间位置上的像素值。
几何变换算法一般包括空间变换运算和插值算法。
Opencv——几何空间变换(仿射变换和投影变换)

【2】变换矩阵知识简述

齐次坐标的概念

图像一般是二维的,坐标形式为(x,y)。
这里我们将其扩展为3维形式的齐次坐标。形式如下:
Opencv——几何空间变换(仿射变换和投影变换)
第三个参数是尺度参数,控制尺度缩放。(1的时候表示尺度不变)
齐次坐标使用n+1维,来表示n维的坐标。它的优点如下所示:

●统一坐标的加法运算和乘法运算, 运算时提高效率。
●表示无穷远的点。 当z=0的时候,表示无穷远的点。
( x,y,z) ----->( x/z, y/z) ;齐次坐标和二维坐标的换算
如,(2,2,1),(4,4,2 )表示同样的点。

几何运算矩阵

Opencv——几何空间变换(仿射变换和投影变换)
最左边是变换后的齐次坐标,中间的是原图点的其次坐标,最右边是变换矩阵,有9个参数,分为4个子矩阵,每个子矩阵具有特殊意义。
T1:比例、旋转、对称、错切
T2:平移
T3:投影
T4:整体缩放(通常我们通过T1实现缩放,所以这里通常为1)
所谓的仿射变换其实就是通过T1、T2进行变换。
所谓的投影变换就是在仿射变换上多用到了T3。
这里我们忽略T4。

【3】图像的仿射变换

为了能够直观地了解参数对于变换的各种影响,我编写了一个程序,通过滑动条来控制参数,同时显示参数改变后的图像。
这里的参数我都是设的正的,你把滑动条从正最大移到0就相当于是逆操作了。
代码如下:

#include <opencv2/opencv.hpp>
#include <iostream>
#include "windows.h"
#include <stdio.h>
#define WINDOW_NAME "【程序窗口】"			//为窗口标题定义的宏

using namespace cv;
using namespace std;

//*--------------------------【全局变量声明】-------------------------------------*/

//*--------------------------【T1】-------------------------------------*/
int g_nValueA = 100;
int g_nValueB = 0;
int g_nValueC = 0;
int g_nValueD = 100;
//*--------------------------【T2】-------------------------------------*/
int g_nValueL = 50;
int g_nValueM = 50;
//*--------------------------【T3】-------------------------------------*/
int g_nValueP = 0;
int g_nValueQ = 0;
//*--------------------------【T4】-------------------------------------*/
int I_max = 400;
int g_nValueS = 100;
int theta = 0;
int change_switch = 0;
int center_x = I_max / 2;
int center_y = I_max / 2;
Mat g_srcImage,g_dstImage;

void on_change(int, void*);	//回调函数

int main()
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN);		//字体为绿色
	//原图,仿射变换后的图,旋转变换后的图
	g_srcImage = Mat::zeros(I_max, I_max, CV_8UC1);
	g_dstImage = Mat::zeros(I_max, I_max, CV_8UC1);
	for (int i = I_max/2;i < I_max/2+50;i++)	//行循环
	{
		for (int j = I_max / 2;j < I_max / 2 + 50;j++)	//列循环
		{
			//-------【开始处理每个像素】---------------
			g_srcImage.at<uchar>(i, j) = 255;
			//-------【处理结束】---------------
		}
	}
	namedWindow(WINDOW_NAME, WINDOW_NORMAL);//WINDOW_NORMAL允许用户*伸缩窗口
	imshow("原图", g_srcImage);
	//【4】创建滑动条来控制阈值
	createTrackbar("a", WINDOW_NAME, &g_nValueA,150, on_change);
	createTrackbar("b", WINDOW_NAME, &g_nValueB, 150, on_change);
	createTrackbar("c", WINDOW_NAME, &g_nValueC, 150, on_change);
	createTrackbar("d", WINDOW_NAME, &g_nValueD, 150, on_change);

	createTrackbar("l", WINDOW_NAME, &g_nValueL, 150, on_change);
	createTrackbar("m", WINDOW_NAME, &g_nValueM, 150, on_change);

	createTrackbar("p", WINDOW_NAME, &g_nValueP, 150, on_change);
	createTrackbar("q", WINDOW_NAME, &g_nValueQ, 150, on_change);

	createTrackbar("s", WINDOW_NAME, &g_nValueS, 150, on_change);
	createTrackbar("角度", WINDOW_NAME, &theta, 360, on_change);
	createTrackbar("switch", WINDOW_NAME, &change_switch, 1, on_change);
	on_change(0,0);	//初始化回调函数
	//【7】轮询等待用户按键,如果ESC键按下则退出程序
	while (1)
	{
		if (waitKey(10) == 27) break;		//按下Esc 退出
	}
	return 0;

}
//*--------------------------【on_Threshold 函数】-------------------------------------*/
void on_change(int, void*)
{
	g_dstImage = Mat::zeros(I_max, I_max, CV_8UC1);
	float a = g_nValueA * 0.01;
	float b = g_nValueB * 0.01;
	float c = g_nValueC * 0.01;
	float d = g_nValueD * 0.01;
	int l = g_nValueL;
	int m = g_nValueM;
	float p = g_nValueP * 0.0005;
	float q = g_nValueQ * 0.0005;
	float s = g_nValueS * 0.01;
	int x_change, y_change;
	//将参数进行处理
	//计算坐标
	if (change_switch == 0)
	{
		for (int x = I_max / 2;x < I_max / 2 + 50;x++)	//行循环
		{
			for (int y = I_max / 2;y < I_max / 2 + 50;y++)	//列循环
			{
				x_change = (a * x + c * y + l) / (p * x + q * y + 1);
				y_change = (b * x + d * y + m) / (p * x + q * y + 1);
				//限幅 
				if (x_change >= I_max) x_change = I_max - 1;
				else if (x_change <= 0) x_change = 0;
				else
				{

				}
				if (y_change >= I_max) y_change = I_max - 1;
				else if (y_change <= 0) y_change = 0;
				else
				{

				}
				g_dstImage.at<uchar>(x_change, y_change) = 255;
			}
		}
	}
	else
	{
		 a = cos(theta);
		 b = sin(theta);
		 c = -1 * sin(theta);
		 d = cos(theta);
		 for (int x = I_max / 2;x < I_max / 2 + 50;x++)	//行循环
		 {
			 for (int y = I_max / 2;y < I_max / 2 + 50;y++)	//列循环
			 {
				 x_change = (x - center_x) * cos(theta) - (y - center_y) * sin(theta) + center_x;
				 y_change = (x - center_x) * sin(theta) + (y - center_y) * cos(theta)+ center_y;
				 //限幅 
				 if (x_change >= I_max) x_change = I_max - 1;
				 else if (x_change <= 0) x_change = 0;
				 else
				 {

				 }
				 if (y_change >= I_max) y_change = I_max - 1;
				 else if (y_change <= 0) y_change = 0;
				 else
				 {

				 }
				 g_dstImage.at<uchar>(x_change, y_change) = 255;
			 }
		 }
	}
	//更新效果图
	imshow("效果图", g_dstImage);
}

原图如下:
Opencv——几何空间变换(仿射变换和投影变换)
接下来看具体变换:

1、平移变换

Opencv——几何空间变换(仿射变换和投影变换)
效果展示:
Opencv——几何空间变换(仿射变换和投影变换)

2、比例缩放

Opencv——几何空间变换(仿射变换和投影变换)
Opencv——几何空间变换(仿射变换和投影变换)
效果展示:
Opencv——几何空间变换(仿射变换和投影变换)

3、旋转

Opencv——几何空间变换(仿射变换和投影变换)
Opencv——几何空间变换(仿射变换和投影变换)
这里的旋转是以原点为中心点的,当我们以(center_x,center_y)为中点,则需要修改公式为:

X’=(X-center_x)*cos(theta)-(Y-center_y)*sin(theta) + center_x;
Y’=(X-center_x)*sin(theta)+(Y-center_y)*cos(theta) +center_y ;

效果展示:
Opencv——几何空间变换(仿射变换和投影变换)

4、对称变换(不做展示)

1、关于X轴变换

Opencv——几何空间变换(仿射变换和投影变换)

2、关于Y轴变换

Opencv——几何空间变换(仿射变换和投影变换)

3、关于直线Y=X变换

Opencv——几何空间变换(仿射变换和投影变换)

4、关于直线Y=-X变换

Opencv——几何空间变换(仿射变换和投影变换)

5、错切变换

Opencv——几何空间变换(仿射变换和投影变换)
Opencv——几何空间变换(仿射变换和投影变换)
效果展示:
Opencv——几何空间变换(仿射变换和投影变换)

6、复合变换

Opencv——几何空间变换(仿射变换和投影变换)

【4】图像的投影变换

Opencv——几何空间变换(仿射变换和投影变换)
点共线特性:原本是一条直线,变换后还是一条直线
Opencv——几何空间变换(仿射变换和投影变换)
效果展示:
Opencv——几何空间变换(仿射变换和投影变换)

【5】应用

Opencv——几何空间变换(仿射变换和投影变换)
由原理可知,变换的本质就是通过对应点组的坐标来求解方程。一个变换是否理想,在公式不做调整的情况下就要看对应点的选择。
这里我们一般选择图像的特征点。这些知识会在以后展开讲,哲理不做过多扩展。(像上面的二维码变换,我们选取的特征点考虑那三个定位点,当然还要再找一个特征点。以后掌握了这方面知识再补充。)

【6】Opencv自带的变换函数:

Opencv中仿射变换的函数:warpAffine()函数

公式依据:
Opencv——几何空间变换(仿射变换和投影变换)

C++: void warpAffine (InputArray src, OutputArray dst, InputArray M, Size
dsize, int flags=INTER_LINEAR,intborderMode=BORDER_CONSTANT, const
Scalar& borderValue=Scalar() )
第一个参数,InputArray 类型的src,输入图像,即源图像,填Mat类的对
象即可。
第二个参数,OutputArray 类型的dst, 函数调用后的运算结果存在这里,
需和源图片有一样的尺寸和类型。
第三个参数,InputArray 类型的M,2x3 的变换矩阵。
第四个参数,Size 类型的dsize,表示输出图像的尺寸。
第五个参数,int 类型的flags, 插值方法的标识符。此参数有默认值
INTER_ LINEAR(线性插值),可选的插值方式如下图所示。
Opencv——几何空间变换(仿射变换和投影变换)
第六个参数,int类型的borderMode,边界像素模式,默认值为
BORDER CONSTANT。
第七个参数,const Scalar&类型的borderValue, 在恒定的边界情况下取的
值,默认值为Scalar(), 即0。

Opencv中计算二维旋转变换矩阵: getRotationMatrix2D()函数

C++: Mat getRotationMatrix2D (Point2fcenter, double angle, double scale)
第一个参数,Point2f 类型的center,表示源图像的旋转中心。
第二个参数,double类型的angle,旋转角度。角度为正值表示向逆时针旋转(坐标原点是左上角)。
第三个参数,double 类型的scale,缩放系数。
Opencv——几何空间变换(仿射变换和投影变换)

int main()
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN);		//字体为绿色
	//【1】参数准备
	//定义两组点,代表两个三角形
	Point2f srcTriangle[3];
	Point2f dstTriangle[3];
	//定义Mat变量(变换矩阵)
	Mat rotMat(2, 3, CV_32FC1);	//CV_32FC1代表多少?
	Mat warpMat(2, 3, CV_32FC1);	//CV_32FC1代表多少?
	Mat srcImage, dstImage_warp, dstImage_warp_roate;
	//原图,仿射变换后的图,旋转变换后的图
	srcImage = imread("D:\\opencv_picture_test\\形态学操作\\黑白.jpg");
	//判断图像是否加载成功
	if (srcImage.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl << endl;
	dstImage_warp = Mat::zeros(srcImage.rows, srcImage.cols, srcImage.type());		//转换图和原图像类型一样大小一样
	//【2】利用三组对应点来计算参数
	srcTriangle[0] = Point2f(0, 0);		//这些选择自己决定
	srcTriangle[1] = Point2f(0, 0);
	srcTriangle[2] = Point2f(0, 0);
	dstTriangle[0] = Point2f(0, 0);
	dstTriangle[1] = Point2f(0, 0);
	dstTriangle[2] = Point2f(0, 0);
	//【3】求得仿射变换参数
	warpMat = getAffineTransform(srcTriangle, dstTriangle);		//利用对应点求得6个参数
	//【4】对原图进行仿射变换
	warpAffine(srcImage,dstImage_warp,warpMat,dstImage_warp.size());
	//【5】获取旋转信息
	Point center = Point(dstImage_warp.cols / 2, dstImage_warp.rows / 2);	//中心点
	double angle = -30.0;			//顺时针30度
	double scale =0.8;
	//【6】通过上面的旋转细节信息求得旋转矩阵
	rotMat = getRotationMatrix2D(center, angle,scale);
	//【7】对缩放后的图像进行旋转
	warpAffine(dstImage_warp,dstImage_warp_roate, rotMat,dstImage_warp.size());
	//【8】显示结果
	namedWindow("原图像", WINDOW_NORMAL);     //定义窗口显示属性
	imshow("原图像", srcImage);
	namedWindow("缩放图", WINDOW_NORMAL);     //定义窗口显示属性
	imshow("缩放图", dstImage_warp);
	namedWindow("缩放旋转图", WINDOW_NORMAL);     //定义窗口显示属性
	imshow("缩放旋转图", dstImage_warp_roate);
	//创建三个窗口
	waitKey(0);
	return 0;
}

效果:
Opencv——几何空间变换(仿射变换和投影变换)
PPT是盗用的我们李竹老师的,嘿嘿。
Opencv——几何空间变换(仿射变换和投影变换)