🧚

OpenCV: 空のMatのclone/copyToではtypeが不一致

2021/08/07に公開

環境

  • OpenCV 4.5.3

cv::Mat::clone

以下のような例を考えます。Mat::clone() の挙動をみています。

入力するMatについては以下のようにします。

  • 行数・列数ともに0
  • depthがCV_8U以外
  • channelsが2以上
#include <iostream>
#include <opencv2/opencv.hpp>

int main()
{
    cv::Mat src(0, 0, CV_32SC2);
    auto dst = src.clone();
        
    std::cout << "src: depth=" << src.depth() << ", channels=" << src.channels() << std::endl
        << "dst: depth=" << dst.depth() << ", channels=" << dst.channels() << std::endl;

    return 0;
}

結果は以下のようになり、depthが0 (=CV_8U), channelsが1になってしまいます。

src: depth=4, channels=2
dst: depth=0, channels=1

cv::Mat_<T>::clone

ちなみに、要素型がテンプレートな Mat_<T> の方だと問題ありません。

cv::Mat_<cv::Vec2i> src(0, 0);
auto dst = src.clone();
        
std::cout << "src: depth=" << src.depth() << ", channels=" << src.channels() << std::endl
    << "dst: depth=" << dst.depth() << ", channels=" << dst.channels() << std::endl;
src: depth=4, channels=2
dst: depth=4, channels=2

たぶん原因

さて、最初に挙げたテンプレートではない方の挙動を追ってみます。

Mat::clone() の中身は単に Mat::copyTo() をしているだけなので、copyToの中を確認します。
https://github.com/opencv/opencv/blob/ba539eb9aad7571361657ea7e00a7c3efcc2f9ba/modules/core/src/matrix.cpp#L519

Mat::copyTo() の以下の箇所が多分原因に思われます。
https://github.com/opencv/opencv/blob/ba539eb9aad7571361657ea7e00a7c3efcc2f9ba/modules/core/src/copy.cpp#L325

テンプレートな Mat_<T> の方だと問題ない(ように見える)理由は、要素型からtype, depth, channels等を直接求めているから、ということで説明が付きそうです。
https://github.com/opencv/opencv/blob/ba539eb9aad7571361657ea7e00a7c3efcc2f9ba/modules/core/include/opencv2/core/mat.inl.hpp#L1548

言い換えれば、Mat_<T>の中身すなわちflagsのフィールド値はやはり同じ問題を抱えているということになります。

回避策

そもそも空のMatをcloneする必要に迫られることはあまりなさそうですし、空のMatのdepth/channelsが狂ったところで困ることも少なそうですが、例えば以下のような独自clone関数を作る手は考えられます。

void myClone(const cv::Mat &src, cv::Mat &dst)
{
    if (src.empty())
    {
        dst.create(src.size(), src.type());
    }
    else
    {
        src.copyTo(dst);
    }
}
cv::Mat src(0, 0, CV_32SC2);
cv::Mat dst;
myClone(src, dst);

std::cout << "depth=" << dst.depth() << ", channels=" << dst.channels() << std::endl;
// depth=4, channels=2

Discussion