Published on

使用 python 提取图片中文字的颜色

Authors

背景

书接上文,我们已经实现了识别图片中的文字并且将文字涂抹掉。接下来,我们需要将文字翻译成目标语言,并重新绘制到图片上。

这里我们先不讲如何进行翻译,下次我们来介绍怎么翻译博大精深的中文。我们先讲一下将文字重新绘制时候碰到的一个问题:文字的颜色。

我们希望重新绘制回去的文字能够保留原来的颜色,这样看起来整张图片仍然能够原来的风格。

提取颜色

首先,我们首先使用的方案是提取识别区域的主色。但是这个方案并不奏效。

text_area

我们可以看到,识别的区域中有大面积的背景颜色,我们使用主色的方式提取颜色,很容易会提取到背景颜色。

我们需要的是文字区域内的颜色,所以我们需要一个更加精细的方案。

首先,我想到的方案就是识别出文字的实际区域,然后在这个区域内提取颜色。

第一步,我尝试了使用 canny 边缘检测算法,计算出文字的边缘。因为我们已经将识别的区域限制在了文字区域内,所以这个图像的「环境」相对是简单的,边缘检测能够很好的识别出文字的边缘。

canny_text

接下来,就是要将这区域内的颜色提取出来,这下就难道我了,我没有专业的图像处理知识,我只能使用一些非专业的手段来解决这个问题。

我的第一个想法是将识别出来的区域向中心缩小,然后提取缩小后的区域上的点的颜色,这样理论上就能提取出文字的颜色。

small_text

但是这只是理想的情况,中文字的特点是笔画繁多,这样的缩小会碰到各种情况,边界不闭合、超出边界、不同的大小区域等等,提取出来的颜色并不是我们想要的。

最后,我们分别在文字区域的 x,y 两个方向的 1/4、1/2 和 3/4 处提取边缘点左右上下 2-5 像素(随机取两个点)的颜色

对于这些颜色,我们继续对其进行处理找出最接近的颜色,我们将每个颜色与所有的颜色计算欧几里得距离,找到这个颜色的最小距离,如果这个最小距离大于阈值(防止同色),我们将这个颜色加入到颜色列表中。

由于这些颜色是我们通过有限的抽样提取的边缘附近的颜色,有效屏蔽了背景的干扰,通过最小距离筛选我们筛选出相同颜色最多的色值作为最后的结果。

def get_colors(colors, cropped_image):
    try:
        colors_lab = np.array([np.array([[color[::-1]]], dtype=np.float32) / 255.0 for color in colors])

        # 将图像转换为NumPy数组,并只处理一半宽度
        image_np = np.array(cropped_image, dtype=np.float32)[:,:cropped_image.size[0]//2] / 255.0

        # 初始化一个空列表来存储符合条件的颜色
        word_colors = []

        # 对于图像中的每个像素
        for pixel_lab in image_np.reshape(-1, image_np.shape[-1]):
            # 计算与颜色列表中每种颜色的距离
            distances = np.linalg.norm(colors_lab - pixel_lab, axis=2)
            # 找到最小距离
            min_distance = np.min(distances)
            # 检查最小距离是否超过阈值
            if min_distance > color_distance_threhold:
                word_colors.append(tuple((pixel_lab * 255.0).astype(np.uint8).tolist()))

        return word_colors
    except Exception as e:
        print(e)
        return None

nearest_color = Counter(colors).most_common(1)[0][0]

效果

text_color_r1 text_color_r2 text_color_r3 text_color_r4 text_color_r5

预告

下次介绍如何使用 RAG + LLM 实现翻译