web-dev-qa-db-ja.com

OpenCVで行列要素にアクセス/変更する方法は?なぜat()はテンプレート化されているのですか?

Mat要素タイプat()を正しく使用するために知っている必要がありますか?たとえば、

Mat rose = Mat(1,180, CV_64F, 0);

次に電話してもいいですか

rose.at<short>(i,j)++;

そうでない場合、どのテンプレート引数を使用する必要がありますか?

なぜ Mat::atはテンプレート化されていますが、Mat自体はテンプレート化されていませんか?

[〜#〜]更新[〜#〜]

この質問には、別のエラーのあるサンプルコードが含まれており、ここにあります: OpenCVで行列をゼロで埋める方法

12
Suzan Cioc

ウィリアムによってすでに適切に指摘されているように、atのテンプレート引数として正しいタイプのみを提供する必要があります。私は信じている cv::Mat自体は、単純化のためだけに作成されたテンプレートではありません。ただし、OpenCVのスタッフは、テンプレートを含むC++機能をサポートしようとしています。このようにして、インターフェースはわずかに異質になった。

明らかな理由により、実行時に型変数からコンパイラー型を推測することはできませんでした。ただし、タイプ変数がこの時点でわかっている場合は、特性クラスを使用して、コンパイル時に推定できます。

template<int I>
struct CvType {};

template<>
struct CvType<CV_64F> { typedef double type_t; };
template<>
struct CvType<CV_32F> { typedef float type_t; };
template<>
struct CvType<CV_8U> { typedef unsigned char type_t; };
// Other types go here

void main()
{
  const int type = CV_64F;
  cv::Mat mat(10, 10, type);
  mat.at<CvType<type>::type_t>(1, 1);
}

この場合、typeの値を変更できます。すべてのatまたは他のメソッド呼び出しでタイプを手動で変更する必要はありません。

9
Mikhail

さて、あなたの編集済み投稿は異なります。整流:

Mat m1 = Mat(1,1, CV_64F, cvScalar(0.));
m1.at<double>(0,0) = 0;

または別の方法を試してください:

Mat m1 = cv::Mat::zeros(1,1, CV_64F);
m1.at<double>(0,0) = 0;
4
LovaBill

Matクラスは、実行時に「タイプ」を変更できるテンプレートクラスではありません。タイプを変更すると便利です。ファイルから読み取るとき。関数がテンプレート関数である必要はないため、Matを関数パラメーターとして使用する場合は、テンプレートでないことが便利です。

ただし、単一の要素( pointersat または iterators を使用)にアクセスするには、データ型が必要です。これはパフォーマンス上の理由で行われたと思います。これは、実行時の型システムと矛盾し、コンパイル時に型がわからない場合、汎用コードの記述が難しくなります。それにもかかわらず、回避策でそれを行うことができます。

最も簡単な方法は、if-else-cascadeを使用することです。

_Mat img = imread("test.png");
if (img.channels() == 1) {
    if (img.depth() == CV_8U) {
        cout << (int)(img.at<uint8_t>(0,0)) << endl;
    }
    else if (img.depth() == CV_8S) {
        /* ... */
    }
    /* ... */
}
/* ... */
else if (img.channels() == 3) {
    if (img.depth() == CV_8U) {
        auto p = img.at<array<uint8_t,3>>(0,0);
        cout << (int)(p[0]) << ";" << (int)(p[1]) << ";" << (int)(p[2]) << endl;
    }
    /* ... */
}
/* ... */
_

しかし、すべてのタイプとチャネルについて書き出すと、これが面倒になると想像できます。とにかくOpenCVによるハードリミットがないため、チャネル数を制限する必要があります。以下の中から4つ選びます。

ヘルパーテンプレートのメタプログラムヘッダーを作成しました。ファンクタにテンプレート化されたoperator()を提供できます。次に、テンプレートメタプログラムを呼び出します。これにより、コンパイル時の型でファンクターが呼び出されます。最初のピクセルを出力し、それがすべてゼロでないかどうかを返す関数については、この例を参照してください。

_struct PrintPixel {
    Mat img;

    // this template function will be called from the metaprogram
    template<int cv_type> // compile time value e.g. CV_8UC3
    bool operator()() {
        using elem_t  = typename CvTypeTraits<cv_type>::base_type;
        using array_t = typename CvTypeTraits<cv_type>::array_type;
        // you could also do static_asserts here

        array_t pixel = img.at<array_t>(0,0);
        for (elem_t val : pixel)
            cout << (double)(val) << ", ";
        cout << endl;
        return any_of(pixel.begin(), pixel.end(), [](elem_t v){return v != 0;});
    }
};
_

operator()の戻り値の型は任意である可能性がありますが、残念ながら画像の型_cv_type_に依存しない場合があります。これは、if-elseカスケードを保持する関数(run関数、以下を参照)の戻り値の型としても使用されるためです。

以下は、「すべて」のチャネル(1〜4)とタイプ、または指定されたセットをチェックできる呼び出しコードです。

_Mat img = imread("test.png");
int t = img.type();

// call functor, check for 1-4 channels and all 7 base types
bool not_zero = CallFunctor::run(PrintPixel{img}, t);

// call functor, check only for 1 or 3 channels and 8 bit unsigned int
CallFunctorRestrictChannelsTo<1,3>::AndBaseTypesTo<CV_8U>::run(PrintPixel{img}, t);
_

tが_CV_8UC1_または_CV_8UC3_でない場合、後者の呼び出しは例外をスローします。同じ制限を頻繁に使用している場合は、using宣言を使用して省略できます(以下のヘッダーファイルの下部を参照)。

したがって、これは使いやすいソリューションであり、ランタイム値から「作成された」コンパイル時値を使用できます。ただし、バックグラウンドでif-else-cascadeがすべてのチェックを実行していることを覚えておいてください(チャネルとタイプが指定された順序で)。これは、チェックされるチャネルとタイプの各組み合わせに対して、1つの具象ファンクタクラスが生成されることを意味します。大きければ悪いかもしれません。したがって、タイプに依存する部分のみを含める必要があります。また、コードサイズを削減するための仮想関数を含む非テンプレートベースを持つテンプレートクラスをインスタンス化するファクトリファンクタになる可能性もあります。

これは、CvTypeTraitsクラスとテンプレートメタプログラム関数を含むヘッダーファイルに従います。下部にあるCallFunctorタイプは、実際にはタイプとチャネルの「制限」の略です。他の制限付きでそのようなものを宣言することもできます。

_#pragma once

#include <cstdint>
#include <type_traits>
#include <array>
#include <opencv2/core/types_c.h>


template<int> struct BaseType { };
template<> struct BaseType<CV_8S>  { using base_type = int8_t;   };
template<> struct BaseType<CV_8U>  { using base_type = uint8_t;  };
template<> struct BaseType<CV_16S> { using base_type = int16_t;  };
template<> struct BaseType<CV_16U> { using base_type = uint16_t; };
template<> struct BaseType<CV_32S> { using base_type = int32_t;  };
template<> struct BaseType<CV_32F> { using base_type = float;    };
template<> struct BaseType<CV_64F> { using base_type = double;   };


template<int t>
struct CvTypeTraits {
    constexpr static int channels = t / CV_DEPTH_MAX + 1;
    using base_type = typename BaseType<t % CV_DEPTH_MAX>::base_type;
    using array_type = std::array<base_type, channels>;
};


template<int currentChannel, int... otherChannels>
struct find_chan_impl {
    template<typename ret_type, int... types>
    struct find_type_impl {
        template<class Functor>
        static inline ret_type run(Functor&& f, int const& c, int const& t) {
            if (c == currentChannel)
                return find_chan_impl<currentChannel>::template find_type_impl<ret_type, types...>::run(std::forward<Functor>(f), c, t);
            else
                return find_chan_impl<otherChannels...>::template find_type_impl<ret_type, types...>::run(std::forward<Functor>(f), c, t);
        }
    };
};

template<>
struct find_chan_impl<0> {
    template<typename ret_type, int... types>
    struct find_type_impl {
        template<class Functor>
        [[noreturn]] static inline ret_type run(Functor&& f, int const& c, int const& t) {
            throw std::runtime_error("The image has " + std::to_string(c) + " channels, but you did not try to call the functor with this number of channels.");
        }
    };
};

template<int channel>
struct find_chan_impl<channel> {
    template<typename ret_type, int currentType, int... otherTypes>
    struct find_type_impl {
        static_assert(currentType < CV_DEPTH_MAX, "You can only restrict to base types, without channel specification");

        template<class Functor>
        static inline ret_type run(Functor&& f, int const& c, int const& t) {
            if (t == currentType)
                return find_type_impl<ret_type, currentType>::run(std::forward<Functor>(f), c, t);
            else
                return find_type_impl<ret_type, otherTypes...>::run(std::forward<Functor>(f), c, t);
        }
    };

    template<typename ret_type, int type>
    struct find_type_impl<ret_type, type> {
        template<class Functor>
        static inline ret_type run(Functor&& f, int const& c, int const& t) {
            return f.template operator()<CV_MAKETYPE(type,channel)>();
        }
    };

    template<typename ret_type>
    struct find_type_impl<ret_type, -1> {
        template<class Functor>
        [[noreturn]] static inline ret_type run(Functor&& f, int const& c, int const& t) {
            throw std::runtime_error("The image is of base type " + std::to_string(t) + ", but you did not try to call the functor with this base type.");
        }
    };
};

template<int... channels>
struct CallFunctorRestrictChannelsTo {
    template<int firstType, int... types>
    struct AndBaseTypesTo {
        template<class Functor>
        static inline auto run(Functor&& f, int t) -> decltype(f.template operator()<firstType>()) {
            using functor_ret_type = decltype(f.template operator()<firstType>());
            std::div_t d = std::div(t, CV_DEPTH_MAX);
            int c             = d.quot + 1;
            int const& base_t = d.rem;
            return find_chan_impl<channels..., 0>::template find_type_impl<functor_ret_type, firstType, types..., -1>::run(std::forward<Functor>(f), c, base_t);
        }
    };

    template<class Functor>
    static inline auto run(Functor&& f, int t) -> decltype(f.template operator()<CV_8S>()) {
        return AndBaseTypesTo<CV_8S, CV_8U, CV_16S, CV_16U, CV_32S, CV_32F, CV_64F>::run(std::forward<Functor>(f), t);
    }
};

template<int... types>
using CallFunctorRestrictBaseTypesTo = CallFunctorRestrictChannelsTo<1,2,3,4>::template AndBaseTypesTo<types...>;

using CallFunctor = CallFunctorRestrictChannelsTo<1,2,3,4>::template AndBaseTypesTo<CV_8S, CV_8U, CV_16S, CV_16U, CV_32S, CV_32F, CV_64F>;
_
3
John