IMREAD_COLOR_RGBの性能検証

この記事は[OpenCV Advent Calendar 2024](https://qiita.com/advent-calendar/2024/opencv)の11日目の記事です．
他の記事は目次にまとめられています．

TL;DR
======
- RGB orderで画像を読み込みたい場合は、`IMREAD_COLOR_RGB`を指定しましょう
- JPEG/PNGの場合、BGR orderでもRGB orderでも性能差はほとんどありません
- `cvtColor(cv::COLOR_BGR2RGB)`は遅いので、ぜひフラグで指定しましょう！

はじめに
=======
OpenCVのMat(e.g. CV_8UC3)はBGR Orderが前提。imgcodecsでカラー画像を読み込むときは、codecs(libjpeg, libtiff)が出力した **RGB Order** を **BGR order** にわざわざ並び替えておりました。しかも最近、DNNモジュールでモデルに適用するべく **RGB order** へ更に並び替えるケースもあり、なかなか問題となっておりました。

そこで！ OpenCV 4.11/5.0から、**画像をRGB orderで直接読み込むフラグ IMREAD_COLOR_RGB** がございますよ！というご紹介です。

https://docs.opencv.org/4.x/d8/d6a/group__imgcodecs__flags.html#gga61d9b0126a3e57d9277ac48327799c80a18afb429fb71972a327314b2f0d8d56a

| Enumerator |  |
|:-|:-|
| IMREAD_COLOR_BGR <br/>Python: cv.IMREAD_COLOR_BGR | If set, always convert image to the 3 channel BGR color image. |
| IMREAD_COLOR <br />Python: cv.IMREAD_COLOR | Same as IMREAD_COLOR_BGR. |
| IMREAD_COLOR_RGB <br/>Python: cv.IMREAD_COLOR_RGB | If set, always convert image to the 3 channel RGB color image. |

性能検証
========

測定環境・ライブラリ
------------------
built-inのlibjpeg-turboとlibpngを、SIMD有効にして用いる。

```
--   Media I/O:
--     ZLib:                        /usr/lib/x86_64-linux-gnu/libz.so (ver 1.3)
--     JPEG:                        build-libjpeg-turbo (ver 3.0.3-70)
--       SIMD Support Request:      YES
--       SIMD Support:              YES
--     WEBP:                        /usr/lib/x86_64-linux-gnu/libwebp.so (ver encoder: 0x020f)
--     AVIF:                        /usr/lib/x86_64-linux-gnu/libavif.so.16.0.4 (ver 1.0.4)
--     PNG:                         build (ver 1.6.43)
--       SIMD Support Request:      YES
--       SIMD Support:              YES (Intel SSE)
--     TIFF:                        /usr/lib/x86_64-linux-gnu/libtiff.so (ver 42 / 4.5.1)
--     JPEG 2000:                   OpenJPEG (ver 2.5.0)
--     OpenEXR:                     build (ver 2.3.0)
--     HDR:                         YES
--     SUNRASTER:                   YES
--     PXM:                         YES
--     PFM:                         YES
```

なお、i7-4790Sくんで測定です！今年のアドカレが最後だなあ・・・

```
kmtr@kmtr-VMware-Virtual-Platform:~/work/build4-main$ cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 60
model name      : Intel(R) Core(TM) i7-4790S CPU @ 3.20GHz
```

測定結果
=======
modules/imgcodecs/perf以下にパフォーマンス測定ツールが入っているので、これをちょっと改造して測定する。

<details><summary>コード差分</summary>

```diff_cpp
kmtr@kmtr-VMware-Virtual-Platform:~/work/opencv4$ git --no-pager diff
diff --git a/modules/imgcodecs/perf/perf_jpeg.cpp b/modules/imgcodecs/perf/perf_jpeg.cpp
index 16610a3044..861ea4533e 100644
--- a/modules/imgcodecs/perf/perf_jpeg.cpp
+++ b/modules/imgcodecs/perf/perf_jpeg.cpp
@@ -44,6 +44,24 @@ PERF_TEST(JPEG, Decode_rgb)
     SANITY_CHECK_NOTHING();
 }

+PERF_TEST(JPEG, Decode_bgr_and_reorder)
+{
+    String filename = getDataPath("stitching/boat1.jpg");
+
+    FILE *f = fopen(filename.c_str(), "rb");
+    fseek(f, 0, SEEK_END);
+    long len = ftell(f);
+    fseek(f, 0, SEEK_SET);
+    vector<uchar> file_buf((size_t)len);
+    EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f));
+    fclose(f); f = NULL;
+
+    TEST_CYCLE() { cv::Mat img = imdecode(file_buf, IMREAD_COLOR_BGR); cv::cvtColor(img,img, cv::COLOR_BGR2RGB ); }
+
+    SANITY_CHECK_NOTHING();
+}
+
+
 PERF_TEST(JPEG, Encode)
 {
     String filename = getDataPath("stitching/boat1.jpg");
@@ -57,4 +75,4 @@ PERF_TEST(JPEG, Encode)

 #endif // HAVE_JPEG

-} // namespace
\ No newline at end of file
+} // namespace
diff --git a/modules/imgcodecs/perf/perf_png.cpp b/modules/imgcodecs/perf/perf_png.cpp
index 5bbef590bd..52dabda5af 100644
--- a/modules/imgcodecs/perf/perf_png.cpp
+++ b/modules/imgcodecs/perf/perf_png.cpp
@@ -47,6 +47,23 @@ PERF_TEST(PNG, decode_rgb)
     SANITY_CHECK_NOTHING();
 }

+PERF_TEST(PNG, decode_bgr_and_reorder)
+{
+    String filename = getDataPath("perf/2560x1600.png");
+
+    FILE *f = fopen(filename.c_str(), "rb");
+    fseek(f, 0, SEEK_END);
+    long len = ftell(f);
+    fseek(f, 0, SEEK_SET);
+    vector<uchar> file_buf((size_t)len);
+    EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f));
+    fclose(f); f = NULL;
+
+    TEST_CYCLE() { cv::Mat img = imdecode(file_buf, IMREAD_COLOR_BGR); cv::cvtColor(img,img, cv::COLOR_BGR2RGB ); }
+
+    SANITY_CHECK_NOTHING();
+}
+
 PERF_TEST(PNG, encode)
 {
     String filename = getDataPath("perf/2560x1600.png");
```
</details>

     
測定結果は、こちら。（１）BGR orderでもRGB orderでも性能差はほぼない、（２）BGR orderで読み込んだ後に`cvtColor`でRGB orderに並び替えると、明らかに性能が低下している。

| Format | BGR | RGB | BGR+reorder |
|:-:|-:|-:|-:|
| JPG  | 34.30 | 34.40 | 99.77 |
| PNG  | 26.76 | 24.49  | 51.77|

<details><summary>ログ</summary>

```
kmtr@kmtr-VMware-Virtual-Platform:~/work/build4-main$ ./bin/opencv_perf_imgcodecs --perf_min_samples=100
[ERROR:0@0.001] global persistence.cpp:566 open Can't open file: '/home/kmtr/work/opencv_extra4/testdata/perf/imgcodecs.xml' in read mode
TEST: Skip tests with tags: 'mem_6gb', 'verylong'
CTEST_FULL_OUTPUT
OpenCV version: 4.10.0-dev
OpenCV VCS version: 4.10.0-431-g7fbf3c1fec
Build type: Release
Compiler: /usr/bin/c++  (ver 13.2.0)
Algorithm hint: ALGO_HINT_ACCURATE
HAL: NO
Parallel framework: pthreads (nthreads=8)
CPU features: SSE SSE2 SSE3 *SSE4.1 *SSE4.2 *AVX *FP16 *AVX2 *AVX512-SKX?
Intel(R) IPP version: ippIP AVX2 (l9) 2021.12.0 (-) Apr 25 2024
Intel(R) IPP features code: 0x8000
OpenCL is disabled
[==========] Running 8 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 4 tests from JPEG
[ RUN      ] JPEG.Decode
[ PERFSTAT ]    (samples=100   mean=34.30   median=34.34   min=33.29   stddev=0.56 (1.6%))
[       OK ] JPEG.Decode (3550 ms)
[ RUN      ] JPEG.Decode_rgb
[ PERFSTAT ]    (samples=100   mean=34.40   median=34.40   min=33.86   stddev=0.27 (0.8%))
[       OK ] JPEG.Decode_rgb (3480 ms)
[ RUN      ] JPEG.Decode_bgr_and_reorder
.
[ PERFSTAT ]    (samples=100   mean=99.77   median=99.70   min=98.11   stddev=0.93 (0.9%))
[       OK ] JPEG.Decode_bgr_and_reorder (10007 ms)
[ RUN      ] JPEG.Encode
[ PERFSTAT ]    (samples=100   mean=30.59   median=30.68   min=29.69   stddev=0.41 (1.3%))
[       OK ] JPEG.Encode (3130 ms)
[----------] 4 tests from JPEG (20167 ms total)

[----------] 4 tests from PNG
[ RUN      ] PNG.decode
[ PERFSTAT ]    (samples=100   mean=26.76   median=26.77   min=25.98   stddev=0.25 (0.9%))
[       OK ] PNG.decode (2682 ms)
[ RUN      ] PNG.decode_rgb
[ PERFSTAT ]    (samples=100   mean=24.49   median=24.49   min=23.89   stddev=0.20 (0.8%))
[       OK ] PNG.decode_rgb (2455 ms)
[ RUN      ] PNG.decode_bgr_and_reorder
[ PERFSTAT ]    (samples=100   mean=31.69   median=31.67   min=31.04   stddev=0.26 (0.8%))
[       OK ] PNG.decode_bgr_and_reorder (3178 ms)
[ RUN      ] PNG.encode
[ PERFSTAT ]    (samples=100   mean=51.77   median=51.79   min=51.02   stddev=0.43 (0.8%))
[       OK ] PNG.encode (5218 ms)
[----------] 4 tests from PNG (13533 ms total)

[----------] Global test environment tear-down
[==========] 8 tests from 2 test cases ran. (33700 ms total)
[  PASSED  ] 8 tests.
kmtr@kmtr-VMware-Virtual-Platform:~/work/build4-main$

```

</details>

# ちょっとだけコード確認
JPEGの場合、jpegturboには、元々BGR orderで画像を読み込む拡張機能がございます。

なのでBGR -> RGBでの性能差は出にくいです。

```cpp
#ifdef JCS_EXTENSIONS
                    cinfo->out_color_space = m_use_rgb ? JCS_EXT_RGB : JCS_EXT_BGR;
                    cinfo->out_color_components = 3;
                    doDirectRead = true; // BGR -> BGR
#else
                    cinfo->out_color_space = JCS_RGB;
                    cinfo->out_color_components = 3;
                    doDirectRead = m_use_rgb ? true : false; // RGB -> BGR
#endif
```

一方、PNGの場合、libpngに元々はBGR decodeをするように指定をしていました。このフラグで、出力データが並び替えされるようになっていた、ということになります。

```cpp
            if( (m_color_type & PNG_COLOR_MASK_COLOR) && color && !m_use_rgb)
                png_set_bgr( png_ptr ); // convert RGB to BGR
            else if( color )
                png_set_gray_to_rgb( png_ptr ); // Gray->RGB
            else
                png_set_rgb_to_gray( png_ptr, 1, 0.299, 0.587 ); // RGB->Gray
```

# まとめ
OpenCVのimgcodecsで、画像をRGB order順に読み込みたい人は、`cvtColor(cv::COLOR_BGR2RGB)`を投げ捨て、`IMREAD_COLOR_RGB`が使えるOpenCV 4.11/5.0 を使いましょう！！


# 補足(2024/12/11)
dandelion先生がご指摘いただきました。

https://x.com/dandelion1124/status/1866796656458600549

もうちょっと詳しく書くと、こんな感じで「OpenCVのMatに含まれるデータは、RGB並びにしよう！」という提案は幾度となく繰り返されております。

https://github.com/opencv/opencv/issues/23906


https://github.com/opencv/opencv/issues/24103

ところが、既存モジュールとの互換性などなどの問題で色々トラブルになるため、実現はしていないと・・・。

個人的な意見で申し上げますと。

- 内部データは基本的にBGR orderのまま
- a) IMREAD_COLOR_RGBで画像データを読み込んだ場合、 b) Mat.setOrder(COLOR_RGB)が呼ばれたら、MatにRGB Orderフラグを立てる。チャネルごとに入れ替えるとか、そういうのは気にしない！ 
- 当該RGB orderフラグが立っている状態で、BGR Order前提の関数が呼び出されたら、Order不整合のログを表示する

ぐらいでいいんじゃないのかしら・・・と思いまする。RGB Orderで入力されたら、自動的にテンポラリなMatにBGR Orderに並び替えて処理し、必要があればRGB Orderに戻してユーザアプリに返却するだなんて、現実的ではございませんので…

以上となります、ご精読ありがとうございました！