[OpenCV 5予習] 新Mat Depthを確認しよう

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

■ ご挨拶
=======
OpenCV Advent Calendarに興味を持って頂き、ありがとうございます！何かの気づき、参考になりましたら幸いに存じます。

また、OpenCV Advent Calendar寄稿者の方も、お忙しい中、ご参加いただき誠にありがとうございます！今後とも何卒よろしくお願いいたします。

■ TL;DR
========
- OpenCV5では、`CV_16BF`(bFloat16)、`CV_Bool`、`CV_32U`、`CV_64U`そして`CV_64S`が拡張
- `imwrite()`するとき非サポート型データは、`CV_8U`に丸められるので注意
- objc bindingがまだできてないかも！

■ 来年2025年は、OpenCV 5元年になりそうです！
=========================================

https://github.com/opencv/opencv/milestones

(11/24) こちらのマイルストーンを見ると、5.0にalphaリリース計画が！日程はございませんが、そろそろぼちぼちリリースされそうですね！（と言い続けて早何年だろうか）

- 4.11.0
- 5.0-alpha
- 5.0-release

■ Mat DepthサポートがOpenCV5から拡張されます！
===========================================
OpenCV 5では、Mat Depthが拡張されます！これに伴い、最大チャネル数も若干減ります。このあたりを軽く掘り下げていきます。

〇 Mat depthの変更
------------------
ヘッダの場所はこちらでございます。 modules/core/include/opencv2/core/hal/interface.h

https://github.com/opencv/opencv/blob/979428d5908b5396237b18cbd50d0b18265e2463/modules/core/include/opencv2/core/hal/interface.h#L72-L89
https://github.com/opencv/opencv/blob/3fddea2ade81d772fc269efe15dabad4c372e4d3/modules/core/include/opencv2/core/hal/interface.h#L69-L80

`CV_CN_MAX`(最大チャネル数)が、512から128に減っております。また、`CV_CN_SHIFT`が3から5に増えて、DEPTHへの割り当てが5bitに増えています。

```diff_cpp
- #define CV_CN_MAX     512
+ #define CV_CN_MAX     128
- #define CV_CN_SHIFT   3
+ #define CV_CN_SHIFT   5
  #define CV_DEPTH_MAX  (1 << CV_CN_SHIFT)

  #define CV_8U   0
  #define CV_8S   1
  #define CV_16U  2
  #define CV_16S  3
  #define CV_32S  4
  #define CV_32F  5
  #define CV_64F  6
  #define CV_16F  7
+ #define CV_16BF 8
+ #define CV_Bool 9
+ #define CV_64U  10
+ #define CV_64S  11
+ #define CV_32U  12
+ #define CV_DEPTH_CURR_MAX 13
```

つまり、cv::Mat::type()で`CV_8UC1`などを取得したときに、OpenCV 4と5とで異なる値になるということですね！

```cpp
#define CV_MAT_DEPTH_MASK       (CV_DEPTH_MAX - 1)
#define CV_MAT_DEPTH(flags)     ((flags) & CV_MAT_DEPTH_MASK)

#define CV_MAKETYPE(depth,cn) (CV_MAT_DEPTH(depth) + (((cn)-1) << CV_CN_SHIFT))
#define CV_MAKE_TYPE CV_MAKETYPE
```

〇CV_16BF
---------
DNN moduleなどで扱うために、bfloat16型サポートが拡張されました！

このあたりは、昨年、@tomoaki_teshima先生、@fukushima1981先生が深堀して下さっております。

https://qiita.com/tomoaki_teshima/items/679fec72aa3cb8357e1c

https://qiita.com/fukushima1981/items/c7c3ba4f8c7c39db888e

浮動小数表現において、float/doubleと同じ指数部表現にして、仮数部をその分犠牲にした型になります。例えば、float <--> bfloat16の変換もfp16に比べれば早いですね！

https://ja.wikipedia.org/wiki/Bf16

| 一般名称  | OpenCV   | 符号 | 指数部 | 仮数部 |
|:--------:|:--------:|:-----:|:-----:|:-----:|
| fp16     | CV_16F   | 1 bit | 5 bit | 10 bit |
| bfloat16 | CV_16BF  | 1 bit | 8 bit | 7 bit |
| float    | CV_32F   | 1 bit | 8 bit | 23 bit |
| double   | CV_64F   | 1 bit | 8 bit | 52 bit |

どれくらいbfloat16型が扱いやすいかというと、cvdev.h内の変換コードあたりが比較しやすいですかね？なかのロジックを見てもビットシフトなんかもない分、かなり簡略化できています。

<details><summary>CV_16F(hfloat)のための実装107行</summary>

```cpp
class hfloat
{
public:
#if CV_FP16_TYPE
    hfloat() = default;
    operator float() const { return (float)h; }
#if defined __ARM_FP16_FORMAT_IEEE
    explicit hfloat(float x) { h = (__fp16)x; }
protected:
    __fp16 h;
#else
    explicit hfloat(float x) { h = (_Float16)x; }
    explicit operator _Float16() const { return h; }
protected:
    _Float16 h;
#endif


#else
    hfloat() : w(0) {}
    explicit hfloat(float x)
    {
    #if CV_FP16 && CV_AVX2
        __m128 v = _mm_load_ss(&x);
        w = (ushort)_mm_cvtsi128_si32(_mm_cvtps_ph(v, 0));
    #else
        Cv32suf in;
        in.f = x;
        unsigned sign = in.u & 0x80000000;
        in.u ^= sign;


        if( in.u >= 0x47800000 )
            w = (ushort)(in.u > 0x7f800000 ? 0x7e00 : 0x7c00);
        else
        {
            if (in.u < 0x38800000)
            {
                in.f += 0.5f;
                w = (ushort)(in.u - 0x3f000000);
            }
            else
            {
                unsigned t = in.u + 0xc8000fff;
                w = (ushort)((t + ((in.u >> 13) & 1)) >> 13);
            }
        }


        w = (ushort)(w | (sign >> 16));
    #endif
    }


    operator float() const
    {
    #if CV_FP16 && CV_AVX2
        float f;
        _mm_store_ss(&f, _mm_cvtph_ps(_mm_cvtsi32_si128(w)));
        return f;
    #else
        Cv32suf out;


        unsigned t = ((w & 0x7fff) << 13) + 0x38000000;
        unsigned sign = (w & 0x8000) << 16;
        unsigned e = w & 0x7c00;


        out.u = t + (1 << 23);
        out.u = (e >= 0x7c00 ? t + 0x38000000 :
                 e == 0 ? (static_cast<void>(out.f -= 6.103515625e-05f), out.u) : t) | sign;
        return out.f;
    #endif
    }


protected:
    ushort w;


#endif
};


inline hfloat hfloatFromBits(ushort w) {
#if CV_FP16_TYPE
    Cv16suf u;
    u.u = w;
    hfloat res(float(u.h));
    return res;
#else
    Cv32suf out;


    unsigned t = ((w & 0x7fff) << 13) + 0x38000000;
    unsigned sign = (w & 0x8000) << 16;
    unsigned e = w & 0x7c00;


    out.u = t + (1 << 23);
    out.u = (e >= 0x7c00 ? t + 0x38000000 :
            e == 0 ? (static_cast<void>(out.f -= 6.103515625e-05f), out.u) : t) | sign;
    hfloat res(out.f);
    return res;
#endif
}
```

</details>


<details><summary>CV_16BF(bfloat)のための実装26行</summary>

```cpp
class bfloat
{
public:
    bfloat() : w(0) {}


    explicit bfloat(float x)
    {
        Cv32suf in;
        in.f = x;
        w = (ushort)((in.u + (((in.u & 0x7fffffff) <= 0x7f7f7fff) << 15)) >> 16);
    }


    operator float() const
    {
        Cv32suf out;
        out.u = w << 16;
        return out.f;
    }


protected:
    ushort w;
};
```

</details>


〇CV_Bool
---------
真偽情報をそのまま入れる事のできる型ですね。Tensor関係で使っている、らしいでございまする。

〇CV_32U / CV_64U / CV_64S
---------------------------
`uint32_t`、`uint64_t`と`int64_t`型でございます。
OpenCVは、大きな整数というと`int32_t`だけだったんですよね…。

■ imwrite()/imencode()でCV_8Uに丸められるかも！
==============================================
さて、ここでTIPSを１つ。ここは覚えていって欲しいポイントでございます。

imwrite()等の画像書き出し機能では、Encoderがサポートしていない色深度の画像は、`CV_8U`に強制変換されます。一応、Warningでるように追加パッチは出しましたけどね…。

https://github.com/opencv/opencv/blob/6e229bda2b7489a9b55aa442cc3ce95671cf9672/modules/imgcodecs/src/loadsave.cpp#L726-L733

```cpp
        Mat temp;
        if( !encoder->isFormatSupported(image.depth()) )
        {
            CV_LOG_ONCE_WARNING(NULL, "Unsupported depth image for selected encoder is fallbacked to CV_8U.");
            CV_Assert( encoder->isFormatSupported(CV_8U) );
            image.convertTo( temp, CV_8U );
            image = temp;
        }
```


~~まだ、TIFF Encoderなんかも新型をサポートしてないので、要注意でございます！
（contributeするチャンスですね！）~~

はい、https://github.com/opencv/opencv/pull/26508 で、TIFF Encoder/Decoderに、CV_32U, CV_64U, CV_64Sのサポートを追加させていただきましたー！(11/29追記）

# objc bindingがまだ非サポートかも？ (12/1追記)

記事公開直前に、objcで新depthサポートが足りてない事に気が付いてしまい、issueも発行しておきました。

~~https://github.com/opencv/opencv/issues/26549~~

実際にビルドして確認し、issue発行してくれる方がいらっしゃいました！！今後は、こちらでバグ管理になると思います！ありがとうございます！！（ということで、obj-c binding userの方はまだOpenCV5使えないです）(12/2追記)

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

```C++:modules/core/include/opencv2/core/hal/interface.h
#define CV_CN_MAX     128
#define CV_CN_SHIFT   5
#define CV_DEPTH_MAX  (1 << CV_CN_SHIFT)
```

```objc:modules/core/misc/objc/common/CvTypeExt.swift
    static let CV_CN_MAX = 512
    static let CV_CN_SHIFT = 3
    static let CV_DEPTH_MAX = 1 << CV_CN_SHIFT
```

```objc:modules/core/misc/objc/test/MatTestObjc.m
#define CV_CN_SHIFT 3
#define CV_DEPTH_MAX (1 << CV_CN_SHIFT)
#define CV_MAT_DEPTH_MASK (CV_DEPTH_MAX - 1)
#define CV_MAT_DEPTH(flags) ((flags) & CV_MAT_DEPTH_MASK)
#define CV_MAKETYPE(depth,cn) (CV_MAT_DEPTH(depth) + (((cn)-1) << CV_CN_SHIFT))
```



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

明日は、 @fukushima1981 先生の「OpenCVで使える画像圧縮フォーマットの比較」でございます！よろしくお願いいたします！