我記得曾經有人對OpenCV的旋轉吐槽,意思是它自己沒有很好的關於選擇的算法。在新的版本里面添加了這些函數(我還沒有時間去看是什么時候pr的)。現在一個比較棘手的問題,就是OpenCV中旋轉是如何定量的,什么是正方向?什么是負方向?什么時候用角度?什么時候用弧度?
下面就是針對這幾個問題,通過查資料、做實驗的方式搞清楚。
一、OpenCV中旋轉式如何定量的
也就是坐標系問題。OpenCV坐標系以(0,0)點為原點,以向下為Y軸正方向,以向右為X軸正方向。
對於旋轉而言,通過“旋轉中點”和“旋轉的角度”兩個值來定量一個“旋轉”。
二、什么是正方向?什么是負方向?什么時候用角度?什么時候用弧度?
一般來說,坐標系中以X軸正方向為原點,而以逆時針為正、順時針為負。
至於什么時候用角度、什么時候用弧度,一般來說,角度180度對應於弧度的PI,由於在使用函數的時候,一般只能以數值類型(不帶單位)作為參數,所以需要根據函數自己的要求選擇弧度還是角度.
三、相關的函數和使用方法。
可能這才是最為關鍵的。我這樣來歸納。和旋轉相關的函數可以大致分為“獲得旋轉”和“使用旋轉”兩類。
“獲得旋轉”而言,旋轉矩形、PCA都可以獲得旋轉;
“使用旋轉”而言,可以使用放射變換
warpAffine
具體而言,包括以下內容:
1、
CV_EXPORTS_W
void rotate(InputArray src, OutputArray dst, int rotateCode);
這是OpenCV在新版本里面提供的選擇函數,第3個參數是旋轉的選擇,具體如下
enum RotateFlags {
ROTATE_90_CLOCKWISE = 0, //Rotate 90 degrees clockwise
ROTATE_180 = 1, //Rotate 180 degrees clockwise
ROTATE_90_COUNTERCLOCKWISE = 2, //Rotate 270 degrees clockwise
};
/** @brief Rotates a 2D array in multiples of 90 degrees.
The function rotate rotates the array in one of three different ways:
* Rotate by 90 degrees clockwise (rotateCode = ROTATE_90).
* Rotate by 180 degrees clockwise (rotateCode = ROTATE_180).
* Rotate by 270 degrees clockwise (rotateCode = ROTATE_270).
@param src input array.
@param dst output array of the same type as src. The size is the same with ROTATE_180,
and the rows and cols are switched for ROTATE_90 and ROTATE_270.
@param rotateCode an enum to specify how to rotate the array; see the enum RotateFlags
@sa transpose , repeat , completeSymm, flip, RotateFlags
*/
ROTATE_90_CLOCKWISE = 0, //Rotate 90 degrees clockwise
ROTATE_180 = 1, //Rotate 180 degrees clockwise
ROTATE_90_COUNTERCLOCKWISE = 2, //Rotate 270 degrees clockwise
};
/** @brief Rotates a 2D array in multiples of 90 degrees.
The function rotate rotates the array in one of three different ways:
* Rotate by 90 degrees clockwise (rotateCode = ROTATE_90).
* Rotate by 180 degrees clockwise (rotateCode = ROTATE_180).
* Rotate by 270 degrees clockwise (rotateCode = ROTATE_270).
@param src input array.
@param dst output array of the same type as src. The size is the same with ROTATE_180,
and the rows and cols are switched for ROTATE_90 and ROTATE_270.
@param rotateCode an enum to specify how to rotate the array; see the enum RotateFlags
@sa transpose , repeat , completeSymm, flip, RotateFlags
*/
那么它存在的問題就是只能旋轉具體的角度(90、180)等,不是很靈活
2、rotate修改
為了解決不是很靈活的問題,自己重新寫的旋轉函數,第3個參數直接是旋轉的角度。
void rotate(
const Mat
& src, Mat
& dst,
float angle)
{
CV_Assert( !src.empty());
float radian = angle / 180. 0 * PI;
int uniSize = max(src.cols, src.rows) * 2;
int dx = (uniSize - src.cols) / 2;
int dy = (uniSize - src.rows) / 2;
copyMakeBorder(src, dst, dy, dy, dx, dx, BORDER_CONSTANT);
//旋轉中心
Point2f center(dst.cols / 2, dst.rows / 2);
Mat affine_matrix = getRotationMatrix2D( center, angle, 1. 0 );
warpAffine(dst, dst, affine_matrix, dst.size());
float sinVal = fabs(sin(radian));
float cosVal = fabs(cos(radian));
//旋轉后的圖像大小
Size targetSize(src.cols * cosVal + src.rows * sinVal,src.cols * sinVal + src.rows * cosVal);
//剪掉四周邊框
int x = (dst.cols - targetSize.width) / 2;
int y = (dst.rows - targetSize.height) / 2;
Rect rect(x, y, targetSize.width, targetSize.height);
dst = Mat(dst, rect);
}
{
CV_Assert( !src.empty());
float radian = angle / 180. 0 * PI;
int uniSize = max(src.cols, src.rows) * 2;
int dx = (uniSize - src.cols) / 2;
int dy = (uniSize - src.rows) / 2;
copyMakeBorder(src, dst, dy, dy, dx, dx, BORDER_CONSTANT);
//旋轉中心
Point2f center(dst.cols / 2, dst.rows / 2);
Mat affine_matrix = getRotationMatrix2D( center, angle, 1. 0 );
warpAffine(dst, dst, affine_matrix, dst.size());
float sinVal = fabs(sin(radian));
float cosVal = fabs(cos(radian));
//旋轉后的圖像大小
Size targetSize(src.cols * cosVal + src.rows * sinVal,src.cols * sinVal + src.rows * cosVal);
//剪掉四周邊框
int x = (dst.cols - targetSize.width) / 2;
int y = (dst.rows - targetSize.height) / 2;
Rect rect(x, y, targetSize.width, targetSize.height);
dst = Mat(dst, rect);
}
3、
getRotationMatrix2D函數
主要用於獲得圖像繞着 某一點的旋轉矩陣
函數調用形式:
Mat getRotationMatrix2D(Point2f center, double angle, double scale)
參數詳解:
Point2f center:表示旋轉的中心點
double angle:表示旋轉的
角度
double scale:圖像縮放因子
例子
#
include
"opencv2/highgui/highgui.hpp"
# include "opencv2/imgproc/imgproc.hpp"
# include <iostream >
# include <stdio.h >
using namespace cv;
using namespace std;
/// 全局變量
char * source_window = "Source image";
char * warp_window = "Warp";
char * warp_rotate_window = "Warp + Rotate";
/** @function main */
int main( int argc, char * * argv )
{
Point2f srcTri[ 3];
Point2f dstTri[ 3];
Mat rot_mat( 2, 3, CV_32FC1 );
Mat warp_mat( 2, 3, CV_32FC1 );
Mat src, warp_dst, warp_rotate_dst;
/// 加載源圖像
src = imread( argv[ 1], 1 );
/// 設置目標圖像的大小和類型與源圖像一致
warp_dst = Mat : :zeros( src.rows, src.cols, src.type() );
/// 設置源圖像和目標圖像上的三組點以計算仿射變換
srcTri[ 0] = Point2f( 0, 0 );
srcTri[ 1] = Point2f( src.cols - 1, 0 );
srcTri[ 2] = Point2f( 0, src.rows - 1 );
dstTri[ 0] = Point2f( src.cols * 0. 0, src.rows * 0. 33 );
dstTri[ 1] = Point2f( src.cols * 0. 85, src.rows * 0. 25 );
dstTri[ 2] = Point2f( src.cols * 0. 15, src.rows * 0. 7 );
/// 求得仿射變換
warp_mat = getAffineTransform( srcTri, dstTri );
/// 對源圖像應用上面求得的仿射變換
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
/** 對圖像扭曲后再旋轉 */
/// 計算繞圖像中點順時針旋轉50度縮放因子為0.6的旋轉矩陣
Point center = Point( warp_dst.cols / 2, warp_dst.rows / 2 );
double angle = - 50. 0;
double scale = 0. 6;
/// 通過上面的旋轉細節信息求得旋轉矩陣
rot_mat = getRotationMatrix2D( center, angle, scale );
/// 旋轉已扭曲圖像
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
/// 顯示結果
namedWindow( source_window, CV_WINDOW_AUTOSIZE );
imshow( source_window, src );
namedWindow( warp_window, CV_WINDOW_AUTOSIZE );
imshow( warp_window, warp_dst );
namedWindow( warp_rotate_window, CV_WINDOW_AUTOSIZE );
imshow( warp_rotate_window, warp_rotate_dst );
/// 等待用戶按任意按鍵退出程序
waitKey( 0);
return 0;
}
# include "opencv2/imgproc/imgproc.hpp"
# include <iostream >
# include <stdio.h >
using namespace cv;
using namespace std;
/// 全局變量
char * source_window = "Source image";
char * warp_window = "Warp";
char * warp_rotate_window = "Warp + Rotate";
/** @function main */
int main( int argc, char * * argv )
{
Point2f srcTri[ 3];
Point2f dstTri[ 3];
Mat rot_mat( 2, 3, CV_32FC1 );
Mat warp_mat( 2, 3, CV_32FC1 );
Mat src, warp_dst, warp_rotate_dst;
/// 加載源圖像
src = imread( argv[ 1], 1 );
/// 設置目標圖像的大小和類型與源圖像一致
warp_dst = Mat : :zeros( src.rows, src.cols, src.type() );
/// 設置源圖像和目標圖像上的三組點以計算仿射變換
srcTri[ 0] = Point2f( 0, 0 );
srcTri[ 1] = Point2f( src.cols - 1, 0 );
srcTri[ 2] = Point2f( 0, src.rows - 1 );
dstTri[ 0] = Point2f( src.cols * 0. 0, src.rows * 0. 33 );
dstTri[ 1] = Point2f( src.cols * 0. 85, src.rows * 0. 25 );
dstTri[ 2] = Point2f( src.cols * 0. 15, src.rows * 0. 7 );
/// 求得仿射變換
warp_mat = getAffineTransform( srcTri, dstTri );
/// 對源圖像應用上面求得的仿射變換
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
/** 對圖像扭曲后再旋轉 */
/// 計算繞圖像中點順時針旋轉50度縮放因子為0.6的旋轉矩陣
Point center = Point( warp_dst.cols / 2, warp_dst.rows / 2 );
double angle = - 50. 0;
double scale = 0. 6;
/// 通過上面的旋轉細節信息求得旋轉矩陣
rot_mat = getRotationMatrix2D( center, angle, scale );
/// 旋轉已扭曲圖像
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
/// 顯示結果
namedWindow( source_window, CV_WINDOW_AUTOSIZE );
imshow( source_window, src );
namedWindow( warp_window, CV_WINDOW_AUTOSIZE );
imshow( warp_window, warp_dst );
namedWindow( warp_rotate_window, CV_WINDOW_AUTOSIZE );
imshow( warp_rotate_window, warp_rotate_dst );
/// 等待用戶按任意按鍵退出程序
waitKey( 0);
return 0;
}
4、通過pca獲得圖像的弧度
double getOrientation(vector
<Point
>
&pts, Point2f
& pos,Mat
& img)
{
//Construct a buffer used by the pca analysis
Mat data_pts = Mat(pts.size(), 2, CV_64FC1);
for ( int i = 0; i < data_pts.rows; ++i)
{
data_pts.at < double >(i, 0) = pts[i].x;
data_pts.at < double >(i, 1) = pts[i].y;
}
//Perform PCA analysis
PCA pca_analysis(data_pts, Mat(), CV_PCA_DATA_AS_ROW);
//Store the position of the object
pos = Point2f(pca_analysis.mean.at < double >( 0, 0),
pca_analysis.mean.at < double >( 0, 1));
//Store the eigenvalues and eigenvectors
vector <Point2d > eigen_vecs( 2);
vector < double > eigen_val( 2);
for ( int i = 0; i < 2; ++i)
{
eigen_vecs[i] = Point2d(pca_analysis.eigenvectors.at < double >(i, 0),
pca_analysis.eigenvectors.at < double >(i, 1));
eigen_val[i] = pca_analysis.eigenvalues.at < double >(i, 0);
}
return atan2(eigen_vecs[ 0].y, eigen_vecs[ 0].x);
}
{
//Construct a buffer used by the pca analysis
Mat data_pts = Mat(pts.size(), 2, CV_64FC1);
for ( int i = 0; i < data_pts.rows; ++i)
{
data_pts.at < double >(i, 0) = pts[i].x;
data_pts.at < double >(i, 1) = pts[i].y;
}
//Perform PCA analysis
PCA pca_analysis(data_pts, Mat(), CV_PCA_DATA_AS_ROW);
//Store the position of the object
pos = Point2f(pca_analysis.mean.at < double >( 0, 0),
pca_analysis.mean.at < double >( 0, 1));
//Store the eigenvalues and eigenvectors
vector <Point2d > eigen_vecs( 2);
vector < double > eigen_val( 2);
for ( int i = 0; i < 2; ++i)
{
eigen_vecs[i] = Point2d(pca_analysis.eigenvectors.at < double >(i, 0),
pca_analysis.eigenvectors.at < double >(i, 1));
eigen_val[i] = pca_analysis.eigenvalues.at < double >(i, 0);
}
return atan2(eigen_vecs[ 0].y, eigen_vecs[ 0].x);
}
這個函數的目的,在於通過PCA方法,獲得當前輪廓的主要方向。
5、直接獲得
某點旋轉以后位置,這個地方使用的是弧度
//獲得單個點經過旋轉后所在精確坐標
Point2f GetPointAfterRotate(Point2f inputpoint,Point2f center, double angle){
Point2d preturn;
preturn.x = (inputpoint.x - center.x) *cos( -angle) - (inputpoint.y - center.y) *sin( -angle) +center.x;
preturn.y = (inputpoint.x - center.x) *sin( -angle) + (inputpoint.y - center.y) *cos( -angle) +center.y;
return preturn;
}
Point2f GetPointAfterRotate(Point2f inputpoint,Point2f center, double angle){
Point2d preturn;
preturn.x = (inputpoint.x - center.x) *cos( -angle) - (inputpoint.y - center.y) *sin( -angle) +center.x;
preturn.y = (inputpoint.x - center.x) *sin( -angle) + (inputpoint.y - center.y) *cos( -angle) +center.y;
return preturn;
}
此外值得注意的一點就是,其實我們可以不基於OpenCV函數,直接計算某點旋轉以后位置,采用的是數學方法。
四、使用例子
綜合使用,請查看:
感謝閱讀至此,希望有所幫助。