前情提要:友人想开发一个和表情包相关的APP,拜托我写一些CV方面的算法。其中有一个算法要求提取出图片中的表情包。
思路分析
废话不多说,最基本的思路是先把图片过一遍边缘检测算法,然后利用findContours找图片中最大的矩形就好了。
但是实际实践中发现这样操作并不好使,首先是如果边缘检测使用常用的Canny,有些图片边缘并不明显,甚至和背景几乎同一种颜色很难做到分离,如果使用Laplacian(Laplacian非常敏感,甚至可以把白色背景UI和白色背景的图片分离,因为表情包通常被压缩过边缘会有色差),则会受到噪点的影响,最终提取的图片会有黑边。因此我采用Canny和Laplacian的混合算法,先由Laplacian把图片初提取。再由Canny检测初提取的图片,如果检测出图片中的矩形面积大于一个阈值(90%),就认为存在黑边,进行剪切。
第二是很多表情包会有文字,表情包中的文字距离图片部分如果较远,则可能被当作是两个不同的部分。我使用dilate尽可能避免分离。
第三是微信查看表情包的界面,下面会显示同类表情包,算法可能会误识别下方的表情包,更一般地,实际情况中软件中可能存在非表情包的图片区域,这是我们想要避免的。我采用寻找最大矩形时利用图片到上下边缘最小距离来加权,让在中间位置的图片权重更大。
算法实现
为了方便读者,文章展示的是Python代码实现的算法,文末附有C++的实现代码。
import cv2
import numpy as np
image = cv2.imread('4.jpg')
# 粗略的裁剪
def crop(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 拉普拉斯算子相对敏感,因此可能裁出边框来
thresh = cv2.Laplacian(gray, cv2.CV_8U, ksize=3)
# 膨胀,最大化边缘
thresh = cv2.dilate(thresh, None, iterations=1)
cv2.imshow('thresh', thresh)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
size_y = image.shape[0]
# 找到最大的boundingRect size
bounds = [cv2.boundingRect(c) for c in contours]
x, y, w, h = bounds[0]
for b in bounds:
if (b[2] * b[3]) *(size_y / 2 - abs(size_y / 2 - b[1] - b[3] / 2)) > (w * h) * (size_y / 2 - abs(size_y / 2 - y - h / 2)): # 优先中间部分的图片
x, y, w, h = b
# 剪切
crop = image[y:y + h, x:x + w]
return crop
# 裁去边框
def crop_border(image, rate=0.90):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.Canny(gray, 120, 200)
cv2.imshow('thresh2', thresh)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 找到最大的boundingRect size
bounds = [cv2.boundingRect(c) for c in contours]
x, y, w, h = bounds[0]
for b in bounds:
if b[2] * b[3] > w * h:
x, y, w, h = b
# 判断是否需要裁剪
if w * h > rate * image.shape[0] * image.shape[1]:
print('crop border')
return image[y:y + h, x:x + w]
else:
return image
c = crop(image)
cv2.imshow('crop', c)
b = crop_border(c)
cv2.imshow('crop_border', b)
cv2.waitKey(0)
实现效果
- 白色背景 & 白色图片
原图2025-01-25T09:25:16.png
第一次边缘检测2025-01-25T09:22:46.png
最终结果2025-01-25T09:23:10.png
2.文字
原图
第一次边缘检测
最终结果
3.多图片干扰
原图
第一次边缘检测
最终结果