Python CV 实现风格化图片转换

前几天遇到一个风格化图片转换的需求,效果像这样:
在这里插入图片描述
像这样,需要用纯色圆形填充图像,形成风格化的图片样式。

实现原理

整体原理还是比较简单的,有点类似与马赛克的处理方式。
假设图片宽 w 像素,高 h 像素,需要使用半径为 r 的圆填充好,那么只需要:

  1. 先把图片划分成边长为 2*r 的栅格;
  2. 选取每个栅格中心点的像素颜色,作为这个栅格要填充的颜色;
  3. 新建画布,对每个栅格绘制实心圆形,颜色是上一步计算的结果。

代码分解

读取图片

import cv2  # pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pip install opencv-python==4.5.5.62

img = cv2.imread(target_file)
print(f'Read image, got width={img.shape[1]}, height={img.shape[0]}')

新建画布

dest = numpy.zeros((img.shape[0], img.shape[1], 3), dtype=numpy.uint8)

这个比较简单,就是创建一个三维数组,最低维有三个元素代表颜色(BGR),第二维是行,最高维是列。

划分并遍历栅格

for i in range(0, len(img), circle_radius * 2):
    # 数组每一行代表图形中的一行,从左上角开始(i相当于y值)
    for j in range(0, len(img[i]), circle_radius * 2):
        # 数组每一列代表图形中的一列,从左上角开始(j相当于x值)
        center = (j + circle_radius, i + circle_radius)  # x, y

这里有个比较坑的点,在使用数组遍历所有像素的时候,第一层循环从从上到下遍历图片的每一行,第二层循环从左到右遍历该行的像素。
但是啊但是,CV 中的点坐标以左上角为原点,右方向为 x 轴正方向,下方向为 y 轴正方向。注意看这个 center,它的点表示要和数组访问恰好相反。

获取颜色并在新画布绘制圆形

point_color_arr = img[min(center[1], len(img)) - 1][min(center[0], len(img[i])) - 1]
point_color = (int(point_color_arr[0]), int(point_color_arr[1]), int(point_color_arr[2]))  # BGR
cv2.circle(dest, center, circle_radius, point_color, -1)

取中点处的颜色,即是我们要的颜色。注意这里访问的时候是 img[center[1]][center[0]],就是因为前面说的,点坐标与数组下标恰好相反。

优化:取栅格中全部颜色的平均值

我们还可以顺带实现一下类似于马赛克的那种,每个格子颜色取平均值,这样能使格子之间的颜色过渡更自然。

n_points = 0
point_color = [0, 0, 0]
for ii in range(i, min(i + circle_radius * 2, len(img)), 1):
    for jj in range(j, min(j + circle_radius * 2, len(img[i])), 1):
        n_points += 1
        point_color += img[ii][jj]
point_color = tuple([int(x / n_points) for x in point_color])

这里唯一要注意的点就是,直接从 img 读出来的是数组,我们需要转换成 int tuple 才能用。

写入文件或显示到屏幕

显示到屏幕可以这样:

cv2.namedWindow("image")
cv2.imshow('image', dest)
cv2.waitKey(100000)  # 显示 10000 ms 即 10s 后消失
cv2.destroyAllWindows()

要把图片保存到文件可以这样:

dest_file = 'dest-test-29-t.png'
cv2.imwrite(dest_file, dest)

完整源代码

circle_radius = 29  # in px
target_file = 'test.png'
dest_file = 'dest-test-29-t.png'
use_average = True
save_file = True

import cv2  # opencv默认读取格式是BGR
import numpy
import tqdm

# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    img = cv2.imread(target_file)
    print(f'Read image, got width={img.shape[1]}, height={img.shape[0]}')

    dest = numpy.zeros((img.shape[0], img.shape[1], 3), dtype=numpy.uint8)
    for i in tqdm.tqdm(range(0, len(img), circle_radius * 2), "Processing: "):
        # 数组每一行代表图形中的一行,从左上角开始(i相当于y值)
        for j in range(0, len(img[i]), circle_radius * 2):
            # 数组每一列代表图形中的一列,从左上角开始(j相当于x值)
            center = (j + circle_radius, i + circle_radius)  # x, y
            # calculate color for this area, get average
            if use_average:
                n_points = 0
                point_color = [0, 0, 0]
                for ii in range(i, min(i + circle_radius * 2, len(img)), 1):
                    for jj in range(j, min(j + circle_radius * 2, len(img[i])), 1):
                        n_points += 1
                        point_color += img[ii][jj]
                point_color = tuple([int(x / n_points) for x in point_color])
            else:
                point_color_arr = img[min(center[1], len(img)) - 1][min(center[0], len(img[i])) - 1]
                point_color = (int(point_color_arr[0]), int(point_color_arr[1]), int(point_color_arr[2]))  # BGR
            cv2.circle(dest, center, circle_radius, point_color, -1)

    if save_file:
        cv2.imwrite(dest_file, dest)
    else:
        cv2.namedWindow("image")
        cv2.imshow('image', dest)
        cv2.waitKey(100000)  # 显示 10000 ms 即 10s 后消失
        cv2.destroyAllWindows()

程序效果

原图:
在这里插入图片描述
使用中心点作为颜色来源:
在这里插入图片描述
使用颜色平均值:
在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值