關於ColourSpace的新功能Image Sequence Probe─以Python輔助處理影像

Regarding ColourSpace’s New Feature, Image Sequence Probe—Facilitated by Python for Image Processing

專業顯示器校正軟體ColourSpace在2023 年 9 月發布重大更新,新增了影像序列探頭 Image Sequence Probe功能。這篇主要不是要介紹軟體功能,而是提供實作上,如何透過Python來排除相機等設備捕捉到影像不確定性問題。

Image Sequence Probe是什麼

簡單說,就是使用者可以透過比較兩個相同序列的圖檔,來製作不同裝置之間的LUT(仔細想想這概念豈不是跟ICC Profile概念類似!?)

執行的原理是,一邊的影像序列中色彩數值為已知條件,也就是特定色彩空間或色域下的已知數值影像序列。另一邊則是我們想得到色彩資訊的未知的色彩空間或色域,例如數位相機、掃描器等;透過這些設備拍攝、掃描已知資訊的影像序列後,會得到新的影像序列檔案,ColourSpace透過比較兩者的色彩數值,建構出兩者的關係,並可以生成校正LUT。

實際應用

我們都知道透過相機拍攝螢幕,與肉眼觀看有一定的落差,原因就是大多數相機感光元件並不滿足所謂的Luther condition

如果相機光譜靈敏度(相機顏色響應)與人類 顏色響應(標準觀察者 xyz 條顏色匹配函數)
呈線性相關,則滿足 Luther 條件。

透過新的ISP虛擬探針功能,就能透過測量相機或其他設備,對於接收光線後的反應或處理,進一步計算出轉換關係。

攝影機捕捉的問題

以虛擬攝影棚為例,如果要製作特定數位攝影機對螢幕牆的校正LUT,實際上會透過螢幕牆撥放連續的影像序列,攝影機拍攝錄製的方式獲得檔案。

實作上除非全程手動一張一張捕捉,不然以錄影的方式,最後得到的檔案勢必會穿插一些「顯示一半」的影像,其實是拍攝到兩個色彩序列可能性。再者,理想上影片抽幀為DPX圖檔(軟體要求的格式),最好可以剛好選中剛好拍攝到不同顏色的畫面,但實務上一定會同實拍到很多張。如果序列檔案間隔不是固定的,那最後要選取適當檔案就會變得非常困難。

上圖就是測試的狀況,可以看到不僅有兩個色塊相間的,實際同色塊的間隔也不穩定,時間越長這個狀況越不可控。

用Python輔助處理

以下提供一個簡易的程式建構筆記,詳細功能與執行需要自行測試。

1.由於OpenCV程式無法直接讀取DPX,先將DPX轉成JPG

A.安裝FFmpeg轉檔程式

安裝FFmpeg並且加到系統變數(PATH),將 FFmpeg 添加到您的系統環境變數(PATH)通常有幾個步驟:

  1. 下載和解壓縮 FFmpeg,下載後,解壓縮到選擇的文件夾,例如 C:\FFmpeg。
  2. 尋找 ffmpeg.exe。通常在 bin 子資料夾,路徑例如 C:\FFmpeg\bin\ffmpeg.exe。
  3. 添加到 PATH 環境變數:打開命令提示字元(Command Prompt)以管理員身份,然後執行以下命令:

setx PATH “%PATH%;C:\FFmpeg\bin”

  1. 驗證安裝,重新開啟一個命令提示字元窗口,輸入 ffmpeg。應該會看到 FFmpeg 的版本信息和使用選項。

B.使用FFmpeg批次轉換DPX成JPG

1. 打開記事本(Notepad)。複製並貼上以下代碼:

batch
Copy code
@echo off
for %%a in (“*.dpx”) do (
ffmpeg -i “%%~a” “%%~na.jpg”
)

儲存這個檔案為 convert_dpx_to_jpg.bat。

2. 將這個 .bat 檔案放到包含您所有DPX檔案的資料夾中。雙擊 .bat 檔案以執行它。
檔案將儲存於同一個資料夾中,附檔名為jpg

2.使用Python進行圖檔比較與篩選

預設轉檔後的JPG資料夾位置 C:\DPX2JPG

A.排除拍攝不良的檔案

部分拍攝圖檔同時包含前、後兩個色塊的畫面,檢查圖片確認錯誤的樣態,假若是同一張圖上、下顏色不同:

每一張圖區分上半部與下半部,平均後計算上、下部分的顏色CIEDE2000色差值,
色差值大於1,將檔案移動到資料夾 C:\DPX2JPG\BAD

B.挑選實際需要的顏色

由於是連續拍攝,重複擷取到的顏色可能不規律,需要仔細檢查。如果採用一般性的檢測方式,方法如下:

將C:\DPX2JPG中的圖檔,選擇檔名數字最小的兩個圖檔,計算兩檔案色彩的CIEDE2000色差值
色差值小於1,則把檔名較小的移動到資料夾 C:\DPX2JPG\SAME
色差值大於1,則把檔名較小的移動到資料夾 C:\DPX2JPG\CHOICE
剩下最後一張圖檔,移動到資料夾 C:\DPX2JPG\CHOICE
將所有檔案比較色差值的數據,記錄到C:\DPX2JPG\results.txt中

C.比對原始DPX與挑選出的JPG
將資料夾 C:\DPX2JPG\CHOICE 裡面的JPG圖檔,比對原檔資料夾 C:\DPX,
同樣檔案名稱的DPX檔案,複製到新資料夾 C:\DPX_Final

D.重新命名
重新命名C:\DPX_Final 裡的檔案,依照原檔案名稱數字的排序由小至大,更改檔案名稱為 stuff000001,stuff000002,stuff000003以此類推

參考Python程式碼

import cv2
import os
import shutil
from colormath.color_objects import LabColor
from colormath.color_diff import delta_e_cie2000

def calculate_ciede2000(color1, color2):
    c1 = LabColor(lab_l=color1[0], lab_a=color1[1], lab_b=color1[2])
    c2 = LabColor(lab_l=color2[0], lab_a=color2[1], lab_b=color2[2])
    return delta_e_cie2000(c1, c2)

def main():
    source_folder = "C:\\DPX2JPG\\"
    bad_folder = source_folder + "BAD\\"
    same_folder = source_folder + "SAME\\"
    choice_folder = source_folder + "CHOICE\\"
    dpx_folder = "C:\\DPX\\"
    final_folder = "C:\\DPX_Final\\"

    for folder in [bad_folder, same_folder, choice_folder, final_folder]:
        if not os.path.exists(folder):
            os.makedirs(folder)

    de_results = []
    image_files = sorted(os.listdir(source_folder))
    for img_file in image_files:
        img = cv2.imread(os.path.join(source_folder, img_file))
        h, w, _ = img.shape
        upper_half = img[:h//2, :]
        lower_half = img[h//2:, :]

        upper_mean = cv2.mean(upper_half)[:3]
        lower_mean = cv2.mean(lower_half)[:3]

        ciede = calculate_ciede2000(upper_mean, lower_mean)
        de_results.append(f"{img_file}: {ciede}")

        if ciede > 1:
            shutil.move(os.path.join(source_folder, img_file), os.path.join(bad_folder, img_file))

    image_files = sorted(os.listdir(source_folder))
    for i in range(0, len(image_files) - 1):
        img1 = cv2.imread(os.path.join(source_folder, image_files[i]))
        img2 = cv2.imread(os.path.join(source_folder, image_files[i + 1]))

        mean1 = cv2.mean(img1)[:3]
        mean2 = cv2.mean(img2)[:3]

        ciede = calculate_ciede2000(mean1, mean2)
        de_results.append(f"{image_files[i]} vs {image_files[i+1]}: {ciede}")

        if ciede < 1:
            shutil.move(os.path.join(source_folder, image_files[i]), os.path.join(same_folder, image_files[i]))
        else:
            shutil.move(os.path.join(source_folder, image_files[i]), os.path.join(choice_folder, image_files[i]))

    shutil.move(os.path.join(source_folder, image_files[-1]), os.path.join(choice_folder, image_files[-1]))

    # Save DE results
    with open(os.path.join(source_folder, "DE_results.txt"), "w") as f:
        f.write("\n".join(de_results))

    rename_results = []
    choice_jpg_files = os.listdir(choice_folder)
    for jpg_file in choice_jpg_files:
        dpx_file = jpg_file.replace('.jpg', '.dpx')
        if os.path.exists(os.path.join(dpx_folder, dpx_file)):
            shutil.copy(os.path.join(dpx_folder, dpx_file), os.path.join(final_folder, dpx_file))

    dpx_files = sorted(os.listdir(final_folder))
    for i, dpx_file in enumerate(dpx_files):
        new_name = f"stuff{i+1:06d}.dpx"
        rename_results.append(f"{dpx_file} -> {new_name}")
        os.rename(os.path.join(final_folder, dpx_file), os.path.join(final_folder, new_name))

    # Save rename results
    with open(os.path.join(source_folder, "Rename_results.txt"), "w") as f:
        f.write("\n".join(rename_results))

if __name__ == "__main__":
    main()

這個代碼包括了:

  1. 計算每張圖片上下半部分的CIEDE2000色差值並進行分類。
  2. 處理相鄰的JPG圖檔,並根據CIEDE2000色差值將其分類。
  3. 將相應的DPX檔案從原始資料夾複製到新資料夾。
  4. 重新命名DPX檔案。
  5. 將色差值和重新命名的對照資料保存到DE_results.txtRename_results.txt
%d