zoukankan      html  css  js  c++  java
  • 【OpenCV学习笔记】之六 手写图像旋转函数---万丈高楼平地起

    话说,平凡之处显真格,这一点也没错!  比如,对旋转图像进行双线性插值,很简单吧?  可,对我,折腾了大半天,也没有达到预期效果!  尤其是三个误区让我抓瞎好久:

    1,坐标旋转公式。   这东西,要用的时候查资料,抄过来,从不记清,猛地一下让人写正确,确实不容易,虽然只是正余弦的排列问题。画图推导的方法也是知道,但是,奈何又记不得三角形的和角展开公式。没办法,只好逐一测试验证了,心血经验,45、90,135,180这几个角度最好都验证一下。

    2,双插的数据来源。 一开始,思维上习惯地数据来源认定应该是旋转之后的,为此施展多种手段都不能较好克服数据有效性、配对性等异常。搞个带掩模的3*3滤波吧,却使图像变模糊了。  绝境反思,数据来源取自源图数据,该是多好的事呀。 仿射变换 warpAffine() 函数中的仿射矩阵就是默认为逆向。

    3,双插的方法。 一直来,都知道X、Y方向要各插值一次,但却不明确它们的相互关系是 串行,而非并行!


    以下贴出我后来完善出的旋转部分代码,有路过的高手请帮忙指点优化一下:


    //旋转 平移 点坐标,依据旋转矩阵而来
    void rotatePoint(const Point2d& src, Point2d& dst, const double angle, const Point2d& offset=Point2f(0,0))
    {
        const double cosAngle =cos(angle);
        const double sinAngle =sin(angle);
        dst.x = src.x * cosAngle + src.y * sinAngle + offset.x;
        dst.y = src.y * cosAngle - src.x * sinAngle + offset.y;
    
    }
    
    //旋转 平移 点坐标 angle中的x值为 cos(angle)  y为sin(angle)
    inline void rotatePoint(const Point2d& src, Point2d& dst, const Point2d& angle, const Point2d& offset=Point2f(0,0))
    {
        //dst.x = src.dot(angle) + offset.x;
        dst.x = src.x * angle.x + src.y * angle.y + offset.x;
        dst.y = src.y * angle.x - src.x * angle.y + offset.y;
    
    }
    
    //双线性插值  a为左上点 b右上 c左下 d右下 权重因子Sx Sy 的取值范围为(0 , 1)由小坐标指向大坐标距离比
    template<typename T> 
    inline double insertDLine(const T a, const T b, const T c, const T d, const double Sx, const double Sy)
    {
        const double Sx1 = 1 -Sx;
        const double Sy1 = 1 -Sy;
        return (a *Sx1 *Sy1 + b *Sx *Sy1 + c *Sx1 *Sy + d *Sx *Sy);
    }
    
    //双线性插值  dst为data图像中的2*2子块 权重因子Sx Sy 的取值范围为(0 , 1)由小坐标指向大坐标距离比
    void insertDLine(const Mat& src, Scalar& dst,const double Sx, const double Sy)
    {
        const int channels =src.channels();
    
        const uchar *pU = src.ptr(0);
        const uchar *pD = src.ptr(1);
        const int depth =min(4, channels);
        for (int i = 0; i < depth; i++)
        {
            dst[i] = insertDLine(pU[i], pU[i + channels], pD[i], pD[i + channels], Sx, Sy);
        }
    
    }
    
    //将源图像旋转一定的角度 
    int rotateImage(const Mat& src, Mat& dst, double angle, const bool isDegree)
    {
        const int channels =src.channels();
        if(channels > 4)
        {
            //dst=src;  // 
            return -1;
        }
    
        //将角度化为弧度 
        if(isDegree)
        {
            angle *=CV_PI/180;
        }
    
        //参数初始化
    
        const double cosAngle =cos(angle);
        const double sinAngle =sin(angle);
    
        const int srcRows = src.rows;
        const int srcCols = src.cols;
    
        const int  dstCols =srcRows *abs(sinAngle) + srcCols *abs(cosAngle);
        const int  dstRows =srcRows *abs(cosAngle) + srcCols *abs(sinAngle);
    
        const int srcRowsLess2 = srcRows -2;
        const int srcColsLess2 = srcCols -2;
    
        const Point2d centerA(srcCols/2 +0.5, srcRows/2 +0.5);
        const Point2d centerB(dstCols/2 +0.5, dstRows/2 +0.5);
        const Point2d rotateAngle(cosAngle, -sinAngle);  //用于从目标图回旋转到初始图,角度取反
    
        Point2d hitPoint;
        int xL, yL;
        Rect insertROI(0, 0, 2, 2);
        Scalar insertVaule;
    
        //申请内存空间,并设置为 0
        dst.create(dstRows, dstCols, CV_8UC(channels));
        dst.setTo(Scalar(0,0,0,0));
    
        for (int i = 0; i < dstRows; i++)
        {
            uchar *pDst=dst.ptr(i);
            for (int j = 0; j  < dstCols; j ++)
            {
                rotatePoint(Point2d(j, i)-centerB, hitPoint, rotateAngle, centerA);
                xL =floor(hitPoint.x);
                yL =floor(hitPoint.y);
                
                //从目标图中回转至源图像,不在区域内的直接跳过
                if (xL < 0 || xL > srcColsLess2 || yL < 0 || yL > srcRowsLess2)
                {
                    continue;
                }
    
                insertROI.x =xL;
                insertROI.y =yL;
                insertDLine(src(insertROI), insertVaule, hitPoint.x -xL, hitPoint.y -yL);
            
                int base =j *channels;
                for (int z = 0; z < channels; z++)
                {
                    pDst[base +z] = insertVaule[z];
                }
    
            }
        }
    
        return 0;
    
    }
    
    
    
    
    
    



    为了展示我的手写旋转函数rotateImage() 与仿射变换 warpAffine() 函数的效果比较,有如下代码段:

        double angle =90.0 * CV_PI/180; //将角度化为弧度 30
        Mat rotateImg;
        rotateImage(colorImg, rotateImg, angle);
    
        const double cosAngle =cos(angle);
        const double sinAngle =sin(angle);
    
        Mat rrM =(Mat_<double>(2,3) << cosAngle, sinAngle, cosAngle *colorImg.rows,  -sinAngle, cosAngle, sinAngle *colorImg.cols);
        Mat rotatewarpAffine; 
        warpAffine(colorImg, rotatewarpAffine, rrM, colorImg.size()*2);
    
    
    


    旋转30度时:

    rotateImage30



    warpAffine 30

    旋转90度时:

    rotateImage90.jpg



    warpAffine 90



  • 相关阅读:
    http://maxie.cnblogs.com/
    有一种爱叫错过
    Lotuser进阶系列(转)——多目录环境中的单点登陆1
    DOMINO中实现PDF在线编辑控件 and so on......(三)
    DOMINO中实现PDF在线编辑控件 and so on......(一)
    Lotuser进阶系列(转)——多目录环境中的单点登陆2
    在两个代理之间传递参数
    利用 DSAPI 为 Domino Web 用户定制用户名和口令认证
    通过 Lotus Domino Java 代理消费 Web 服务
    代理中如何获取参数么?
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3266737.html
Copyright © 2011-2022 走看看