C++ 如何访问/修改 OpenCV 中的矩阵元素?为什么 at() 是模板化的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17039665/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:52:06  来源:igfitidea点击:

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

c++templatesopencvmatrix

提问by Suzan Cioc

Should I know Matelement type for use at()correctly? For example, if I have

我应该知道正确Mat使用的元素类型at()吗?例如,如果我有

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

then can I call

那我可以打电话吗

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

If not then which template argument should I use?

如果不是,那么我应该使用哪个模板参数?

Why Mat::atis templatized while Matitself is not?

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

UPDATE

更新

This question contained sample code with another error, which is now here: How to fill Matrix with zeros in OpenCV?

这个问题包含带有另一个错误的示例代码,现在在这里:如何在 OpenCV 中用零填充矩阵?

采纳答案by Mikhail

As already properly pointed out by William, you should provide only the correct type as a template argument for at. I believe that cv::Matitself is not made template only for simplification. However, OpenCV crew are trying to support C++ features, including templates. The interface became slightly heterogeneous in this way.

正如威廉已经正确指出的那样,您应该只提供正确的类型作为at. 我相信它cv::Mat本身并不是为了简化而制作的模板。但是,OpenCV 团队正在尝试支持 C++ 功能,包括模板。界面以这种方式变得略微异构。

You could not deduce compiler type from a type variable at runtime for obvious reason. However, you can deduce it at compile time, if your type variable is known at this point, using a traits class:

出于显而易见的原因,您无法在运行时从类型变量推导出编译器类型。但是,您可以在编译时推断它,如果此时您的类型变量是已知的,则使用特征类:

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);
}

In this case you can change the value of typeand wouldn't need to change types manually for all the ator other methods calls.

在这种情况下,您可以更改 的值type并且不需要为所有at或其他方法调用手动更改类型。

回答by LovaBill

Well now your editedpost differs. Rectified:

那么现在你编辑的帖子不同了。纠正:

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

or try different:

或尝试不同的:

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

回答by John

The Matclass is not a template class, which allows to change its "type" at runtime. Changing the type is useful, e.g. when reading from a file. Not being a template is convenient, when using a Matas function parameter, since the function is not required to be a template function.

Mat班是不是一个模板类,它允许在运行时改变它的“类型”。更改类型很有用,例如从文件中读取时。当使用Matas 函数参数时,不作为模板很方便,因为函数不需要是模板函数。

However, for accessing single elements (with pointers, ator iterators) you need the data type. I guess this is done for performance reasons. This contradicts to the runtime type system and makes it harder to write generic code, when you do not know the type at compile time. Nevertheless, you can do it with a workaround.

但是,要访问单个元素(使用pointersatiterators),您需要数据类型。我想这是出于性能原因。当您在编译时不知道类型时,这与运行时类型系统相矛盾并且使编写通用代码变得更加困难。不过,您可以通过一种解决方法来做到这一点。

The simplest method is just to use an if-else-cascade:

最简单的方法就是使用 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;
    }
    /* ... */
}
/* ... */

But you can imagine that this becomes cumbersome, if you write it out for all types and channels. You have to limit the number of channels anyway, since there is no hard limit by OpenCV. We choose 4 in the following.

但是你可以想象,如果你为所有类型和渠道写出来,这会变得很麻烦。无论如何,您必须限制通道数量,因为 OpenCV 没有硬性限制。我们在下面选择4。

I wrote a helper template metaprogram header, that does the job. You can provide a functor with a templated operator(). Then call the template metaprogram, which will call the functor with a compile time type. See this example for a functor that prints the first pixel and returns whether it is not all zero:

我写了一个辅助模板元程序头,它完成了这项工作。您可以提供带有模板化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;});
    }
};

Note, the return type of the operator()can be arbitrary, but may not depend on the image type cv_typeunfortunately. This is because it is also used as the return type of the function that holds the if-else cascade (the runfunction, see below).

请注意, 的返回类型operator()可以是任意的,但cv_type不幸的是可能不依赖于图像类型。这是因为它也用作保存 if-else 级联的run函数的返回类型(该函数,见下文)。

Here is the calling code, which can check for "all" channels (1-4) and types or for a specified set:

这是调用代码,它可以检查“所有”通道(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);

The latter call will throw an exception, if tis not CV_8UC1or CV_8UC3. If you are often using the same restriction, you can abbreviate it, with a using declaration (see at the bottom of the header file below).

后一个调用将抛出异常,如果t不是CV_8UC1CV_8UC3。如果您经常使用相同的限制,您可以使用 using 声明将其缩写(参见下面头文件的底部)。

So, this is an easy-to-use solution, which allows you to use a compile time value "made" from a run time value. But remember, in the background an if-else-cascade is doing all the checks (in the order the channels and types were specified). This implies that for each combination of channel and type that is checked one concrete functor class is generated. If it is large, that might be bad. So it should only include the type dependent parts. It could also be a factory functor that instanciates a templated class with a non-template base with virtual functions to reduce code size.

因此,这是一个易于使用的解决方案,它允许您使用从运行时值“生成”的编译时值。但请记住,在后台,一个 if-else-cascade 正在执行所有检查(按照指定通道和类型的顺序)。这意味着对于每个被检查的通道和类型的组合,都会生成一个具体的函子类。如果它很大,那可能很糟糕。所以它应该只包含类型相关的部分。它也可以是一个工厂函子,它使用具有虚函数的非模板基实例化模板化类以减少代码大小。

It follows the header file, which contains the CvTypeTraitsclass and the template metaprogram functions. At the bottom you can see that the CallFunctortype is really just an abbreviation for a "restriction" of types and channels. You could also declare something like that with other restrictions.

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