今天跟大家唠唠我这几天搞的“十八色图”,别想歪,这可不是啥颜色小电影,是我捣鼓的一个图像处理的小玩意儿,灵感来自网上看到的一些复古配色图,觉得挺有意思,就想自己动手做一个。
我啥也不懂,对着电脑屏幕发呆。心想,这玩意儿从哪儿下手? 后来一琢磨,这不就是个颜色映射嘛把一张图的颜色,按照特定的规则,映射到另一组颜色上。
说干就干,我先是上网搜资料,各种图像处理的库,看得我眼花缭乱。选定 Python 的 PIL 库,这玩意儿用起来还算顺手,文档也比较全。
第一步,读取图片。这很简单,PIL 库里有个 `*()` 函数,直接把图片路径丢进去就行。
python
from PIL import Image
img = *("*")
读取完图片,下一步就是定义那“十八色”。我可没真搞十八种颜色,那样太复杂,先从最简单的黑白灰开始。我定义一个颜色列表:
python
colors = [(0, 0, 0), (128, 128, 128), (255, 255, 255)] # 黑,灰,白
然后,我需要把图片里的每个像素,都映射到这三种颜色之一。怎么映射?最简单的方法就是计算像素的亮度值,然后根据亮度值的大小,选择对应的颜色。
python
def map_color(pixel):
将像素映射到颜色列表中的一个颜色
r, g, b = pixel
brightness = (r + g + b) / 3 # 计算亮度值
if brightness < 85:
return colors[0] # 黑色
elif brightness < 170:
return colors[1] # 灰色
else:
return colors[2] # 白色
这个 `map_color()` 函数,接收一个像素的 RGB 值,计算亮度,然后根据亮度值返回颜色列表中对应的颜色。
就是遍历图片的每个像素,把颜色都映射一遍。
python
width, height = *
new_img = *("RGB", (width, height)) # 创建一个新的图片,用于存放处理后的像素
for x in range(width):
for y in range(height):
pixel = *((x, y))
new_pixel = map_color(pixel)
new_*((x, y), new_pixel)
这段代码,先创建一个新的空白图片,大小和原始图片一样。然后,遍历原始图片的每个像素,调用 `map_color()` 函数进行颜色映射,把映射后的像素值,放到新的图片里。
把新的图片保存下来:
python
new_*("*")
跑一下程序,效果还不错,原始图片变成黑白灰风格。但是,总感觉有点粗糙,颜色过渡太生硬。
为让颜色过渡更自然一些,我加入抖动算法。抖动算法的原理,简单来说,就是把颜色映射的误差,分散到周围的像素上,这样看起来颜色过渡就更平滑。
我用的是 Floyd-Steinberg 抖动算法,这玩意儿网上资料很多,抄起来也方便。 主要思路就是把当前像素点的量化误差按照一定的比例扩散到相邻的像素点上。
python
def floyd_steinberg_dither(image, colors):
使用 Floyd-Steinberg 抖动算法进行颜色量化
width, height = *
pixels = *() # 加载像素数据,方便修改
for y in range(height):
for x in range(width):
old_pixel = pixels[x, y]
new_pixel = map_color(old_pixel) # 找到最接近的颜色
pixels[x, y] = new_pixel # 更新像素
old_r, old_g, old_b = old_pixel
new_r, new_g, new_b = new_pixel
error_r = old_r - new_r
error_g = old_g - new_g
error_b = old_b - new_b
# 将误差扩散到相邻像素
if x < width - 1:
r, g, b = pixels[x + 1, y]
pixels[x + 1, y] = (
int(min(255, max(0, r + error_r 7 / 16))),
int(min(255, max(0, g + error_g 7 / 16))),
int(min(255, max(0, b + error_b 7 / 16))),
if x > 0 and y < height - 1:
r, g, b = pixels[x - 1, y + 1]
pixels[x - 1, y + 1] = (
int(min(255, max(0, r + error_r 3 / 16))),
int(min(255, max(0, g + error_g 3 / 16))),
int(min(255, max(0, b + error_b 3 / 16))),
if y < height - 1:
r, g, b = pixels[x, y + 1]
pixels[x, y + 1] = (
int(min(255, max(0, r + error_r 5 / 16))),
int(min(255, max(0, g + error_g 5 / 16))),
int(min(255, max(0, b + error_b 5 / 16))),
if x < width - 1 and y < height - 1:
r, g, b = pixels[x + 1, y + 1]
pixels[x + 1, y + 1] = (
int(min(255, max(0, r + error_r 1 / 16))),
int(min(255, max(0, g + error_g 1 / 16))),
int(min(255, max(0, b + error_b 1 / 16))),
# 使用抖动算法
img = *("*")
img = *("RGB") # 确保是 RGB 模式
floyd_steinberg_dither(img, colors)
*("output_*")
加抖动算法之后,效果果然好多,颜色过渡更自然,看起来也更舒服。
这只是一个最简单的例子,真正的“十八色图”,肯定要用到更多的颜色,更复杂的映射规则,甚至还要考虑到图像的纹理、光照等等因素。不过通过这回实践,我对图像处理有一个初步的解,也算是迈出第一步。
以后有时间,我还会继续研究,争取做出更炫酷的“十八色图”效果。 这回就先分享到这儿,下次再见!