使用Python将图片转换为单通道黑白图片

本文介绍如何使用python将图片转换为纯黑白的单通道图片。文中用到的脚本支持彩色、灰度、带alpha通道的输入图片以及SVG矢量图,支持调整输出图片大小以及设置灰度阈值。

最后介绍如何输出SSD1306 OLED显示屏可用的XBM文件,并利用输出的XBM数据在0.96寸的OLED屏幕上显示图标,例如下图中的温度、闹钟及云朵图标通过本脚本生成:

使用方法

通过pip安装依赖包:

pip3 install --user pillow argparse numpy cairosvg

下文拷贝python脚本为tobw.py.

要把非单通道图片(如彩色、灰度或带alpha通道的PNG图片)转换图片 只需指定输入图片路径和输除路径即可:

$ python tobw.py -i input.png -o output_bw.jpg

通过-h选项查看其他可选参数:

$ python tobw.py -h
usage: tobw.py [-h] -i INPUT_IMAGE -o OUTPUT_IMAGE [-t THRESHOLD] [-r] [-s SIZE] [-p]

optional arguments:
  -h, --help            show this help message and exit
  -i INPUT_IMAGE, --input-image INPUT_IMAGE
                        Input image
  -o OUTPUT_IMAGE, --output-image OUTPUT_IMAGE
                        output image
  -t THRESHOLD, --threshold THRESHOLD
                        (Optional) Threshold value, a number between 0 and 255
  -r, --invert-bw       (Optional) Invert black and white
  -s SIZE, --size SIZE  (Optional) Resize image to the specified size, eg., 16x16
  -p, --print           (Optional) Print result

-s 缩放图片

使用-s选项设置输出图片的大小,不设置时,输出与输入大小相同。例如把输出图片设置微16X16像素大小:

python3 tobw.py -i input.png -o output_bw.jpg -s 16x16

需要注意如果输出图片长宽比与输入图片长宽比不一致,图片会被拉伸或压缩。

-t 调整阈值

使用-t选项设置输出为黑或白像素点的灰度阈值,取值范围为0-255. 值越小就有更多像素点被设置为白色。如果输出图片丢失的细节过多时,可以适当调整本参数。

反转输出的黑白像素

使用-r参数后,原来输出白色的像素点将显示微黑色,反之亦然。输入图片的明暗区域恰好与显示屏相反时可以使用本参数。

在ESP8266 Arduino里使用

使用esp8266-oled-ssd1306库提供的drawXbm(x, y, img_width, img_height, img)方法在屏幕上绘制图片,drawXbm的5个参数分别是

  • x, y: 图片的坐标,屏幕左上角坐标为(0, 0)此处设置的坐标对应图片左上角位置
  • img_width: 图片的实际宽度。
  • img_height: 图片的实际高度。
  • img: XBM图片字节,使用tobw.py-p参数获取。

esp8266-oled-ssd1306使用XBM格式图片,使用tobw.py-p参数获取,例如下面的示例把svg图片转换为12x12像素输出,并打印XBM格式数据。注意返回的im_bits,这就是drawXbm最后一个参数需要的数据。

$ python3 tobw.py -i rain.svg -o rain_bw.jpg -s 12x12 -t 200  -p
111000001111
110001000111
110011100011
100111110001
001111111100
011111111110
001100000100
000000000000
100000000001
111000001111
111100111111
111100111111

Result as XBM image:
#define im_width 12
#define im_height 12
static char im_bits[] = {
0x07,0x0f,0x23,0x0e,0x73,0x0c,0xf9,0x08,0xfc,0x03,0xfe,0x07,0x0c,0x02,0x00,
0x00,0x01,0x08,0x07,0x0f,0xcf,0x0f,0xcf,0x0f
};

源码

# Convert image to pure black and white
# Show usages:
# python tobw.py -h
# python tobw.py -i rain.svg -o rain_bw.jpg  -s 12x12 -t 200
from PIL import Image, UnidentifiedImageError
import argparse
import numpy as np
from cairosvg import svg2png
import os
from random import random

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--input-image", help="Input image", type=str, required=True)
ap.add_argument("-o", "--output-image", help="output image", type=str, required=True)
ap.add_argument("-t", "--threshold",
                help="(Optional) Threshold value, a number between 0 and 255", type=int, default=128)
ap.add_argument("-r", "--invert-bw",
                help="(Optional) Invert black and white", action='store_true')
ap.add_argument("-s", "--size", help="(Optional) Resize image to the specified size, eg., 16x16", type=str)

args = vars(ap.parse_args())

invert_bw = args['invert_bw']
threshold = args['threshold']
if threshold > 255 or threshold < 0:
    raise Exception('Invalid threshold value!')
img_in = args['input_image']
img_out = args['output_image']

if args['size']:
    size = [int(n) for n in args['size'].lower().split('x')]
else:
    size = None

high = 255
low = 0
if invert_bw:
    high = 0
    low = 255

def replace_ext(filename, new_ext):
    ext = filename.split('.')[-1] if '.' in filename else ''
    return filename[:len(filename)-len(ext)] + str(new_ext)


def remove_transparency(im, bg_colour=(255, 255, 255)):
    # https://stackoverflow.com/questions/35859140/remove-transparency-alpha-from-any-image-using-pil/35859141
    # Only process if image has transparency (http://stackoverflow.com/a/1963146)
    if im.mode in ('RGBA', 'LA') or (im.mode == 'P' and 'transparency' in im.info):
        alpha = im.convert('RGBA').getchannel('A')
        bg = Image.new("RGBA", im.size, bg_colour + (255,))
        bg.paste(im, mask=alpha)
        return bg
    else:
        return im


# color image

try:
    col = Image.open(img_in)
except UnidentifiedImageError:
    if (img_in.lower().endswith('.svg')):
        tmp = replace_ext(img_in, '{}.png'.format(random()))
        svg2png(url=img_in, write_to=tmp)
        col = Image.open(tmp)
    else:
        raise Exception('unknown image type')

if size:
    col = col.resize(size)
col = remove_transparency(col)
gray = col.convert('L')
bw = gray.point(lambda x: low if x < threshold else high, '1')
bw.save(img_out)

for u8 in np.uint8(bw):
    print(''.join(str(c) for c in u8))

print()
print('Result as XBM image:')
if (img_out.lower().endswith('.xbm')):
    print(open(img_out).read())
else:
    xbm_out = replace_ext(img_in, '{}.xbm'.format(random()))
    bw.save(xbm_out)
    print(open(xbm_out).read())
    os.remove(xbm_out)

if tmp is not None:
    os.remove(tmp)
留言