Opencv——幾何空間變換(仿射變換和投影變換)

【1】幾何變換(空間變換)簡述

圖像的幾何變換,又稱空間變換,是圖形處理的一個方面,是各種圖形處理算法的基礎。它將一幅圖像中的座標位置映射到另一幅圖像中的新座標位置,其實質是改變像素的空間位置,估算新空間位置上的像素值。
幾何變換算法一般包括空間變換運算和插值算法。
集中常見的變換

【2】變換矩陣知識簡述

齊次座標的概念

圖像一般是二維的,座標形式爲(x,y)。
這裏我們將其擴展爲3維形式的齊次座標。形式如下:
座標形式
第三個參數是尺度參數,控制尺度縮放。(1的時候表示尺度不變)
齊次座標使用n+1維,來表示n維的座標。它的優點如下所示:

●統一座標的加法運算和乘法運算, 運算時提高效率。
●表示無窮遠的點。 當z=0的時候,表示無窮遠的點。
( x,y,z) ----->( x/z, y/z) ;齊次座標和二維座標的換算
如,(2,2,1),(4,4,2 )表示同樣的點。

幾何運算矩陣

運算矩陣
最左邊是變換後的齊次座標,中間的是原圖點的其次座標,最右邊是變換矩陣,有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);
}

原圖如下:
原圖
接下來看具體變換:

1、平移變換

平移變換
效果展示:
平移

2、比例縮放

比例縮放
比例縮放
效果展示:
縮放

3、旋轉

在這裏插入圖片描述
在這裏插入圖片描述
這裏的旋轉是以原點爲中心點的,當我們以(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 ;

效果展示:
旋轉

4、對稱變換(不做展示)

1、關於X軸變換

1

2、關於Y軸變換

2

3、關於直線Y=X變換

3

4、關於直線Y=-X變換

4

5、錯切變換

1
2
效果展示:
錯切

6、複合變換

複合變換

【4】圖像的投影變換

投影變換
點共線特性:原本是一條直線,變換後還是一條直線
變換
效果展示:
投影變換

【5】應用

矯正圖像
由原理可知,變換的本質就是通過對應點組的座標來求解方程。一個變換是否理想,在公式不做調整的情況下就要看對應點的選擇。
這裏我們一般選擇圖像的特徵點。這些知識會在以後展開講,哲理不做過多擴展。(像上面的二維碼變換,我們選取的特徵點考慮那三個定位點,當然還要再找一個特徵點。以後掌握了這方面知識再補充。)

【6】Opencv自帶的變換函數:

Opencv中仿射變換的函數:warpAffine()函數

公式依據:
公式依據

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(線性插值),可選的插值方式如下圖所示。
在這裏插入圖片描述
第六個參數,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,縮放係數。
計算公式

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;
}

效果:
效果
PPT是盜用的我們李竹老師的,嘿嘿。
圖像

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章