OpenCV入门教程03.11:仿射变换

索引地址:系列索引

几何操作

仿射变换是指在向量空间中进行一次线性变换(乘以一个矩阵)并加上一个平移(加上一个向量),变换为另一个向量空间的过程。在有限维的情况下,每个仿射变换可以由一个矩阵A和一个向量b给出,它可以写作A和一个附加的列b。一个仿射变换对应于一个矩阵和一个向量的乘法,而仿射变换的复合对应于普通的矩阵乘法,只要加入一个额外的行到矩阵的底下,这一行全部是0除了最右边是一个1,而列向量的底下要加上一个1。

Affine Transform描述了一种二维仿射变换的功能,它是一种二维坐标之间的线性变换,保持二维图形的“平直性”(即变换后直线还是直线,圆弧还是圆弧)和“平行性”(其实是保持二维图形间的相对位置关系不变,平行线还是平行线,而直线上的点位置顺序不变,另特别注意向量间夹角可能会发生变化)。仿射变换可以通过一系列的原子变换的复合来实现包括:平移(Translation)、缩放(Scale)、翻转(Flip)、旋转(Rotation)和错切(Shear).

本篇文章使用的是仿射变换函数。

利用opencv实现仿射变换一般会涉及到warpAffine和getRotationMatrix2D两个函数,其中warpAffine可以实现一些简单的重映射,而getRotationMatrix2D可以获得旋转矩阵。

函数原型:

1
2
3
4
5
6
7
8
void cv::warpAffine     (   InputArray      src,
OutputArray dst,
InputArray M,
Size dsize,
int flags = INTER_LINEAR,
int borderMode = BORDER_CONSTANT,
const Scalar & borderValue = Scalar()
)
  • src: 输入图像
  • dst: 输出图像,尺寸由dsize指定,图像类型与原图像一致
  • M: 2X3的变换矩阵
  • dsize: 指定图像输出尺寸
  • flags: 插值算法标识符,有默认值INTER_LINER

平移

图像的平移也分为两步:首先定义好图像的平移矩阵,分别指定x方向和y方向上的平移量tx和ty,平移矩阵的形式如下:

M=[10tx01ty]M = \begin{bmatrix} 1 & 0 & t_{x} \\ 0 & 1 & t_{y} \end{bmatrix}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <opencv2/opencv.hpp>

using namespace cv;

int main() {
cv::Mat src = cv::imread("1.jpg");
cv::Mat dst;

cv::Size dst_sz = src.size();

//定义平移矩阵
cv::Mat t_mat = cv::Mat::zeros(2, 3, CV_32FC1);

t_mat.at<float>(0, 0) = 1;
t_mat.at<float>(0, 2) = 20; //水平平移量tx
t_mat.at<float>(1, 1) = 1;
t_mat.at<float>(1, 2) = 10; //竖直平移量ty

//根据平移矩阵进行仿射变换
cv::warpAffine(src, dst, t_mat, dst_sz);

//显示平移效果
cv::imshow("image", src);
cv::imshow("result", dst);

cv::waitKey(0);

return 0;
}

测试效果如图:

move

镜像

在opencv2和opencv中,cv::flip()支持图像的翻转(上下翻转、左右翻转,以及同时均可)。

函数原型:

1
2
3
4
5
void cv::flip(
cv::InputArray src, // 输入图像
cv::OutputArray dst, // 输出
int flipCode = 0 // >0: 沿y-轴翻转, 0: 沿x-轴翻转, <0: x、y轴同时翻转
);

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
cv::Mat image = cv::imread("1.jpg");

cv::Mat image_fliped;
cv::flip(image, image_fliped, -1);

cv::imshow("original", image);
cv::imshow("fliped", image_fliped);

cv::waitKey(0);
return 0;
}

上下翻转效果:

flip

旋转

图像的旋转具体实现分为两步:先根据旋转角度和旋转中心获取旋转矩阵;然后根据旋转矩阵进行仿射变换,即可实现任意角度和任意中心的旋转效果。旋转矩阵的形式如下:

M=[αβ(1α)center.xβcenter.yβαβcenter.x(1α)center.y]M = \begin{bmatrix} \alpha & \beta & (1-\alpha) center.x & \beta center.y \\ -\beta & \alpha & \beta center.x & (1-\alpha) center.y \end{bmatrix}

其中:

α=scale cosθ\alpha = scale \ cos{\theta}

β=scale sinθ\beta = scale \ sin{\theta}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <opencv2/opencv.hpp>

using namespace cv;

int main() {
cv::Mat src = cv::imread("1.jpg");
cv::Mat dst;

//旋转角度
double angle = 45;

cv::Size src_sz = src.size();
cv::Size dst_sz(src_sz.height, src_sz.width);
int len = std::max(src.cols, src.rows);

//指定旋转中心
cv::Point2f center(len / 2., len / 2.);

//获取旋转矩阵(2x3矩阵)
cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, 1.0);

//根据旋转矩阵进行仿射变换
cv::warpAffine(src, dst, rot_mat, dst_sz);

//显示旋转效果
cv::imshow("image", src);
cv::imshow("result", dst);

cv::waitKey(0);

return 0;
}

测试效果:

rotate

缩放

函数原型:

1
2
3
void resize( InputArray src, OutputArray dst,
Size dsize, double fx = 0, double fy = 0,
int interpolation = INTER_LINEAR );

其中第一,二个参数是输入和输出的图像;

第三个参数为调整之后的图像尺寸;

第四个参数fx为x方向的缩放因子,若fx为0,fx = dsize.width/src.cols;

第五个参数fy为y方向的缩放因子,若fy为1,fy = dsize.height/src.rows;

第六个参数interpolation:这个是指定插值的方式,图像缩放之后,肯定像素要进行重新计算的,就靠这个参数来指定重新计算像素的方式,有以下几种:

  • INTER_NEAREST - 最邻近插值
  • INTER_LINEAR - 双线性插值,如果最后一个参数你不指定,默认使用这种方法
  • INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
  • INTER_CUBIC - 4x4像素邻域内的双立方插值
  • INTER_LANCZOS4 - 8x8像素邻域内的Lanczos插值

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;

// 图像按比例缩放
///@ width 输入要缩放图像的宽度
void Resize(const Mat &srcImage, Mat &destImage, int width)
{
int ncols = width;
int nrows = srcImage.rows*width / srcImage.cols;
resize(srcImage, destImage, cv::Size(nrows, ncols));
}

int main()
{
//读入图像,并判断图像是否读入正确
cv::Mat srcImage = imread("1.jpg");
if (!srcImage.data)
return -1;
imshow("srcImage", srcImage);

//将图片按比例缩放至宽为250像素的大小
Mat destImage;
double angle = 9.9;//角度
Resize(srcImage, destImage, 400);
imshow("dst", destImage);

waitKey(0);
return 0;
}

测试效果:

resize

错切

图像的错切变换实际上是平面景物在投影平面上的非垂直投影效果。图像错切变换也称为图像剪切、错位或错移变换。图像错切的原理就是保持图像上各点的某一坐标不变,将另一个坐标进行线性变换,坐标不变的轴称为依赖轴,坐标变换的轴称为方向轴。图像错切一般分为两种情况:水平方向错切和垂直方向错切。

水平方向的错切,即沿x轴方向关于y的错切,即x=x0+c*y0, y=y0

若c>0,则沿+x轴方向错切,反之,如果c<0,则沿-x轴方向错切.

旋转文本图像矫正

OpenCV入门教程04.08:简单文字切割中介绍过文字切割。在Word中,部分文字会设置为斜体,以表达特别效果。如果是进行深度学习或者神经网络训练的话,需要提供训练数据,如果直接对斜体进行分类,那么就需要大量的斜体数据。如果我们把斜体扭正,这样我们就只需要提供正常的字体数据,就可以了。

本文将实现斜体文字矫正,处理流程为:

  • 膨胀,将空隙连接起来
  • 框出轮廓
  • 旋转每个轮廓

首先先准备斜体文字

src

完整测试代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include"opencv2/opencv.hpp"
using namespace std;
using namespace cv;


int main(){
//获取原始数据
Mat img = imread("text.png");
imshow("src",img);

Mat elements = getStructuringElement(MORPH_RECT,Size(10,10));
Mat dstImg;
erode(img,dstImg,elements);
imshow("erode",dstImg);

//将其转换为黑底白字
Mat imgGray,imgBin;
cvtColor(dstImg,imgGray,COLOR_BGR2GRAY);
imshow("gray",imgGray);
threshold(imgGray,imgBin,100,255,THRESH_BINARY_INV);
imshow("bin",imgBin);

//获取每个字符
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(imgBin,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());

for(size_t i=0;i<contours.size();i++){
RotatedRect RRect = minAreaRect(contours[i]);
if(RRect.size.area() < 100){
continue;
}
Point2f vertex[4];
RRect.points(vertex);
for(int j=0;j<4;j++){
line(img,Point(vertex[j]),Point(vertex[(j+1)%4]),Scalar(0,255,0),2,LINE_8);
}
double angle = RRect.angle;
if(angle < - 45) angle+=90;
//print("%f",angle);
//旋转文字
Mat rot_mat = getRotationMatrix2D(RRect.center,angle,1);
Mat rotated;
warpAffine(img,rotated,rot_mat,img.size(),INTER_CUBIC);
imshow("res",rotated);
}
printf("%d",contours.size());
imshow("src2",img);


waitKey(0);
}

效果为:

rotate

文字矫正原本是用在之前的OCR中的,后来OpenCV的处理比不上神经网络就只是演示之用。


OpenCV入门教程03.11:仿射变换
https://blog.jackeylea.com/opencv/opencv-warp-affine-transfom/
作者
JackeyLea
发布于
2021年6月21日
许可协议