002.01 圖片移除背景成PNG檔案

Jason990420發表於2019-08-26

建立一個程式, 用來處理一般圖片, 移除類似顏色成為透明的背景.

建檔日期: 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

相關文章