Opencv图像识别从零到精通(30)---重映射,仿射变换

一、序言

面对图像处理的时候,我们会旋转缩放图像,例如前面所提高的resize 插值改变,也是几何变换:

几何运算需要空间变换和灰度级差值两个步骤的算法,像素通过变换映射到新的坐标位置,新的位置可能是在几个像素之间,即不一定为整数坐标。这时就需要灰度级差值将映射的新坐标匹配到输出像素之间。最简单的插值方法是最近邻插值,就是令输出像素的灰度值等于映射最近的位置像素,该方法可能会产生锯齿。这种方法也叫零阶插值,相应比较复杂的还有一阶和高阶插值。

除了插值算法感觉只要了解就可以了,图像处理中比较需要理解的还是空间变换

空间变换对应矩阵的仿射变换。一个坐标通过函数变换的新的坐标位置:

所以在程序中我们可以使用一个2*3的数组结构来存储变换矩阵:

以最简单的平移变换为例,平移(b1,b2)坐标可以表示为:

因此,平移变换的变换矩阵及逆矩阵记为:

缩放变换:将图像横坐标放大(或缩小)sx倍,纵坐标放大(或缩小)sy倍,变换矩阵及逆矩阵为:

选择变换:图像绕原点逆时针旋转a角,其变换矩阵及逆矩阵(顺时针选择)为:

二、重映射

重映射:

把一个图像中一个位置的像素放置到另一个图片指定位置的过程.

为了完成映射过程, 有必要获得一些插值为非整数像素坐标,因为源图像与目标图像的像素坐标不是一一对应的.

简单的说就是改变图片的位置(左,右,上,下,颠倒)

<span style="font-size:18px;">C++: void remap(InputArray src, OutputArraydst, InputArray map1, InputArray map2, int interpolation, intborderMode=BORDER_CONSTANT
, const Scalar& borderValue=Scalar())  </span>
    • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位或者浮点型图像。
    • 第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放函数调用后的输出结果,需和源图片有一样的尺寸和类型。
    • 第三个参数,InputArray类型的map1,它有两种可能的表示对象。
      • 表示点(x,y)的第一个映射。
      • 表示CV_16SC2 , CV_32FC1 或CV_32FC2类型的X值。
    • 第四个参数,InputArray类型的map2,同样,它也有两种可能的表示对象,而且他是根据map1来确定表示那种对象。
      • 若map1表示点(x,y)时。这个参数不代表任何值。
      • 表示CV_16UC1 , CV_32FC1类型的Y值(第二个值)。

  • 第五个参数,int类型的interpolation,插值方式,之前的resize( )函数中有讲到,需要注意,resize( )函数中提到的INTER_AREA插值方式在这里是不支持的,所以可选的插值方式如下:
  • INTER_NEAREST - 最近邻插值
  • INTER_LINEAR – 双线性插值(默认值)
  • INTER_CUBIC – 双三次样条插值(逾4×4像素邻域内的双三次插值)
  • INTER_LANCZOS4 -Lanczos插值(逾8×8像素邻域的Lanczos插值)

  • 第六个参数,int类型的borderMode,边界模式,有默认值BORDER_CONSTANT,表示目标图像中“离群点(outliers)”的像素值不会被此函数修改。
  • 第七个参数,const Scalar&类型的borderValue,当有常数边界时使用的值,其有默认值Scalar( ),即默认值为0。

其中要变换的模式如下

三、仿射变换

仿射变换(Affine Transformation)是空间直角坐标系的变换,从一个二维坐标变换到另一个二维坐标,仿射变换是一个线性变换,他保持了图像的“平行性”和“平直性”,即图像中原来的直线和平行线,变换后仍然保持原来的直线和平行线,仿射变换比较常用的特殊变换有平移(Translation)、缩放(Scale)、翻转(Flip)、旋转(Rotation)和剪切(Shear)。

其中,点1, 2 和 3 (在图一中形成一个三角形) 与图二中三个点是一一映射的关系, 且他们仍然形成三角形, 但形状已经和之前不一样了。我们能通过这样两组三点求出仿射变换 (可以选择自己喜欢的点),
接着就可以把仿射变换应用到图像中去。

而我们通常使用2 x 3的矩阵来表示仿射变换。

考虑到我们要使用矩阵 A 和 B 对二维向量 做变换,
所以也能表示为下列形式:

  或者
    

即:       

也可以理解是坐标系的旋转和缩放、平移

<span style="font-size:18px;">C++: void warpAffine(InputArray src,OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, intborderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())  </span>
  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
  • 第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,需和源图片有一样的尺寸和类型。
  • 第三个参数,InputArray类型的M,2×3的变换矩阵。
  • 第四个参数,Size类型的dsize,表示输出图像的尺寸。
  • 第五个参数,int类型的flags,插值方法的标识符。此参数有默认值INTER_LINEAR(线性插值),可选的插值方式如下:
    • INTER_NEAREST - 最近邻插值
    • INTER_LINEAR - 线性插值(默认值)
    • INTER_AREA - 区域插值
    • INTER_CUBIC –三次样条插值
    • INTER_LANCZOS4 -Lanczos插值
    • CV_WARP_FILL_OUTLIERS - 填充所有输出图像的象素。如果部分象素落在输入图像的边界外,那么它们的值设定为 fillval.
    • CV_WARP_INVERSE_MAP –表示M为输出图像到输入图像的反变换,即 。因此可以直接用来做象素插值。否则, warpAffine函数从M矩阵得到反变换。
  • 第六个参数,int类型的borderMode,边界像素模式,默认值为BORDER_CONSTANT。
  • 第七个参数,const Scalar&类型的borderValue,在恒定的边界情况下取的值,默认值为Scalar(),即0。

四、例子

<span style="font-size:18px;">#include<opencv2/opencv.hpp>
#include<iostream>
#include<vector>
using namespace cv;
using namespace std;
int main()
{
    Mat srcImage = imread("lena.jpg", 1);
    imshow("【原图】", srcImage);
    Mat grayImage;
    cvtColor(srcImage, grayImage, CV_BGR2GRAY);
    Mat XImage, YImage;
    Mat dstImage;
    dstImage.create(srcImage.size(), srcImage.type());
    XImage.create(srcImage.size(), CV_32FC1);
    YImage.create(srcImage.size(), CV_32FC1);
    for (int i = 0; i < srcImage.rows; i++)
    {
        for (int j = 0; j < srcImage.cols; j++)
        {
            XImage.at<float>(i, j) = static_cast<float>(srcImage.cols - j);
            YImage.at<float>(i, j) = static_cast<float>(i);
        }
    }
    remap(srcImage, dstImage, XImage, YImage, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));
    imshow("【重映射后】", dstImage);
    waitKey(0);
    return 0;
}  </span>

<span style="font-size:18px;">#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";
 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("lena.jpg", 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() );
   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;
  }</span>

五、matlab

f=imread('d:\lena.jpg');
tform=maketform('affine',[-1 0 0;0 1 0;0 0 1]);
ff=imtransform(f,tform);
imshow(f)
figure
imshow(ff)

图像识别算法交流 QQ群:145076161,欢迎图像识别与图像算法,共同学习与交流

时间: 2024-10-10 08:08:19

Opencv图像识别从零到精通(30)---重映射,仿射变换的相关文章

Opencv图像识别从零到精通(33)----moravec角点、harris角点

一.角点 图像处理和与计算机视觉领域,兴趣点(interest points),或称作关键点(keypoints).特征点(feature points) 被大量用于解决物体识别,图像识别.图像匹配.视觉跟踪.三维重建等一系列的问题.我们不再观察整幅图,而是选择某些特殊的点,然后对他们进行局部有的放矢的分析.如果能检测到足够多的这种点,同时他们的区分度很高,并且可以精确定位稳定的特征,那么这个方法就有使用价值. 图像特征类型可以被分为如下三种: <1>边缘                   

Opencv图像识别从零到精通(7)----图像平移、旋转、镜像

根据vc6.0c++的学习经验,如果可以很好的自己编程,让图像进行平移旋转这些操作,那么就好像能够清楚的看见图像的内部结构当然这里你怎么访问像素,这个可以自己选一种适合的,最多的是ptr指针,at也是挺多的.看着很简单的变换,可以对图像处理上手的更快,当然对于旋转可能就稍微i难了一点,不过opencv提供了resize(0,remap()等这样的函数,可以方便的让我们进行学习-特别是旋转的时候,有很多的变换,你可以任意旋转一个角度,也可能一直旋转,当然还可以保持图像大小不变的旋转和大小变换的旋转

Opencv图像识别从零到精通(26)---分水岭

分水岭是区域分割三个方法的最后一个,对于前景背景的分割有不错的效果. 分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭.分水岭的概念和形成可以通过模拟浸入过程来说明.在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭. 分水岭

Opencv图像识别从零到精通(24)------漫水填充,种子填充,区域生长、孔洞填充

可以说从这篇文章开始,就结束了图像识别的入门基础,来到了第二阶段的学习.在平时处理二值图像的时候,除了要进行形态学的一些操作,还有有上一节讲到的轮廓连通区域的面积周长标记等,还有一个最常见的就是孔洞的填充,opencv这里成为漫水填充,其实也可以叫种子填充,或者区域生长,基本的原理是一样的,但是应用的时候需要注意一下,种子填充用递归的办法,回溯算法,漫水填充使用堆栈,提高效率,同时还提供了一种方式是扫描行.经常用来填充孔洞,现在来具体看看. 漫水填充:也就是用一定颜色填充联通区域,通过设置可连通

Opencv图像识别从零到精通(32)----直方图对比,模版匹配,方向投影

0.预备知识 归一化就是要把需要处理的数据经过处理后(通过某种算法)限制在你需要的一定范围内. 函数原型: <span style="font-size:18px;">void normalize(InputArray src,OutputArray dst, double alpha=1,doublebeta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray() ) </span>

Opencv图像识别从零到精通(13)----点线圆矩形与鼠标事件

图像中不可少的元素就是点.线.圆.椭圆.矩形,多边形,同时这些也是物体的特征组成单位,在图像识别中必不可少.所以要首先去认识这个元素怎么定义和使用,同时鼠标是电脑的窗口,我们很多的处理都会用到鼠标.本文主要有下面三个部分: (1) 点.线.圆.椭圆.矩形的基础应用 (2)点.线.圆.椭圆.矩形的进阶应用 (3)鼠标事件 一.点.线.圆.椭圆.矩形的基础应用 绘制点的函数: Point a = Point (600,600); 文字函数putText()函数 void putText( CvArr

Opencv图像识别从零到精通(29)-----图像金字塔,向上上下采样,resize插值

金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似.我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低 一.两个金字塔 高斯金字塔(Gaussianpyramid): 用来向下采样,主要的图像金字塔 拉普拉斯金字塔(Laplacianpyramid): 用来从金字塔低层图像重建上层未采样图像,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用. 高斯金字塔不同(DoG)又称为拉普拉斯金字塔,给出计算方式前,先加强一下定义 记得在上面我

Opencv图像识别从零到精通(28)----Kmeans

K-means算法算是个著名的聚类算法了,不仅容易实现,并且效果也不错,训练过程不需人工干预,实乃模式识别等领域的居家必备良品啊,今天就拿这个算法练练手.属于无监督学习中间接聚类方法中的动态聚类 流程: 1.随机选取样本中的K个点作为聚类中心 2.计算所有样本到各个聚类中心的距离,将每个样本规划在最近的聚类中 3.计算每个聚类中所有样本的中心,并将新的中心代替原来的中心 4.检查新老聚类中心的距离,如果距离超过规定的阈值,则重复2-4,直到小于阈值 聚类属于无监督学习,以往的回归.朴素贝叶斯.S

Opencv图像识别从零到精通(27)---grabcut

这是基于图论的分割方法,所以开始就先介绍了 Graph cuts,然后再到Grab cut   一. Graph cuts Graph cuts是一种十分有用和流行的能量优化算法,在计算机视觉领域普遍应用于前背景分割(Image segmentation).立体视觉(stereo vision).抠图(Image matting)等. 此类方法把图像分割问题与图的最小割(min cut)问题相关联.首先用一个无向图G=<V,E>表示要分割的图像,V和E分别是顶点(vertex)和边(edge)