对OpenCV中“旋转”的思考和实验
我记得曾经有人对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 */
那么它存在的问题就是只能旋转具体的角度(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); }
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; }
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); }
这个函数的目的,在于通过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; }
此外值得注意的一点就是,其实我们可以不基于OpenCV函数,直接计算某点旋转以后位置,采用的是数学方法。
感谢阅读至此,希望有所帮助。