1. 程式人生 > >opencv 影象仿射變換 計算仿射變換後對應特徵點的新座標 影象旋轉、縮放、平移

opencv 影象仿射變換 計算仿射變換後對應特徵點的新座標 影象旋轉、縮放、平移

常常需要最影象進行仿射變換,仿射變換後,我們可能需要將原來影象中的特徵點座標進行重新計算,獲得原來影象中例如眼睛瞳孔座標的新的位置,用於在新得到影象中繼續利用瞳孔位置座標。

關於仿射變換的詳細介紹,請見上面連結的部落格。

我這裡主要介紹如何在已經知道原影象中若干特徵點的座標之後,計算這些特徵點進行放射變換之後的座標,然後做一些補充。

** 在原文中,很多功能函式都是使用的cvXXX,例如cv2DRotationMatrix( center, degree,1, &M);  這些都是老版本的函式,在opencv2以後,應該儘量的使用全新的函式,所以在我的程式碼中,都是使用的最新的函式,不再使用 cvMat, 而是全部使用 Mat 型別。 **

1. 特徵點對應的新的座標計算

假設已經有一個原影象中的特徵點的座標 CvPoint point;  那麼計算這個point的對應的仿射變換之後在新的影象中的座標位置,使用的方法如下函式:

// 獲取指定畫素點放射變換後的新的座標位置
CvPoint getPointAffinedPos(const CvPoint &src, const CvPoint ¢er, double angle)
{
	CvPoint dst;
	int x = src.x - center.x;
	int y = src.y - center.y;

	dst.x = cvRound(x * cos(angle) + y * sin(angle) + center.x);
	dst.y = cvRound(-x * sin(angle) + y * cos(angle) + center.y);
	return dst;
}

要特別注意的是,在對一個原影象中的畫素的座標進行計算仿射變換之後的座標的時候,一定要按照仿射變換的基本原理,將原來的座標減去仿射變換的旋轉中心的座標,這樣仿射變換之後得到的座標再加上仿射變換旋轉中心座標才是原座標在新的仿射變換之後的影象中的正確座標。

下面給出計算對應瞳孔座標旋轉之後的座標位置的示例程式碼:

// AffineTransformation.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "stdio.h"
#include "iostream"

#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

// 獲取指定畫素點放射變換後的新的座標位置
CvPoint getPointAffinedPos(const CvPoint &src, const CvPoint ¢er, double angle);
Mat ImageRotate(Mat & src, const CvPoint &_center, double angle);
Mat ImageRotate2NewSize(Mat& src, const CvPoint &_center, double angle);

int _tmain(int argc, _TCHAR* argv[])
{
	string image_path = "D:/lena.jpg";
	Mat img = imread(image_path);
	cvtColor(img, img, CV_BGR2GRAY);

	Mat src;
	img.copyTo(src);


	CvPoint Leye;
	Leye.x = 265;
	Leye.y = 265;
	CvPoint Reye;
	Reye.x = 328;
	Reye.y = 265;

	// draw pupil
	src.at<unsigned char>(Leye.y, Leye.x) = 255;
	src.at<unsigned char>(Reye.y, Reye.x) = 255;

	imshow("src", src);

	//
	CvPoint center;
	center.x = img.cols / 2;
	center.y = img.rows / 2;

	double angle = 15L;

	Mat dst = ImageRotate(img, center, angle);

	// 計算原特徵點在旋轉後圖像中的對應的座標
	CvPoint l2 = getPointAffinedPos(Leye, center, angle * CV_PI / 180);
	CvPoint r2 = getPointAffinedPos(Reye, center, angle * CV_PI / 180);

	// draw pupil
	dst.at<unsigned char>(l2.y, l2.x) = 255;
	dst.at<unsigned char>(r2.y, r2.x) = 255;

	//Mat dst = ImageRotate2NewSize(img, center, angle);
	imshow("dst", dst);


	waitKey(0);
	return 0;
}

Mat ImageRotate(Mat & src, const CvPoint &_center, double angle)
{
	CvPoint2D32f center;
	center.x = float(_center.x);
	center.y = float(_center.y);

	//計算二維旋轉的仿射變換矩陣
	Mat M = getRotationMatrix2D(center, angle, 1);

	// rotate
	Mat dst;
	warpAffine(src, dst, M, cvSize(src.cols, src.rows), CV_INTER_LINEAR);
	return dst;
}

// 獲取指定畫素點放射變換後的新的座標位置
CvPoint getPointAffinedPos(const CvPoint &src, const CvPoint ¢er, double angle)
{
	CvPoint dst;
	int x = src.x - center.x;
	int y = src.y - center.y;

	dst.x = cvRound(x * cos(angle) + y * sin(angle) + center.x);
	dst.y = cvRound(-x * sin(angle) + y * cos(angle) + center.y);
	return dst;
}

這裡,我們先通過手工找到瞳孔座標,然後計算在影象旋轉之後瞳孔的座標。

執行結果如圖:

原影象

旋轉之後的影象:


2. 旋轉中心對於旋轉的影響

然後我們看看仿射變換旋轉點的選擇對於旋轉之後的影象的影響,一般情況下,我們選擇影象的中心點作為仿射變換的旋轉中心,獲得的旋轉之後的影象與原影象大小一樣。

計算程式碼: 

int _tmain(int argc, _TCHAR* argv[])
{
	string image_path = "D:/lena.jpg";
	Mat img = imread(image_path);
	cvtColor(img, img, CV_BGR2GRAY);

	Mat src;
	img.copyTo(src);


	CvPoint Leye;
	Leye.x = 265;
	Leye.y = 265;
	CvPoint Reye;
	Reye.x = 328;
	Reye.y = 265;

	// draw pupil
	src.at<unsigned char>(Leye.y, Leye.x) = 255;
	src.at<unsigned char>(Reye.y, Reye.x) = 255;

	imshow("src", src);

	//
	/*CvPoint center;
	center.x = img.cols / 2;
	center.y = img.rows / 2;*/
	CvPoint center;
	center.x = 0;
	center.y = 0;

	double angle = 15L;

	Mat dst = ImageRotate(img, center, angle);

	// 計算原特徵點在旋轉後圖像中的對應的座標
	CvPoint l2 = getPointAffinedPos(Leye, center, angle * CV_PI / 180);
	CvPoint r2 = getPointAffinedPos(Reye, center, angle * CV_PI / 180);

	// draw pupil
	dst.at<unsigned char>(l2.y, l2.x) = 255;
	dst.at<unsigned char>(r2.y, r2.x) = 255;

	//Mat dst = ImageRotate2NewSize(img, center, angle);
	imshow("dst", dst);


	waitKey(0);
	return 0;
}

這裡繞著(0,0)點進行旋轉,旋轉之後的影象:


繞著左下角旋轉:

	CvPoint center;
	center.x = 0;
	center.y = img.rows;

旋轉之後的影象:


3. 縮放因子對於旋轉影象的影響

上面我們的程式碼都沒有新增縮放資訊,現在對上面的程式碼進行稍加修改,新增縮放參數,然後看一下如何計算對應的新的座標。

#include "stdafx.h"
#include "stdio.h"
#include "iostream"

#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

// 獲取指定畫素點放射變換後的新的座標位置
CvPoint getPointAffinedPos(const CvPoint &src, const CvPoint ¢er, double angle, double scale);
Mat ImageRotate(Mat & src, const CvPoint &_center, double angle, double scale);
Mat ImageRotate2NewSize(Mat& src, const CvPoint &_center, double angle, double scale);

int _tmain(int argc, _TCHAR* argv[])
{
	string image_path = "D:/lena.jpg";
	Mat img = imread(image_path);
	cvtColor(img, img, CV_BGR2GRAY);
	double scale = 0.5;

	Mat src;
	img.copyTo(src);


	CvPoint Leye;
	Leye.x = 265;
	Leye.y = 265;
	CvPoint Reye;
	Reye.x = 328;
	Reye.y = 265;

	// draw pupil
	src.at<unsigned char>(Leye.y, Leye.x) = 255;
	src.at<unsigned char>(Reye.y, Reye.x) = 255;

	imshow("src", src);

	//
	CvPoint center;
	center.x = img.cols / 2;
	center.y = img.rows / 2;

	double angle = 15L;

	Mat dst = ImageRotate(img, center, angle, scale);

	// 計算原特徵點在旋轉後圖像中的對應的座標
	CvPoint l2 = getPointAffinedPos(Leye, center, angle * CV_PI / 180, scale);
	CvPoint r2 = getPointAffinedPos(Reye, center, angle * CV_PI / 180, scale);

	// draw pupil
	dst.at<unsigned char>(l2.y, l2.x) = 255;
	dst.at<unsigned char>(r2.y, r2.x) = 255;

	//Mat dst = ImageRotate2NewSize(img, center, angle);
	imshow("dst", dst);


	waitKey(0);
	return 0;
}

Mat ImageRotate(Mat & src, const CvPoint &_center, double angle, double scale)
{
	CvPoint2D32f center;
	center.x = float(_center.x);
	center.y = float(_center.y);

	//計算二維旋轉的仿射變換矩陣
	Mat M = getRotationMatrix2D(center, angle, scale);

	// rotate
	Mat dst;
	warpAffine(src, dst, M, cvSize(src.cols, src.rows), CV_INTER_LINEAR);
	return dst;
}

// 獲取指定畫素點放射變換後的新的座標位置
CvPoint getPointAffinedPos(const CvPoint &src, const CvPoint ¢er, double angle, double scale)
{
	CvPoint dst;
	int x = src.x - center.x;
	int y = src.y - center.y;

	dst.x = cvRound(x * cos(angle) * scale  + y * sin(angle) * scale + center.x);
	dst.y = cvRound(-x * sin(angle) * scale + y * cos(angle) * scale + center.y);
	return dst;
}

當縮放尺度為0.5的時候,程式的執行結果如圖:


4.  根據旋轉與縮放尺度獲得與原始影象大小不同的影象大小(新的合適的大小)

上面的計算中,一直都是放射變換之後計算得到的影象和原始影象一樣大,但是因為旋轉、縮放之後影象可能會變大或者變小,我們再次對上面的程式碼進行修改,這樣在獲得仿射變換之後的影象前,需要重新計算生成的影象的大小。

計算方法:

	double angle2 = angle * CV_PI / 180;
	int width = src.cols;
	int height = src.rows;

	double alpha = cos(angle2) * scale; 
	double beta = sin(angle2) * scale;

	int new_width = (int)(width * fabs(alpha) + height * fabs(beta));
	int new_height = (int)(width * fabs(beta) + height * fabs(alpha));

另外,因為我們的影象旋轉是按照原影象的中心,所以當獲取到影象的仿射變換矩陣之後,我們需要根據新生成的影象的大小,給仿射變換矩陣新增平移資訊。

或者可以這麼說,我們新計算得到的影象的大小,讓原始影象繞著新的影象大小的中心進行旋轉。

	//計算二維旋轉的仿射變換矩陣
	Mat M = getRotationMatrix2D(center, angle, scale);

	// 給計算得到的旋轉矩陣新增平移

	M.at<double>(0, 2) += (int)((new_width - width )/2);
	M.at<double>(1, 2) += (int)((new_height - height )/2);

然後另外需要注意的是,如果你在原始影象中有一些特徵點的座標,這些特徵點的座標對映到新的影象上的時候,需要在以前的方法的基礎上增加平移資訊。
// 獲取指定畫素點放射變換後的新的座標位置
CvPoint getPointAffinedPos(Mat & src, Mat & dst, const CvPoint &src_p, const CvPoint ¢er, double angle, double scale)
{
	double alpha = cos(angle) * scale; 
	double beta = sin(angle) * scale;

	int width = src.cols;
	int height = src.rows;

	CvPoint dst_p;
	int x = src_p.x - center.x;
	int y = src_p.y - center.y;

	dst_p.x = cvRound(x * alpha  + y * beta + center.x);
	dst_p.y = cvRound(-x * beta + y * alpha + center.y);

	int new_width = dst.cols;
	int new_height = dst.rows;

	int movx = (int)((new_width - width)/2);
	int movy = (int)((new_height - height)/2);

	dst_p.x += movx;
	dst_p.y += movy;

	return dst_p;
}


我們仿射變換函式程式碼:

Mat ImageRotate2NewSize(Mat& src, const CvPoint &_center, double angle, double scale)
{
	double angle2 = angle * CV_PI / 180;
	int width = src.cols;
	int height = src.rows;

	double alpha = cos(angle2) * scale; 
	double beta = sin(angle2) * scale;

	int new_width = (int)(width * fabs(alpha) + height * fabs(beta));
	int new_height = (int)(width * fabs(beta) + height * fabs(alpha));

	CvPoint2D32f center;
	center.x = float(width / 2);
	center.y = float(height / 2);
	//計算二維旋轉的仿射變換矩陣
	Mat M = getRotationMatrix2D(center, angle, scale);

	// 給計算得到的旋轉矩陣新增平移

	M.at<double>(0, 2) += (int)((new_width - width )/2);
	M.at<double>(1, 2) += (int)((new_height - height )/2);

	// rotate
	Mat dst;
	warpAffine(src, dst, M, cvSize(new_width, new_height), CV_INTER_LINEAR);
	return dst;
}

主函式:
int _tmain(int argc, _TCHAR* argv[])
{
	string image_path = "D:/lena.jpg";
	Mat img = imread(image_path);
	cvtColor(img, img, CV_BGR2GRAY);
	double scale = 0.5;

	Mat src;
	img.copyTo(src);

	CvPoint Leye;
	Leye.x = 265;
	Leye.y = 265;
	CvPoint Reye;
	Reye.x = 328;
	Reye.y = 265;

	// draw pupil
	src.at<unsigned char>(Leye.y, Leye.x) = 255;
	src.at<unsigned char>(Reye.y, Reye.x) = 255;

	imshow("src", src);

	//
	CvPoint center;
	center.x = img.cols / 2;
	center.y = img.rows / 2;

	double angle = 15L;

	//Mat dst = ImageRotate(img, center, angle, scale);
	Mat dst = ImageRotate2NewSize(img, center, angle, scale);

	// 計算原特徵點在旋轉後圖像中的對應的座標
	CvPoint l2 = getPointAffinedPos(src, dst, Leye, center, angle * CV_PI / 180, scale);
	CvPoint r2 = getPointAffinedPos(src, dst, Reye, center, angle * CV_PI / 180, scale);

	// draw pupil
	dst.at<unsigned char>(l2.y, l2.x) = 255;
	dst.at<unsigned char>(r2.y, r2.x) = 255;


	imshow("dst", dst);


	waitKey(0);
	return 0;
}

仿射變換結果以及瞳孔重新座標計算結果:


5. 根據三個點進行仿射變換

根據給點的三個點,由這三個點之前的座標以及變換之後的座標,對原影象進行仿射變換,不過需要事先知道三個點仿射變換的座標位置。
int _tmain(int argc, _TCHAR* argv[])
{
	string image_path = "D:/lena.jpg";
	Mat img = imread(image_path);

	Point2f src_points[3];
	src_points[0] = Point2f(100, 100);
	src_points[1] = Point2f(400, 100);
	src_points[2] = Point2f(250, 300);

	Point2f dst_points[3];
	dst_points[0] = Point2f(100, 100);
	dst_points[1] = Point2f(400, 300);
	dst_points[2] = Point2f(100, 300);

	Mat M1 = getAffineTransform(src_points, dst_points);

	Mat dst;
	warpAffine(img, dst, M1, cvSize(img.cols, img.rows), INTER_LINEAR);

	imshow("dst", dst);

	//cvtColor(img, img, CV_BGR2GRAY);
	//double scale = 1.5;

	//Mat src;
	//img.copyTo(src);

	//CvPoint Leye;
	//Leye.x = 265;
	//Leye.y = 265;
	//CvPoint Reye;
	//Reye.x = 328;
	//Reye.y = 265;

	//// draw pupil
	//src.at<unsigned char>(Leye.y, Leye.x) = 255;
	//src.at<unsigned char>(Reye.y, Reye.x) = 255;

	//imshow("src", src);

	////
	//CvPoint center;
	//center.x = img.cols / 2;
	//center.y = img.rows / 2;

	//double angle = 15L;

	////Mat dst = ImageRotate(img, center, angle, scale);
	//Mat dst = ImageRotate2NewSize(img, center, angle, scale);

	//// 計算原特徵點在旋轉後圖像中的對應的座標
	//CvPoint l2 = getPointAffinedPos(src, dst, Leye, center, angle * CV_PI / 180, scale);
	//CvPoint r2 = getPointAffinedPos(src, dst, Reye, center, angle * CV_PI / 180, scale);

	//// draw pupil
	//dst.at<unsigned char>(l2.y, l2.x) = 255;
	//dst.at<unsigned char>(r2.y, r2.x) = 255;
	//imshow("dst", dst);


	waitKey(0);
	return 0;
}

結果: