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したのは…? 誰だ…?誰だ…?誰…? あ… オレだった…!(*ノωノ)
// 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
/*****************************************************************************************\
* 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が便利だよ、というご紹介。