前情提要:友人想开发一个和表情包相关的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)

实现效果

  1. 白色背景 & 白色图片
    原图
    2025-01-25T09:25:16.png
    2025-01-25T09:25:16.png

    第一次边缘检测
    2025-01-25T09:22:46.png
    2025-01-25T09:22:46.png

    最终结果
    2025-01-25T09:23:10.png
    2025-01-25T09:23:10.png

2.文字
原图
2025-01-25T09:25:41.png

2025-01-25T09:25:41.png

第一次边缘检测
2025-01-25T09:26:07.png
2025-01-25T09:26:07.png

最终结果
2025-01-25T09:26:23.png
2025-01-25T09:26:23.png

3.多图片干扰
原图
2025-01-25T09:28:45.png

2025-01-25T09:28:45.png

第一次边缘检测
2025-01-25T09:29:20.png
2025-01-25T09:29:20.png

最终结果
2025-01-25T09:29:34.png
2025-01-25T09:29:34.png

C++ 代码

src.7z

最后修改:2025 年 01 月 25 日
如果觉得我的文章对你有用,请随意赞赏