今日のコネタ: perf::matType, perf::matDepth

TL;DR
=====
- OpenCVでmat typeに関するテストを書くときは、perf::matType, matDepthが便利だよ、というご紹介。

■ はじめに
==========
誰得、私得の落穂ひろいネタです。

OpenCVの鬼門（？）の一つが、Mat Typeではないでしょうかね？

- CV_8U / CV_8S / CV_16U / CV_16S / CV_32S / CV_32F / CV_64F / CV_16F (OpenCV4まで)
- CV_16BF / CV_Bool / CV_64U / CV_64S / CV_32U (OpenCV5から)

例えば新機能などを作ったら、色々なMatDepthでテストもしたい。
場合によっては、チャンネル数も加えたMatTypeが欲しくなる。

そうなるとテストを書くときにどうするか非常に悩ましい。ここを掘り下げていく。

■ ダメな例 tiff
===============
まず、ダメな例を見ていただこう。 

> https://github.com/opencv/opencv/blob/4.8.1/modules/imgcodecs/test/test_tiff.cpp

なんと汚らわしい！なんと汚らしい ！

誰なんだ…？ この汚いコードをcommitしたのは…？ 誰だ…？誰だ…？誰…？ あ… オレだった…！(*ﾉωﾉ)

https://github.com/opencv/opencv/pull/23433

```
// Re-define Mat type as enum for showing on Google Test.
enum CV_ddtCn{
  _CV_8UC1  = CV_8UC1,  _CV_8UC3  = CV_8UC3,  _CV_8UC4  = CV_8UC4,
  _CV_8SC1  = CV_8SC1,  _CV_8SC3  = CV_8SC3,  _CV_8SC4  = CV_8SC4,
  _CV_16UC1 = CV_16UC1, _CV_16UC3 = CV_16UC3, _CV_16UC4 = CV_16UC4,
  _CV_16SC1 = CV_16SC1, _CV_16SC3 = CV_16SC3, _CV_16SC4 = CV_16SC4,
  _CV_32SC1 = CV_32SC1, _CV_32SC3 = CV_32SC3, _CV_32SC4 = CV_32SC4,
  _CV_16FC1 = CV_16FC1, _CV_16FC3 = CV_16FC3, _CV_16FC4 = CV_16FC4,
  _CV_32FC1 = CV_32FC1, _CV_32FC3 = CV_32FC3, _CV_32FC4 = CV_32FC4,
  _CV_64FC1 = CV_64FC1, _CV_64FC3 = CV_64FC3, _CV_64FC4 = CV_64FC4,
};

static inline
void PrintTo(const CV_ddtCn& val, std::ostream* os)
{
    const int val_type = static_cast<int>(val);

    switch ( CV_MAT_DEPTH(val_type) )
    {
    case CV_8U  : *os << "CV_8U" ; break;
    case CV_16U : *os << "CV_16U" ; break;
    case CV_8S  : *os << "CV_8S" ; break;
    case CV_16S : *os << "CV_16S" ; break;
    case CV_32S : *os << "CV_32S" ; break;
    case CV_16F : *os << "CV_16F" ; break;
    case CV_32F : *os << "CV_32F" ; break;
    case CV_64F : *os << "CV_64F" ; break;
    default     : *os << "CV_???" ; break;
    }
    *os << "C" << CV_MAT_CN(val_type);
}
```

■ perf::MatDepthのご紹介
============================

人は過ちをする生き物なのです。そこからどう立ち直るのかが重要なのです（？）。
それでは、半年後の熊太郎さんのテストコードを見てみよう

```
INSTANTIATE_TEST_CASE_P(/* */, Core_Arith_Regression24163,
    testing::Combine(
        testing::Values(perf::MatDepth(CV_8U), CV_8S, CV_16U, CV_16S, CV_32S), // MatType
        testing::Values( 3, 4, 5, 6),    // MatHeight
        testing::Values(-2,-1, 0, 1, 2), // src1
        testing::Values(   -1, 0, 1   )  // src2
    )
);
```

見慣れない `perf::MatDepth()` が使われている。これによって、テストを実行したときに、CV_8Uなどが表示されるようになっている！圧倒的大勝利！

じゃあ、これはどこに定義されているのか？というと、tsモジュールになる。

opencv/modules/ts/include/opencv2/ts/ts_perf.hpp

```cpp
/*****************************************************************************************\
*                MatType - printable wrapper over integer 'type' of Mat                   *
\*****************************************************************************************/
class MatType
{
public:
    MatType(int val=0) : _type(val) {}
    operator int() const {return _type;}


private:
    int _type;
};

/*****************************************************************************************\
*     CV_ENUM and CV_FLAGS - macro to create printable wrappers for defines and enums     *
\*****************************************************************************************/


#define CV_ENUM(class_name, ...)                                                        \
    namespace {                                                                         \
    using namespace cv;using namespace cv::cuda; using namespace cv::ocl;               \
    struct class_name {                                                                 \
        class_name(int val = 0) : val_(val) {}                                          \
        operator int() const { return val_; }                                           \
        void PrintTo(std::ostream* os) const {                                          \
            const int vals[] = { __VA_ARGS__ };                                         \
            const char* svals = #__VA_ARGS__;                                           \
            for(int i = 0, pos = 0; i < (int)(sizeof(vals)/sizeof(int)); ++i) {         \
                while(isspace(svals[pos]) || svals[pos] == ',') ++pos;                  \
                int start = pos;                                                        \
                while(!(isspace(svals[pos]) || svals[pos] == ',' || svals[pos] == 0))   \
                    ++pos;                                                              \
                if (val_ == vals[i]) {                                                  \
                    *os << std::string(svals + start, svals + pos);                     \
                    return;                                                             \
                }                                                                       \
            }                                                                           \
            *os << "UNKNOWN";                                                           \
        }                                                                               \
        static ::testing::internal::ParamGenerator<class_name> all() {                  \
            const class_name vals[] = { __VA_ARGS__ };                                  \
            return ::testing::ValuesIn(vals);                                           \
        }                                                                               \
    private: int val_;                                                                  \
    };                                                                                  \
    static inline void PrintTo(const class_name& t, std::ostream* os) { t.PrintTo(os); } }

CV_ENUM(MatDepth, CV_8U, CV_8S, CV_16U, CV_16S, CV_32S, CV_32F, CV_64F, CV_16F)
```

なかなか面白いコードになっていて、 "CV_8U, CV_8S...”という指定を_VA_ARGS_から配列に移し入れ、指定されたら該当する部分を検索して出力するとなっている。

これを使えば、google testのValues()指定なども活用できます、やったー！！！

まとめ
=====
- OpenCVでmat typeに関するテストを書くときは、perf::matType, matDepthが便利だよ、というご紹介。