一、cv2.getPerspectiveTransform
cv2.getPerspectiveTransform(src, dst) → retval
src:源图像中待测矩形的四点坐标
sdt:目标图像中矩形的四点坐标
一、cv2.warpAffine
放射变换函数,可实现旋转,平移,缩放;变换后的平行线依旧平
cv2.warpAffine(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None) --> dst
src:输入图像 dst:输出图像
M:2×3的变换矩阵
dsize:变换后输出图像尺寸
flag:插值方法
borderMode:边界像素外扩方式
borderValue:边界像素插值,默认用0填充
变换矩阵M可通过cv2.getAffineTransfrom(points1, points2)函数获得
变换矩阵的获取需要至少三组变换前后对应的点坐标,设取原图上的三个点组成矩阵points1,变换后的三个点组成的矩阵points2
points1 = np.float32([ [30,30], [100,40], [40,100] ])
points2 = np.float32([ [60,60], [40,100], [80,20] ])
M = cv2.getAffineTransform(points1, points2)
= array([[-0.33333333, 0.33333333, 60. ],
[ 0.66666667, -0.66666667, 60. ]])
Affine_img = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
二、cv2.warpPerspective
透视变换函数,可保持直线不变形,但是平行线可能不再平行
cv2.warpPerspective(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None) --> dst
其相关参数和cv2.warpAffine函数的类似,不再做介绍
它的变换矩阵可以通过cv2.getPerspectiveTransform()函数获得,其原理和cv2.getAffineTransfrom()相同,只是投射变换至少需要四组变换前后对应的点坐标,设取原图上的四个点组成矩阵points1,变换后的四个点组成的矩阵points2
如:
points1 = np.float32([ [30,30], [10,40], [40,10], [5,15] ])
points2 = np.float32([ [0,0], [400,0], [0,400], [400,400] ])
M = cv2.getPerspectiveTransform(points1, points2)
= array([[-9.08777969e+00, -4.54388985e+00, 4.08950086e+02],
[-5.37005164e+00, -1.07401033e+01, 4.83304647e+02],
[-1.15318417e-02, -1.35972461e-02, 1.00000000e+00]])
Perspective_img = cv2.warpPerspective(img, M, (img.shape[1], img.shape[0]))
三、快速应用工具
# -*- coding: utf-8 -*-
"""
@author: yuki_ho
"""
import cv2
import numpy as np
# -----------------------鼠标操作相关------------------------------------------
lsPointsChoose = []
tpPointsChoose = []
pointsCount = 0
count = 0
pointsMax = 6
def on_mouse(event, x, y, flags, param):
global img, point1, point2, count, pointsMax
global lsPointsChoose, tpPointsChoose # 存入选择的点
global pointsCount # 对鼠标按下的点计数
global img2, ROI_bymouse_flag
img2 = img.copy() # 此行代码保证每次都重新再原图画 避免画多了
# -----------------------------------------------------------
# count=count+1
# print("callback_count",count)
# --------------------------------------------------------------
if event == cv2.EVENT_LBUTTONDOWN: # 左键点击
pointsCount = pointsCount + 1
# 为了保存绘制的区域,画的点稍晚清零
# if (pointsCount == pointsMax + 1):
# pointsCount = 0
# tpPointsChoose = []
print('pointsCount:', pointsCount)
point1 = (x, y)
print(x, y)
# 画出点击的点
cv2.circle(img2, point1, 10, (0, 255, 0), 2)
# 将选取的点保存到list列表里
lsPointsChoose.append([x, y]) # 用于转化为darry 提取多边形ROI
tpPointsChoose.append((x, y)) # 用于画点
# ----------------------------------------------------------------------
# 将鼠标选的点用直线连起来
print(len(tpPointsChoose))
for i in range(len(tpPointsChoose) - 1):
print('i', i)
cv2.line(img2, tpPointsChoose[i], tpPointsChoose[i + 1], (0, 0, 255), 2)
# ----------------------------------------------------------------------
# ----------点击到pointMax时可以提取去绘图----------------
cv2.imshow('src', img2)
# -------------------------右键按下清除轨迹-----------------------------
if event == cv2.EVENT_RBUTTONDOWN: # 右键点击
print("right-mouse")
pointsCount = 0
tpPointsChoose = []
lsPointsChoose = []
print(len(tpPointsChoose))
for i in range(len(tpPointsChoose) - 1):
print('i', i)
cv2.line(img2, tpPointsChoose[i], tpPointsChoose[i + 1], (0, 0, 255), 2)
cv2.imshow('src', img2)
# -------------------------双击 结束选取-----------------------------
if event == cv2.EVENT_LBUTTONDBLCLK:
# -----------绘制感兴趣区域-----------
ROI_byMouse()
ROI_bymouse_flag = 1
lsPointsChoose = []
def ROI_byMouse():
global src, ROI, ROI_flag, mask2
mask = np.zeros(img.shape, np.uint8) # (450, 800, 3)
pts = np.array([lsPointsChoose], np.int32) # pts是多边形的顶点列表(顶点集)
pts = pts.reshape((-1, 1, 2))
print(pts) #所勾选的坐标
# 这里 reshape 的第一个参数为-1, 表明这一维的长度是根据后面的维度的计算出来的。
# OpenCV中需要先将多边形的顶点坐标变成顶点数×1×2维的矩阵,再来绘制
# --------------画多边形---------------------
mask = cv2.polylines(mask, [pts], True, (255, 255, 255))
##-------------填充多边形---------------------
mask2 = cv2.fillPoly(mask, [pts], (255, 255, 255))
cv2.imshow('mask', mask2)
cv2.imwrite('mask.jpg', mask2)
contours, hierarchy = cv2.findContours(cv2.cvtColor(mask2, cv2.COLOR_BGR2GRAY), cv2.RETR_TREE,
cv2.CHAIN_APPROX_NONE)
ROIarea = cv2.contourArea(contours[0])
print("ROIarea:", ROIarea)
ROI = cv2.bitwise_and(mask2, img)
# cv2.imwrite('ROI.jpg', ROI)
cv2.imshow('ROI', ROI)
#暂时 下面 只针对 3 或 4 个点
crop_change = transform4pts(ROI, pts, ROI.shape) if len(pts) > 3 else transform3pts(ROI, pts, ROI.shape)
cv2.imshow('ROI_trans', crop_change)
def ROI_test(img_path):
img = cv2.imread(img_path)
# 自己定义坐标点
pts = np.array(
[[[359,96]],
[[739,97]],
[[1164,661]],
[[83,663]]
])
crop_img = cropFill(img, pts)
cv2.imshow('crop_img', crop_img)
crop_change = transform4pts(crop_img, pts, crop_img.shape) if len(pts) > 3 else transform3pts(crop_img, pts, crop_img.shape)
cv2.imshow('trans_img', crop_change)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 截取并填充
def cropFill(src_img, pts):
mask = np.zeros(src_img.shape, np.uint8) # 空的画板
# --------------画多边形---------------------
mask = cv2.polylines(mask, [pts], True, (255, 255, 255))
##-------------填充多边形---------------------
mask2 = cv2.fillPoly(mask, [pts], (255, 255, 255))
contours, hierarchy = cv2.findContours(cv2.cvtColor(mask2, cv2.COLOR_BGR2GRAY), cv2.RETR_TREE,
cv2.CHAIN_APPROX_NONE)
ROI = cv2.bitwise_and(mask2, src_img)
return ROI
# 3点 透视变换
def transform3pts(src_img, pts, out_size):
# print(out_size) #(730, 1176, 3) # H W
pts = pts.astype(np.float32)
# 自定义吧
dpts = np.array([
[0, 0],
[out_size[1], 0],
[out_size[1], out_size[0]],
], dtype=np.float32)
M = cv2.getAffineTransform(pts, dpts)
img_result = cv2.warpAffine(src_img, M , (out_size[1],out_size[0])) #透视变换
return img_result
# 4点 透视变换
def transform4pts(src_img, pts, out_size):
# print(out_size) #(730, 1176, 3) # H W
pts = pts.astype(np.float32)
# 自定义吧
dpts = np.array([
[0, 0],
[out_size[1], 0],
[out_size[1], out_size[0]],
[0, out_size[0]],
], dtype=np.float32)
M = cv2.getPerspectiveTransform(pts, dpts) #变换矩阵 ABC变换到A'B'C'
img_result = cv2.warpPerspective(src_img, M , (out_size[1],out_size[0])) #透视变换
return img_result
if __name__ == '__main__':
img = cv2.imread('6.jpg') #1176,730
# ---------------------------------------------------------
# --图像预处理,设置其大小
# height, width = img.shape[:2]
# size = (int(width * 0.3), int(height * 0.3))
# img = cv2.resize(img, size, interpolation=cv2.INTER_AREA)
# ------------------------------------------------------------
ROI = img.copy()
cv2.namedWindow('src')
cv2.setMouseCallback('src', on_mouse)
cv2.imshow('src', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# ROI_test('6.jpg')
四、效果
五、调整工具
import argparse
import os
import tkinter as tk
from tkinter import filedialog
import cv2
import numpy as np
parser = argparse.ArgumentParser()
# parser.add_argument('source', type=str)
parser.add_argument('--height', type=int, default=1080)
parser.add_argument('--width', type=int, default=1920)
args = parser.parse_args()
print(args)
root = tk.Tk()
root.title('不规则四边形裁剪')
pos = {
'TL': (tk.IntVar(root, value=0), tk.IntVar(root, value=0)),
'TR': (tk.IntVar(root, value=0), tk.IntVar(root, value=1600)),
'BL': (tk.IntVar(root, value=900), tk.IntVar(root, value=0)),
'BR': (tk.IntVar(root, value=900), tk.IntVar(root, value=1600)),
}
output = (tk.IntVar(root, value=args.height), tk.IntVar(root, value=args.width))
img = None
fileName = None
fileExtension = None
inputRatio = None
def mark_line(img):
if img is None:
return
img_temp = img.copy()
cv2.line(
img_temp,
(pos['TL'][1].get(), pos['TL'][0].get()),
(pos['TR'][1].get(), pos['TR'][0].get()),
(0, 0, 255), 2, cv2.LINE_AA
)
cv2.line(
img_temp,
(pos['BL'][1].get(), pos['BL'][0].get()),
(pos['BR'][1].get(), pos['BR'][0].get()),
(0, 0, 255), 2, cv2.LINE_AA
)
cv2.line(
img_temp,
(pos['TL'][1].get(), pos['TL'][0].get()),
(pos['BL'][1].get(), pos['BL'][0].get()),
(0, 0, 255), 2, cv2.LINE_AA
)
cv2.line(
img_temp,
(pos['TR'][1].get(), pos['TR'][0].get()),
(pos['BR'][1].get(), pos['BR'][0].get()),
(0, 0, 255), 2, cv2.LINE_AA
)
return img_temp
def crop_image():
sourcePoints = np.array([
(pos['TL'][1].get(), pos['TL'][0].get()),
(pos['TR'][1].get(), pos['TR'][0].get()),
(pos['BR'][1].get(), pos['BR'][0].get()),
(pos['BL'][1].get(), pos['BL'][0].get()),
], dtype=np.float32)
print(sourcePoints)
dstPoints = np.array([
[0, 0],
[output[1].get(), 0],
[output[1].get(), output[0].get()],
[0, output[0].get()],
], dtype=np.float32)
print('output',output)
print(dstPoints)
M = cv2.getPerspectiveTransform(sourcePoints, dstPoints)
print('M:',M)
print('zuobiao:',(output[1].get(), output[0].get()) )
img_result = cv2.warpPerspective(img, M, (output[1].get(), output[0].get()))
return img_result
def show_images():
if img is None:
return
resize_img_to_show('input', mark_line(img))
resize_img_to_show('output', crop_image(), small=True)
cv2.setMouseCallback('input', mouseClicked)
def resize_img_to_show(name, img_temp, small=False):
if img_temp is None:
return
height = img_temp.shape[0]
width = img_temp.shape[1]
if small and height <= 700:
ratio = 1
else:
ratio = 700 / height
height = int(height * ratio)
width = int(width * ratio)
cv2.imshow(name, cv2.resize(img_temp, (width, height)))
if name == 'input':
global inputRatio
inputRatio = ratio
def openFile():
global img, fileName, fileExtension
file_path = filedialog.askopenfilename()
print(file_path)
if file_path is not None:
fileName = os.path.basename(file_path)
fileExtension = os.path.splitext(file_path)[1]
img = cv2.imread(file_path, cv2.IMREAD_UNCHANGED)
pos['TL'][0].set(0)
pos['TL'][1].set(0)
pos['TR'][0].set(0)
pos['TR'][1].set(img.shape[1] - 1)
pos['BL'][0].set(img.shape[0] - 1)
pos['BL'][1].set(0)
pos['BR'][0].set(img.shape[0] - 1)
pos['BR'][1].set(img.shape[1] - 1)
show_images()
def saveFile():
global img
if img is None:
return
out_path = filedialog.asksaveasfilename(
filetypes=[
('PNG File', '*.png'),
('JPEG File', '*.jpg'),
('All files', '*'),
],
initialfile=fileName,
defaultextension=fileExtension,
)
print(out_path)
if out_path:
img_result = crop_image()
cv2.imwrite(out_path, img_result)
def changePos(corner, xy, diff):
def wrapper():
pos[corner][xy].set(pos[corner][xy].get() + diff)
show_images()
return wrapper
def changeOutput(xy, diff):
def wrapper():
output[xy].set(output[xy].get() + diff)
show_images()
return wrapper
def mouseClicked(event, p1, p0, flags, param):
if event == cv2.EVENT_LBUTTONDOWN or (event == cv2.EVENT_MOUSEMOVE and (flags & cv2.EVENT_FLAG_LBUTTON)):
p0 = int(p0 / inputRatio)
p1 = int(p1 / inputRatio)
print(p0, p1)
minDis = 1e9
minCorner = None
for corner in pos:
tempDis = (pos[corner][0].get() - p0)**2 + (pos[corner][1].get() - p1)**2
if tempDis < minDis:
minDis = tempDis
minCorner = corner
pos[minCorner][0].set(p0)
pos[minCorner][1].set(p1)
show_images()
ROW = 0
tk.Button(root, text='Open File', command=openFile).grid(row=ROW, column=0, columnspan=2, sticky='W')
for xy in [0, 1]:
ROW += 1
COL = -1
for corner in ['TL', 'TR']:
COL += 1
tk.Label(root, text='{}{}'.format(corner, xy)).grid(row=ROW, column=COL)
COL += 1
tk.Entry(root, textvariable=pos[corner][xy]).grid(row=ROW, column=COL)
for diff in [1, 10, 100]:
COL += 1
tk.Button(root, text=' {:+d} '.format(-diff), command=changePos(corner, xy, -diff)).grid(row=ROW, column=COL)
COL += 1
tk.Button(root, text=' {:+d} '.format(+diff), command=changePos(corner, xy, +diff)).grid(row=ROW, column=COL)
ROW += 1
tk.Label(root, text='').grid(row=ROW, column=0)
for xy in [0, 1]:
ROW += 1
COL = -1
for corner in ['BL', 'BR']:
COL += 1
tk.Label(root, text='{}{}'.format(corner, xy)).grid(row=ROW, column=COL)
COL += 1
tk.Entry(root, textvariable=pos[corner][xy]).grid(row=ROW, column=COL)
for diff in [1, 10, 100]:
COL += 1
tk.Button(root, text=' {:+d} '.format(-diff), command=changePos(corner, xy, -diff)).grid(row=ROW, column=COL)
COL += 1
tk.Button(root, text=' {:+d} '.format(+diff), command=changePos(corner, xy, +diff)).grid(row=ROW, column=COL)
ROW += 1
tk.Label(root, text='').grid(row=ROW, column=0)
ROW += 1
COL = -1
for xy in [0, 1]:
COL += 1
tk.Label(root, text='Out{}'.format(xy)).grid(row=ROW, column=COL)
COL += 1
tk.Entry(root, textvariable=output[xy]).grid(row=ROW, column=COL)
for diff in [1, 10, 100]:
COL += 1
tk.Button(root, text=' {:+d} '.format(-diff), command=changeOutput(xy, -diff)).grid(row=ROW, column=COL)
COL += 1
tk.Button(root, text=' {:+d} '.format(+diff), command=changeOutput(xy, +diff)).grid(row=ROW, column=COL)
ROW += 1
tk.Button(root, text='Save File', command=saveFile).grid(row=ROW, column=0, columnspan=2, sticky='W')
root.mainloop()