透視變換簡析

開篇注:博客是爲了更好的思考,希望能以此記錄自己的學習歷程。本文寫於2018年09月11日,修改於2018年09月12日。隨着時間流逝,可能有些內容已經失效,望讀者望文觀義,get到關鍵點。假如對文中有啥有疑問、有想法、感覺不太對的地方歡迎留言交流~。

引言

大學時就感覺OpenCV挺有意思,比如裏面的透視變換,通過四個點就可以計算一張二維圖和另外一張二維圖之間的映射關係,後續通過映射關係就可以將兩者之中任意一個圖中的元素映射到另外一個圖。很遺憾工作後纔開始瞭解其原理。

正文

我是從博客入手學習的,CSDN博主 小魏的修行路 的 兩篇博文給了我很大啓發,很感謝。兩篇博文鏈接如下:
https://blog.csdn.net/xiaowei_cqu/article/details/26471527
https://blog.csdn.net/xiaowei_cqu/article/details/26478135

1、捋博文的內容

參照 https://blog.csdn.net/xiaowei_cqu/article/details/26471527 這篇文章,我先按照博主的思路,將四邊形A變換爲四邊形B的過程變成: 四邊形A先到單位正方形的變換,然後加上單位正方形到四邊形B的變換兩個階段。這裏就手推(看這字,妥妥手推的(逃. )一下單位正方形到四邊形變換的過程:
手推公式1
手推公式2
小魏的代碼實現就是想要求四邊形A變換爲四邊形B,先求四邊形A到單位方形的變換矩陣H1H_{1},再求單位方形到四邊形B的變換矩陣H2H_{2},那麼四邊形A到四邊形B的變換矩陣HH就等於 H1H2H_{1} * H_{2}
因爲已經推導出了方形到四邊形的透視變換矩陣計算公式,所以具體在求四邊形A到方形的透視變換矩陣時,先求單位方形到四邊形A轉換的矩陣FF,那麼H1=F1H_{1} = F^{-1},利用矩陣公式F=F1FF^* = F^{-1}*|F|,推得H1=FFH_{1} = \frac{F^*}{|F|}

我們再來回顧一下,已知四邊形A到四邊形B的透視變換矩陣HH,求四邊形A中某點(x,yx, y)在四邊形B中對應的點(x,yx^{'}, y^{'}):
x=C11x+C21y+C31C13x+C23y+C33;x^{'} = \frac{C_{11} * x + C_{21} * y + C_{31}}{C_{13} * x + C_{23} * y + C_{33}};y=C12x+C22y+C32C13x+C23y+C33 y^{'} = \frac{C_{12} * x + C_{22} * y + C_{32}}{C_{13} * x + C_{23} * y + C_{33}}
透視變換矩陣HH乘以非0係數對以上對應點的推導沒有影響。

那麼,簡單起見,我們就令H1=FH_{1} = F^*
綜上所述就是對小魏博客中透視變換部分的推導與分析。

2、進一步思考

現在我有一個255 * 255 * 24(位深)圖片:
原圖
我想讓它變換到一張600 * 500 *24的圖上,位置(默認座標爲(x, y)形式)爲:
(117, 31) // top left
(420, 25) // top right
(120, 218) // bottom left
(418, 450) // bottom right

我們創建一個 600 * 500 * 24黑色背景的圖片,然後繼續之前的圖A到圖B的透視變換矩陣爲HH的定義。

(1)我們設255 * 255的圖爲圖A,設600 * 500的圖爲圖B

這種前提下,我們求得的透視變換矩陣HH是圖A到圖B的映射。用學過的數學想一下,圖A中點都可以映射到圖B中,但是圖B中不是每個點都與圖A中的點有映射關係。所以會出現啥結果呢?見下圖:
透視變換1
圖B中與圖A中沒有建立映射關係的點顯示爲背景色(黑色)。

(2)我們設255 * 255的圖爲圖B,設600 * 500的圖爲圖A

這種前提下,我們求得的透視變換矩陣HH是還是圖A到圖B的映射(說了句廢話,咱們前面不是預定義了圖A到圖B的透視變換矩陣爲HH)。但是不同的是600 * 500的待操作圖/目標圖變成了圖A,這樣待操作圖/目標圖每個點都在建立映射關係的另外一張圖中有映射。見下圖:
透視變換2

(3)不管設置誰爲圖A圖B,該插值插值

這樣怎麼也不會出現(1)中所述情況了。

我是建議,把已知的圖像作爲圖B, 待操作圖/目標圖作爲圖A,這樣也不用後續的插值操作。
到了這裏,大家也該明白了,OpenCV裏面也要考慮這種情況,免得透視變換後的圖中出現(1)中情況不知所措。

結尾

結尾,附上一個測試樣例(PerspectiveTransform類用的是小魏的,請去她的博客自行下載):

#include "PerspectiveTransform.h"

#include <iostream> 
#include <fstream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp> 
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc/imgproc.hpp>
#include <math.h>

using namespace cv;
using namespace std;

int main() {
	Mat img = imread("E:/test.jpg");
	int img_height = img.rows;
	int img_width = img.cols;

	Mat img_trans = Mat::zeros(500, 600, CV_8UC3);
	int img_height2 = img_trans.rows;
	int img_width2 = img_trans.cols;

	PerspectiveTransform tansform = PerspectiveTransform::quadrilateralToQuadrilateral(
		117, 31, // top left
		420, 25, // top right
		120, 218,// bottom left
		418, 450,
		0, 0, // top left
		img_height - 1, 0, // top right
		0, img_width - 1,// bottom left
		img_height - 1, img_width - 1
		);

	vector<float> ponits;
	for (int i = 0; i < img_height2; i++) {
		for (int j = 0; j < img_width2; j++) {
			ponits.push_back(j);
			ponits.push_back(i);
		}
	}
	tansform.transformPoints(ponits);
	for (int i = 0; i < img_height2; i++) {
		uchar*  t = img_trans.ptr<uchar>(i);
		for (int j = 0; j < img_width2; j++) {
			int tmp = i * img_width2 + j;
			int x = ponits[tmp * 2];
			int y = ponits[tmp * 2 + 1];
			if (x<0 || x>(img_width - 1) || y<0 || y>(img_height - 1))
				continue;
			uchar* p = img.ptr<uchar>(y);
			t[j * 3] = p[x * 3];
			t[j * 3 + 1] = p[x * 3 + 1];
			t[j * 3 + 2] = p[x * 3 + 2];
		}
	}
	imwrite("E:/trans.jpg", img_trans);
	
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章