- 实现透视变换矫正功能——指定顶点、边缘检测、傅里叶变换方法
- 完成UI界面
一、功能介绍
在平面图像处理中,因为镜头角度等原因,容易导致图像出现倾斜、变形等情况,为了方便后续处理我们常常需要进行图像矫正,其中主要技术原理是两种变换类型–仿射变换(Affine Transformation)和透视变换(Perspective Transformation)。
透视变换较放射变换可以矫正变形,是三维空间上的非线性变换,可看作是仿射变换的更一般形式,简单讲即通过一个3x3的变换矩阵将原图投影到一个新的视平面(ViewingPlane),在视觉上的直观表现就是产生或消除了远近感。
二、通过指定顶点进行变换
指定需要裁剪的原图部位的四个顶点坐标与裁剪后的四个顶点在新窗口的顶点坐标,通过cv2.getPerspectiveTransform函数设置透视变换矩阵。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import matplotlib.pyplot as plt
def perspective_correction_function(instance):
#读取图片
img=instance.pers_cor_original[0][0]
src = cv2.imread(img)
#获取图像大小
rows, cols = src.shape[:2]
#设置图像透视变换矩阵
pos1 = np.float32([[114, 82], [287, 156], [8, 322], [216, 333]])
pos2 = np.float32([[0, 0], [188, 0], [0, 262], [188, 262]])
M = cv2.getPerspectiveTransform(pos1, pos2)
#图像透视变换
result = cv2.warpPerspective(src, M, (190, 272))
#显示图像
cv2.imshow("original", src)
cv2.imshow("result", result)
#等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()
实现效果:
三、通过边缘检测实现变换
通过灰度图读入图片,通过高斯模糊去噪后,通过闭合运算提取图像轮廓边缘,从而定位区域的四个顶点,设置透视变换矩阵。
""""""""""""" Canny边缘检测 """""""""""""
def Img_Outline(input_dir):
original_img = cv2.imread(input_dir)
gray_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray_img, (9, 9), 0) # 高斯模糊去噪(设定卷积核大小影响效果)
_, RedThresh = cv2.threshold(blurred, 165, 255, cv2.THRESH_BINARY) # 设定阈值165(阈值影响开闭运算效果)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) # 定义矩形结构元素
closed = cv2.morphologyEx(RedThresh, cv2.MORPH_CLOSE, kernel) # 闭运算(链接块)
opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel) # 开运算(去噪点)
return original_img, gray_img, RedThresh, closed, opened
def findContours_img(original_img, opened):
contours, hierarchy = cv2.findContours(opened, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
c = sorted(contours, key=cv2.contourArea, reverse=True)[1] # 计算最大轮廓的旋转包围盒
rect = cv2.minAreaRect(c) # 获取包围盒(中心点,宽高,旋转角度)
box = np.int0(cv2.boxPoints(rect)) # box
draw_img = cv2.drawContours(original_img.copy(), [box], -1, (0, 0, 255), 3)
print("box[0]:", box[0])
print("box[1]:", box[1])
print("box[2]:", box[2])
print("box[3]:", box[3])
return box,draw_img
def Perspective_transform(box,original_img):
# 获取画框宽高(x=orignal_W,y=orignal_H)
orignal_W = math.ceil(np.sqrt((box[3][1] - box[2][1])**2 + (box[3][0] - box[2][0])**2))
orignal_H= math.ceil(np.sqrt((box[3][1] - box[0][1])**2 + (box[3][0] - box[0][0])**2))
# 原图中的四个顶点,与变换矩阵
pts1 = np.float32([box[0], box[1], box[2], box[3]])
pts2 = np.float32([[int(orignal_W+1),int(orignal_H+1)], [0, int(orignal_H+1)], [0, 0], [int(orignal_W+1), 0]])
# 生成透视变换矩阵;进行透视变换
M = cv2.getPerspectiveTransform(pts1, pts2)
result_img = cv2.warpPerspective(original_img, M, (int(orignal_W+3),int(orignal_H+1)))
return result_img
def perspective_correction_function_canny(instance):
img = "test.png"
img = instance.img_root_path
original_img, gray_img, RedThresh, closed, opened = Img_Outline(img)
box, draw_img = findContours_img(original_img, opened)
result_img = Perspective_transform(box, original_img)
cv2.imshow("original", original_img)
cv2.imshow("draw_img", draw_img)
cv2.imshow("result_img", result_img)
instance.m_image=result_img
instance.updata_image()
顶点坐标:
变换过程:
四、通过傅里叶变换实现变换
(一)傅立叶变换
在频域里面,对于一幅图像,高频部分代表了图像的细节、纹理信息;低频部分代表了图像的轮廓信息。如果对一幅精细的图像使用低通滤波器,那么滤波后的结果就剩下了轮廓了。如果图像受到的噪声恰好位于某个特定的“频率”范围内,则可以通过滤波器来恢复原来的图像。傅里叶变换在图像处理中可以做到:图像增强与图像去噪,图像分割之边缘检测,图像特征提取,图像压缩等等。
(二)傅立叶变换的实现过程
- 将图像延展到最佳尺寸以提高运行速度
- 为傅里叶变换的结果(实部和虚部)分配存储空间——添加额外通道存储复数部分
- 进行离散傅立叶变换——dtf()可对一维或二维浮点数数组进行正向或反向傅里叶变换
- 将复数转换为幅度——magnit()可计算二维矢量的幅值
- 对数尺度缩放——由于傅立叶变换的幅值范围较大,使用对数尺度来替换线性尺度
- 剪切和重分布幅度图象限——将幅度图的四个角点重叠至图片中心
- 归一化——使用normalize()将图像像素值归一到0~255
`
使用的函数:
cv2.getOptimalDFTSize(vecsize)
# vecsize: 传入:image.shape[0]/image.shape[1]
cv2.copyMakeBorder(src,top,bottom,left,right,borderType,dst=None)
"""
src: 图像
top,bottom,left,right: 上/下/左/右边扩充像素(int)
borderType: 边界类型:
BORDER_CONSTANT: 常量,增加的变量通通为value
BORDER_REFLICATE: 直接用边界的颜色填充,比如 : aaaaaa | abcdefgh | hhhhhhh
BORDER_REFLECT: 镜像:比如 : fedcba | abcdefgh | hgfedcb
BORDER_REFLECT_101:倒映,和上面类似,但在倒映时,会把边界空开:比如 : gfedcb | abcdefgh |gfedcba
BORDER_WRAP: 没有规律的,比如: cdefgh | abcdefgh | abcdefg
"""
cv2.magnitude(InputArray x, InputArray y, OutPutArray magnitude)
"""
计算输入矩阵x和y对应该的每个像素平方求和后开根号保存在输出矩阵magnitude中。
"""
(三) 图像矫正
通过傅里叶变换实现图像矫正首先获取图像的傅立叶变换图,通过Hough直线检测计算倾斜角度,从而旋转矫正。
""""""""""""" 傅里叶变换 """""""""""""
def perspective_correction_function_fourier(instance):
#1、灰度化读取文件,
img = cv2.imread('fourier.png',0)
img = instance.img_root_path
img = cv2.imread(img,0)
#2、图像延扩
h, w = img.shape[:2]
new_h = cv2.getOptimalDFTSize(h)
new_w = cv2.getOptimalDFTSize(w)
right = new_w - w
bottom = new_h - h
nimg = cv2.copyMakeBorder(img, 0, bottom, 0, right, borderType=cv2.BORDER_CONSTANT, value=0)
cv2.imshow('new image', nimg)
#3、执行傅里叶变换,并过得频域图像
f = np.fft.fft2(nimg)
fshift = np.fft.fftshift(f)
magnitude = np.log(np.abs(fshift))
#二值化
magnitude_uint = magnitude.astype(np.uint8)
ret, thresh = cv2.threshold(magnitude_uint, 11, 255, cv2.THRESH_BINARY)
print(ret)
cv2.imshow('thresh', thresh)
print(thresh.dtype)
#霍夫直线变换
lines = cv2.HoughLinesP(thresh, 2, np.pi/180, 30, minLineLength=40, maxLineGap=100)
print(len(lines))
#创建一个新图像,标注直线
lineimg = np.ones(nimg.shape,dtype=np.uint8)
lineimg = lineimg * 255
piThresh = np.pi/180
pi2 = np.pi/2
print(piThresh)
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(lineimg, (x1, y1), (x2, y2), (0, 255, 0), 2)
if x2 - x1 == 0:
continue
else:
theta = (y2 - y1) / (x2 - x1)
if abs(theta) < piThresh or abs(theta - pi2) < piThresh:
continue
else:
print(theta)
angle = math.atan(theta)
angle = angle * (180 / np.pi)
angle = (angle - 90)/(w/h)
center = (w//2, h//2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(img, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
cv2.imshow('line image', lineimg)
cv2.imshow('rotated', rotated)
instance.m_image=rotated
instance.updata_image()
实现效果
五、UI界面
""""""""""""" 透视变换矫正"""""""""""""
# 创建一个窗口,定义位置,标题等属性
instance.widget_perspective_correction = QWidget()
instance.widget_perspective_correction.setGeometry(200, 200, 550, 120)
instance.widget_perspective_correction.setWindowTitle('透视变换矫正')
# 创建一个按钮,该按钮的父对象为instance.widget_perspective_correction
# 当该按钮被点击时执行信号发射函数
instance.button_widget_perspective_correction = QPushButton("基于顶点检测", instance.widget_perspective_correction)
instance.button_widget_perspective_correction.setGeometry(30, 30, 150, 50)
instance.button_widget_perspective_correction.clicked.connect(instance.signal_perspective_correction_appoint_emit)
instance.button_widget_perspective_correction_canny = QPushButton("基于边缘检测", instance.widget_perspective_correction)
instance.button_widget_perspective_correction_canny.setGeometry(200, 30, 150, 50)
instance.button_widget_perspective_correction_canny.clicked.connect(instance.signal_perspective_correction_canny_emit)
instance.button_widget_perspective_correction_fourier = QPushButton("基于傅里叶变换", instance.widget_perspective_correction)
instance.button_widget_perspective_correction_fourier.setGeometry(370, 30, 150, 50)
instance.button_widget_perspective_correction_fourier.clicked.connect(instance.signal_perspective_correction_fourier_emit)
# 创建一个action,当该action被触发时运行show_perspective_correction
action_perspective_correction_widget_show = QAction('&透视变换矫正', instance)
action_perspective_correction_widget_show.triggered.connect(instance.show_perspective_correction_widget)