簡體   English   中英

如何在OpenCV中訪問/修改矩陣元素? 為什么在()被模板化了?

[英]How to access/modify matrix element in OpenCV? Why at() is templatized?

我應該知道at()正確使用的Mat元素類型嗎? 例如,如果我有

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

然后我可以打電話

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

如果沒有那么我應該使用哪個模板參數?

為什么Mat::at是模板化而Mat本身不是?

UPDATE

這個問題包含另一個錯誤的示例代碼,現在在這里: 如何在OpenCV中用零填充Matrix?

正如William已正確指出的那樣,您應該只提供正確的類型作為at的模板參數。 我相信cv::Mat本身不僅僅是為了簡化而制作模板。 但是,OpenCV工作人員正在嘗試支持C ++功能,包括模板。 通過這種方式,界面變得略微異構。

由於顯而易見的原因,您無法在運行時從類型變量中推斷出編譯器類型。 但是,如果此時已知類型變量,則可以使用traits類在編譯時推導出它:

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或其他方法調用手動更改類型。

那么現在你編輯的帖子有所不同 修正:

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;

Mat類不是模板類,它允許在運行時更改其“類型”。 更改類型很有用,例如從文件中讀取時。 當使用Mat作為函數參數時,不是模板是方便的,因為該函數不需要是模板函數。

但是,要訪問單個元素(使用指針atiterators ),您需要數據類型。 我想這是出於性能原因。 這與運行時類型系統相矛盾,並且當您在編譯時不知道類型時,會使編寫通用代碼變得更加困難。 不過,您可以通過解決方法來實現。

最簡單的方法就是使用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_8UC1CV_8UC3 ,則后一個調用將拋出異常。 如果您經常使用相同的限制,可以使用using聲明來縮寫它(請參閱下面頭文件的底部)。

因此,這是一個易於使用的解決方案,它允許您使用運行時值“make”的編譯時間值。 但請記住,在后台if-else-cascade正在進行所有檢查(按照指定的通道和類型的順序)。 這意味着對於檢查的通道和類型的每個組合,生成一個具體的仿函數類。 如果它很大,那可能會很糟糕。 所以它應該只包括類型相關的部分。 它也可以是工廠仿函數,它使用具有虛函數的非模板庫來實現模板化類,以減少代碼大小。

它遵循頭文件,其中包含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>;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM