纸张图像边框提取、摆正以及是否填写检测

时间:2024-03-15 15:55:55

前言

前面也分享过一些opencv的一些图像处理方式,那我今天介绍一个用opencv来提取合同、纸张或者证件的边框并去掉背景,将图像摆正的做法,然后也根据这个思路,介绍下校验是否填写,或者签名的一个思路。话不多说,来看下实现的效果图(图片是我无聊的时候乱写的纸,逃~),具体代码我会放在我的github https://github.com/Wangzg123/fileclipper 上,大家可以去下载
纸张图像边框提取、摆正以及是否填写检测
纸张图像边框提取、摆正以及是否填写检测
首先来检验介绍一下实现的步骤
1、将图像转为固定高度的图片(这一步是为了适配边缘检测的阈值,下面会介绍)
2、用opencv的边缘检测算法提取边缘
3、找出闭合的轮廓并计算他们的面积,提取最大面积
4、拟合最大面积的的四边形的4个顶点
5、通过opencv的透视变化拟合出长方形的纸张边缘

一、边缘检测

这一步通过opencv的Canny函数将边缘提取出来(具体的功能介绍请自己查阅),值得一提的是在canny前为防止一些噪点必须通过高斯模糊去噪点,然后也膨胀边缘使图像更容易闭合

import cv2
import numpy as np


# 固定尺寸
def resizeImg(image, height=900):
    h, w = image.shape[:2]
    pro = height / h
    size = (int(w * pro), int(height))
    img = cv2.resize(image, size)
    return img


# 边缘检测
def getCanny(image):
    # 高斯模糊
    binary = cv2.GaussianBlur(image, (3, 3), 2, 2)
    # 边缘检测
    binary = cv2.Canny(binary, 60, 240, apertureSize=3)
    # 膨胀操作,尽量使边缘闭合
    kernel = np.ones((3, 3), np.uint8)
    binary = cv2.dilate(binary, kernel, iterations=1)
    return binary

path = r'C:\Users\wzg\Desktop\test.jpg'
outpath = r'C:\Users\wzg\Desktop\getCanny.jpg'
img = cv2.imread(path)
img = resizeImg(img)
print('shape =', img.shape)
binary_img = getCanny(img)
cv2.imwrite(outpath, binary_img)

# output:	shape = (900, 420, 3)

纸张图像边框提取、摆正以及是否填写检测

二、找出纸张边缘

通过findContours拟合出所有的轮廓,然后找出最大的轮廓即是纸质的边缘(因为拍摄的时候,我们的目标图形一般是最大的)

# 代码承接上文
# 求出面积最大的轮廓
def findMaxContour(image):
    # 寻找边缘
    _, contours, _ = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    # 计算面积
    max_area = 0.0
    max_contour = []
    for contour in contours:
        currentArea = cv2.contourArea(contour)
        if currentArea > max_area:
            max_area = currentArea
            max_contour = contour
    return max_contour, max_area


path = r'C:\Users\wzg\Desktop\test.jpg'
outpath = r'C:\Users\wzg\Desktop\findMaxContour.jpg'
img = cv2.imread(path)
img = resizeImg(img)
binary_img = getCanny(img)
max_contour, max_area = findMaxContour(binary_img)
cv2.drawContours(img, max_contour, -1, (0, 0, 255), 3)
cv2.imwrite(outpath, img)

纸张图像边框提取、摆正以及是否填写检测

三、找出四边形的四个顶点

拟合最大面积的的四边形的4个顶点

# 代码承接上文
# 多边形拟合凸包的四个顶点
def getBoxPoint(contour):
    # 多边形拟合凸包
    hull = cv2.convexHull(contour)
    epsilon = 0.02 * cv2.arcLength(contour, True)
    approx = cv2.approxPolyDP(hull, epsilon, True)
    approx = approx.reshape((len(approx), 2))
    return approx

path = r'C:\Users\wzg\Desktop\test.jpg'
outpath = r'C:\Users\wzg\Desktop\getBoxPoint.jpg'
img = cv2.imread(path)
img = resizeImg(img)
binary_img = getCanny(img)
max_contour, max_area = findMaxContour(binary_img)
boxes = getBoxPoint(max_contour)
for box in boxes:
   cv2.circle(img, tuple(box), 5, (0, 0, 255), 2)
print(boxes)
cv2.imwrite(outpath, img)

纸张图像边框提取、摆正以及是否填写检测

四、透视变换

至此为止,我们都是在resize后的图片上找那四个顶点,我们还必须根据这4个点映射回原图的点。而且我们得到的图像是一个梯形形状,这时我们还要通过透视变化改成长方形的形状。

# 代码承接上文
# 适配原四边形点集
def adaPoint(box, pro):
    box_pro = box
    if pro != 1.0:
        box_pro = box/pro
    box_pro = np.trunc(box_pro)
    return box_pro


# 四边形顶点排序,[top-left, top-right, bottom-right, bottom-left]
def orderPoints(pts):
    rect = np.zeros((4, 2), dtype="float32")
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    return rect


# 计算长宽
def pointDistance(a, b):
    return int(np.sqrt(np.sum(np.square(a - b))))


# 透视变换
def warpImage(image, box):
    w, h = pointDistance(box[0], box[1]), \
           pointDistance(box[1], box[2])
    dst_rect = np.array([[0, 0],
                         [w - 1, 0],
                         [w - 1, h - 1],
                         [0, h - 1]], dtype='float32')
    M = cv2.getPerspectiveTransform(box, dst_rect)
    warped = cv2.warpPerspective(image, M, (w, h))
    return warped



path = r'C:\Users\wzg\Desktop\test.jpg'
outpath = r'C:\Users\wzg\Desktop\result.jpg'
image = cv2.imread(path)
ratio = 900 / image.shape[0]
img = resizeImg(image)
binary_img = getCanny(img)
max_contour, max_area = findMaxContour(binary_img)
boxes = getBoxPoint(max_contour)
boxes = adaPoint(boxes, ratio)
boxes = orderPoints(boxes)
# 透视变化
warped = warpImage(image, boxes)
cv2.imwrite(outpath, warped)

纸张图像边框提取、摆正以及是否填写检测

五、其他

至此为止,我们的纸张边缘提取、摆正已经完成了,可以知道通过opencv可以实现以上的功能,我这里还有一个解决一些校验一些必填选项的是否填写的思路。如下面两种图,我们要检测红框处是否签了名。
纸张图像边框提取、摆正以及是否填写检测
解决思路是这样的:
1、将两张合同通过我上面的算法提取出正四边形的纸张
2、通过合同的预设点设置检测区域(比如上面在空白合同的位置标注矩形的左上角和右下角)
3、裁剪出标注区域(同样像素)并做二值化处理(转为只有0 和 1的矩阵)
4、因为写了的和没写的合同像素值有差别,那么如校验的大于一个阈值,那么就签了名,反之亦然
解决效果可以如下示(我就不贴代码了,逃~)
纸张图像边框提取、摆正以及是否填写检测

总结

以上用opencv的方法介绍了怎么提取纸张(其实不管白纸都可以,只要是个物体即可)的边缘,然后通过透视变化摆正,最后提了一个是否签名等填写的检测的解决思路。本文具体代码可以到我github https://github.com/Wangzg123/fileclipper 上下载,本人水平有限,欢迎各位大神积极讨论或者给我意见(私信我即可)