导读
本文主要介绍如何使用 OpenCV + GrabCut实现一个文档自动扫描仪。(公众号:OpenCV与AI深度学习)
实现步骤: 【1】通过形态学处理,得到一个空白页。这里直接用闭运算即可,闭运算是膨胀,然后是腐蚀。不断重复这些关闭操作,直到你得到一个空白页。
kernel = np.ones((5,5),np.uint8) img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations= 3)
为什么我们想要一个空白文档呢?因为后面会进行边缘检测,并且我们不希望被页面的文字内容干扰该。 【2】用GrabCut去掉背景。
我们将角落 20 像素作为背景,GrabCut 会自动确定前景和背景,只留下文档。 mask = np.zeros(img.shape[:2],np.uint8) bgdModel = np.zeros((1,65),np.float64) fgdModel = np.zeros((1,65),np.float64) rect = (20,20,img.shape[1]-20,img.shape[0]-20) cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT) mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8') img = img*mask2[:,:,np.newaxis]
【3】Canny边缘检测 + 轮廓提取。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (11, 11), 0)
# Edge Detection.
canny = cv2.Canny(gray, 0, 200)
canny = cv2.dilate(canny, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))
# Blank canvas.
con = np.zeros_like(img)
# Finding contours for the detected edges.
contours, hierarchy = cv2.findContours(canny, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# Keeping only the largest detected contour.
page = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
con = cv2.drawContours(con, page, -1, (0, 255, 255), 3)
【4】角点检测 + 排序。
# Blank canvas.
con = np.zeros_like(img)
# Loop over the contours.
for c in page:
# Approximate the contour.
epsilon = 0.02 * cv2.arcLength(c, True)
corners = cv2.approxPolyDP(c, epsilon, True)
# If our approximated contour has four points
if len(corners) == 4:
break
cv2.drawContours(con, c, -1, (0, 255, 255), 3)
cv2.drawContours(con, corners, -1, (0, 255, 0), 10)
# Sorting the corners and converting them to desired shape.
corners = sorted(np.concatenate(corners).tolist())
# Displaying the corners.
for index, c in enumerate(corners):
character = chr(65 + index)
cv2.putText(con, character, tuple(c), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 1, cv2.LINE_AA)
角点排序:
def order_points(pts):
'''Rearrange coordinates to order:
top-left, top-right, bottom-right, bottom-left'''
rect = np.zeros((4, 2), dtype='float32')
pts = np.array(pts)
s = pts.sum(axis=1)
# Top-left point will have the smallest sum.
rect[0] = pts[np.argmin(s)]
# Bottom-right point will have the largest sum.
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1)
# Top-right point will have the smallest difference.
rect[1] = pts[np.argmin(diff)]
# Bottom-left will have the largest difference.
rect[3] = pts[np.argmax(diff)]
# Return the ordered coordinates.
return rect.astype('int').tolist()
【5】透视变换对齐文档。
# Getting the homography.
M = cv2.getPerspectiveTransform(np.float32(corners), np.float32(destination_corners))
# Perspective transform using homography.
final = cv2.warpPerspective(orig_img, M, (destination_corners[2][0], destination_corners[2][1]), flags=cv2.INTER_LINEAR)
【6】扩展测试。
我们在 23 种不同的背景和不同的方向上进行了测试,自动文档扫描仪几乎在所有情况下都运行良好。
失败情况:
当文档的一部分在图像之外时,可能会丢失一个角落,GrabCut 无法扫描。这是使用 GrabCut 的唯一限制。在大多数其他情况下,我们的文档扫描仪运行良好。
这种方法的另一个限制是边缘和轮廓检测。如果背景中存在大量噪声,则会检测到许多不需要的边缘,并且在某些情况下,轮廓检测步骤可能会将这些边缘误认为是文档。此外,如果文档边缘与背景无法区分,则轮廓检测可能无法完全正常工作。
但 GrabCut 和轮廓检测并不是唯一经过验证的文档扫描方法。对于消费级文档扫描解决方案,首选角点检测和分割等深度学习技术,因为它们更强大。