python图像倾斜校正_图像倾斜矫正

没有找到关于图像倾斜矫正的综述性文献,那就自己整理一下吧。

图像倾斜可以分为两种情况,一种是平面倾斜,这种情况下拍照设备与试卷平行,拍出来的图像只需要进行旋转即可完成矫正;另一种是Z轴倾斜,这种情况下拍照设备与试卷存在一定的角度,拍出来的图像要先进行透视变换,然后再进行旋转等操作才可以完成矫正。

图像倾斜矫正关键在于根据图像特征自动检测出图像倾斜方向和倾斜角度。

对于平面倾斜,先利用边缘(轮廓)检测算法算法找到图像的边界,然后利用 Radon变换法(基于投影的方法) 、 Hough变换法 、线性回归法等找到倾斜角度,然后再利用 仿射变换 进行旋转。

对于Z轴倾斜,先利用边缘(轮廓)检测算法找到图像的边界,然后利用 透视变换 把视平面上的点投影到现实平面,然后再利用仿射变换进行旋转。

实践

边缘检测

# -*- coding: utf-8 -*-

import numpy as np

import cv2

img = cv2.imread('../image/tilt.jpg')

GrayImage = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

CannyImage = cv2.Canny(GrayImage,50, 150, apertureSize=3)

cv2.imshow('gray',GrayImage)

cv2.imshow('canny',CannyImage)

cv2.waitKey(0)

edges=cv.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) 参数说明:

image:输入图像。

threshold1:最小阈值。

threshold2:最大阈值。

apertureSize:Sobel算子的孔径大小。

更多内容参考:

以上代码,调用了OpenCV中的canny方法找到了边缘,但是,文字的边缘也被显示出来了。那么,怎么去掉文字边缘,只要长方形框的边缘呢?OpenCV也提供了方法,使用findContours来查找轮廓,使用drawContours来绘制轮廓。

# -*- coding: utf-8 -*-

import numpy as np

import cv2

img = cv2.imread('../image/tilt.jpg')

GrayImage = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

CannyImage = cv2.Canny(GrayImage,50, 150, apertureSize=3)

ret,BinImage=cv2.threshold(CannyImage,127,255,cv2.THRESH_BINARY)

_, contours, _= cv2.findContours(BinImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

cv2.imshow('bin',BinImage)

cv2.drawContours(img, contours, -1, (0,255, 0), 1)

cv2.imshow('edge',img)

cv2.waitKey(0)

contours, hierarchy = cv.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) 参数说明:

image:输入图像,二值化图像。

mode:轮廓检索模式,请参阅 RetrievalModes 。

如果按照 官方文档 调用,会报错,参考 ValueError: too many values to unpack 解决。实际上,这是因为opencv3之后该函数的返回值有三个,而官方文档有多个版本,比如 这一版 中就说明了有三个参数。

计算倾斜角

以上,已经找到了图片的边缘,接下来计算倾斜角度。

# -*- coding: utf-8 -*-

import numpy as np

import cv2

img = cv2.imread('../image/tilt.jpg')

GrayImage = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

CannyImage = cv2.Canny(GrayImage,50, 150, apertureSize=3)

ret,BinImage=cv2.threshold(CannyImage,127,255,cv2.THRESH_BINARY)

lines = cv2.HoughLinesP(BinImage, 1, np.pi / 180, 160, minLineLength=200, maxLineGap=180)

# 寻找长度最长的线

distance = []

for line in lines:

x1, y1, x2, y2 = line[0]

dis = np.sqrt(pow((x2 - x1), 2) + pow((y2 - y1), 2))

distance.append(dis)

max_dis_index = distance.index(max(distance))

max_line = lines[max_dis_index]

x1, y1, x2, y2 = max_line[0]

# 获取旋转角度

angle = cv2.fastAtan2((y2 - y1), (x2 - x1))

print(angle)

以上代码,很尴尬,并不是利用findContours的结果进行计算的,而是一个新的思路。为什么没有使用contours?因为不会写代码。。。如果要继续findContours思路,那么可以参考 图像矫正技术深入探讨 进行改写。

仿射变换

求出倾斜角度之后,利用仿射变换进行旋转。

# 计算图片中心

centerpoint = (img.shape[1]/2,img.shape[0]/2)

# 获取旋转矩阵

rotate_mat = cv2.getRotationMatrix2D(centerpoint,angle,1.0)

correct_image = cv2.warpAffine(img,rotate_mat,(img.shape[1],img.shape[0]),borderValue =(255,255,255) )

cv2.imshow('right',correct_image)

cv2.waitKey(0)

Z轴倾斜

import cv2

import numpy as np

def gray_and_bin(init_img):

gray_img = cv2.cvtColor(init_img, cv2.COLOR_BGR2GRAY)

blur_img = cv2.GaussianBlur(gray_img, (3, 3), 0) # 高斯模糊去噪(设定卷积核大小影响效果)

canny_img = cv2.Canny(blur_img, 35, 189, apertureSize=3)

_, bin_img = cv2.threshold(canny_img, 127, 255, cv2.THRESH_BINARY) # 设定阈值165(阈值影响开闭运算效果)

return gray_img, bin_img

def points_and_box(init_img, bin_img):

image, contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

c = sorted(contours, key=cv2.contourArea, reverse=True)[0] # 计算最大轮廓的旋转包围盒

rect = cv2.minAreaRect(c) # 获取包围盒(中心点,宽高,旋转角度)

box = np.int0(cv2.boxPoints(rect)) # box

# box_img = cv2.drawContours(init_img.copy(), [box], -1, (0, 0, 255), 2)

# cv2.imshow('box',box_img)

empty_img = np.zeros(init_img.shape, np.uint8) # 创建空白图像

empty_img[...] = 255 # 设置白底

# cv2.imshow('test',empty_img)

cv2.drawContours(empty_img, contours, -1, (0, 0, 0), 1) # 在空白图像上绘制试卷轮廓

# cv2.imshow('edge', empty_img)

_, bin_img2 = gray_and_bin(empty_img)

# cv2.imshow('bin',bin_img2)

lines = cv2.HoughLinesP(bin_img2, 1, np.pi / 180, 100, minLineLength=200, maxLineGap=10)

for i in range(int(np.size(lines) / 4)):

for x1, y1, x2, y2 in lines[i]:

cv2.line(empty_img, (x1, y1), (x2, y2), (255, 255, 0), 1)

# cv2.imshow('line',empty_img)

points = None

if len(contours) > 0:

contours = sorted(contours, key=cv2.contourArea, reverse=True)

for c in contours:

peri = cv2.arcLength(c, True) # 轮廓按大小降序排序

approx = cv2.approxPolyDP(c, 0.02 * peri, True) # 获取近似的轮廓

if len(approx) == 4: # 近似轮廓有四个顶点

points = approx

break

print('piont[0]', points[0]) # 左下

print('piont[1]', points[1]) # 左上

print('piont[2]', points[2]) # 右上

print('piont[3]', points[3]) # 右下

print('box[0]:', box[0]) # 右下

print('box[1]:', box[1]) # 右上

print('box[2]:', box[2]) # 左上

print('box[3]:', box[3]) # 左下

return points,box

def perspective_transform(box,points,init_img):

# 原图中试卷的四个顶点

pts1 = np.float32([points[0], points[1], points[2], points[3]])

# box中的四个顶点

pts2 = np.float32([box[3], box[2], box[1], box[0]])

# 生成透视变换矩阵;进行透视变换

M = cv2.getPerspectiveTransform(pts1,pts2)

result_img = cv2.warpPerspective(init_img, M, (1200, 1300))

return result_img

if __name__=='__main__':

init_img = cv2.imread('../image/init.jpg')

gray_img, bin_img = gray_and_bin(init_img)

points, box = points_and_box(init_img,bin_img)

result_img = perspective_transform(box,points,init_img)

# cv2.imshow('init',init_img)

cv2.namedWindow('result', 0)

cv2.resizeWindow('result', 640, 1200)

cv2.imshow('result', result_img)

# cv2.imwrite('result.jpg',result_img)

cv2.waitKey(0)

以上结果,并不是很理想。透视矫正完成了,但是存在黑边白边,试卷长宽比例也有问题,也没有进行平面倾斜矫正。就先这样吧,作为一个小白,做成这样已经很不容易了。

后记

图像倾斜矫正这部分,是图像预处理中的一个难点。以上,虽然实现了基本的图像倾斜矫正算法,但是算法原理还需要进一步学习。而且,上面的代码中使用的图片都是理想的,干扰很小,而实际拍摄的图片,除了目标物体(试卷),还会出现其他物体,比如签字笔文具盒等等。所以,后续还需要考虑其他物体的干扰。

另外,发现OpenCV成为了开发过程中的瓶颈,想要实现一些功能,每次都要求助于百度谷歌。因此,必须找时间系统学习一下OpenCV。

你可能感兴趣的:(python图像倾斜校正)