图像的几何变化
一、仿射变换
仿射变换是将一个二维坐标转换到另一个二维坐标的过程。仿射变换是一种线性变换。变换前是直线的,变换后依然是直线;变换前是平行线的,变换后依然是平行线。
在OpenCV中实现仿射变换需要两步,第一步通过getAffineTransform()函数来计算仿射变换矩阵(矩阵大小为2*3),第二步通过warpAffine()函数实现仿射变换。
java
//计算仿射变换矩阵
Mat Imgproc.getAffineTransform(MatOfPoint2f src, MatOfPoint2f dst)
- src:原图像中的3个点的坐标
- dst:原图像中的3个点在目标图像中对应的坐标
java
//对图像进行仿射变换
void Imgproc.warpAffine(Mat src, Mat dst, Mat M, Size dsize, int flags)
- src:输入图像
- dst:输出图像,尺寸和dsize一致,数据类型与src相同
- dsize:输出图像的尺寸
- flags:差值方法,常用参数如下:
- Imgproc.INTER_NEAREST:最临近插值
- Imgproc.INTER_LINEAR:线性插值
- Imgproc.INTER_AREA:区域插值
- Imgproc.INTER_CUBIC:三次样条插值
- Imgproc.INTER_LANCZOS4:Lanczos插值
- Imgproc.INTER_LINEAR_EXACT:位精确双线性插值
- Imgproc.INTER_NEAREST_EXACT:位精确最近邻插值
- Imgproc.INTER_MAX:用掩码进行插值
- Imgproc.WARP_FILL_OUTLITERS:填充所有输出图像像素,如有像素落在输入图像边界外,则将它们设为0
- Imgproc.WARP_INVERSE_MAP:反变换
仿射变换的范围很广,平移、旋转、缩放、翻转实际上都属于仿射变换。
java
public class WarpAffine {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取图像
Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/leaf.png");
//定义原图像中3个点的坐标
Point[] pt1 = new Point[3];
pt1[0] = new Point(136, 56);
pt1[1] = new Point(45, 160);
pt1[2] = new Point(215, 150);
//定义目标图像中3个点的坐标
Point[] pt2 = new Point[3];//输出图像点集
pt2[0] = new Point(50, 80);
pt2[1] = new Point(50, 180);
pt2[2] = new Point(200, 100);
//getAffineTransform()函数用到的数据类型
MatOfPoint2f mop1 = new MatOfPoint2f(pt1);
MatOfPoint2f mop2 = new MatOfPoint2f(pt2);
//计算仿射变换矩阵并进行仿射变换
Mat dst = new Mat();
Mat mat = Imgproc.getAffineTransform(mop1, mop2);
Imgproc.warpAffine(src, dst, mat, src.size());
//在原图像中标记3个点的坐标
Scalar color = new Scalar(255, 255, 255);
for (int n = 0; n < 3; n++) {
Imgproc.circle(src, pt1[n], 2, color, 2);
Imgproc.putText(src, n + 1 + "", pt1[n], 0, 1, color, 3);
}
//在目标图像中标记3个点的坐标
for (int n = 0; n < 3; n++) {
Imgproc.circle(dst, pt2[n], 2, color, 2);
Imgproc.putText(dst, n + 1 + "", pt2[n], 0, 1, color, 3);
}
//在屏幕上显示变化前后的图像
HighGui.imshow("src", src);
HighGui.waitKey(0);
HighGui.imshow("warped", dst);
HighGui.waitKey(0);
//在控制台输出仿射变换矩阵
System.out.println(mat.dump());
System.exit(0);
}
}
原图:
变换后:

仿射变换矩阵:
java
[0.9302325581395348, 0.813953488372093, -122.0930232558139;
-0.4364937388193202, 0.5796064400715565, 106.9051878354204]
二、透视变化
仿射变换又称三点变换,因为它只用到3个点,而透视变换则用到了4个点,因此也被称为4点变换。透视变换是利用投影成像的原理将物体重新投射到另一个成像平面,透视变换的转换矩阵也与仿射变换的矩阵不同,是一个3*3的矩阵。

在OpenCV中实现透视变换也分两步,第一步通过getPerspectiveTransform()函数来计算透视变化矩阵(3*3),第二步通过warpPerspective()函数实现透视变换。
java
//计算透视变换矩阵
Mat Imgproc.getPerspectiveTransform(Mat src, Mat dst)
- src:原图像中的4个点的坐标
- dst:原图像中4个点在目标图像中对应的坐标
java
//对图像进行透视变化
void Imgproc.warpPerspective(Mat src, Mat dst, Mat M, Size dsize)
- src:输入图像
- dst:输出图像,尺寸和dsize一致,数据类型与src相同
- M:3*3的变化矩阵
- dsize:输出图像的尺寸
java
public class PerspectiveTransform {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取图像
Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/book.png");
//定义原图像中4个点的坐标
Point[] pt1 = new Point[4];
pt1[0] = new Point(95, 129);
pt1[1] = new Point(260, 157);
pt1[2] = new Point(57, 469);
pt1[3] = new Point(248, 454);
//定义目标图像中4个点坐标
Point[] pt2 = new Point[4];
pt2[0] = new Point(0, 0);
pt2[1] = new Point(300, 0);
pt2[2] = new Point(0, 600);
pt2[3] = new Point(300, 600);
//getPerspectiveTransform()函数用到的数据类型
MatOfPoint2f mop1 = new MatOfPoint2f(pt1);
MatOfPoint2f mop2 = new MatOfPoint2f(pt2);
//计算透视变换矩阵并进行仿射变换
Mat dst = new Mat();
Mat matrix = Imgproc.getPerspectiveTransform(mop1, mop2);
Imgproc.warpPerspective(src, dst, matrix, new Size(300, 600));
//在屏幕上显示变换前后的图像
HighGui.imshow("src", src);
HighGui.waitKey(0);
HighGui.imshow("dst", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
原图:

变换后:
程序的输入图像是书中的一页,其4个定位点明显不在一个平面上,经过透视变换以后还原成一张完整的图像。
三、平移
图像平移是将一张图像中所有的点都按照指定的平移量在水平和垂直方向上进行移动,平移后的图像与原图像相同。平移后图像上的每个点都可以在原图像中汇总阿道对应的点。假设原图像的点的坐标为(x0, y0),平移量为(a,b),平移前后的坐标关系可以用数学公式表示如下:
java
x1=x0+a
y1=y0+b
平移的实现有多种方法,最容易想到的方法就是利用循环语句对像素赋值。这种方法其实不需要OpenCV就可以实现。那么OpenCV中有没有函数可以实现平移呢?利用仿射变换的warpAffine()函数同样可以实现图像平移。
实际上,仿射变换的关键是找到三组对应点,有了这些点就能计算出仿射变换的矩阵。现在定义3个点,例如(0,0)、(0,10)和(10,0)。假设x方向平移30像素,y方向平移50像素。简单计算可知平移后这3个点的坐标为(30,50)、(30,60)和(40,50),然后使用仿射变换即可。
但是这个过程还是有点烦琐,实际上,平移这种简单操作的转换矩阵也很简单。让图像在x方向平移a,在y方向平移b的矩阵如下:
java
1 0 a
0 1 b
java
public class Translate {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//构建用于平移的矩阵
Mat mat = new Mat(2, 3, CvType.CV_64F);
double[] data = new double[]{1,0,30,0,1,500};
mat.put(0,0, data);
//读取图像并在屏幕上显示
Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/pond.png");
HighGui.imshow("src", src);
HighGui.waitKey(0);
//进行平移并在屏幕上显示平移后的图像
Mat dst = new Mat();
Imgproc.warpAffine(src, dst, mat, src.size());
HighGui.imshow("transiate", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
原图:

平移后:
程序中利用Mat类的put()函数构建了平移矩阵,然后用warpAffine()函数实现了平移操作。
四、旋转
在OpenCV中也没有专门用于旋转的函数,实现图像的旋转同样是通过仿射变换实现的。在OpenCV中实现图像的旋转分为两步,第一步通过getRotationMatrix2D()函数来计算旋转矩阵,第二步通过warpAffine()函数实现旋转。
java
//计算二维旋转的仿射矩阵
Mat Imgproc.getRotationMatrix2D(Point center, double angle, double scale)
- center:原图像中的旋转中心
- angle:以度为单位的旋转角度,正值表示逆时针旋转(假设坐标原点为左上角)
- scale:缩放比例因子。旋转中可以实现缩放,如不缩放,则用1表示
java
public class Rotate {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取图像并在屏幕上显示
Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/fish.png");
HighGui.imshow("src", src);
HighGui.waitKey(0);
//计算旋转用的仿射矩阵
Mat dst = new Mat();
Point center = new Point(src.width() / 2.0, src.height() / 2.0);
Mat matrix = Imgproc.getRotationMatrix2D(center, 33.0, 1.0);
//旋转图像并在屏幕上显示
Imgproc.warpAffine(src, dst, matrix, src.size());
HighGui.imshow("ROtated", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
原图:
旋转后:
五、缩放
java
void Imgproc.resize(Mat src, Mat dst, Size dsize, double fx, double fy)
- src:输入图像
- dst:输出图像,其数据类型与src相同
- dsize:输出图像的大小,如dsize的大小为0,则dsize=Size(round(fxsrc.cols), round(fysrc.rows))
- fx:水平方向缩放比例。如fx为0,则fx=(double)dsize.width/src.cols
- fy:垂直方向缩放比例。如fy为0,则fy=(double)dsize.height/src.rows
此函数实际上有两套参数,可以通过dsize设置输出图像大小或者通过fx和fy设置缩放比例。两套参数使用哪一套应根据参数值确定。
java
public class Scale {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取图像并在屏幕上显示
Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/church.png");
HighGui.imshow("src", src);
HighGui.waitKey(0);
//获取原图像尺寸
float width = src.width();
float height = src.height();
//将原图像放大至1.2倍
Mat dst1 = new Mat();
float scale1 = 1.2f;
Imgproc.resize(src, dst1, new Size(width * scale1, height * scale1));
//将原图像缩小至0.8倍
Mat dst2 = new Mat();
float scale2 = 0.8f;
Imgproc.resize(src, dst2, new Size(width * scale2, height * scale2));
//在屏幕上显示放大和缩小后的图像
HighGui.imshow("Bigger", dst1);
HighGui.waitKey(0);
HighGui.imshow("Smaller", dst2);
HighGui.waitKey(0);
System.exit(0);
}
}
原图:

放大后:
缩小后:
六、翻转
java
//对给定的2D矩阵沿x轴、y轴或两个轴进行翻转
void Core.flip(Mat src, Mat dst, int flipCode)
- src:输入图像
- dst:输出图像,和src具有相同的尺寸和数据类型
- flipCode:指定翻转方向的标志,该标志的用法如下:
- 等于0:绕x轴翻转
- 大于0:绕y轴翻转
- 小于0:绕x和y两个轴翻转
java
public class Flip {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取图像并显示
Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/leaf.png");
HighGui.imshow("src", src);
HighGui.waitKey(0);
//绕y轴翻转
Mat f1 = new Mat();
Core.flip(src, f1, 1);
//将两副图像进行水平拼接
List<Mat> mats = new ArrayList<>();
mats.add(src);
mats.add(f1);
Mat d1 = new Mat();
Core.hconcat(mats, d1);
HighGui.imshow("d1", d1);
HighGui.waitKey(0);
//绕x轴翻转
Mat f2 = new Mat();
Core.flip(src, f2, 0);
//绕x和y两个轴翻转
Mat f3 = new Mat();
Core.flip(src, f3, -1);
//移除mats中元素
mats.remove(1);
mats.remove(0);
//将x轴翻转图像和两个轴翻转图像进行水平拼接
mats.add(f2);
mats.add(f3);
Mat d2 = new Mat();
Core.hconcat(mats, d2);
//将两幅拼接后的图像垂直再拼接
mats.remove(1);
mats.remove(0);
mats.add(d1);
mats.add(d2);
Mat d3 = new Mat();
Core.vconcat(mats, d3);
//在屏幕上显示最后结果
HighGui.imshow("All", d3);
HighGui.waitKey(0);
System.exit(0);
}
}


