OpenCV のピクセル形式が BGR である理由

# はじめに

主に Python のOpenCV 利用者の間でよくあがる疑問。
何故、imread で取得したピクセル値の並びが RGB でなく BGR なのか？
NumPy 配列として RGB 順でうっかりアクセスしたり、matplotlib 等の外部プログラムに BGR 配列をそのまま渡して色味が変わる罠が有名ですね。

:::note info
BGR から RGB の並びに変換したい時は cvtColor が便利です。
imgRGB = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2RGB)
:::

:::note info
https://github.com/opencv/opencv/pull/25809
2024/7/3 OpenCV 4.x ブランチに IMREAD_COLOR_RGB がマージされたので、近いうちに RGB の順でも読めるようになりそうです。
:::

# はじめに結論

OpenCV の低レベル層の前身 IPL (Intel Image Proecssing Library) の開発を始めた頃の一般的な並びが BGR で、IPL は元々 Windows 用[^1]かつ性能重視のプロダクトなので、Windows OS の内部画像形式 DIB に合わせた可能性が高いです。
また、この DIB の基本構造は OS/2 まで遡る事ができて、当時の IBM PC に載っていた VGA RAMDAC の入力が BGR 形式な事から、根本的な起源はそちらかもしれません。

[^1]: Intel® Image Processing Library Reference Manual の Hardware and Software Requirements の項目で確認。(The Image Processing Library runs on personal computers that are based on Intel® architecture processors and running Microsoft* Windows*,
Windows 95, 98, or Windows NT* operating system. The library integrates
into the customer’s application or library written in C or C++)

# きっかけの記事

- OpenCVのネイティブピクセルフォーマットがBGRな理由
  - https://qiita.com/yohhoy/items/d1a29c8f15be2d56cc52

なお、本エントリは上記の記事への勝手補足です。
コメントに書いても良かったのですがちょっと長くなりそうなので。

> 「なぜ一般に使われているRGB順ではないのですか？」
> （略）
> 「なぜ米国の鉄道レール幅は4フィートと8.5インチなのか？」
> この質問への答えは、もちろん「ローマ帝国時代の馬のお尻の幅由来です！」

これだと誤魔化されたように感じてたところ、@yohhoy 先生からツイッターで補足を頂きまして。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"> 面白いところだけ引用していますが、元記事後半にはちゃんと答え <a href="https://learnopencv.com/why-does-opencv-use-bgr-color-format/"> https://learnopencv.com/why-does-openc... </a> が載っておりますゆえ... "E.g. in Windows, when specifying color value using COLORREF they use the BGR format 0x00bbggrr."</p>  <a href="https://twitter.com/yoya/status/1427493292904644613">午前1:50 2021年8月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

以下に少し掘り下げた説明をします。

# BGR である理由

- Why does OpenCV use BGR color format ? | LearnOpenCV #
  - https://learnopencv.com/why-does-opencv-use-bgr-color-format/

> The reason the early developers at OpenCV chose BGR color format is that back then BGR color format was popular among camera manufacturers and software providers. E.g. in Windows, when specifying color value using COLORREF they use the BGR format 0x00bbggrr.

(訳) OpenCV 初期の開発者が BGR カラー形式を選択した理由は当時、カメラメーカーやソフトウェア供給者の間で一般的だった為です。例えば、Windowsでは COLORREF で色の値を指定する時、BGR 形式の 0x00bbggrr を用います。

# BGR ではじまった理由 (推測)

いくつか要因を並べてみます。いずれも決定的な証拠がなく推測の域を出ません。

## CPU byte order

byte が逆順といえば、PC で主に使われていた Intel CPU がリトルエンディアンである事をまず連想するでしょう。なお、IPL (Intel Image Processing Library) の頃から BGR がデフォルトで、その開発元は Intel です。

先ほど例に出された Windows の COLORREF は以下の形式です。

- COLORREF
  - https://learn.microsoft.com/ja-jp/windows/win32/gdi/colorref

```c++:
// Color constants.
const COLORREF rgbRed   =  0x000000FF;
const COLORREF rgbGreen =  0x0000FF00;
const COLORREF rgbBlue  =  0x00FF0000;
const COLORREF rgbBlack =  0x00000000;
const COLORREF rgbWhite =  0x00FFFFFF;
```

ただ、これはリトルエンディアンの CPU だと下位ビットがバイナリ的に先頭になります。

```c:
#include <stdio.h>
int main(void) {
  long l = 0x12345678;
  unsigned char *c = (unsigned char *) &l;
  printf("%x %x %x %x\n", c[0], c[1], c[2], c[3]);
  return 0;
}
// 結果: 78 56 34 12  (Intel CPU での実行結果)
```

もう一つ分かり易い例を。

```c:
#include <stdio.h>
int main(void) {
  long l = ('B' << 16) + ('G' << 8) + 'R';  // 上位ビットから BGR の順
  char *c = (char *) &l;
  printf("%c %c %c\n", c[0], c[1], c[2]);
  return 0;
}
// 結果: R G B (Intel CPU での実行結果)
```

つまり、Windows の COLORREF はリトルエンディアンだとバイナリで RGB の順を維持するように働きます。これだと逆になっていません。

## DIB/BMP RGBTRIPlE/RGBQUAD

Windows API で例に出すなら、COLORREF よりも、こちらが適切でしょう。

- RGBTRIPLE 構造体-  RGBQUAD 構造体
  - https://learn.microsoft.com/ja-jp/windows/win32/api/wingdi/ns-wingdi-rgbtriple
  - https://learn.microsoft.com/ja-jp/windows/win32/api/wingdi/ns-wingdi-rgbquad

```c:
typedef struct tagRGBTRIPLE {
  BYTE rgbtBlue;
  BYTE rgbtGreen;
  BYTE rgbtRed;
} RGBTRIPLE, *PRGBTRIPLE, *NPRGBTRIPLE, *LPRGBTRIPLE;

typedef struct tagRGBQUAD {
  BYTE rgbBlue;
  BYTE rgbGreen;
  BYTE rgbRed;
  BYTE rgbReserved;
} RGBQUAD;
```

こちらは DIB と呼ばれる Windows が内部で画像の RGB データを保持する際の RGB の並びそのもので、見ての通り BGR 順です。その外部ファイル出力として BMP 形式でお馴染みかもしれません。
これに合わせたろう事は想像できますが、何故 RGB でなくその逆順の BGR なのかの説明にはなりません。単に逆で定義されているという事実だけです。

## VGA pixel order

当時の VGA アダプタに BGR 順のものがあって、それに合わせたとの説があります。
これには、実例として挙がる IBM VGA アダプタ RAMDAC が使われていた時期より、OpenCV の開発はだいぶ後の方で、根拠として怪しいとの指摘もあります。

- Why does OpenCV interpret image data using BGR instead of RGB?
   - https://www.quora.com/Why-does-OpenCV-interpret-image-data-using-BGR-instead-of-RGB

実は、DIB のデータ構造は OS/2 の頃まで遡れるので、丁度話がつながりまして、むしろこれが「ローマ帝国時代の馬のお尻の幅」なのかもしれません。

- DIB 情報ヘッダ 〜 OS/2 の情報ヘッダ
  - http://wisdom.sakura.ne.jp/system/winapi/win32/win115.html

何故、昔の VGA RAMDAC が BGR だったのかは、こちらに興味深い説明を見つけました。

- https://retrocomputing.stackexchange.com/questions/3023/why-bgr-color-order
> The original IBM VGA implementation made use of the INMOS 171/176 RAMDAC chip, which is used as the Color-Lookup Table (CLUT) for VGA color mode displaying up to 256 colors from an 18-bit color palette. This RAMDAC had a peculiar way of reading and writing color palette entries using its 8-bit wide data bus. Three bus cycles are required to read or write the 18-bit color palette entry one byte at a time. During each access, an internal address register value is incremented so that the cycles read/write Red, Green, then Blue in that order. The internal register resets after blue.

はじめの IBM VGA が INMOS 171/176 RAMDAC で、 これに BGR を渡すと RGB にひっくり返してディスプレイ表示するそうです。(24bit でなく) 18bit カラーを 8bitバス転送する際の都合らしいのですが、詳しい仕組みは以下のデータシートの 6ページ目にそれらしき記述があるので、興味のある方はどうぞ。

- CMOS Monolithic 256318 Color Palette RAM-DAC
  - https://www.analog.com/media/en/technical-documentation/obsolete-data-sheets/35026349641821151ADV476.pdf

全ページで覆い被さる OBSOLUTE の大きな斜めの文字は、Adobe Acrobat の編集機能で簡単に消せるので、読む前に処理するのをお勧めします。

RGB 表示したいのを H/W 都合で逆順の BGR でメモリ転送している理屈は納得しやすいでしょう。

# BGR であり続ける理由 (推測)

BGR で始まったとして、途中から RGB に変えれば良いでしょ。といった意見もあるでしょう。
まぁ影響が大き過ぎます。デグレード怖いし、なにより外部プログラムとの連携もそのままだと壊れる。相当のメリットが見込めない限りやりませんよね。

## RGB 提案 issue (2022/12/30 追記)

@dandelion1124 先生から、native RGB を提案する issue の紹介がありましたので、追記します。いつもありがとうございます！

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">IPLのドキュメント（web上にアーカイブ的な意味合いで残している人がいますが流布していいのか不明）ではDIBとの互換性についてかなり強調して書かれているので意識してそうではあります。あと、デフォルトをRGBにしようとチケット立てた人の末路はここで読めます・・・。</p>  <a href="https://twitter.com/dandelion1124/status/1608662089148821505">正午12:11 2022年12月30日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

- [Proposal] imread and imdecode RGB data #16665
  - https://github.com/opencv/opencv/issues/16665

- 提案
> For main DNN frameworks it need to read RGB (not BGR!) data from jpeg files. But now OpenCV can decode images only in BGR colour space.
> (...)
> Now for fastest jpeg decoding cv::imread is the worst choise.

(意訳) DNN フレームワークに渡す画像は RGB の並びなのに cv::imread で JPEG を読むと BGR の並びなの、最悪じゃない？

- 回答
> Anything that breaks compatibility unlikely would be accepted without a strong reason (critical bug, but not "optimization").

(訳) 致命的な不具合とかの強い理由がない限り互換性を壊すべきではありません。

> Like item (5) shows, DNN requires images in planar format. So there is no significant difference between interleaved BGR or RGB image formats to reach the target (OpenCV's blobFromImage() handles that).
> Native color format for JPEG is neither RGB or BGR. It is YCbCr4:2:0.

(だいぶ意訳) DNN は RGB packed (RGBRGB...)でなくplanar(RR...GG..BB...) です。なので BGR packed (BGRBGR...) からの変換コストと対して変わりません。あと JPEG の native 形式は RGB でも BGR でもなく YCbCr4:2:0 です。

正論ですね。提案する理由の筋が悪いです。
仮に BGR2RGB が必要で頻繁にありそうなユースケースを提示出来れば、また違った流れになっていたかも知れません。

# (追記 2023/116) Bottom-Up DIB

ツイッターで指摘を頂いたので追記します。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">BGR が DIB 由来なら、ピクセルの格納順は下から上にしなくてよかったのかな…？ <a href="https://t.co/N7nDPxJaKJ">https://t.co/N7nDPxJaKJ</a></p>&mdash; ねぎとろ🐾 (@D05E1) <a href="https://twitter.com/D05E1/status/1719999800060617106?ref_src=twsrc%5Etfw">November 2, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

OS/2 時代はビットマップ情報が Bottom-Up (画像の横ラインが下から上に並ぶ)だった為、その情報構造を引き継いだ Windows も Bottom-Up がデフォルトです。
ただ、Windows では height に負の値を入れる事で、Top-Down(画像の横ラインが上から下に並ぶ) にも対応しています。Top-Down DIB と呼ばれます。

- https://learn.microsoft.com/ja-jp/windows/win32/directshow/top-down-vs--bottom-up-dibs

BMP ファイル形式にもこの仕様が影響するので、お馴染みの方も多いかもしれません。
ちなみに、DirectShow は Top-Down のみ対応です、
そういえば、IPL がリリースされた 1997 年は、ActiveMovie が DirectShow と改称されたのと同じ年ですね。

:::note info
初期 IPL マニュアルの Revision History にて "First release. 07/97" とあるのを確認しました。
:::

BGR を RGB にひっくり返すのに比べれば、データ転送を上から見て横ライン順方向(Top-Down)で処理するのと逆方向(Bottom-Up)で行うので、メモリブロックを意識して処理すればパフォーマンスの差は少なそうですし、当時の状況として、DIB のボトムアップよりトップダウン仕様に合わせるのは自然だと思われます。

# 備考

IPL の開発初期のコードを見れば分かるかもしれませんが、Intel が BSD ライセンスで公開した頃にはコードは整理され起源を辿れるような(試行錯誤などの)痕跡は消えているでしょう。
確実な根拠が欲しければ結局のところ当時の開発者に当たるしかない。といった身も蓋も無い結論になりそうです。

# 参考

- OpenCVのネイティブピクセルフォーマットがBGRな理由
  - https://qiita.com/yohhoy/items/d1a29c8f15be2d56cc52
- Why does OpenCV use BGR color format ? | LearnOpenCV #
  - https://learnopencv.com/why-does-opencv-use-bgr-color-format/
- Why does OpenCV interpret image data using BGR instead of RGB?
  - https://www.quora.com/Why-does-OpenCV-interpret-image-data-using-BGR-instead-of-RGB
- CMOS Monolithic 256318 Color Palette RAM-DAC
  - https://www.analog.com/media/en/technical-documentation/obsolete-data-sheets/35026349641821151ADV476.pdf
- [Proposal] imread and imdecode RGB data #16665
  - https://github.com/opencv/opencv/issues/16665
- DIB 情報ヘッダ
  - http://wisdom.sakura.ne.jp/system/winapi/win32/win115.html