文章目录
前言
Opencv 单应矩阵的应用,学习的时候网上找了很多代码都是C语言写的,小小的研究使用Python实现该功能,顺便写记录一下。
一、前置知识
1. 什么是单应矩阵
关于该方面的解释,可以参考 计算机视觉 公众号中的一篇文章《从零开始一起学习SLAM | 神奇的单应矩阵》
这里简单说一下:
- 单应:
单应指的概念就是指: 一 一 对应。 - 单应矩阵(Homography matrix):
描述同一平面的点在不同图像上的映射关系。
二、源图
1. 目标图像(img_dest)
2. 替换的图片(img_src)
三、思路与代码
1. 思路:
1.1 获取坐标
由于要将img_src和图像替换到img_dest中的广告牌位置,所以第一步要获取两个位置的坐标信息,
img_src的坐标即为四点个的坐标,而img_dest广告牌需要动态获得(通过自定义鼠标行为动作)。
# 首先,加载待替换的源图像,并获得该图像的长度等信息,cv.IMREAD_COLOR 表示加载原图
img_src = cv.imread('img_src.webp', cv.IMREAD_COLOR)
h, w, c = img_src.shape
# 将选中的四个点存放在集合中,在收集四个点时,
# 四个点的点击顺序需要按照 img_src_coordinate 中的点的相对位置的前后顺序保持一致(如下截图顺序)
img_src_coordinate = np.array([[x, y] for x in (0, w - 1) for y in (0, h - 1)])
print(img_src_coordinate)
目标图像的坐标获取比较麻烦,需要定义鼠标动作来获取
# 鼠标操作,鼠标选中源图像中需要替换的位置信息
def mouse_action(event, x, y, flags, replace_coordinate_array):
cv.imshow('collect coordinate', img_dest_copy)
if event == cv.EVENT_LBUTTONUP:
# 画圆函数,参数分别表示原图、坐标、半径、颜色、线宽(若为-1表示填充)
# 这个是为了圈出鼠标点击的点
cv.circle(img_dest_copy, (x, y), 2, (0, 255, 255), -1)
# 用鼠标单击事件来选择坐标
# 将选中的四个点存放在集合中
print(f'{x}, {y}')
replace_coordinate_array.append([x, y])
# 加载目标图像
img_dest = cv.imread('img_dest.webp', cv.IMREAD_COLOR)
# 将源数据复制一份,避免后来对该数据的操作会对结果有影响
img_dest_copy = np.tile(img_dest, 1)
# 源图像中的数据
# 定义一个数组,用来存放要源图像中要替换的坐标点,该坐标点由鼠标采集得到
replace_coordinate = []
cv.namedWindow('collect coordinate')
cv.setMouseCallback('collect coordinate', mouse_action, replace_coordinate)
while True:
# 当采集到四个点后,可以按esc退出鼠标采集行为
if cv.waitKey(20) == 27:
#
break
print(replace_coordinate)
1.2 得到替换后的图像
replace_coordinate = np.array(replace_coordinate)
# 根据选中的四个点坐标和代替换的图像信息完成单应矩阵
matrix, mask = cv.findHomography(img_src_coordinate, replace_coordinate, 0)
print(f'matrix: {matrix}')
perspective_img = cv.warpPerspective(img_src, matrix, (img_dest.shape[1], img_dest.shape[0]))
cv.imshow('img', perspective_img)
该步骤完成后,结果如下:
1.3 图像拼接
# 降噪,去掉最大或最小的像素点
retval, threshold_img = cv.threshold(perspective_img, 0, 255, cv.THRESH_BINARY)
# 将降噪后的图像与之前的图像进行拼接
cv.copyTo(src=threshold_img, mask=np.tile(threshold_img, 1), dst=img_dest)
cv.copyTo(src=perspective_img, mask=np.tile(perspective_img, 1), dst=img_dest)
cv.imshow('result', img_dest)
结果图如下:
四、完整代码
import cv2 as cv
import numpy as np
# 鼠标操作,鼠标选中源图像中需要替换的位置信息
def mouse_action(event, x, y, flags, replace_coordinate_array):
cv.imshow('collect coordinate', img_dest_copy)
if event == cv.EVENT_LBUTTONUP:
# 画圆函数,参数分别表示原图、坐标、半径、颜色、线宽(若为-1表示填充)
# 这个是为了圈出鼠标点击的点
cv.circle(img_dest_copy, (x, y), 2, (0, 255, 255), -1)
# 用鼠标单击事件来选择坐标
# 将选中的四个点存放在集合中,在收集四个点时,四个点的点击顺序需要按照 img_src_coordinate 中的点的相对位置的前后顺序保持一致
print(f'{x}, {y}')
replace_coordinate_array.append([x, y])
if __name__ == '__main__':
# 首先,加载待替换的源图像,并获得该图像的长度等信息,cv.IMREAD_COLOR 表示加载原图
img_src = cv.imread('img_src.jpg', cv.IMREAD_COLOR)
h, w, c = img_src.shape
# 获得图像的四个边缘点的坐标
img_src_coordinate = np.array([[x, y] for x in (0, w - 1) for y in (0, h - 1)])
print(img_src_coordinate)
# cv.imshow('replace', replace)
print("===========================")
# 加载目标图像
img_dest = cv.imread('img_dest.webp', cv.IMREAD_COLOR)
# 将源数据复制一份,避免后来对该数据的操作会对结果有影响
img_dest_copy = np.tile(img_dest, 1)
# 源图像中的数据
# 定义一个数组,用来存放要源图像中要替换的坐标点,该坐标点由鼠标采集得到
replace_coordinate = []
cv.namedWindow('collect coordinate')
cv.setMouseCallback('collect coordinate', mouse_action, replace_coordinate)
while True:
# 当采集到四个点后,可以按esc退出鼠标采集行为
if cv.waitKey(20) == 27:
break
print(replace_coordinate)
replace_coordinate = np.array(replace_coordinate)
# 根据选中的四个点坐标和代替换的图像信息完成单应矩阵
matrix, mask = cv.findHomography(img_src_coordinate, replace_coordinate, 0)
print(f'matrix: {matrix}')
perspective_img = cv.warpPerspective(img_src, matrix, (img_dest.shape[1], img_dest.shape[0]))
cv.imshow('img', perspective_img)
# cv.imshow('threshold', threshold_img)
# 降噪,去掉最大或最小的像素点
retval, threshold_img = cv.threshold(perspective_img, 0, 255, cv.THRESH_BINARY)
# 将降噪后的图像与之前的图像进行拼接
cv.copyTo(src=threshold_img, mask=np.tile(threshold_img, 1), dst=img_dest)
cv.copyTo(src=perspective_img, mask=np.tile(perspective_img, 1), dst=img_dest)
cv.imshow('result', img_dest)
cv.waitKey()
cv.destroyAllWindows()