この記事はOpenCV Advent Calendar 2024の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元年になりそうです!
(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に増えています。
- #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とで異なる値になるということですね!
#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先生が深堀して下さっております。
浮動小数表現において、float/doubleと同じ指数部表現にして、仮数部をその分犠牲にした型になります。例えば、float <--> bfloat16の変換もfp16に比べれば早いですね!
一般名称 | 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内の変換コードあたりが比較しやすいですかね?なかのロジックを見てもビットシフトなんかもない分、かなり簡略化できています。
CV_16F(hfloat)のための実装107行
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
}
CV_16BF(bfloat)のための実装26行
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;
};
〇CV_Bool
真偽情報をそのまま入れる事のできる型ですね。Tensor関係で使っている、らしいでございまする。
〇CV_32U / CV_64U / CV_64S
uint32_t
、uint64_t
とint64_t
型でございます。
OpenCVは、大きな整数というとint32_t
だけだったんですよね…。
■ imwrite()/imencode()でCV_8Uに丸められるかも!
さて、ここでTIPSを1つ。ここは覚えていって欲しいポイントでございます。
imwrite()等の画像書き出し機能では、Encoderがサポートしていない色深度の画像は、CV_8U
に強制変換されます。一応、Warningでるように追加パッチは出しましたけどね…。
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追記)
#define CV_CN_MAX 128
#define CV_CN_SHIFT 5
#define CV_DEPTH_MAX (1 << CV_CN_SHIFT)
static let CV_CN_MAX = 512
static let CV_CN_SHIFT = 3
static let CV_DEPTH_MAX = 1 << CV_CN_SHIFT
#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で使える画像圧縮フォーマットの比較」でございます!よろしくお願いいたします!