图像几何变换时为何要用到插值算法?_24、opencv图像仿射变换

图像的几何操作,也就是那些起源于三维几何和投影几何的变换。这种操作包括均匀和非均匀调整大小(后者称为扭曲)。执行这些操作的原因很多:例如,扭曲和旋转图像,以便它可以叠加在现有场景上或人为地放大用于对象识别的一组训练图像.可以执行的功能如拉伸,收缩,扭曲和旋转图像被称为几何变换。对于平面区域,有两种风格的几何变换:使用2×3矩阵的变换,称为仿射变换;基于3×3矩阵进行变换,这被称为透视变换。

仿射变换是任何可以用矩阵乘法和矢量加法形式表示的变换。在OpenCV中,表示这种转换的标准风格是2×3矩阵。定义:

7d58601d86dfcd236b39bd2c21a86cc7.png

可以很容易地看到仿射变换A·X + B的效果恰好等于将向量X扩展到向量X'中,并简单地将X'左乘T。

仿射变换可以如下表示:平面中的任何平行四边形ABCD可以通过一些仿射变换映射到任何其他平行四边形A'B'C'D'。如果这些平行四边形的面积不为零,则隐含的仿射变换由两个平行四边形的(三个顶点)唯一地定义。可以想象一个仿射变换,把图像绘制成一张大橡胶片,然后通过变形来制作不同种类的平行四边形。

当多个图像是同一个对象的不同的视图,想要计算与不同视图相关的实际变换。在这种情况下,经常使用仿射变换而不是透视变换来对视图建模,因为它们具有较少的参数,因此更容易解决。缺点是只能通过单应性来模拟真实的视角扭曲,所以仿射变换产生的表示不能满足视图之间所有可能的关系。

仿射变换可以将矩形转换为平行四边形。可以挤压形状,但必须保持两侧平行;可以旋转或缩放。透视转换提供了更大的灵活性;透视变换可以将矩形变成任意的四边形

仿射变换有两种情况。在第一种情况下,有一个想要转换的图像; 在第二种情况下,有一个想要计算转换结果的点列表。 这些情况在概念上非常相似,但在实际方面却非常不同。 因此,OpenCV有两种不同的功能。

在第一种情况下,输入和输出格式是图像,并且隐含的要求是扭曲假定像素是底层图像的密集表示。 这意味着图像变形必须处理插值,以便输出图像平滑且看起来很自然。 OpenCV提供的用于密集变换的仿射变换函数是warpAffine():

void cv::warpAffine(

cv::InputArray src,

cv::OutputArray dst,

cv::InputArray M,

cv::Size dsize,

int flags = cv::INTER_LINEAR,

int borderMode = cv::BORDER_CONSTANT,

const cv::Scalar& borderValue = cv::Scalar()

);

这里src和dst分别是源和目标图像。 输入M是前面介绍的2×3矩阵,用于量化所需的变换。目标图像中的每个元素都是从源图中计算的,如下所示:

69efea9e80a17fe2d586f5a5c81a492c.png

然而,一般来说,这个方程右边所指的位置不是一个整数像素。在这种情况下,有必要使用插值为dst(x,y)找到合适的值。另外还有一个选项WARP_INVERSE_MAP。这个选项允许从dst到src反向扭曲,而不是从src到dst。最后参数用于边界,并且与图像卷积中的参数具有相同的含义。

OpenCV提供了两个函数来帮助生成矩阵M,第一个用于已经有两个图像,知道这两个图像通过仿射变换相关或者希望以这种方式近似:

cv::Mat cv::getAffineTransform(

const cv::Point2f* src,

const cv::Point2f* dst

);

这里src和dst是包含三个二维点的数组。 返回值是一个数组,它是从这些点计算出来的仿射变换。

例1显示了一些使用这些函数的代码。在这个例子中,首先通过构造两个三分量点,然后使用getAffineTransform()将其转换为实际转换矩阵来获得warpAffine()矩阵参数。然后做一个仿射变形,然后旋转图像。对于源图像中的代表性点阵列,取三点:(0,0),(0,height-1)和(width-1,0)。 然后指定这些点将映射到相应数组中的位置。

例1 仿射变换示意

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
 Mat src = imread("E:/仿射变换.jpg", 1);
namedWindow("原图", 0);
 imshow("原图", src);
 Point2f srcTri[] = 
 {
 Point2f(0,0), 
 Point2f(src.cols - 1, 0), 
 Point2f(0, src.rows - 1) 
 };
 Point2f dstTri[] = {
 Point2f(src.cols*0.f, src.rows*0.33f), 
 Point2f(src.cols*0.85f, src.rows*0.25f), 
 Point2f(src.cols*0.15f, src.rows*0.7f) 
 };
 // 计算仿射矩阵
 Mat warp_mat = cv::getAffineTransform(srcTri, dstTri);
 Mat dst, dst2;
 warpAffine(
 src,
 dst,
 warp_mat,
 src.size(),
 INTER_LINEAR,
 BORDER_CONSTANT,
 Scalar()
 );
 for (int i = 0; i < 3; ++i)
 circle(dst, dstTri[i], 5, cv::Scalar(255, 0, 255), -1);
 namedWindow("仿射变换", 0);
 imshow("仿射变换", dst);
 waitKey();
 for (int frame = 0;; ++frame) 
 {
 Point2f center(src.cols*0.5f, src.rows*0.5f);
 double angle = frame * 3 % 360, scale = (cos((angle - 60)* CV_PI / 180) + 1.05)*0.8;
 Mat rot_mat = getRotationMatrix2D(center, angle, scale);
 warpAffine(
 src,
 dst2,
 rot_mat,
 src.size(),
 INTER_LINEAR,
 BORDER_CONSTANT,
 Scalar()
 );
 namedWindow("仿射变换图像", 0);
 imshow("仿射变换图像", dst2);
 if (waitKey(30) >= 0)
 break;
 }
 waitKey(0);
 return 0;
}

bb41309e69fbe3159625566907f69bde.png

8603ef01e6db1bf6e0fd5f91c09880e9.gif

计算映射矩阵的第二种方法是使用cv :: getRotationMatrix2D(),它可以计算围绕某个任意点的旋转的映射矩阵,并结合可选的缩放比例。

cv::Mat cv::getRotationMatrix2D(

cv::Point2f center,

double angle,

double scale

);

第一个参数center是旋转的中心点。 接下来的两个参数给出了旋转的大小和缩放比例。 该函数返回映射矩阵M,它是浮点数的2×3矩阵。

warpAffine()是处理密集映射,对于稀疏映射,最好使用transform()。

void cv::transform(

cv::InputArray src,

cv::OutputArray dst,

cv::InputArray mtx

);

通常,src是具有Ds通道的N×1阵列,其中N是要变换的点的数量,Ds是这些源点的维度。 输出数组dst将具有相同的大小,但可能具有不同数量的通道Dd。 变换矩阵mtx是一个Ds×Dd矩阵,然后将其应用于src的每个元素,之后将结果放入dst

给定以2×3矩阵表示的仿射变换,通常希望能够计算逆变换,其可用于将所有变换点“放回”到它们来自的地方。这是用invertAffineTransform()实现的:

void cv::invertAffineTransform(

cv::InputArray M,

cv::OutputArray iM

);

这个函数需要一个2×3的数组M,并返回另一个2×3的数组iM,它反转M。注意,cv :: invertAffineTransform()实际上不作用于任何图像,它只提供逆变换。一旦有了iM,就可以像使用M一样使用它,可以使用cv :: warpAffine()或cv :: transform()。

在实际应用中,可以利用仿射变换将倾斜的图像旋正。如下图所示,如果对于识别的字符是倾斜的,要进行字符分割可能不准确,尤其是分割算法要适应在线检测的大量图像的时候,这时,可以采用仿射变换将字符区域旋转到水平位置,这时再利用分割算法就可以适应大规模的图像分割。

9fb67bce9cf92d9b18338e2d9167b161.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值