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
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!