我应该知道正确Mat
使用的元素类型吗?at()
例如,如果我有
Mat rose = Mat(1,180, CV_64F, 0);
那我可以打电话吗
rose.at<short>(i,j)++;
如果不是,那么我应该使用哪个模板参数?
为什么Mat::at
是模板化的,而Mat
它本身不是?
更新
这个问题包含带有另一个错误的示例代码,现在在这里:如何在 OpenCV 中用零填充矩阵?
我应该知道正确Mat
使用的元素类型吗?at()
例如,如果我有
Mat rose = Mat(1,180, CV_64F, 0);
那我可以打电话吗
rose.at<short>(i,j)++;
如果不是,那么我应该使用哪个模板参数?
为什么Mat::at
是模板化的,而Mat
它本身不是?
更新
这个问题包含带有另一个错误的示例代码,现在在这里:如何在 OpenCV 中用零填充矩阵?
正如威廉已经正确指出的那样,您应该只提供正确的类型作为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
或其他方法调用的类型。
那么现在你编辑的帖子不同了。纠正:
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
as 函数参数时,不是模板很方便,因为函数不需要是模板函数。
但是,要访问单个元素(使用指针、at或迭代器),您需要数据类型。我想这是出于性能原因。当您在编译时不知道类型时,这与运行时类型系统相矛盾,并且使编写通用代码变得更加困难。不过,您可以使用解决方法来做到这一点。
最简单的方法就是使用 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
如果is notCV_8UC1
或,后一个调用将引发异常CV_8UC3
。如果你经常使用相同的限制,你可以用 using 声明来缩写它(见下面头文件的底部)。
因此,这是一个易于使用的解决方案,它允许您使用从运行时值“生成”的编译时值。但请记住,在后台,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>;