使用opencv实现图像的畸形矫正:仿射变换

1 仿射变换

1.1 什么是仿射变换

在图像处理中,经常需要对图像进行各种操作如平移、缩放、旋转、翻转等,这些都是图像的仿射变换。图像仿射变换又称为图像仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。通常图像的旋转加上拉升就是图像仿射变换,仿射变换需要一个M矩阵实现,但是由于仿射变换比较复杂,很难找到这个M矩阵.

1.2 仿射变换的数学表达

仿射变换也称仿射投影,是指几何中,对一个向量空间进行线性变换并接上一个平移,变换为另一个向量空间。所以,仿射变换其实也就是再讲如何来进行两个向量空间的变换
假设有一个向量空间k:

还有一个向量空间j:

 如果我们想要将向量空间由k变为j,可以通过下面的公式进行变换

将上式进行拆分可得

 

我们再将上式转换为矩阵的乘法 

通过参数矩阵M就可以实现两个向量空间之间的转换,在进行仿射变换的时候我们也只需要一个矩阵M就可以实现平移、缩放、旋转和翻转变换。

1.3 opencv中的仿射变换

OpenCV中使用warpAffine函数来实现仿射变换

cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) → dst
  • src:输入的图像数组
  • M:仿射变换矩阵
  • dsize:变换后图像的大小
  • flags:使用的插值算法
  • borderValue:边界的填充值

1.3.1 图像平移

在平面坐标系有点P(x,y)和点P′(x′,y′),如果我们想要将P点移动到P',通过下面的变换就可以实现

 其中Δx和Δy就是x方向上和y方向上的偏移量,我们将其转换为矩阵的形式

 上面的矩阵M就是仿射变换的平移参数,使用OpenCV中的warpAffine函数实现如下:

import cv2
import numpy as np
import matplotlib.pyplot as pltdef show_cmp_img(original_img,transform_img):_, axes = plt.subplots(1, 2)# 显示图像axes[0].imshow(original_img)axes[1].imshow(transform_img)# 设置子标题axes[0].set_title("original image")axes[1].set_title("transform image")plt.show()# 定义一个图像平移矩阵
# x向左平移(负数向左,正数向右)100
# y向下平移(负数向上,正数向下)200个像素
M = np.array([[1, 0, -100], [0, 1, 200]], dtype=np.float)# 读取需要平移的图像
img = cv2.imread("../data/girl02.jpg")# 将图片由BGR转为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 定义平移后图像的大小,保持和原图大小一致
dsize = img.shape[:2][::-1]# 便于大家观察这里采用白色来填充边界
translation_img = cv2.warpAffine(img, M, dsize, borderValue=(255, 255, 255))# 显示图像
show_cmp_img(img, translation_img)

运行结果显示如下:

1.3.2 图像翻转

使用opencv的仿射变换实现图像的水平翻转、垂直翻转、镜像反转(同时进行水平和垂直翻转)

上图中的A、B、C、D表示图像的四个顶点,如果我们需要对图像进行水平翻转,那么我们就需要将 A点和B点进行交换,C点和D点进行交换,沿着x轴的中线进行对称交换位置,通过下面的式子可以实现水平翻转

上式中的w表示图像的宽度,同理可得垂直翻转的实现公式

上式中的h表示的是图像的高图像翻转的变换矩阵:

使用OpenCV中的warpAffine函数实现如下:

import cv2
import matplotlib.pyplot as plt
import numpy as npdef show_cmp_img(original_img,transform_img):_, axes = plt.subplots(1, 2)# 显示图像axes[0].imshow(original_img)axes[1].imshow(transform_img)# 设置子标题axes[0].set_title("original image")axes[1].set_title("transform image")plt.show()horizontal_flip = True
vertical_flip = Trueimg = cv2.imread("../data/girl02.jpg")# 获取输入图片的宽和高
height,width = img.shape[:2]# 初始化变换矩阵
M = np.array([[0, 0, 0], [0, 0, 0]], dtype=np.float)# 水平翻转
if horizontal_flip:M[0] = [-1, 0, width]# 垂直翻转
if vertical_flip:M[1] = [0, -1, height]# 将图片由BGR转为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 定义缩放后图片的大小
img_flip = cv2.warpAffine(img, M, (width, height))show_cmp_img(img, img_flip)

运行结果显示如下:

OpenCV的flip函数翻转图像

flip函数参数:

  • src:输入的图像数组
  • flipCode:图像翻转参数,1表示水平翻转,0表示垂直翻转,-1表示镜像翻转
img = cv2.imread("../data/girl02.jpg")#水平翻转
horizontal_flip_img = cv2.flip(img,1)#垂直翻转
vertical_flip_img = cv2.flip(img,0)#镜像翻转
mirror_flip_img = cv2.flip(img,-1)

numpy的索引翻转图像

img = cv2.imread("../data/girl02.jpg")#水平翻转
horizontal_flip_img = img[:,::-1]#垂直翻转
vertical_flip_img = img[::-1]#镜像翻转
mirror_flip_img = img[::-1,::-1]

 1.3.3 图像缩放

如果我们想要对坐标系的P点进行缩放操作,通过下面的公式就可以实现

 通过,在x和y前面添加一个缩放系数即可,同样我们将其转换为矩阵形式

通过上面的矩阵M我们就可以实现对图片的缩放,使用OpenCV中的warpAffine函数实现如下:

import cv2
import numpy as np
import matplotlib.pyplot as pltdef show_cmp_img(original_img,transform_img):_, axes = plt.subplots(1, 2)# 显示图像axes[0].imshow(original_img)axes[1].imshow(transform_img)# 设置子标题axes[0].set_title("original image")axes[1].set_title("transform image")plt.show()# 定义宽缩放的倍数
fx = 0.5# 定义高缩放的倍数
fy = 2# 定义一个图像缩放矩阵
M = np.array([[fx, 0, 0], [0, fy, 0]], dtype=np.float)# 读取图像
img = cv2.imread("../data/girl02.jpg")# 获取图片的宽和高
height, width = img.shape[:2]# 将图片由BGR转为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 定义缩放后图片的大小
scale_img = cv2.warpAffine(img, M, (int(width*fx), int(height*fy)))# 显示图像
show_cmp_img(img, scale_img)

结果显示如下:

opencv中的resize函数也能实现一样的效果。

1.3.4 图像旋转

围绕原点旋转:我们先来看看一个二维平面上的点在围绕原点是如何旋转的

上图中点v在围绕原点旋转θ度之后得到了点v′,我们将坐标点用极坐标的形式来表示可以得到 v(rcosϕ,rsinϕ),所以v′(rcos(θ+ϕ),rsin(θ+ϕ))利用正弦和余弦将其展开可得

然后再将上式用矩阵M表示,可得

特别注意:我们在建立直角坐标系的时候是以左下角为原点建立的,然而对于图像而言是以左上角为原点建立的,所以我们需要对角度θ进行取反,结合三角函数的特性,M矩阵的表达式如下

还需要注意的是这里的角度都是弧度制,所以我们还需要对其进行转换,转换代码如下

#将角度转换为弧度制
radian_theta = theta/180 * np.pi

将图片围绕原点进行逆时针旋转θ度,opencv的代码实现如下:

import cv2
import numpy as np
import matplotlib.pyplot as pltdef show_cmp_img(original_img,transform_img):_, axes = plt.subplots(1, 2)# 显示图像axes[0].imshow(original_img)axes[1].imshow(transform_img)# 设置子标题axes[0].set_title("original image")axes[1].set_title("transform image")plt.show()theta = 30# 将角度转换为弧度制
radian_theta = theta/180 * np.pi# 定义围绕原点旋转的变换矩阵
M = np.array([[np.cos(radian_theta), np.sin(radian_theta), 0],[-np.sin(radian_theta), np.cos(radian_theta), 0]])
# 读取图像
img = cv2.imread("../data/girl02.jpg")# 定义旋转后图片的宽和高
height, width = img.shape[:2]# 将图片由BGR转为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 围绕原点逆时针旋转\theta度
rotate_img = cv2.warpAffine(img, M, (width, height))# 显示图像
show_cmp_img(img,rotate_img)

 运行结果显示如下:

1.3.5 围绕任意点旋转

下图的v点在围绕点(a,b)旋转90度得到v′。可以将其等价于先将v点平移到v1​点,然后再将v1​点围绕原点旋转90度得到v2​点,最后再将v2​点沿着v点平移的反方向平移相同长度,最终得到v′。这样我们就将围绕任意坐标点旋转的问题转换成了围绕原点旋转的问题

我们来回顾一下,围绕原点旋转坐标的变换公式:

 

在围绕原点旋转变换公式的基础上,我们将其改进为围绕任意点c(a,b)旋转,我们现在原来的坐标进行平移,得到变换后的坐标,最后再沿着之前平移的反方向进行平移,就得到围绕任意点旋转的变换公式:

将其展开可得

 

将上式用矩阵M表示: 

上式中的c(a,b)表示旋转中心,因为坐标系问题需要对θ进行取反,最终M矩阵的表达式如下

使用opencv的代码如下:

import cv2
import numpy as np
import matplotlib.pyplot as pltdef show_cmp_img(original_img,transform_img):_, axes = plt.subplots(1, 2)# 显示图像axes[0].imshow(original_img)axes[1].imshow(transform_img)# 设置子标题axes[0].set_title("original image")axes[1].set_title("transform image")plt.show()img = cv2.imread("../data/girl02.jpg")theta = 30
height, width = img.shape[:2]# 定义围绕图片的中心旋转
point_x, point_y = int(width/2), int(height/2)# 将角度转换为弧度制
radian_theta = theta / 180 * np.pi# 定义围绕任意点旋转的变换矩阵
M = np.array([[np.cos(radian_theta), np.sin(radian_theta),(1-np.cos(radian_theta))*point_x-point_y*np.sin(radian_theta)],[-np.sin(radian_theta), np.cos(radian_theta),(1-np.cos(radian_theta))*point_y+point_x*np.sin(radian_theta)]])# 将图片由BGR转为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 定义旋转后图片的宽和高
height, width = img.shape[:2]# 围绕原点逆时针旋转\theta度
rotate_img = cv2.warpAffine(img, M, (width, height))# 显示图像
show_cmp_img(img, rotate_img)

运行结果显示如下:

围绕图像中心旋转后的图片部分被裁剪掉了,如果我们想让旋转之后的图片仍然是完整,代码如下:

import cv2
import numpy as np
import matplotlib.pyplot as pltdef show_cmp_img(original_img,transform_img):_, axes = plt.subplots(1, 2)# 显示图像axes[0].imshow(original_img)axes[1].imshow(transform_img)# 设置子标题axes[0].set_title("original image")axes[1].set_title("transform image")plt.show()img = cv2.imread("../data/girl02.jpg")theta = 30
is_completed = True
height, width = img.shape[:2]# 定义围绕图片的中心旋转
point_x, point_y = int(width/2), int(height/2)# 将角度转换为弧度制
radian_theta = theta / 180 * np.pi# 定义围绕任意点旋转的变换矩阵
M = np.array([[np.cos(radian_theta), np.sin(radian_theta),(1-np.cos(radian_theta))*point_x-point_y*np.sin(radian_theta)],[-np.sin(radian_theta), np.cos(radian_theta),(1-np.cos(radian_theta))*point_y+point_x*np.sin(radian_theta)]])
# 将图片由BGR转为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 定义旋转后图片的宽和高
height, width = img.shape[:2]# 判断旋转之后的图片是否需要保持完整
if is_completed:# 增大旋转之后图片的宽和高,防止被裁剪掉new_height = height * np.cos(radian_theta) + width * np.sin(radian_theta)new_width = height * np.sin(radian_theta) + width * np.cos(radian_theta)# 增大变换矩阵的平移参数M[0, 2] += (new_width - width) * 0.5M[1, 2] += (new_height - height) * 0.5height = int(np.round(new_height))width = int(np.round(new_width))
# 围绕原点逆时针旋转\theta度
rotate_img = cv2.warpAffine(img, M, (width, height))
# 显示图像
show_cmp_img(img, rotate_img)

运行结果显示如下:

2 使用opencv实现图像的畸形矫正

在日常处理图片过程中,我们经常遇到扭曲的图片,首先我们要对扭曲的图片进行校正,然后在送入深度模型进行处理,扭曲的图片如下所示:

为实现将倾斜的目标矫正过来,首先,我们需要使用轮廓检测等方法获取到目标的4个关键点坐标值;然后利用相应的变换获取到新的4个坐标点;接着利用这4对关键点计算出仿射变换矩阵M;最后应用仿射变换矩阵到目标中即可。步骤如下:

  • 读取输入图片;
  • 获取原始目标的4个坐标点(左上,左下,右上,右下);
  • 通过4个坐标点计算出新的坐标点;
  • 使用opencv计算仿射变换矩阵M;
  • 应用仿射变换进行变换并进行结果显示。

2.1 获取四个顶点坐标

def get4points(img: np.ndarray, thed, n):# 灰度和二值化gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)ret, binary = cv2.threshold(gray, thed, 255, cv2.THRESH_BINARY)# 搜索轮廓contours, hierarchy = cv2.findContours(binary,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)# 按轮廓长度选取需要轮廓len_list = []for i in range(len(contours)):len_list.append(len(contours[i]))# 选第二长的sy = np.argsort(np.array(len_list))[-n]# 寻找顶点sum_list = []dif_list = []for i in contours[sy]:sum = i[0][0]+i[0][1]sum_list.append(sum)dif_list.append(i[0][0]-i[0][1])id_lb = np.argsort(np.array(sum_list))id_lb2 = np.argsort(np.array(dif_list))lu_id , rd_id = id_lb[0], id_lb[-1]ld_id , ru_id = id_lb2[0], id_lb2[-1]points = np.array([contours[sy][lu_id][0], contours[sy][rd_id][0],contours[sy][ld_id][0], contours[sy][ru_id][0]])return points, contours, sy

2.2 仿射变换

def four_point_transform(image, pts):# 获取坐标点,并将它们分离开来rect = order_points(pts)(tl, tr, br, bl) = rect# 计算新图片的宽度值,选取水平差值的最大值widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))maxWidth = max(int(widthA), int(widthB))# 计算新图片的高度值,选取垂直差值的最大值heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))maxHeight = max(int(heightA), int(heightB))# 构建新图片的4个坐标点dst = np.array([[0, 0],[maxWidth - 1, 0],[maxWidth - 1, maxHeight - 1],[0, maxHeight - 1]], dtype="float32")# 获取仿射变换矩阵并应用它M = cv2.getPerspectiveTransform(rect, dst)# 进行仿射变换warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))# 返回变换后的结果return warped

2.3 完整代码

# coding=utf-8
import numpy as np
import cv2
import matplotlib.pyplot as pltdef get4points(img: np.ndarray, thed, n):# 灰度和二值化gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)ret, binary = cv2.threshold(gray, thed, 255, cv2.THRESH_BINARY)# 搜索轮廓contours, hierarchy = cv2.findContours(binary,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)# 按轮廓长度选取需要轮廓len_list = []for i in range(len(contours)):len_list.append(len(contours[i]))# 选第二长的sy = np.argsort(np.array(len_list))[-n]# 寻找顶点sum_list = []dif_list = []for i in contours[sy]:sum = i[0][0]+i[0][1]sum_list.append(sum)dif_list.append(i[0][0]-i[0][1])id_lb = np.argsort(np.array(sum_list))id_lb2 = np.argsort(np.array(dif_list))lu_id , rd_id = id_lb[0], id_lb[-1]ld_id , ru_id = id_lb2[0], id_lb2[-1]points = np.array([contours[sy][lu_id][0], contours[sy][rd_id][0],contours[sy][ld_id][0], contours[sy][ru_id][0]])return points, contours, sydef order_points(pts):# 初始化坐标点rect = np.zeros((4, 2), dtype = "float32")# 获取左上角和右下角坐标点s = pts.sum(axis = 1)rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# 分别计算左上角和右下角的离散差值diff = np.diff(pts, axis = 1)rect[1] = pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]return rectdef four_point_transform(image, pts):# 获取坐标点,并将它们分离开来rect = order_points(pts)(tl, tr, br, bl) = rect# 计算新图片的宽度值,选取水平差值的最大值widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))maxWidth = max(int(widthA), int(widthB))# 计算新图片的高度值,选取垂直差值的最大值heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))maxHeight = max(int(heightA), int(heightB))# 构建新图片的4个坐标点dst = np.array([[0, 0],[maxWidth - 1, 0],[maxWidth - 1, maxHeight - 1],[0, maxHeight - 1]], dtype="float32")# 获取仿射变换矩阵并应用它M = cv2.getPerspectiveTransform(rect, dst)# 进行仿射变换warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))# 返回变换后的结果return warpeddef show_cmp_img(original_img, transform_img):_, axes = plt.subplots(1, 2)# 显示图像axes[0].imshow(original_img)axes[1].imshow(transform_img)# 设置子标题axes[0].set_title("original image")axes[1].set_title("transform image")plt.show()# 读取图片
image = cv2.imread('../data/warp01.png')points, _, _ = get4points(image, 127, 1)# 获取原始的坐标点
pts = np.array(points, dtype="float32")# 对原始图片进行变换
warped = four_point_transform(image, pts)show_cmp_img(image, warped)

运行结果显示如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/171259.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何显示标注的纯黑mask图

文章目录 前言一、二分类mask显示二、多分类mask显示 前言 通常情况下,使用标注软件标注的标签图看起来都是纯黑的,因为mask图为单通道的灰度图,而灰度图一般要像素值大于128后,才会逐渐显白,255为白色。而标注的时候…

HarmonyOS开发(三):ArkTS基础

1、ArkTS演进 Mozilla创建了JS ---> Microsoft创建了TS ----> Huawei进一步推出ArkTS 从最初的基础逻辑交互(JS),到具备类型系统的高效工程开发(TS),再到融合声明式UI、多维状态管理等丰富的应用开发能力&…

WordPress 文档主题模板Red Line -v0.2.2

此主题作为框架,做承载第三方页面之用,例如飞书文档等, 您可以将视频图片等资源放第三方文档上,通过使用此主题做目录用。 此主题使用前后端分离开发,也使用了一些技术尽量不影响正常的SEO,还望注意。 源码…

Flink SQL --命令行的使用(02)

1、窗口函数: 1、创建表: -- 创建kafka 表 CREATE TABLE bid (bidtime TIMESTAMP(3),price DECIMAL(10, 2) ,item STRING,WATERMARK FOR bidtime AS bidtime ) WITH (connector kafka,topic bid, -- 数据的topicproperties.bootstrap.servers m…

阿里云容器镜像服务的运维总结

一、背景 容器镜像服务,作为一个可选付费产品,主要作用是存储docker的镜像仓库,供k8s拉取到Pod节点里。 你可以自己搭建一个harbor镜像仓库,在公司的开发环境下,将image推送到仓库;然后在生产k8s从仓库拉取…

「NLP+网安」相关顶级会议期刊 投稿注意事项+会议等级+DDL+提交格式

「NLP网安」相关顶级会议&期刊投稿注意事项 写在最前面一、会议ACL (The Annual Meeting of the Association for Computational Linguistics)IH&MMSec (The ACM Workshop on Information Hiding, Multimedia and Security)CCS (The ACM Conference on Computer and Co…

Git常用指令以及常见问题解决

摘要:记录本人Git常用指令以及常见问题解决 1.Git流程 2.具体操作 git init:初始化目录(一般直接git clone远端的工程,这一步都可以省略掉); 输入命令“git config --global user.name xxx”来配置你的用…

大语言模型可以学习规则11.13

大型语言模型可以学习规则 摘要1 引言2 准备3 从假设到理论3.1 诱导阶段:规则生成和验证3.2 演绎阶段:使用规则库进行显式推理 4 实验评估实验装置4.2 数字推理 5 相关工作 摘要 当提示一些例子和中间步骤时,大型语言模型(LLM&am…

机器视觉行业,日子不过了吗?都进入打折潮,双11只是一个借口,打广告出新招,日子不好过是真的

我就不上图了,大家注意各个机器视觉公司公众号,为什么打折?打广告也只是宣传手段,进入打折潮,内卷严重,价格战变成白刃战,肯定日子不好过了。

如何计算掩膜图中多个封闭图形的面积

import cv2def calMaskArea(image,idx):mask cv2.inRange(image, idx, idx)contours, hierarchy cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)for contour in contours:area cv2.contourArea(contour)print("图形的面积为", area) image是…

Rust语言基础:从Hello World开始

大家好,我是[lincyang]。 我们将一起探索Rust语言的基础,从最经典的程序入手——“Hello, World!”。 Rust简介 Rust是一种系统编程语言,由Mozilla赞助开发,旨在提供内存安全、并发性和实用性。它的设计思想强调安全性和性能&…

【C语言】深入解开指针(二)

🌈write in front :🔍个人主页 : 啊森要自信的主页 🌈作者寄语 🌈: 小菜鸟的力量不在于它的体型,而在于它内心的勇气和无限的潜能,只要你有决心,就没有什么事情是不可能的…