デジタル画像の基礎
Digital Image Fundamentals
デジタル画像とは
デジタル画像は、2次元の格子状に配置されたピクセル(画素)の集合である。各ピクセルは位置 $(x, y)$ と輝度値(または色値)を持つ。
y 軸が下向きである理由
数学のグラフでは y 軸を上向きに描くが、画像処理では原点を左上に置き、y 軸を下向きにとる。 これはCRTディスプレイの時代に、電子ビームが画面の左上から右へ、上から下へとラスタ走査(raster scan)していたことに由来する。 メモリ上でも画像データは左上のピクセルから順に格納されることが多く、行番号が増えるほど画面の下方に対応する。 この慣習は多くの画像フォーマットや画像処理ライブラリ(OpenCV, PIL など)に引き継がれている。ただし BMP は既定で下の行から画素を格納し、OpenGL のテクスチャ座標は原点が左下であるなど、例外もある。
解像度とビット深度
解像度(Resolution)
ここでは画像のピクセル数(画素寸法)を表す(印刷やディスプレイでは、1インチあたりの画素数 dpi/ppi を解像度と呼ぶこともある)。$W \times H$ で表記され、例えば 1920×1080 は約200万画素。 テレビやディスプレイの「4K」「8K」も水平方向の画素数に由来する呼称で、4K は 3840×2160(約830万画素)、8K は 7680×4320(約3300万画素)である。
※ 民生用テレビやディスプレイでは「4K UHD」(3840×2160) が標準で、映画業界の「DCI 4K」(4096×2160) とは異なる。
ビット深度(Bit Depth)
1 つのチャンネル(1 つの値)を表現するビット数。1 画素あたりのビット数は、これにチャンネル数を掛けたものになる:
- 1ビット: 2値画像(白黒)
- 8ビット: 256階調(0〜255)。$n$ ビットでは $0$ から $2^n - 1$ の範囲
- 16ビット: 65536階調
- 24ビット(1画素あたり): RGB 各8ビット × 3 チャンネル(フルカラー)
チャンネル数
チャンネル数(Number of Channels)
各ピクセルが持つ値の個数を表す。
| チャンネル数 | 名称 | 各ピクセルの値 | 用途 |
|---|---|---|---|
| 1 | グレースケール | 輝度値 1 個 | 白黒写真、医療画像、エッジ検出の前処理 |
| 3 | カラー(RGB) | 赤・緑・青の 3 個 | 一般的なカラー画像 |
| 4 | RGBA | 赤・緑・青・透明度の 4 個 | PNG、アイコン、UIデザイン |
チャンネル数が増えるほど 1 ピクセルあたりのデータ量が増える。 画像処理では、カラー画像をグレースケールに変換して 1 チャンネルにすることで計算量を削減する手法がよく使われる。 具体的な変換式(ガンマ補正済みの RGB に対する ITU-R BT.601 の係数による $Y = 0.299R + 0.587G + 0.114B$ など)は 「色空間」で扱う。
画像のメモリサイズ
非圧縮の画素配列としてメモリ上に置いたときのデータサイズは、以下で計算できる(JPEG や PNG のファイルは圧縮されるので、ファイルの大きさとは異なる):
$$\text{サイズ (bytes)} = W \times H \times \frac{\text{1チャンネルあたりのビット深度}}{8} \times \text{チャンネル数}$$例: 1920×1080のフルHD画像
- グレースケール(8bit): $1920 \times 1080 \times 1 = 2,073,600$ bytes ≈ 2MB
- カラー(24bit RGB): $1920 \times 1080 \times 3 = 6,220,800$ bytes ≈ 6MB
画像の数学的表現
画像は数学的に以下のように表現される:
グレースケール画像
行列 $\mathbf{I} \in \mathbb{R}^{H \times W}$ として表現される。行 $y$・列 $x$ の成分が、ピクセル $(x, y)$ の輝度値 $I(x,y)$ である。
$$\mathbf{I} = \begin{pmatrix} I(0,0) & I(1,0) & \cdots & I(W-1,0) \\ I(0,1) & I(1,1) & \cdots & I(W-1,1) \\ \vdots & \vdots & \ddots & \vdots \\ I(0,H-1) & I(1,H-1) & \cdots & I(W-1,H-1) \end{pmatrix}$$※ 画像を関数として見ると $I(x, y)$(横, 縦)と書くが、行列・配列としては添字が行・列の順なので、NumPy/OpenCV では img[y, x] の順序でアクセスする。
カラー画像
3次元テンソル $\mathbf{I} \in \mathbb{R}^{H \times W \times C}$(C: チャンネル数、通常3)
$$I(x, y) = \begin{pmatrix} R(x,y) \\ G(x,y) \\ B(x,y) \end{pmatrix}$$OpenCV はなぜ BGR 順序なのか
多くの画像ライブラリ(PIL, matplotlib など)はカラー画像を RGB 順の配列で扱うが、OpenCV の imread は BGR 順の配列を返す。
これは技術的な合理性ではなく、歴史的な経緯による。
OpenCV は1999年に Intel が開発を開始したが、当時 Windows で主流だったカメラ API(Video for Windows)やビットマップ形式(BMP/DIB)が BGR 順序でデータを返していたため、それに合わせたとされる。
Windows の BMP が BGR なのは、画素を 0xAARRGGBB(上位から A, R, G, B)の 32 ビット整数として扱い、リトルエンディアンの Intel x86 でメモリに置くと B, G, R, A の順になることに由来するとされる。
OpenCV がクロスプラットフォーム化した後も、既存コードとの互換性のために BGR のまま維持されている。
OpenCV で読み込んだ画像を他のライブラリ(PIL, matplotlib など)で表示する際は cv2.cvtColor(img, cv2.COLOR_BGR2RGB) で変換が必要である。
コード例(Python)
import numpy as np
import cv2
# 画像の読み込み
img_color = cv2.imread('image.jpg') # カラー (H, W, 3)
img_gray = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # グレースケール (H, W)
# 画像のプロパティ
print(f"Shape: {img_color.shape}") # (高さ, 幅, チャンネル数)
print(f"Dtype: {img_color.dtype}") # uint8 (0-255)
print(f"Size: {img_color.nbytes} bytes") # 総バイト数(.size は要素数。uint8 なら同じ値)
# ピクセルへのアクセス
pixel = img_color[100, 200] # (B, G, R) の値
gray_value = img_gray[100, 200] # 輝度値
# 新しい画像の作成
blank = np.zeros((480, 640, 3), dtype=np.uint8) # 黒画像
white = np.ones((480, 640), dtype=np.uint8) * 255 # 白画像
まとめ
- デジタル画像はピクセルの2次元配列
- 解像度は画像のピクセル数、1チャンネルあたりのビット深度は各チャンネルの階調数を決める
- グレースケールは2次元行列、カラーは3次元テンソル
- 画像処理では行列演算が基本となる
よくある質問
デジタル画像の y 軸はなぜ下向きなのか
CRT ディスプレイの時代に、電子ビームが画面の左上から右へ、上から下へとラスタ走査していたことに由来する。メモリ上でも画像データは左上のピクセルから順に格納されることが多いため、行番号が増えるほど画面の下方に対応する。この慣習は OpenCV や PIL など多くの画像処理ライブラリに引き継がれているが、BMP は既定で下の行から画素を格納し、OpenGL のテクスチャ座標は原点が左下であるなど、例外もある。
OpenCV はなぜ BGR 順序なのか
OpenCV は1999年に Intel で開発が始まり、当時の Windows のカメラ API や BMP 形式が BGR 順序でデータを扱っていたため、それに合わせたとされる。BMP の BGR は、画素を 0xAARRGGBB の 32 ビット整数として扱い、リトルエンディアンの x86 でメモリに置くと B, G, R, A の順になることに由来するとされる。互換性のため、現在も BGR のまま維持されている。