• 图像仿射变换之旋转变换


    需要对图像进行旋转变换,以为利用opencv会很简单,只需要调用cvGetQuadrangleSubPix函数或者cvWarpAffine函数即可。

    但是,经过实验发现:牛逼的人都是相似的,苦逼的人各有各的苦逼!!!!

     实验过程如下:

    首先从网上找了奔跑的兔子的程序,原文:opencv 任意角度旋转图像

    首先利用文章中的方法一进行实验,可惜程序报错,原来cvGetQuadrangleSubPix已不支持5个参数,变成了3个参数,也就是说插值方法和插值像素值都不再是你我能控制的了的了。

    其代码为:

    View Code
    int main()
    {
    IplImage *src,*dst, *img_tmp;
    double angle=60;
    int method =1;
    if(!(src=cvLoadImage("src.jpg",1/*CV_LOAD_IMAGE_GRAYSCALE*/)) )
    {
    return -1;
    }
    dst = cvCloneImage(src);
    dst->origin = src->origin;
    cvZero(dst);
    cvNamedWindow("at",1);
    cvShowImage("at",src);
    cvWaitKey(0);

    double anglerad = (CV_PI* (angle/180)) ;
    int newheight =int (fabs(( sin(anglerad)*src->width )) + fabs(( cos(anglerad)*src->height )) );
    int newwidth =int (fabs(( sin(anglerad)*src->height)) + fabs(( cos(anglerad)*src->width)) );
    img_tmp = cvCreateImage(cvSize(newwidth,newheight), IPL_DEPTH_8U, 3);
    cvFillImage(img_tmp,0);//目的图像 使用扩展的大小
    float m[6];
    CvMat M = cvMat( 2, 3, CV_32F, m );
    if(1==method)
    {
    //方法一 提取象素四边形,使用子象素精度

    int w = src->width;
    int h = src->height;

    m[0] = (float)(cos(angle*CV_PI/180.));
    m[1] = (float)(sin(angle*CV_PI/180.));
    m[2] = w*0.5f;
    m[3] = -m[1];
    m[4] = m[0];
    m[5] = h*0.5f;

    cvGetQuadrangleSubPix( src, dst, &M);//图像大小不变
    cvGetQuadrangleSubPix( src, img_tmp, &M);//+CV_WARP_FILL_OUTLIERS 改变图像大小

    //方法一 提取象素四边形,使用子象素精度
    }
    cvShowImage("at",dst);
    cvWaitKey(0);
    cvShowImage("at",img_tmp);
    cvWaitKey(0);


    return 0;
    }

    图像原图是

    dst输出结果是:

    img_tmp的输出结果:

    从输出图像能够发现特别让人蛋疼的事情就是插值的效果!!!没办法,用cvWarpAffine方法试试。

    当然奔跑的兔子方法二有问题,但是思路可以借鉴:

    他的代码如下,我没有试:

    View Code
      if(2==method)
    {
    //方法二 使用 二维旋转的仿射变换矩阵 存在问题 要求输入和输出图像一样大 旋转中心不对

    CvPoint2D32f center;
    center.x=float (Img_old->width/2.0+0.5);//float (Img_tmp->width/2.0+0.5);
    center.y=float (Img_old->height/2.0+0.5);//float (Img_tmp->height/2.0+0.5);
    cv2DRotationMatrix( center, angle,1, &M);

    cvWarpAffine( Img_old, dst, &M,CV_INTER_LINEAR,cvScalarAll(0) );//小图
    //小目标图像


    //对图像进行扩展
    // 只能一定角度以内 不同象限的不同对待
    int dx=int((newwidth -Img_old->width )/2+0.5);
    int dy=int((newheight-Img_old->height)/2+0.5);

    uchar* old_ptr,*temp_ptr;

    for( int y=0 ; y<Img_old->height; y++) //为了不越界
    {
    for (int x=0 ; x< Img_old->width; x++)
    {
    old_ptr = &((uchar*)(Img_old->imageData + Img_old->widthStep*y))[(x)*3];
    temp_ptr = &((uchar*)(Img_tmp->imageData + Img_tmp->widthStep*(y+dy)))[(x+dx)*3];
    temp_ptr[0]=old_ptr[0]; //green
    temp_ptr[1]=old_ptr[1]; //blue
    temp_ptr[2]=old_ptr[2]; //Red
    }
    }


    center.x=float (Img_tmp->width/2.0+0.5);
    center.y=float (Img_tmp->height/2.0+0.5);
    cv2DRotationMatrix( center, angle,1, &M);

    IplImage* temp = cvCloneImage( Img_tmp );//生成输出图像
    cvWarpAffine( Img_tmp, temp , &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );//大图
    Img_tmp=cvCloneImage( temp );

    //问题
    //cvWarpAffine( Img_tmp, Img_tmp, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );//大图

    //方法二 使用 二维旋转的仿射变换矩阵
    }

    发现这次的方法依旧不好。旋转中心的确定很让人头疼,一气之下,找了另一篇文章:图像变换——计算机视觉图像处理

    看过之后,若有所悟,于是大刀阔斧,进行了实验。

    不用旋转center了,我自己造一个m矩阵,应该也可以实现旋转功能。根据文章提到的内容,好确定矩阵的四个参数,但是平移还是不好确定。当然肯定是和旋转点或者图像原点有关,经过实验,发现了巨大的秘密,终于成功了。

    实验思路是,首先根据图像的旋转角度,确定出xmin,xmax,ymin,ymax,这样就可以计算出新图像的高度和宽度。

    计算方法是:

     xmin = xmax = ymin = ymax = 0;
     bound(nx-1,0,ca,sa,&xmin,&xmax,&ymin,&ymax);
     bound(0,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);
     bound(nx-1,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);

    其中,ca 是旋转角度的余弦值,sa是旋转角度的正旋值。nx是原图像的宽度,ny是原图像的高度。

    bound函数的功能就是确定xmin xmax ymin ymax的值。

    函数体为:

    bound函数体
    void bound(int x, int y, float ca, float sa, int *xmin, int *xmax, int *ymin, int *ymax)
    /* int x,y;
    float ca,sa;
    int *xmin,*xmax,*ymin,*ymax;
    */
    {
    int rx,ry;
    // 顺时针旋转
    rx = (int)floor(ca*(float)x+sa*(float)y);
    ry = (int)floor(-sa*(float)x+ca*(float)y);
    if (rx<*xmin) *xmin=rx; if (rx>*xmax) *xmax=rx;
    if (ry<*ymin) *ymin=ry; if (ry>*ymax) *ymax=ry;
    }

    相信大家都会看明白。

    确定了边界之后,就可以计算图像高度和宽度了:

    sx = xmax-xmin+1;
    sy = ymax-ymin+1;

    然后自己设置M矩阵的值:

     m[0] = ca;
     m[1] = sa;
     m[2] =-(float)xmin;
     m[3] =-m[1];
     m[4] = m[0];
     m[5] =-(float)ymin;

    当真是费了九牛二虎之力才找出来平移的值。其实很简单,就是将左上方的图像点,平移值图像原点。

    设置之后,调用方法cvWarpAffine( src, newImage, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );一直正常了。

    完整代码为:

    完整代码,可运行
    int main()
    {
    IplImage *src,*dst, *img_tmp;
    double angle=60;
    int method =2;
    if(!(src=cvLoadImage("src.jpg",1/*CV_LOAD_IMAGE_GRAYSCALE*/)) )
    {
    return -1;
    }
    dst = cvCloneImage(src);
    dst->origin = src->origin;
    cvZero(dst);
    cvNamedWindow("at",1);
    cvShowImage("at",src);
    cvWaitKey(0);

    double anglerad = (CV_PI* (angle/180)) ;
    int newheight =int (fabs(( sin(anglerad)*src->width )) + fabs(( cos(anglerad)*src->height )) );
    int newwidth =int (fabs(( sin(anglerad)*src->height)) + fabs(( cos(anglerad)*src->width)) );
    img_tmp = cvCreateImage(cvSize(newwidth,newheight), IPL_DEPTH_8U, 3);
    cvFillImage(img_tmp,0);//目的图像 使用扩展的大小
    float m[6];
    CvMat M = cvMat( 2, 3, CV_32F, m );
    if(1==method)
    {
    //方法一 提取象素四边形,使用子象素精度

    int w = src->width;
    int h = src->height;

    m[0] = (float)(cos(angle*CV_PI/180.));
    m[1] = (float)(sin(angle*CV_PI/180.));
    m[2] = w*0.5f;
    m[3] = -m[1];
    m[4] = m[0];
    m[5] = h*0.5f;

    cvGetQuadrangleSubPix( src, dst, &M);//图像大小不变
    cvGetQuadrangleSubPix( src, img_tmp, &M);//+CV_WARP_FILL_OUTLIERS 改变图像大小
    cvShowImage("at",dst);
    cvWaitKey(0);
    cvShowImage("at",img_tmp);
    //方法一 提取象素四边形,使用子象素精度
    }

    if (2==method)
    {
    int nx,ny;
    float ca,sa;
    int xmin,xmax,ymin,ymax,sx,sy;

    ca = (float)cos((double)(angle)*CV_PI/180.0);
    sa = (float)sin((double)(angle)*CV_PI/180.0);
    nx = src->width;
    ny=src->height;
    xmin = xmax = ymin = ymax = 0;
    bound(nx-1,0,ca,sa,&xmin,&xmax,&ymin,&ymax);
    bound(0,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);
    bound(nx-1,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);

    sx = xmax-xmin+1;
    sy = ymax-ymin+1;
    IplImage* newImage;
    newImage=cvCreateImage(cvSize(sx,sy),src->depth,src->nChannels);
    m[0] = ca;
    m[1] = sa;
    m[2] =-(float)xmin;
    m[3] =-m[1];
    m[4] = m[0];
    m[5] =-(float)ymin;
    cvWarpAffine( src, newImage, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );
    cvNamedWindow("newImage");
    cvShowImage("newImage",newImage);
    cvWaitKey(0);
    }
    cvWaitKey(0);
    return 0;
    }

    方法二效果图为:

    方法三,处理的是灰度单通道图像。刚开始,将彩色图像处理成灰度图像,但仍然是三通道图像,因此实验总是不成功,为此抑郁了好几天。今天终于开窍了。但,遇到的问题是,取设置图像时,好像只能用uchar类型,用float int 等,出错,这点不理想。

    请看方法三代码:

    方法三代码
    if(3==method)
    {
    gray = cvCreateImage(cvSize(src->width,src->height),8,1);
    if (src->nChannels!=1)
    {
    cvCvtColor(src,gray,CV_BGR2GRAY);
    src = gray;
    }

    int x,y, x1,y1,adr,nx,ny;
    float ca,sa,xp,yp,a11,a12,a21,a22,ux,uy,xtrans,ytrans;
    int tx1,ty1,tx2,ty2,xmin,xmax,ymin,ymax,sx,sy;
    ca = (float)cos((double)(angle)*CV_PI/180.0);
    sa = (float)sin((double)(angle)*CV_PI/180.0);
    nx = src->width;
    ny=src->height;
    xmin = xmax = ymin = ymax = 0;
    bound(nx-1,0,ca,sa,&xmin,&xmax,&ymin,&ymax);
    bound(0,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);
    bound(nx-1,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);

    sx = xmax-xmin+1;
    sy = ymax-ymin+1;
    xtrans = ytrans = 0.0;
    IplImage* method3Img;
    method3Img=cvCreateImage(cvSize(sx,sy),IPL_DEPTH_8U,1);

    for (x=xmin;x<=xmax;x++)
    for (y=ymin;y<=ymax;y++) {
    xp = ca*(float)x-sa*(float)y + xtrans;
    yp = sa*(float)x+ca*(float)y + ytrans;
    x1 = (int)floor(xp);
    y1 = (int)floor(yp);
    ux = xp-(float)x1;
    uy = yp-(float)y1;
    /*CV_IMAGE_ELEM(image, uchar, yp,xp);*/


    /*adr = y1*nx+x1;*/
    tx1 = (x1>=0 && x1<nx);
    tx2 = (x1+1>=0 && x1+1<nx);
    ty1 = (y1>=0 && y1<ny);
    ty2 = (y1+1>=0 && y1+1<ny);
    /*v_11f = ( (float*)(src->imageData + src->widthStep*y1) )[x1];
    v_12f = ( (float*)(src->imageData + src->widthStep*(y1+1)) )[x1];
    v_21f = ( (float*)(src->imageData + src->widthStep*y1) )[x1+1];
    v_22f = ( (float*)(src->imageData + src->widthStep*(y1+1)) )[x1+1];
    */
    a11 = (tx1 && ty1? CV_IMAGE_ELEM(src,uchar,y1,x1):255);
    a12 = (tx1 && ty2? CV_IMAGE_ELEM(src,uchar,y1+1,x1):255);
    a21 = (tx2 && ty1? CV_IMAGE_ELEM(src,uchar,y1,x1+1):255);
    a22 = (tx2 && ty2? CV_IMAGE_ELEM(src,uchar,y1+1,x1+1):255);
    if(a11<255.0)
    {
    float tmp = CV_IMAGE_ELEM(src,uchar,y1,x1);
    int x = a11;
    }
    /*out[(y-ymin)*sx+x-xmin] =
    (1.0-uy)*((1.0-ux)*a11+ux*a21)+uy*((1.0-ux)*a12+ux*a22);
    */
    float value = (1.0-uy)*((1.0-ux)*a11+ux*a21)+uy*((1.0-ux)*a12+ux*a22);
    CV_IMAGE_ELEM(method3Img, uchar, y-ymin, x-xmin ) =cvFloor(value);
    }

    cvNamedWindow("newImage3");
    cvShowImage("newImage3",method3Img);
    cvWaitKey(0);

    }

    效果图是:

  • 相关阅读:
    python 基础——generate生成器
    python 基础——*args和**kwargs
    猴子补丁
    python 元类——metaclass
    javascript 数据类型
    C/C++语言实现单链表(带头结点)
    C++仿函数和回调函数的异同
    C++利用动态数组实现顺序表(不限数据类型)
    const的用法
    数据结构之线性表
  • 原文地址:https://www.cnblogs.com/slysky/p/2410743.html
Copyright © 2020-2023  润新知