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


前言


前面也分享过一些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 上下载,本人水平有限,欢迎各位大神积极讨论或者给我意见(私信我即可)

  • 20
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
提取纸张边框,可以通过以下步骤实现: 1. 图像预处理:使用图像处理库(例如OpenCV)对原始图像进行灰度化、二值化、降噪等处理,使得边缘提取更加准确。 2. 边缘检测:使用Canny算法或Sobel算子等边缘检测算法,检测图像中的边缘。这里需要根据实际情况调整参数,以确保检测到了纸张边缘。 3. 轮廓检测:根据边缘检测的结果,使用findContours函数检测图像中的所有轮廓。 4. 提取纸张边框:根据轮廓的特征(例如面积、周长、凸包等),筛选出纸张边框的轮廓。可以根据实际情况调整筛选条件。 5. 裁切摆正:根据纸张边框的四个角点,使用透视变换将原始图像裁切并旋转到水平方向。 代码示例: ```python import cv2 # 读取原始图像 img = cv2.imread('input.jpg') # 灰度化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 降噪 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) # 边缘检测 edges = cv2.Canny(binary, 30, 150) # 轮廓检测 contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 提取纸张边框 paper_contour = None for contour in contours: # 筛选条件:面积、周长、凸包等 area = cv2.contourArea(contour) perimeter = cv2.arcLength(contour, True) hull = cv2.convexHull(contour) solidity = 1.0 * area / cv2.contourArea(hull) if area > 50000 and perimeter > 1000 and solidity > 0.9: paper_contour = contour break # 裁切摆正 if paper_contour is not None: rect = cv2.minAreaRect(paper_contour) box = cv2.boxPoints(rect) box = box.astype(int) cv2.drawContours(img, [box], 0, (0, 0, 255), 2) M = cv2.getPerspectiveTransform(box, np.array([[0, 0], [800, 0], [800, 1100], [0, 1100]], dtype=np.float32)) dst = cv2.warpPerspective(img, M, (800, 1100)) cv2.imshow('result', dst) else: print('No paper found') cv2.waitKey(0) cv2.destroyAllWindows() ``` 上述代码中,使用了Canny算法进行边缘检测,使用findContours函数进行轮廓检测,使用最小外接矩形和透视变换进行裁切摆正。需要注意的是,提取纸张边框的筛选条件需要根据实际情况调整。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值