跳轉到

1.1 數位影像的表示#

概念說明#

一張數位影像,本質上是一個二維(彩色影像則是三維)的數值陣列。影像被切割成許多微小的方格,每個方格稱為一個「像素」(pixel)。每個像素攜帶該位置的亮度或顏色資訊。

灰階影像中,每個像素是一個數值,通常介於 0(黑)到 255(白)之間,用 8 個位元儲存,稱為 8-bit 深度。若使用 16-bit,數值範圍擴大到 0–65535,可表現更細膩的明暗層次,常用於天文與醫學影像。

彩色影像則常用三個通道 R(紅)、G(綠)、B(藍)疊加而成,每個通道各 8-bit,因此一個像素需要 24 bits。影像的「解析度」指的是長與寬各有多少像素,例如 1920×1080 表示寬 1920 個、高 1080 個像素,共約 207 萬像素。

在程式中,我們習慣用 numpy 的二維陣列表示灰階影像,用「高 × 寬 × 3」的三維陣列表示彩色影像。注意陣列的第一維是列(y 方向、往下),第二維是行(x 方向、往右),與數學座標的習慣相反,初學者常常在此處混淆。

影像在記憶體中的布局#

一張 H×W 的灰階影像佔用 H×W 位元組(8-bit 時);一張 H×W 的 RGB 影像佔用 3×H×W 位元組。例如一張 1080p 彩色影像未壓縮時約需 6 MB,這解釋了為什麼影像壓縮技術(第 6 章)如此重要。有些影像格式(如 GIF、部分 PNG)使用「索引式影像」:影像本身只儲存編號,另有一張調色盤(palette)把編號對應到顏色,可節省儲存空間。

演算法步驟:建立並存取一張影像#

  1. 匯入 numpy 與 PIL。
  2. 用 numpy 建立全 0 或隨機的陣列作為影像。
  3. 用索引 (y, x) 讀寫單一像素;用切片存取區域。
  4. 將 numpy 陣列轉為 PIL Image 並儲存或顯示。

Python 程式碼範例#

import numpy as np
from PIL import Image

# 建立一張 4x4 的灰階影像
img = np.array([
    [  0,  50, 100, 150],
    [ 60, 110, 160, 200],
    [120, 170, 220, 255],
    [ 10,  20,  30,  40]
], dtype=np.uint8)

# 讀取單一像素(y=1, x=2)
print("像素(1,2) =", img[1, 2])

# 存取一個區域(前兩列、右邊兩行)
print("區域:\n", img[0:2, 2:4])

# 統計資訊
print("最小/最大/平均:", img.min(), img.max(), img.mean())

# 建立一張 8x8 的隨機彩色影像並存檔
color = np.random.randint(0, 256, (8, 8, 3), dtype=np.uint8)
Image.fromarray(color).save("color.png")
print("彩色影像外觀:", color.shape)  # (8, 8, 3)

# 讀回來確認
back = np.array(Image.open("color.png"))
print("存取後一致:", np.array_equal(back, color))

執行後會印出像素值、區域切片與影像形狀,驗證影像就是 numpy 陣列的事實。

複雜度分析#

讀寫單一像素:O(1)。掃描整張影像(例如求平均值):每個像素恰好存取一次,時間複雜度 O(H×W),空間複雜度 O(1)(不另開大陣列時)。任何逐像素運算的成本都與像素總數成正比,這是所有影像演算法的基本量測單位。

練習#

  1. 建立一張 256×256 的灰階影像,左半邊全 0、右半邊全 255,並存成 PNG。觀察其直方圖會長什麼樣子?
  2. 寫一段程式,將一張影像的四個角落各 50×50 的區域設為紅色(彩色影像)。
  3. 一張 4K(3840×2160)的 8-bit RGB 影像未壓縮需要多少 MB?若改成 16-bit 灰階又要多少?