线上审批等场景经常会用到手写签名、公司鲜章等,这篇文章介绍的就是如何定位抠图A4纸上的签名和鲜章的,并且可以批量处理。
主要使用opencv进行图像处理,把图像中的文字和印章轮廓处理出来,然后再进行定位裁剪,最后背景透明化。
先放效果图
代码
?印章部分
def yz(imgname):
original_image = cv2.imread(imgname)
image = original_image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
canny = cv2.Canny(blurred, 120, 255, 1)
# 找到图片中的轮廓
cnts = cv2.findContours(canny.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# 按照面积将所有轮廓逆序排序
contours2 = sorted(cnts, key=lambda a: cv2.contourArea(a), reverse=True)
ROI_number = 0
for c in contours2:
area = cv2.contourArea(c)
print(area)
# 只抠面积大于1200的轮廓,一般印章的轮廓面积比较大,可根据实际情况调整
if area < 1200: break
x, y, w, h = cv2.boundingRect(c)
# 调整裁剪的位置,避免印章的边缘缺失
x -= 20
y -= 20
w += 40
h += 40
cv2.rectangle(image, (x, y), (x + w, y + h), (36, 255, 12), 1)
ROI = original_image[y:y + h, x:x + w]
# cv2.imshow("ROI", ROI)
img_name = imgname[0: imgname.rindex(".")] + '_{}.png'.format(ROI_number)
cv2.imwrite(img_name, ROI)
pic = Image.open(img_name)
# 转为RGBA模式
pic = pic.convert('RGBA')
width, height = pic.size
# 获取图片像素操作入口
array = pic.load()
for i in range(width):
for j in range(height):
# 获得某个像素点,格式为(R, G, B, A)元组
pos = array[i, j]
# RGB三者都大于240(很接近白色了)
isEdit = (sum([1 for x in pos[0:3] if x > 240]) == 3)
if isEdit:
# 更改为透明
array[i, j] = (255, 255, 255, 0)
# 保存图片
pic.save(img_name)
ROI_number += 1
?签字部分,原理和印章一样,只不过签字有的不是连着的,所以要合并相邻的轮廓,保证扣出来的是完整的签名
def opcvimg(cur_path):
# 加载图像、灰度、高斯模糊、自适应阈值
image = cv2.imread(cur_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (9, 9), 0)
thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 30)
# 扩大以合并相邻的文本轮廓
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10))
dilate = cv2.dilate(thresh, kernel, iterations=6)
# 查找轮廓、突出显示文本区域并提取 ROI
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
ROI_number = 0
for c in cnts:
area = cv2.contourArea(c)
if area > 100:
x, y, w, h = cv2.boundingRect(c)
x -= 20
y -= 20
w += 40
h += 40
# cv2.rectangle(image, (x, y), (x + w, y + h), (36, 255, 12), 2)
ROI = image[y:y + h, x:x + w]
img_name = cur_path[0: cur_path.rindex(".")] + '_{}.png'.format(ROI_number)
cv2.imwrite(img_name, ROI)
ROI_number += 1
cv2.imwrite(img_name, ROI)
pic = Image.open(img_name)
pic = pic.convert('RGBA')
width, height = pic.size
array = pic.load()
for i in range(width):
for j in range(height):
pos = array[i, j]
isEdit = (sum([1 for x in pos[0:3] if x > 240]) == 3)
if isEdit:
array[i, j] = (255, 255, 255, 0)
pic.save(img_name)
完整代码,引入flask-restful,api使得程序可以组件化单独部署,提供抠图能力
#!/usr/bin/python
# encoding: utf-8
import cv2
import PIL.Image as Image
import os
from flask import Flask
from flask_restful import Api, Resource, request
app = Flask(__name__)
api = Api(app)
class DetectSign(Resource):
""" 提取签名和印章 """
def get(self):
try:
path = request.args['path']
type = request.args['type']
show_files(path, type)
return {"msg": "成功", "code": 200}
except Exception as e:
print(e)
return {"msg": "失败", "code": 500}
def show_files(path, type):
if path is None:
return
if ".jpg" in path or ".png" in path or ".gif" in path:
try:
if type == '0':
yz(path)
else:
opcvimg(path)
except Exception as e:
print(e)
else:
# 首先遍历当前目录所有文件及文件夹
file_list = os.listdir(path)
# 准备循环判断每个元素是否是文件夹还是文件,是文件的话,把名称传入list,是文件夹的话,递归
for file in file_list:
# 利用os.path.join()方法取得路径全名,并存入cur_path变量,否则每次只能遍历一层目录
cur_path = os.path.join(path, file)
# 判断是否是文件夹
if os.path.isdir(cur_path):
show_files(cur_path)
else:
print(cur_path)
if ".jpg" in cur_path or ".png" in cur_path or ".gif" in cur_path:
try:
if type == '0':
yz(cur_path)
else:
opcvimg(cur_path)
except Exception as e:
print(e)
def opcvimg(cur_path):
image = cv2.imread(cur_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (9, 9), 0)
thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 30)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10))
dilate = cv2.dilate(thresh, kernel, iterations=6)
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
ROI_number = 0
for c in cnts:
area = cv2.contourArea(c)
if area > 100:
x, y, w, h = cv2.boundingRect(c)
x -= 20
y -= 20
w += 40
h += 40
# cv2.rectangle(image, (x, y), (x + w, y + h), (36, 255, 12), 2)
ROI = image[y:y + h, x:x + w]
img_name = cur_path[0: cur_path.rindex(".")] + '_{}.png'.format(ROI_number)
cv2.imwrite(img_name, ROI)
ROI_number += 1
cv2.imwrite(img_name, ROI)
pic = Image.open(img_name)
pic = pic.convert('RGBA')
width, height = pic.size
array = pic.load()
for i in range(width):
for j in range(height):
pos = array[i, j
isEdit = (sum([1 for x in pos[0:3] if x > 240]) == 3)
if isEdit:
array[i, j] = (255, 255, 255, 0)
pic.save(img_name)
def yz(imgname):
original_image = cv2.imread(imgname)
image = original_image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
canny = cv2.Canny(blurred, 120, 255, 1)
cnts = cv2.findContours(canny.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
contours2 = sorted(cnts, key=lambda a: cv2.contourArea(a), reverse=True)
ROI_number = 0
for c in contours2:
area = cv2.contourArea(c)
print('图像面积:', area)
if area < 1200: break
x, y, w, h = cv2.boundingRect(c)
x -= 20
y -= 20
w += 40
h += 40
cv2.rectangle(image, (x, y), (x + w, y + h), (36, 255, 12), 1)
ROI = original_image[y:y + h, x:x + w]
# cv2.imshow("ROI", ROI)
img_name = imgname[0: imgname.rindex(".")] + '_{}.png'.format(ROI_number)
cv2.imwrite(img_name, ROI)
pic = Image.open(img_name)
pic = pic.convert('RGBA')
width, height = pic.size
array = pic.load()
for i in range(width):
for j in range(height):
pos = array[i, j]
isEdit = (sum([1 for x in pos[0:3] if x > 240]) == 3)
if isEdit:
array[i, j] = (255, 255, 255, 0)
pic.save(img_name)
ROI_number += 1
# 设置路由
api.add_resource(DetectSign, '/x-api/v1/ai/detect/sign')
if __name__ == '__main__':
# 将host设置为0.0.0.0,则外网用户也可以访问到这个服务
app.run('0.0.0.0', 8386, debug=True)
有编译打包好的 exe程序文件可以下载。
https://download.csdn.net/download/puremilkll/21739417
|