002.01 图片移除背景成PNG文件

建立一个程序,用来处理一般图片,移除类似颜色成为透明的背景.

建档日期: 2019/08/26
更新日期: 2020/09/29
相关软件信息:

WIN 10 Python 3.8.3 PIL Pillow 7.1.2 PySimpleGUI 4.29.0.14


目标:#

  1. 可开启任意图片,并显示.
  2. 点击图片上任意一点以指定要移除的区域
  3. 以颜色的偏差作为是否同一区域的认定
  4. 预设颜色偏差值为 16, RGB 三色都在偏差值之内,可以更改偏差值.
  5. 可以存为 PNG 文件.
  6. 不使用递回呼叫,避免出错.
  7. 可以 Undo 一次.

程序画面#

002.01 图片去外框处理范例


代码及说明#

  1. 库的导入
import ctypes
from io import BytesIO
from pathlib import Path
from PIL import Image
import PySimpleGUI as sg
  1. 建立图片处理的类
class Picture():

    def __init__(self):
        self.im = None # 处理中的图片
        self.old_im = None # 保留图片供 Undo
        self.default_tolerance = 16 # 预设颜色偏差值
        self.tolerances = [0, 8, 16, 32, 64, 128] # 可选颜色偏差值
        self.picture = None # 显示的图片对象
        self.width, self.height = self.size = (1600, 800) # 图片显示区尺寸
        self.transparency = (0, 0, 0, 0) # 用来取代的透明值
  1. 根据点的颜色值及偏差值,计算容许的颜色范围
    def limit(self, color, tolerance):
        result = []
        for c in color:
            result += [
                min(max(c-tolerance, 0), 255), min(max(c+tolerance, 0), 255)]
        return result
  1. 背景处理的准备
    def change(self, x, y):
        r, g, b, a = self.im.getpixel((x, y))
        e = self.tolerance
        self.r0, self.r1, self.g0, self.g1, self.b0, self.b1 = self.limit(
            (r, g, b), self.tolerance)
        self.old_im = self.im.copy()
        self.update(x, y)
  1. 检查该点是否被认为是同色的
    def is_same(self, x, y):
        r, g, b, a = self.im.getpixel((x, y))
        return True if (
            self.r0 <= r <= self.r1 and
            self.g0 <= g <= self.g1 and
            self.b0 <= b <= self.b1 and
            a != 0
            ) else False
  1. 点检查更新
  • 设置所有的点都未检
  • 检查起点,设为已检,如果为同色,则更改为透明点
  • 新增检查点为上下左右四点,直到无点可检
    def update(self, x, y):
        checked = [[False for y in range(self.im.height)]
            for x in range(self.im.width)]
        to_do = [(x, y)]
        while to_do:
            temp = []
            for x, y in to_do:
                checked[x][y] = True
                if self.is_same(x, y):
                    self.im.putpixel((x, y), self.transparency)
                    if x-1 >= 0 and not checked[x-1][y]:
                        temp.append((x-1, y))
                    if x+1 < self.im.width and not checked[x+1][y]:
                        temp.append((x+1, y))
                    if y-1 >= 0 and not checked[x][y-1]:
                        temp.append((x, y-1))
                    if y+1 < self.im.height and not checked[x][y+1]:
                        temp.append((x, y+1))
            to_do = temp.copy()
  1. 更新图片的显示
    def new(self):
        if self.picture:
            graph.delete_figure(self.picture)
        with BytesIO() as output:
            self.im.save(output, format="PNG")
            data = output.getvalue()
        self.picture = graph.draw_image(data=data, location=(self.x0, self.y0))
  1. 显示屏的图片点击处理
    def click(self, position):
        if self.picture is None:
            return
        x1, y1 = position
        x, y = x1-self.x0, self.y0-y1
        if not (0<=x<self.im.width and 0<=y<self.im.height):
            return
        self.change(x, y)
        self.new()
  1. 新图片文件的开启
    def open(self):
        filename = sg.popup_get_file('Open new image', no_window=True, modal=True)
        if filename and Path(filename).is_file():
            try:
                self.im = Image.open(filename).convert(mode='RGBA')
            except:
                return
            self.x0 = (self.width-self.im.width)//2
            self.y0 = (self.height+self.im.height)//2
            self.tolerance = self.default_tolerance
            window['TOLERANCE'].update(value=self.tolerance)
            self.new()
  1. 图片保存为 PNG 文件
    def save(self):
        if self.im is None:
            return
        filename = sg.popup_get_file('Save new image', no_window=True,
            modal=True, save_as=True)
        if filename:
            if not filename.lower().endswith(".png"):
                filename += ".png"
            self.im.save(filename)
        return
  1. Undo 已保留的前图片
    def undo(self):
        if self.old_im:
            self.im = self.old_im.copy()
            self.new()
  1. 程序预设
ctypes.windll.user32.SetProcessDPIAware() # Set unit of GUI to pixels
font = ('Courier New', 16, 'bold')
p = Picture()
  1. 程序窗口布局
layout = [
    [sg.Button("OPEN", font=font),
     sg.Button("SAVE", font=font),
     sg.Button("UNDO", font=font),
     sg.Text("Tolerance", font=font),
     sg.Combo(p.tolerances, p.default_tolerance, font=font, key='TOLERANCE',
        enable_events=True)],
    [sg.Graph(p.size, (0, 0), p.size, enable_events=True, key='GRAPH',
        background_color='darkgreen')],
]
  1. 程序窗口生成
window = sg.Window('Move Outline of Image', layout, finalize=True)
graph = window['GRAPH']
  1. 事件处理
while True:

    event, values = window.read()

    if event == sg.WINDOW_CLOSED:
        break
    elif event == 'OPEN':
        p.open()
    elif event == 'SAVE':
        p.save()
    elif event == 'UNDO':
        p.undo()
    elif event == 'TOLERANCE':
        p.tolerance = values['TOLERANCE']
    elif event == 'GRAPH':
        p.click(values['GRAPH'])
  1. 程序窗口关闭
window.close()
本作品采用《CC 协议》,转载必须注明作者和本文链接
Jason Yang