Python 全攻略 - 第 16 章 與圖片共舞

Python 全攻略 - 第 16 章 Playing with Images

● 前言

這篇筆記的內容及大綱,主要是按照 Wilson 老師分享的簡報檔中針對 Playing with Images 第16章內容的安排順序及範圍所撰寫的。主要是介紹如何使用 Jython and JES 來進行影像的處理及操作,包含了影像的讀取、顯示、處理、編輯、合成、濾鏡、特效等等。這篇筆記的內容主要是以 Wilson 老師的簡報檔為主,並加入了一些自己的理解及補充,希望能夠對大家有所幫助。

● Jython and JES

  • 在本章中,我們將使用 Jython 來處理影像。 Jython 是用Java編寫的對於 Python 程式語言的一種實現(Base on Python 2.7)。 Jython除了包含 Python 內建函數之外,還額外提供了處理圖像用的其他方法和類別函式庫。

  • 首先我們需要下載 Jython 及安裝 Jython,關於 Jython 的安裝及使用,可以參考 Wilson 老師於第 16 章編號181影片的教學,或參考以下網址:Jython 安裝使用

  • 在開始操作圖像處理之前,我們需要先知道圖片是如何編碼的;如下圖,左圖是一隻貓熊的圖片,而右圖則是將左圖眼睛位置放大後的圖片,從右圖我們可以看到,所有的顏色其實是一個個方格的色塊所組成,而這方格我們稱之為像素,換言之,圖片是由像素所組成的;其中每個像素都是由紅、綠、藍 3 種顏色組合而成的點。

  • 每個顏色通道儲存1個位元組(8位元); 因此,有 256 種不同的紅色, 256 不同種類的綠色,以及256種不同的藍色。這 24 位元可以組成 2 的 24 次方 = 16777216 種不同種類的顏色。每個顏色通道是以紅、黃、綠三原色來標記 (R, G, B),每個原色的值從 0 到 255。

  • 例如:(255, 0, 0) 是紅色,(0, 255, 0) 是綠色,(0, 0, 255) 是藍色,(255, 255, 255) 是白色,(0, 0, 0) 是黑色。

  • Jython 是基於 Python 2.7 的語法,因此在使用 Jython 時,我們需要注意一些 Python 2.7 與 Python 3.x 在一些語法上的差異。

    • Python2 使用 raw_input(),而 Python3 則用 input()。

    • 在 Python 2 中,整數除以整數將始終傳回整數。 如果我們希望它傳回一個浮點數,那麼我們必須將分母或分子設為浮點數。

    • 在 Python 2 中,print 是一個語句,而在 Python 3 中,print 是一個函數。 → print “Hello” (Python 2) → print(“Hello”) (Python 3)

    • 還有一些其他差異。如有興趣,可以參考以下網址:The key differences between Python 2.7.x and Python 3.x with examples

● gatech JES 介紹

JES (Jython Environment for Student) 是喬治亞理工學院發展出來 for 學生學習 Jython 的工具,也是一個 IDE,它提供了一個編輯器,可以編寫和執行 Jython 代碼。JES 也提供了一些函數和類別,用於處理圖像和音頻。JES 是一個很好的工具,可以幫助學生學習 Jython,並且可以用來進行一些簡單的圖像和音頻處理。

相關的介紹及安裝,可參考 Wison 老師的視頻教學影片,但我個人其實不太推薦 JES,因為安裝有點麻煩,且無法與 Python3 程式共用,我個人其實有更推薦使用 Jython 的方法。

那就是使用 JES4py 這個社群所開發的套件,這是一個基於 JES 的 Python 3.x 版本,可以在 Python 3.x 環境中使用 JES 的功能,這樣就可以使用 Python 3.x 的語法來進行影像處理。其官方的連結如下:JES4py

● 補充: 其實還有其他更常用的圖像處理套件 – scikit-image

scikit-image 是一個基於 Python 的圖像處理庫,它提供了許多圖像處理的功能,包括圖像的讀取、顯示、處理、編輯、合成、濾鏡、特效等等。scikit-image 是一個很好的工具,可以幫助我們進行圖像處理,並且可以用來進行一些複雜的圖像處理。其官方的連結如下:scikit-image

關於 scikit-image 的使用,可參考這篇: Full Tutorial on Image Processing in Skimage

● Media Programming with Jython

1 以下是我們可以使用 Jython 中的一些函數:

  • pickAFile(): 開啟一個視窗供使用者選擇文件,然後返回所選檔案的整個檔案路徑。

  • makePicture(filepath): 將路徑名作為輸入,讀取文件,並且從中建立一張圖片。返回一張新圖片。

  • show(picture): 顯示圖片,顯示作為輸入提供的圖片。

  • explore(picture): 和 show(picture) 功能有些類似,唯一不同的是,除了顯示圖像,會在圖像上方,多出幾個標示 X, Y 軸及像素點 (R, G, B) 色調屬性值的工具列,而當你的滑鼠在圖像上的不同位置停駐時,就會以上述工具列來標示目前滑鼠停駐位置的 X, Y 軸及像素點的 (R, G, B) 色調屬性。

2 以下是我們可以用來處理圖片的函數列表:

  • getWidth(picture): 返回圖片的寬度,將圖片物件作為輸入並返回其圖片的長度 (從左到右的像素數)。

  • getHeight(picture): 返回圖片的高度,將圖片物件作為輸入並返回其圖片的高度 (從上到下的像素數)。

  • getPixels(picture): 返回圖片中所有像素的列表,將圖片物件作為輸入並返回一個包含所有像素(由左而右,從上到下)的像素列表。

  • getPixel(picture, x, y): 返回圖片中特定像素的顏色,將圖片物件及其 x 和 y 座標作為輸入並返回該 x 和 y 座標的像素的顏色。

  • getColor(pixel): 返回像素的顏色,將像素物件作為輸入並返回其顏色。

  • getRed(pixel): 返回像素的紅色值,將像素物件作為輸入並返回其紅色值。

  • getGreen(pixel): 返回像素的綠色值,將像素物件作為輸入並返回其綠色值。

  • getBlue(pixel): 返回像素的藍色值,將像素物件作為輸入並返回其藍色值。

  • setColor(pixel, color): 設置像素的顏色,將像素物件和顏色作為輸入並將像素的顏色設置為該輸入參數的顏色。

  • makeColor(r, g, b): 創建一個新的顏色,將紅色、綠色和藍色值作為輸入,並返回一個新的顏色。

  • setRed(pixel, int): 設置像素的紅色值,將像素和整數作為輸入並將像素的 紅色值設置為該輸入整數。

  • setGreen(pixel, int): 設置像素的綠色值,將像素和整數作為輸入並將像素的 綠色值設置為該輸入整數。

  • setBlue(pixel, int): 設置像素的藍色值,將像素和整數作為輸入並將像素的 藍色值設置為該輸入整數。

3 其他的一些函數:

  • getX(pixel): 返回像素的 x 座標,將像素物件作為輸入並返回其 x 座標。

  • getY(pixel): 返回像素的 y 座標,將像素物件作為輸入並返回其 y 座標。

  • setX(pixel, int): 設置像素的 x 座標,將像素物件和整數值作為輸入並將像素的 x 座標設置為該整數。

  • setY(pixel, int): 設置像素的 y 座標,將像素物件和整數值作為輸入並將像素的 y 座標設置為該整數。

  • writePictureTo(picture, path): 將圖片寫入文件,將圖片物件和檔案路徑字串作為輸入參數並將圖片寫入輸入的檔案路徑的檔案中。

  • pickAFolder(): 選擇一個文件夾,返回所選文件夾的完整路徑字串。

[Note]: 由於 JES 對 Python 3.x 的支援不太好,而 JES 的安裝也很認 JAVA runtime 的版本和環境,因此後面的程式示範,我是改用 JES4py 這個社群所開發的元件庫,這是一個基於 JES 的 Python 3.x 版本,可以在 Python 3.x 環境中使用 JES 的功能,這樣就可以使用 Python 3.x 的語法來進行影像處理。其官方的連結如下:JES4py

  • 因此首先我們先使用下列的指令來安裝 JES4py。
python -m pip install -U jes4py  

[Note]: 如在 Visual Studio 的 Jupyter Notebook 環境的程式碼區,若因程式碼執行需求需要安裝 python 套件,你必須使用 % 字元來讓 Jupyter Notebook 知道這是一個命令行指令,而不是 Python 程式碼;而如果在 Google Colab 的環境中,則需改用 ! 字元,來讓 Google Colab 知道這是一個命令行指令,而不是 Python 程式碼。

簡單應用範例如下:

# import jes4py module
from jes4py import *

# 取得檔案路徑
filename = pickAFile()

# 讀取圖片並返回圖片物件
pic = makePicture(filename)

# 將圖片物件顯示到螢幕上
show(pic)

# 將圖片物件顯示在一個探索編輯器中
# explore(pic)

# 取得圖片的寬度和高度
width = getWidth(pic)
height = getHeight(pic)
print(f'this piciture size is {width} x {height} pixels')

● Playing with Picture – Brighter 明亮模式

如果圖片太暗,我們可以調亮圖片。它有什麼作用 意思是讓圖片變亮?它的字面意思是——我們將所有像素放入 一張圖片並增加每個 pixel 像素的 RGB 值。 當 RGB 數值增大會使得每個像素向白色移動;整個畫面就會變得更明亮。

大致應用範例程式碼如下:

import os
from jes4py import *

def brighten_image_jes(input_path, output_path, brightness_increase):
    ''' 將圖片刷淡並另存新檔 '''
    # 使用 jes4py 讀取圖片
    original_image = makePicture(input_path)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)
    
    # 調亮圖片
    for x in range(width):
        for y in range(height):
            pixel = getPixel(original_image, x, y)
            
            # 分離 RGB 分量
            r = getRed(pixel)
            g = getGreen(pixel)
            b = getBlue(pixel)
            
            # 增加 RGB 值
            r = min(255, r + brightness_increase)
            g = min(255, g + brightness_increase)
            b = min(255, b + brightness_increase)
            
            # 更新像素顏色
            setColor(pixel, makeColor(r, g, b))
    
    # 寫入調亮後的圖片
    writePictureTo(original_image, output_path)

# 使用範例
filename = pickAFile()
# pic = makePicture(filename)
# show(pic)
# explore(pic)
# width = getWidth(pic)
# height = getHeight(pic)
# print(f'this piciture size is {width} x {height} pixels')

input_path = filename
brightness_increase = 50  # 調亮程度,可以根據需要調整
output_path = os.path.join(os.path.dirname(filename), f'color_light{brightness_increase}_' + os.path.basename(filename))
brighten_image_jes(input_path, output_path, brightness_increase)
pic = makePicture(output_path)
explore(pic)

● Playing with Picture – Fake Sunset 偽日落模式

我們可以用一張海灘照片來製作偽日落。怎樣製作呢?我們需要將每個像素的綠色和藍色值各減少30%;那麼,每個 pixel 像素的紅色會變得更加突出。然後,畫面開始看起來就像日落時分的情境。

大致應用範例程式碼如下:

import os
from jes4py import *

def apply_sunset_effect_jes(image_path, output_path):
    ''' 將圖片變成夕陽效果並另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)

    # 迭代每個像素
    for x in range(width):
        for y in range(height):
            pixel = getPixel(original_image, x, y)
            r = getRed(pixel)
            g = max(0, getGreen(pixel) - int(getGreen(pixel) * 0.3))
            b = max(0, getBlue(pixel) - int(getBlue(pixel) * 0.3))
            setColor(pixel, makeColor(r, g, b))

    # 儲存修改後的圖片
    writePictureTo(original_image, output_path)

# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'fakesunset_' + os.path.basename(filename))
apply_sunset_effect_jes(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

● Playing with Picture – Negate A Picture 負片(底片)模式

負片是完全反轉,其中亮區顯得暗,暗區顯得亮。因此,我們只需要做:

𝑛𝑒𝑤𝐶𝑜𝑙𝑜𝑟𝐶ℎ𝑎𝑛𝑛𝑒𝑙 = 255 − 𝑜𝑙𝑑𝐶𝑜𝑙𝑜𝑟𝐶ℎ𝑎𝑛𝑛𝑒𝑙

對於每個顏色通道。如果我們考慮白色和 黑色的。如果顏色是黑色,那麼 255 − 0 = 255。都是紅、綠、 藍色變成 255,新的否定顏色是白色。

大致應用範例程式碼如下:

import os
from jes4py import *

def negate_image_jes(image_path, output_path):
    ''' 將圖片變成負片效果並另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)

    # 迭代每個像素
    for x in range(width):
        for y in range(height):
            pixel = getPixel(original_image, x, y)
            r = 255 - getRed(pixel)
            g = 255 - getGreen(pixel)
            b = 255 - getBlue(pixel)
            setColor(pixel, makeColor(r, g, b))

    # 儲存修改後的圖片
    writePictureTo(original_image, output_path)

# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'negate_' + os.path.basename(filename))
negate_image_jes(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

● Playing with Picture – Grayscale 灰階模式

RGB 比例經過校準,以便於當顏色的三原色–紅/綠/藍三分量的數值相等,此時的顏色模式就是所謂的灰階模式。例如,𝑟𝑒𝑑=50, 𝑔𝑟𝑒𝑒𝑛=50, 𝑏𝑙𝑢𝑒=50 is gray,不偏向紅色、綠色或藍色調。

理解它的一種更簡單的方法是思考黑白。 灰色是介於黑色和白色之間的東西。黑色的 RGB 全為 0,且 白色的 RGB 均為 255。

這意味著我們的 RGB 編碼支援 256 級灰階度,從全黑的 (𝑟𝑒𝑑 = 0, 𝑔𝑟𝑒𝑒𝑛 = 0, 𝑏𝑙𝑢𝑒 = 0) 到全白的 (255, 255, 255)

如果我們想把一張圖片變成黑白圖片,那麼最棘手的部分就是得弄清楚複製的值應該是什麼。事實證明 有一個非常簡單的方法來計算它:我們對每個像素的三個分量顏色進行平均。

𝑔𝑟𝑎𝑦𝑠𝑐𝑎𝑙𝑒 = (𝑟𝑒𝑑 + 𝑔𝑟𝑒𝑒𝑛 + 𝑏𝑙𝑢𝑒) / 3

大致應用範例程式碼如下:

import os
from jes4py import *

def grayscale_image_jes(image_path, output_path):
    ''' 將圖片變成灰階效果並另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)

    # 迭代每個像素
    for x in range(width):
        for y in range(height):
            pixel = getPixel(original_image, x, y)
            gray = (getRed(pixel) + getGreen(pixel) + getBlue(pixel)) // 3
            setColor(pixel, makeColor(gray, gray, gray))

    # 儲存修改後的圖片
    writePictureTo(original_image, output_path)

# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'grayscale_' + os.path.basename(filename))
grayscale_image_jes(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

● Playing with Picture – Copy Top Half 上下鏡像

如果我們想將圖片的上半部分複製到下半部分,那麼 我們需要思考——如何在清單中找到鏡像點?如果 我們考慮一個清單:

這個清單的總長度是7,中間點是索引3。

(0, 6), (1, 5), (2, 4), (3, 3)

其數學關係為: (index, len(list) – 1 – index)

其中 Index 表示第一個索引到目前索引的距離(從左數),並且 len(列表) – 1 – 索引 意味著距離 鏡像點的最後一個索引(從右數)。

大致應用範例程式碼如下:

import os
from jes4py import *

def copy_top_half_jes(image_path, output_path):
    ''' 將圖片的上半部分鏡像後複製到下半部分並另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    
    # 取得圖片上的所有像素點
    pixels = getPixels(original_image)
    
    # 複製上半部,故由起點以遞迴方式抓取圖片前半的每個點
    for i in range(0, len(pixels) // 2):
        p = pixels[i]
        c = getColor(p)
        # 依據圖片中心點切對半來進行鏡射 --> 取得鏡像點
        mirror_p = pixels[len(pixels) - 1 - i]
        # 將顏色設定到鏡像點
        setColor(mirror_p, c)

    # 儲存修改後的圖片
    writePictureTo(original_image, output_path)

# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'copy_top_half_' + os.path.basename(filename))
copy_top_half_jes(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

如果我們想把圖片的上半部分直接(不上下鏡像)複製到下半部分,我們也可以循環遍歷圖片中的一半像素,然後找到鏡像點。

How o find a mirror point from a pixels sheet(2)

如上面的清單, pixel 像素透過以下方式配對 (0, 3), (1, 4), (2, 5); 如果我們將上半部複製到 用這個公式的下半部分,我們得到:

大致應用範例程式碼如下:

import os
from jes4py import *

def copy_top_half_directly(image_path, output_path):
    ''' 將圖片的上半部分直接複製到下半部分並另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)

    # 創建一個新圖片
    new_image = makeEmptyPicture(width, height)

    # 方法1. 
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)

    # 創建一個新圖片
    new_image = makeEmptyPicture(width, height)

    # 迭代每個像素
    # for x in range(width):
    #     for y in range(height):
    #         original_pixel = getPixel(original_image, x, y)
    #         new_pixel = getPixel(new_image, x, y)
    #         if y < height // 2:
    #             # 上半部分直接複製
    #             setColor(new_pixel, getColor(original_pixel))
    #         else:
    #             # 下半部分複製上半部分的對應像素
    #             mirror_pixel = getPixel(original_image, x, y - height // 2)
    #             setColor(new_pixel, getColor(mirror_pixel))

    # 方法2.
    # 取得圖片上的所有像素點
    pixels = getPixels(original_image)
    
    # 複製上半部,故由起點以遞迴方式抓取圖片前半的每個點
    for i in range(0, len(pixels) // 2):
        p = pixels[i]
        c = getColor(p)
        # 因為下半部不顛倒,故鏡像點公式為: mirror_p = len(list) // 2 + i - 1
        mirror_p = pixels[len(pixels) // 2 + i - 1]
        # 設定鏡像點顏色
        setColor(mirror_p, c)

    # 儲存修改後的圖片
    writePictureTo(new_image, output_path)

# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'copy_top_half_directly_' + os.path.basename(filename))
copy_top_half_directly(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

● Pictures Technique with Selection 圖片選擇技術

如果我們想將熊貓圖片的全黑像素顏色更改為白色,我們需要一個找出什麼顏色是黑色的方法?例如,一些熊貓圖片中的白色像素是:

r = 0, g = 10, b = 15
r = 33, g = 20, b = 34
r = 23, g = 17, b = 25
r = 24, g = 10, b = 35

因此,我們需要將所有這些顏色視為 黑色的 系統地。 我們可以透過計算兩種顏色之間的距離來做到這一點:

這意味著我們必須考慮每種顏色作為向量或點三維空間。 而 2 種顏色之間的距離可以透過查找來找到 2 個點的笛卡兒距離。

Jython 有一個內建的距離函數: distance(c1, c2),這樣當我們要計算兩個像素點距離時,我們就可以用這函數來計算。

● Pictures Technique – Blue Panda 藍色熊貓

我們可以利用上述 Jython 的距離函數將熊貓變成藍色熊貓。

首先我們必須循環遍歷特定的圖片區域,然後在迴圈中使用距離函數來找出接近顏色白色的像素,然後將該像數的顏色變更為藍色。

大致應用範例程式碼如下:

import os
from jes4py import *

# 定義一個函數來判斷顏色是否接近白色
def isCloseToWhite(pixel):
    red, green, blue = getRed(pixel), getGreen(pixel), getBlue(pixel)
    # 定義接近白色的標準,這裡假設 RGB 三色中的每一色都要大於 200
    return red > 200 and green > 200 and blue > 200


def turn_blue(image_path, output_path):
    # 載入圖片
    original_image = makePicture(image_path)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)
    
    # 方法1.
    # 遍歷圖片的每一個像素
    # 迭代每個像素
    # for x in range(width):
    #     for y in range(height):
    #         pixel = getPixel(original_image, x, y)
    #         # x > 160 and y < 330 --> 限制只有貓熊圖案的區域需要做變色處理
    #         if isCloseToWhite(pixel) and x > 160 and y < 330:
    #             # 將該像素的顏色改為藍色
    #             setColor(pixel, makeColor(0, 0, 255))

    # 方法2.
    for x in range(140, 450):
        for y in range(35, 320):
            p = getPixel(original_image, x, y)
            c = getColor(p)
            
            if distance(c, white) < 200:
                setColor(p, blue) 
    
    # 保存修改後的圖片
    writePictureTo(original_image, output_path)


# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'blue_panda_' + os.path.basename(filename))
turn_blue(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

● Pictures Technique – Edge Detection 邊緣偵測

邊緣偵測是一種影像處理技術,用於尋找影像中物件的邊界。

它的工作原理是檢測(像素間)有亮度不連續的位置。

邊緣偵測經常用於影像處理、電腦視覺和機器視覺中的影像分割和資料擷取。

其實這也是我們人類視覺系統的內建功能。

例如,你的鼻子或眼睛周圍沒有清晰的線條,但任何孩子都可以畫出一張臉,其中一個勾號代表鼻子,兩個圓圈代表眼睛——我們(觀賞者)都會認出它是一張臉(的輪廓)!

如下圖,圖像邊緣偵測演算法正在發覺“我左手掌右下側的像素間的差異?

如果沒有差​​異或只是一點點微小差異,我就會把自己(該像素點)設定為黑色。如果相鄰像素差異很大,就將自己(該像素點)設定為白色。

大致應用範例程式碼如下:

import os
from jes4py import *


def edge_detection(image_path, output_path):
    # 載入圖片
    original_image = makePicture(image_path)
    
    for px in getPixels(original_image):
        x = getX(px)
        y = getY(px)
        if y < getHeight(original_image) - 1 and x < getWidth(original_image) - 1:
            sum = getRed(px) + getGreen(px) + getBlue(px)
            # botrt --> bottom right point
            botrt = getPixel(original_image, x + 1, y + 1)
            sum2 = getRed(botrt) + getGreen(botrt) + getBlue(botrt)
            diff = abs(sum2 - sum) #取得兩者"亮度"差異大小,不管正負,只管大小,故取絕對值
            newColor = makeColor(diff, diff, diff) # 差異越小,偏黑;差異越大,偏白
            setColor(px, newColor)    

    # 可進一步透過閾值來過濾以加強或調整輪廓清晰度
    for px in getPixels(original_image):
        x = getX(px)
        y = getY(px)
        if y < getHeight(original_image) - 1 and x < getWidth(original_image) - 1:
            sum = getRed(px) + getGreen(px) + getBlue(px)
            if sum > 100: # 閾值: 100
                setColor(px, white)
            else:
                setColor(px, black)                 

    # 保存修改後的圖片
    writePictureTo(original_image, output_path)


# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'edge_detection_' + os.path.basename(filename))
edge_detection(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

如上面範例就可將手的邊緣偵測出來。但如果要只擷取手的輪廓,可以使用 flood fill 演算法,由於演算法較為複雜,就不在這裡說明,有興趣的人可以自己使用 flood fill 作為關鍵字進行相關的搜尋。

● Pictures Technique – Chromakey 色度鍵

當我們在看電視上新聞報導之後的氣象預報時,電視天氣預報員揮手致意 顯示(背後氣象圖)風暴鋒面即將橫過地圖。

但其實實際情況是,他們(電視天氣預報員)是站在一個固定的背景(綠屏)前拍攝的。

顏色(通常是藍色或綠色),然後背景顏色被替換以數位方式顯示所需的像素地圖;

這就是所謂的 Chromakey 色度鍵,或稱為色彩嵌空。

Pictures Technique – Chromakey(1)

關於 Chromakey 色鍵的技術的細節描述,可以參考以下網址:Chromakey

這裡我們將聚焦於 Jython 如何實現 Chromakey 色鍵的技術。

給定一張圖片,我們如何偵測綠色背景?好吧,如果藍色+紅色小於綠色,那就是綠色!

然後將綠色背景依照像素位置替換為另一張圖片的同一像素點,這樣就可以實現 Chromakey 色鍵的效果。

大致應用範例程式碼如下:

import os
from jes4py import *


def chromakey(image_path1, image_path2, output_path):
    # 載入圖片(原圖都必須是 jpg 格式)
    source_image = makePicture(image_path1)
    background_image = makePicture(image_path2)
    
    for px in getPixels(source_image):
        x = getX(px)
        y = getY(px)

        if (getRed(px) + getBlue(px) < getGreen(px)):
            replace_px = getPixel(background_image, x, y)
            replace_color = getColor(replace_px)
            setColor(px, replace_color)
            
    # 保存修改後的圖片
    # 直接存檔非 jpg 格式會出錯: OSError: cannot write mode RGBA as JPEG
    writePictureTo(source_image, output_path)    


# 使用範例
# 先取一個要嵌套的對象圖形(必須是 jpg 格式)
filename = pickAFile()
input_path1 = filename

# 再取要被嵌套的背景圖形(必須是 jpg 格式)
filename = pickAFile()
input_path2 = filename

# 產生輸出擋路路徑
output_path = os.path.join(os.path.dirname(filename), 'chromakey_' + os.path.basename(filename))

# 將要嵌套的對象圖形以及要被嵌套的背景圖形進行嵌套(置換背景)處理並存檔
chromakey(input_path1, input_path2, output_path)

# 將輸出的嵌套圖形使用 explorer() 圖形編輯器做檢查
pic = makePicture(output_path)
explore(pic)

● Mirroring an Image – Along with Vertical/Horizontal Line 垂直/水平鏡像

我們如何產生一張圖片的垂直線鏡像?好吧,我們可以使用巢狀的 for 迴圈。

對於目標張圖中的每條水平線,我們只需循環自 𝑥 = 0 到圖片的半寬。然後,找到鏡像點,複製貼上顏色。

大致應用範例程式碼如下:

import os
from jes4py import *


def h_mirror(image_path, output_path):
    ''' 垂直線鏡像處理後另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)

    # 迭代每個像素
    for x in range(width // 2):
        for y in range(height):
            p = getPixel(original_image, x, y)
            # 取得垂直鏡像點(沿中心垂直線做水平鏡像)
            mirror = getPixel(original_image, width - 1 - x, y)
            setColor(mirror, getColor(p))

    # 儲存修改後的圖片
    writePictureTo(original_image, output_path)
    

# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'h_mirror_' + os.path.basename(filename))
h_mirror(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

同理,對於水平線鏡像,原理其實類似於垂直線鏡像。

我們只需循環自 𝑦 = 0 到圖片的半高。然後,找到鏡像點,複製貼上顏色。

大致應用範例程式碼如下:

import os
from jes4py import *


def v_mirror(image_path, output_path):
    ''' 水平線鏡像處理後另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)

    # 迭代每個像素
    for x in range(width):
        for y in range(height // 2):
            p = getPixel(original_image, x, y)
            # 取得水平鏡像點(沿中心垂直線做水平鏡像)
            mirror = getPixel(original_image, x, height - 1 - y)
            setColor(mirror, getColor(p))

    # 儲存修改後的圖片
    writePictureTo(original_image, output_path)
    

# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'v_mirror_' + os.path.basename(filename))
v_mirror(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

● Mirroring an Image – Along with Diagonal-1 對角鏡像-1

那如果我們想沿著方形圖片左上右下的對角線產生圖片的鏡像該怎麼做?

在這種情況下,我們只考慮圖片是正方形時的情況。

如以下附圖所示,這是我們想要的效果:

針對這樣的需求,我們該如何設計演算法?

首先,我們可以看一下以下的表格:

Mirroring an Image – Along with Diagonal-1b

  1. 對角線是 像素座標的 set 集合: {(𝑥, 𝑦):𝑥=𝑦}

  2. 像素座標 (x, y) 的對角鏡像點座標是 (y, x)

因此,我們只需要設計一個能遍歷存取所有(上圖中)黃色像素點的雙層迴圈。

  • 外層 for迴圈 可以從 𝑦 = 0 到 y 等於圖片高度值 - 1

  • 內層 for 迴圈 可以從 𝑥 = 0 到 𝑥 = 𝑦,如果 y 值很大,那麼 x 值就跟著很大

如此,我們的演算法準代碼將如下:

function mirror(picture):	
  for y from 0 to getHeight(picture) - 1:
    for x from 0 to y:
      p1 =  pixel from (x, y), p2 = pixel from (y, x)
      change p2’s color to p1’s color

大致應用範例程式碼如下:

import os
from jes4py import *


def ltilt_mirror(image_path, output_path):
    ''' 左斜對角線鏡像處理後另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)

    # 迭代每個像素
    for y in range(height):
        for x in range(y + 1):
            p = getPixel(original_image, x, y)
            # 取得對角線鏡像點(沿左斜對角線做鏡像)
            mirror = getPixel(original_image, y, x)
            setColor(mirror, getColor(p))    

    # 儲存修改後的圖片
    writePictureTo(original_image, output_path)
    

# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'ltilt_mirror_' + os.path.basename(filename))
ltilt_mirror(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

● Mirroring an Image – Along with Diagonal-2 對角鏡像-2

如上面對角鏡像的延伸,這次將對角從左上右下的對角改為左下右上的對角

在這種情況下,同樣的我們只考慮圖片是正方形時的情況。

如以下附圖所示,這是我們想要的效果:

而針對這樣的需求,我們又該如何設計演算法?

和上面類似,我們首先可以看一下以下的表格:

Mirroring an Image – Along with Diagonal-2

從觀察中,我們可以歸納出 (x, y) 對角鏡像點的公式如下:

(𝑝𝑖𝑐𝑡𝑢𝑟𝑒𝑊𝑖𝑑𝑡ℎ – 1 – 𝑦, 𝑝𝑖𝑐𝑡𝑢𝑟𝑒𝐻𝑒𝑖𝑔ℎ𝑡 – 1 − 𝑥)

因此同理,我們只需要設計一個能遍歷存取所有(上圖中)藍色像素點的雙層迴圈。

  • 外層 for迴圈 可以從 𝑦 = 0 到 y 等於圖片高度值 - 1

  • 內層 for 迴圈 可以從 𝑥 = 0 到 𝑥 = 圖片寬度值 - 1

依照上面的推論,我們可歸納出以下的演算式準代碼:

function mirror(picture):	
  for y from 0 to getHeight(picture) - 1:
    for x from 0 to getWidth(picture) – 1 - y:
      p1 =  pixel from (x, y)
      p2 = pixel from(getHeight(pic)-1-y,getWidth(pic)-1-x)
      change p2’s color to p1’s color

大致應用範例程式碼如下:

import os
from jes4py import *


def rtilt_mirror(image_path, output_path):
    ''' 右斜對角線鏡像處理後另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)

    # 迭代每個像素
    for y in range(height):
        for x in range(width - y):
            p = getPixel(original_image, x, y)
            # 取得對角線像點(沿右斜對角線做鏡像)
            mirror = getPixel(original_image, height - 1 - y, width - 1 - x)
            setColor(mirror, getColor(p))    

    # 儲存修改後的圖片
    writePictureTo(original_image, output_path)
    

# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'rtilt_mirror_' + os.path.basename(filename))
rtilt_mirror(input_path, output_path)
pic = makePicture(output_path)
explore(pic)

● MISC – Copy the Picture to Somewhere Else - Shrink Case 縮小圖像

在 JES 中有一個函數 makeEmptyPicture(width, height)。

它可以創建一個空的圖片,如果我們想將圖片複製到其他地方,我們可以呼叫 makeEmptyPicture(width, height) 這個函數。然後我們可以經由將原始圖片的每個像素的顏色複製到新的空白圖片中的方式來將原始圖片迭代複製出一個新比例的圖片。藉此,我們可以將圖片縮小或放大。

首先我們單看圖片複製到新圖特定插入位置的情況,如下圖:

大致應用範例程式碼如下:

import os
from jes4py import *


def copyInGeneral(image_path, targetX, targetY, output_path):
    ''' 複製圖片至新的空白圖片中目標插入點位置,然後再另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    canvas = makeEmptyPicture(1000, 1000)
    
    for x in range(getWidth(original_image)):
        for y in range(getHeight(original_image)):
            p1 = getPixel(original_image, x, y)
            p2 = getPixel(canvas, targetX + x, targetY + y)
            setColor(p2, getColor(p1))

    # 儲存修改後的圖片
    writePictureTo(canvas, output_path)


# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'copyInGeneral_' + os.path.basename(filename))
copyInGeneral(input_path, 0, 0, output_path)
pic = makePicture(output_path)
explore(pic)

MISC – Copy the Picture to Somewhere Else - Shrink Case 縮小圖像

再來我們來看圖片縮小的情況,如下圖:

調整圖像大小是操作電腦的常見任務。我們如何能將圖像進行縮小呢?有一個實用且直接的方法是 – 對於每 4 個像素 (x - 2, y - 2),我們僅複製右上角的像素並將其貼到新的空白圖片中。 (這聽起來似乎不太現實。但是,它確實有效!)

MISC – Copy the Picture to Somewhere Else - Shrink Case

如上圖,以下是操作圖片縮小演算法的準代碼:

function scaleDown(pic):	
  w = getWidth(pic)
  h = getHeight(pic)
  canvas = new empty picture with size = (w/2, h/2)
  targetX = 0
  targetY = 0
  for x from 0 to w, step = 2:
    for y from 0 to h, step = 2:
      p1 = get pixel from pic at x, y
      p2 = get pixel from canvas at targetX, targetY
      set p2’s color to p1’s color
      targetY = targetY + 1
    targetX = targetX + 1
    targetY = 0

大致應用範例程式碼如下:

import os
from jes4py import *


def copyInShrink(image_path, step, output_path):
    ''' 複製圖片至已縮小的新的空白圖片中,然後再另存新檔 '''
    if (isinstance(step, int) != True):
        raise Exception('縮小倍率必須是整數')
    
    if(step < 1):
        raise Exception('縮小倍率不可小於1') 
    
    # 載入圖片
    original_image = makePicture(image_path)
    canvas = makeEmptyPicture(getWidth(original_image) // step, getHeight(original_image) // step)
    
    i = 0
    # 每 step x step 像素矩陣中,只取右上角(第一個)的像素
    for x in range(0, getWidth(original_image), step):
        j = 0
        for y in range(0, getHeight(original_image), step):
            p1 = getPixel(original_image, x, y)
            p2 = getPixel(canvas, i, j)
            setColor(p2, getColor(p1))
            j = j + 1
        i = i + 1        

    # 儲存修改後的圖片
    writePictureTo(canvas, output_path)


# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'copyInShrink_' + os.path.basename(filename))
copyInShrink(input_path, 2, output_path)
pic = makePicture(output_path)
explore(pic)

● MISC – Copy the Picture to Somewhere Else - Enlarge Case 放大圖像

放大圖像與我們之前所操作的圖像縮小的程序類似。

首先我們需要為每個原始圖片的像素製作(擴充)四(2 的平方或 N 的平方;看倍率 N 的設定)個新的副本,然後再將它們放在新的空白圖片上 。

MISC – Copy the Picture to Somewhere Else - Enlarge Case

如上圖,以下是操作圖片放大演算法的準代碼:

function scaleUp(pic):
  w = getWidth(pic)
  h = getHeight(pic)
  canvas = new empty picture with size = (w*2, h*2)
  targetX = 0
  targetY = 0
  for x from 0 to w:
    for y from 0 to h:
      p1 = get pixel from pic at x, y
      p2 = get pixel from canvas at targetX, targetY
      p3 = p2’s right, p4 = p2’s bottom, p5 = p2’s bottom right
      set p2, p3, p4, p5 color to p1’s color
      targetY = targetY + 2
    targetX = targetX + 2
    targetY = 0

大致應用範例程式碼如下:

import os
from jes4py import *


# 此範例只是簡單示意,因此 step 放大倍率只能使用整數 
def copyInEnlarge(image_path, step, output_path):
    ''' 複製圖片至已放大的新的空白圖片中,然後再另存新檔 '''
    if (isinstance(step, int) != True):
        raise Exception('放大倍率必須是整數')
    
    if(step < 1):
        raise Exception('放大倍率不可小於1')    
    
    # 載入圖片
    original_image = makePicture(image_path)
    canvas = makeEmptyPicture(getWidth(original_image) * step, getHeight(original_image) * step)
    
    for x in range(getWidth(original_image)):
        for y in range(getHeight(original_image)):
            p1 = getPixel(original_image, x, y)
            # 每次從原圖像取得的像素,依照 step 放大倍率的矩陣,
            # 對放大後的空圖片進行像素的區域移動填充。
            for k in range(x * step, (x + 1) * step):
                for l in range( y * step, (y + 1) * step):
                    p2 = getPixel(canvas, k, l)
                    setColor(p2, getColor(p1))     

    # 儲存修改後的圖片
    writePictureTo(canvas, output_path)


# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'copyInEnlarge_' + os.path.basename(filename))
copyInEnlarge(input_path, 2, output_path)
pic = makePicture(output_path)
explore(pic)

[Note]: Wilson 老師這裡示範的方法只能用於 2 x 2 的放大倍率上,其實只要修正成我上述範例程式碼的 k, l 的 for 迴圈即可完成整個放大級距範圍的移動填充,而能適用於 2 被以上更大的放大倍率變動。

● MISC – Blur 模糊化

由於演算法的性質,當我們將圖片放大時 (按比例放大),我們通常會在線條中看到粗糙的邊緣和尖銳的台​​階 我們稱之為 pixelation 像素化,如下圖所示。

為了解決這個問題,我們可以使用模糊化技術。模糊化是一種圖像處理技術,用於使圖像變得模糊。

改進它的一種方法是模糊圖像。有大量不同的模糊演算法;我們將在此使用的演算方式如下:

  1. 首先我們取一個像素的上、右、下、左像素,每五個像素成一組,找出該組的紅、綠、藍的平均值。

  2. 將每一組的中心像素值改為我們在第 1 步求得的紅、綠、藍平均值。

  3. 重複這個過程,直到我們處理完所有像素。

這種模糊演算法的目的意在減少像素的顏色及其相鄰像素的顏色兩者之間的差異。

大致應用範例程式碼如下:

import os
from jes4py import *


def ImageBlur(image_path, output_path):
    ''' 圖像模糊化處理後另存新檔 '''
    # 載入圖片
    original_image = makePicture(image_path)
    clone_image = duplicatePicture(original_image)
    
    # 獲取圖片的寬度和高度
    width = getWidth(original_image)
    height = getHeight(original_image)
    
    for x in range(1, width - 1):
        for y in range(1, height - 1):
            # 建立像素分組
            top = getPixel(original_image, x, y - 1)
            left = getPixel(original_image, x - 1, y)
            bottom = getPixel(original_image, x, y + 1)
            right = getPixel(original_image, x + 1, y)
            # 因之後要修改到複製的圖像中,故這裡選擇複製的圖像來源
            center = getPixel(clone_image, x, y)
            # 將分組像素的各個 R, G, B 取平均值,然後造出新的顏色,設定到新的分組中心像素點上
            newRed = (getRed(top) + getRed(left) + getRed(bottom) + getRed(right) + getRed(center)) // 5
            newGreen = (getGreen(top) + getGreen(left) + getGreen(bottom) + getGreen(right) + getGreen(center)) // 5
            newBlue = (getBlue(top) + getBlue(left) + getBlue(bottom) + getBlue(right) + getBlue(center)) // 5
            setColor(center, makeColor(newRed, newGreen, newBlue))              

    # 保存修改後的圖片
    writePictureTo(clone_image, output_path)


# 使用範例
filename = pickAFile()
input_path = filename
output_path = os.path.join(os.path.dirname(filename), 'ImageBlur_' + os.path.basename(filename))
ImageBlur(input_path, output_path)
pic = makePicture(output_path)
explore(pic)  

下圖是經由此演算方式模糊化前與後經放大後的圖片的區別。

從圖上可看出,左圖為模糊化前的圖像,從左圖我們可看到鋒利的邊緣。

右圖為模糊化後的圖像,從右圖我們可看到圖像變得模糊,不再有鋒利的邊緣。

● 總結

在這一章中,透過 Wilson 老師的介紹,我們學習了如何使用 Jython 來處理圖像。這當中包含了 Jython 框架的安裝,及相應的 IDE - JES 編輯器的介紹。以及介紹了 Jython 裡常用的一些圖像處理函數及其原理,例如: 如何讀取圖像、處理圖像、寫入圖像、如何調亮圖像、製作偽日落、製作負片、製作灰階圖像、製作上下鏡像、製作藍色熊貓、製作邊緣偵測、製作色度鍵效果、製作垂直/水平鏡像、製作對角鏡像等等的圖像處理方法。

不過 Jython 必須搭配 JES 才能使用,不免讓使用者在 Python 程式自動化要整合其他功能時,會覺得有些不便。但其實在一些 Github 社群大神的努力下,這其實是有解的,如想把 Jython 的圖像函數應用到 Python 3.x 環境中,其實我們可以使用 JES4py 這個套件,這是一個基於 JES 的 Python 3.x 的整合版本,可以在 Python 3.x 環境中使用 JES 的功能,這樣就可以使用 Python 3.x 的語法來進行影像處理。

除了 JES4py 這個元件庫之外,其實還有其他更常用的圖像處理元件庫,例如前面有提到過的 scikit-image 元件庫,它也是一個基於 Python 的圖像處理庫,提供了許多圖像處理的功能,包括圖像的讀取、顯示、處理、編輯、合成、濾鏡、特效等等。scikit-image 是一個很好的工具,可以幫助我們進行圖像處理,並且可以用來進行一些複雜的圖像處理。

後記 - subprocess.CalledProcessError 例外的解法

因為共學同學的反應,發現一個有趣的錯誤,就是上述筆記中的程式,如果在 thonny 中執行並無問題,但如果跑在 vscode 中,不論是使用作業系統安裝的 python 來跑或是使用 virtualenv 來跑,都會出現 subprocess.CalledProcessError,這其實都是因為 JES4PY Library 中的 pickAFile() 函數使用了 subprocess 方式來呼叫一個開啟檔案介面程式來取得檔案路徑,我推測應該是原先的 pickAFile() 使用 subprocess 方式呼叫,沒注意 subprocess shell 通常會是另一個獨立於 program 當下 thread 之外的不可控程序,而導致的例外拋出,由於我沒用過 subprocess,無法幫原作者 Debug 問題,但在試錯過程中,也找到兩種解法,這裡也跟大家做個分享:

第一種方式,最簡單,避開使用 pickAFile() 函數,直接將檔案路徑寫死在程式碼中,這樣就不會有 subprocess 的問題了。

第二種方式,自己自訂一個不用 subprocess 呼叫的 pickAFile() 函數來複寫(override)掉 JES4PY Library 中的 pickAFile() 函數,這樣也可以避開 subprocess 的問題了,這裡提供我寫的 pickAFile() 函數程式碼(其實是直接把原作者的 pickAFile() 函數程式碼複製過來改寫,拿掉 subprocess 呼叫):

import os
from jes4py import *

def pickAFile():
    import wx
    app = wx.App()
    frame = wx.Frame(None, -1, 'PickAFile')

    # Create open file dialog
    path = None
    openFileDialog = wx.FileDialog(frame, 'Pick A File', os.getcwd(),
        "", "", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
    if openFileDialog.ShowModal() == wx.ID_OK:
        path = openFileDialog.GetPath()
    
    # Clean up
    openFileDialog.Destroy()
    return path

filename = pickAFile()
if filename!=None and filename.length() > 0:
    pic = makePicture(filename)
    explore(pic)

記住,在每個檔案中,加入這個自訂的 pickAFile() 函數,或者你也可以寫一個自訂類別,將 pickAFile() 函數放在那裏,記得改個函數名稱,比方說叫 customPickAFile(),避免與原先的 JES4PY 裡的 pickAFile() 函數發生撞名而衝突。然後讓 filename = customPickAFile() 改用 customPickAFile() 來取得圖檔路徑,這樣可以少寫一些程式碼。

以上是遭遇 subprocess.CalledProcessError 的解法,希望對大家有所幫助。

1個讚

現在要用Python做影像處理,比較建議使用的library是OpenCV和Pillow(scikit-image沒那麼主流但也不差)

2個讚

感謝 Howard 兄的建議,會講 Jython 主要是 Udemy Python 全攻略課程中有介紹,而我這其實是為下禮拜的共學分享所準備。本來心裡就有些 OS 想說老師怎麼介紹這個不是太熱門的圖像處理套件,而且安裝上也有些麻煩,不過可能就像當初 Dr. Angela Yu 的 Python 百日馬介紹使用 Thonny 這個 Allin1 的 IDE,而 JES 之於 Jython 也有點這個味道,算是給剛入門的人,多一些嘗試,不過話說回來,這篇筆記主要是依照老師授課的脈絡所整理的筆記,因此也只能照著老師提供的知識點來講,但總的來說,對啊,的確,就如同 Howard 兄所言,實況上可能 opencv 其實更為 popular~

1個讚

不過話說回來,雖然日常蠻常在使用一些圖像處理,但老師的這個章節其實我覺得用什麼套件並不是重點,相對的是,從裡面老師的課程解說中,講解了很多圖像處理背後的原理,這些原理我其實原先還真是不太知道呢? (可能是我太不好學了,竟然連這種基本原理也不知道,大家別太鄙視我~) 因此學了這章節,我自己算是長了很多圖像處理原理的知識,覺得還蠻驚豔的~

1個讚

這篇筆記算是我前後修改最多的,也是範例最多的,原先第一版,因為 Copilot 幫了倒忙,放在上面的程式碼都非使用 Jython Library,這樣筆記就有點尷尬了,因此又花了很大的心思和時間,把每個圖像處理的範例實作出來。在邊重看老師影片時,每每老師在解說某個圖像處理的演算法,在老師展示程式碼之前,我都先經過自己思考後,把程式碼寫出來,也確保都能執行且結果和應該要有的效果一致,這過程其實還蠻有成就感的。

這一節 Jython 圖像處理庫的介紹,我個人認為重點並不在各種圖像處理的函數本身,而是在於每種圖像處理背後演算的意義,還蠻有趣的,如對圖像處理技術有興趣的,也歡迎來參考這篇筆記,一起切礳。

1個讚

筆記中的範例,我其實當初是用 Thonny 這套可攜式的 Python IDE 來驗證撰寫的,但在線上共學討論中,有同學告訴我,當他直接把範例程式碼貼到 vscode 中,執行,會遭遇到 subprocess.CalledProcessError 的例外問題而無法完成執行,這迫使他又回頭去用 JES 及Jython 環境。

對此我做了一些研究,發現其實是原作者寫的 pickAFile() 函數使用了 subprocess 來呼叫另 一個圖形介面程式所造成(原作者 pickXXX 函數都用了同樣做法,都有同樣問題),詳細的問題解析及解決辦法,我放在筆記最末的後記,提供大家參考。

為何要放在後記,而不是直接修改到範例程式中,這是因為我覺得除錯其實是很好的學習程式方式,而且往往你遭遇了問題,會對問題的發生及後來的解決會更有印象,因此我希望對新來的閱讀者而言,他們由上到下閱讀,也會遭遇這樣的問題,而在思考不出解法時,再得到後記中解決辦法的提示,印象會更深刻,也能更加深學習的效果,這也是我把這問題的解法放在最後面的初衷,期許大家在共學中都能獲得很好的學習體驗及享受自學共學的樂趣。

1個讚