• 对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函数,直接计算某点旋转以后位置,采用的是数学方法。
     
     
    四、使用例子
    综合使用,请查看:
    感谢阅读至此,希望有所帮助。
     
     
     
     





  • 相关阅读:
    Java多线程——volatile关键字、发布和逸出
    线程安全性的基础知识
    maven web不能创建src/main/java等文件等问题
    web环境中的spring MVC
    Spring AOP 概述
    golang统计出其中英文字母、空格、数字和其它字符的个数
    go语言求1到100之内的质数
    golang fmt占位符
    golang---map类型
    golang切片类型
  • 原文地址:https://www.cnblogs.com/jsxyhelu/p/16947900.html
Copyright © 2020-2023  润新知