前言:
本专栏主要结合OpenCV4(C++版本),来实现一些基本的图像处理操作、经典的机器学习算法(比如K-Means、KNN、SVM、决策树、贝叶斯分类器等),以及常用的深度学习算法。
系列文章:
- OpenCV4机器学习(一):OpenCV4+VS2017环境搭建与配置
- OpenCV4机器学习(二):图像的读取、显示与存储
- OpenCV4机器学习(三):颜色空间(RGB、HSI、HSV、Lab、Gray)之间的转换
一、基本原理
图像的平移、缩放、镜像和旋转等都属于几何变换。在数字图像处理中,几何变换由两个基本操作构成:坐标变换和灰度内插。坐标变换公式可表示为: ( x , y ) = T [ ( v , w ) ] (x, y) = T[(v, w)] (x,y)=T[(v,w)]。
其中,(v, w)是源图像中的像素坐标;(x, y)是变换后图像中的像素坐标。
最常见的空间变换之一是仿射变换,其一般形式如下:
根据矩阵T中元素的取值不同,仿射变换又可分为:恒等变换、尺度变换、旋转变换、平移变换、垂直变换或者水平剪切变换。具体如下表所示:
变换名称 | 仿射矩阵T | 坐标公式 |
---|---|---|
恒等变换 | [ 1 0 0 0 1 0 0 0 1 ] \left[ \begin{matrix}1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{matrix} \right] ⎣⎡100010001⎦⎤ | x = v , y = w x=v, y=w x=v,y=w |
尺度变换 | [ c x 0 0 0 c y 0 0 0 1 ] \left[ \begin{matrix}c_{x} & 0 & 0 \\ 0 & c_{y} & 0 \\ 0 & 0 & 1 \end{matrix} \right] ⎣⎡cx000cy0001⎦⎤ | x = c x v , y = c y w x =c_{x}v, y=c_{y}w x=cxv,y=cyw |
旋转变换 | [ c o s θ s i n θ 0 − s i n θ c o s θ 0 0 0 1 ] \left[ \begin{matrix}cos\theta & sin\theta & 0 \\ -sin\theta &cos\theta & 0 \\ 0 & 0 & 1 \end{matrix} \right] ⎣⎡cosθ−sinθ0sinθcosθ0001⎦⎤ | x = v c o s θ − w s i n θ , y = v s i n θ + w c o s θ x=vcos\theta-wsin\theta, y=vsin\theta+wcos\theta x=vcosθ−wsinθ,y=vsinθ+wcosθ |
平移变换 | [ 1 0 0 0 1 0 t x t y 1 ] \left[ \begin{matrix}1 & 0 & 0 \\ 0 & 1 & 0 \\ t_{x} & t_{y} & 1 \end{matrix} \right] ⎣⎡10tx01ty001⎦⎤ | x = v + t x , y = w + t y x=v+t_{x}, y=w+t_{y} x=v+tx,y=w+ty |
垂直变换 | [ 1 0 0 s h 1 0 0 0 1 ] \left[ \begin{matrix}1 & 0 & 0 \\ s_{h} & 1 & 0 \\ 0 & 0 & 1 \end{matrix} \right] ⎣⎡1sh0010001⎦⎤ | x = v + w s h , y = w x=v+ws_{h}, y=w x=v+wsh,y=w |
水平剪切变换 | [ 1 s v 0 0 1 0 0 0 1 ] \left[ \begin{matrix}1 & s_{v} & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{matrix} \right] ⎣⎡100sv10001⎦⎤ | x = v , y = w + v s v x=v, y=w+vs_v x=v,y=w+vsv |
二、仿射变换 warpAfine
在 OpenCV 中,实现仿射变换的函数为:cv::warpAffine 函数。其定义如下:
void cv::warpAffine(InputArray src,
OutputArray dst,
InputArray M,
Size dsize,
int flags = INTER_LINEAR,
int borderMode = BORDER_CONSTANT,
const Scalar= & boaderValue = Scalar())
各参数介绍:
- src:输入图像
- dst:输出图像
- M:变换矩阵
- dsize:输出图像尺寸;如果dsize=(0,0),则由下式计算:dsize=Size(round(fx * src.cols), round(fy * src.rows))
- flags:组合标志位
- borderMode:像素外推方法
- borderValue:在边界位恒定值的情况下使用的数值,默认为0
通过改变 warpAffine 函数中的变换矩阵 M ,可以轻松实现:恒等变换、尺度变换、旋转变换、平移变换、垂直变换或者水平剪切变换。
三、尺度变换
(1)resize 函数实现
尺度变换又称图像缩放,在OpenCV中,可使用resize函数来实现尺度变换。resize函数定义如下:
void cv::resize(InputArray src,
OutputArray dst,
Size dsize,
Double fx=0,
Double fy=0,
Int interpolation = INTER_LINEAR)
各参数介绍:
- src:输入图像
- dst:输出图像
- dsize:输出图像尺寸;如果dsize=(0,0),则由下式计算:dsize=Size(round(fx * src.cols), round(fy * src.rows))
- fx:水平轴缩放比例因子;fx、fy不能全部为0。
- fy:垂直轴缩放比例因子
- interpolation:插值算法
通过调用 resize 函数,可将原图缩放为原来的一半大小:
resize(img, img_resize, cv::Size(0, 0), 0.5, 0.5, 1);
(2)warpAffine 函数实现
所谓尺度变换,就是要将图像中像素的原坐标(v, w)按照
x
=
c
x
v
,
y
=
c
y
w
x =c_{x}v, y=c_{y}w
x=cxv,y=cyw 的规则,转换到(x, y)。它的简化形式可由下式表示:
因此,通过 warpAffine 函数,将变换矩阵 M 设置为:
也可以轻松实现尺度变换,具体实现方法如下:
warpAffine(img, img_warpAffine, M, size, INTER_LINEAR, 1); //仿射变换
(3)实战小结
下面分别使用 resize 函数和 warpAfine 函数,来实现尺度变换:
#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("D:\\CSDN\\dog.jpg", 1);
imshow("original", img);
/*************** 尺度变换 ***************/
//方法1:resize
Mat img_resize;
resize(img, img_resize, cv::Size(0, 0), 0.5, 0.5, 1);
imshow("resize", img_resize);
//方法2:仿射变换
Mat img_warpAffine;
Mat M = Mat::zeros(2, 3, CV_32F); //声明尺度变换M矩阵(2x3)
float cx = 0.5, cy = 0.5;
M.at<float>(0, 0) = cx;
M.at<float>(1, 1) = cy;
cv::Size size = img.size();
size.width *= cx;
size.height *= cy;
warpAffine(img, img_warpAffine, M, size, INTER_LINEAR, 1); //仿射变换
imshow("warpAffine", img_warpAffine);
waitKey(0);
return 0;
}
显示如下所示:
四、平移变换
对于平移变换,就是要将图像中像素的原坐标(v, w)按照
x
=
v
+
t
x
,
y
=
w
+
t
y
x=v+t_{x}, y=w+t_{y}
x=v+tx,y=w+ty 的规则,转换到(x, y)。它的简化形式可由下式表示:
因此,通过 warpAffine 函数,将变换矩阵 M 设置为:
可以轻松实现平移变换,具体实现方法如下:
#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("D:\\CSDN\\lena.jpeg", 1);
imshow("original", img);
Mat img_trans;
Mat M1 = cv::Mat::eye(2, 3, CV_32F); //声明平移变换矩阵M1(2x3),并初始化对角线元素为1
float tx = 40, ty = 20; //设置平移变换参数
M1.at<float>(0, 2) = tx;
M1.at<float>(1, 2) = ty;
//为了让平移后的图像能完整地显示,此处做扩边处理,将原图在上、下、左、右四个方向分别扩充相应的元素,并对扩充区域填充恒定灰度值200
int top = 0, bottom = ty, left = 0, right = tx;
cv::copyMakeBorder(img, img_trans, top, bottom, left, right, BORDER_CONSTANT, cv::Scalar(200)); //扩边
cv::warpAffine(img_trans, img_trans, M1, img_trans.size(), INTER_LINEAR, BORDER_TRANSPARENT);
imshow("translation", img_trans);
waitKey(0);
return 0;
}
原图和平移变换之后的效果如下图所示:
五、旋转变换
对于旋转变换,就是要将图像中像素的原坐标(v, w)按照
x
=
v
c
o
s
θ
−
w
s
i
n
θ
,
y
=
v
s
i
n
θ
+
w
c
o
s
θ
x=vcos\theta-wsin\theta, y=vsin\theta+wcos\theta
x=vcosθ−wsinθ,y=vsinθ+wcosθ 的规则,转换到(x, y)。它的简化形式可由下式表示:
因此,通过 warpAffine 函数,将变换矩阵 M 设置为:
可以轻松实现旋转变换,具体实现方法如下:
#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("D:\\CSDN\\lena.jpeg", 1);
Mat img_rotate;
double angle = 45; //设定旋转角度
int border = 0.207 * img.cols;
cv::copyMakeBorder(img, img_rotate, border, border, border, border, BORDER_CONSTANT, cv::Scalar(0)); //扩边
cv::Point2f center(img_rotate.cols / 2., img_rotate.rows / 2.); //指定旋转中心
cv::Mat M2 = cv::getRotationMatrix2D(center, angle, 1.0); //获取旋转变换矩阵M2
cv::warpAffine(img_rotate, img_rotate, M2, img_rotate.size(), INTER_LINEAR, BORDER_REPLICATE);
cv::imshow("rotate", img_rotate);
waitKey(0);
return 0;
}
经过绕中心点45度,旋转变换之后的效果图所下所示:
六、剪切变换
剪切变换包含垂直剪切和水平剪切,下面以垂直剪切为例,就是要将图像中像素的原坐标(v, w)按照 x = v + w s h , y = w x=v+ws_{h}, y=w x=v+wsh,y=w 的规则,转换到(x, y)。它的简化形式可由下式表示:
因此,通过 warpAffine 函数,将变换矩阵 M 设置为:
可以轻松实现垂直旋转变换,具体实现方法如下:
#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("D:\\CSDN\\lena.jpeg", 1);
Mat img_shear_vertical;
int border = 40;
cv::copyMakeBorder(img, img_shear_vertical, 10, 10, 10, 10+4* border, BORDER_CONSTANT, cv::Scalar(0)); //扩边
Mat M3 = cv::Mat::eye(2, 3, CV_32F); //声明垂直剪切变换M3矩阵(2x3),初始化对角线为1
float sv = 0.3; // 垂直剪切系数
M3.at<float>(0, 1) = sv;
cv::warpAffine(img_shear_vertical, img_shear_vertical, M3, img_shear_vertical.size(), INTER_LINEAR, BORDER_REPLICATE);
cv::imshow("shear_vertical", img_shear_vertical);
waitKey(0);
return 0;
}
原图经过垂直旋转变换狗的效果如下图所示:
本专栏所有完整的代码将在我的GitHub仓库上更新,欢迎大家前往学习:
进入GitHub仓库,点击 star (红色箭头所示),第一时间获取干货:
最好的关系是互相成就,各位的「三连」就是【AI 菌】创作的最大动力,我们下期见!