The Mat class is not a template class that allows you to change its "type" at run time. Changing type is useful, for example. when reading from a file. Not being a template is convenient when using the function parameter Mat as, since the function is not required to be a template function.
However, to access individual elements (with pointers , at or iterators ) you need a data type. I think this was done for performance reasons. This contradicts a runtime type system and makes it difficult to write generic code when you do not know the type at compile time. However, you can do this with a workaround.
The easiest way is to use an 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 it becomes cumbersome if you write it for all types and channels. In any case, you need to limit the number of channels, since OpenCV does not have a hard limit. In the future, we choose 4.
I wrote the metaprogram header of the helper template that does the job. You can provide a functor with the operator() pattern. Then call the template metaprogram, which will call a functor with a compile-time type. See this example for a functor that prints the first pixel and whether it returns not all zero:
struct PrintPixel { Mat img; // this template function will be called from the metaprogram template<int cv_type> // compile time value eg 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 that the return type of operator() may be arbitrary, but it may not depend on the type of image cv_type . This is due to the fact that it is also used as the return type of a function that contains the if-else cascade ( run function, see below).
Here is the call code that can check "all" channels (1-4) and types for either the specified set:
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 last call will throw an exception if t not CV_8UC1 or CV_8UC3 . If you often use the same restriction, you can shorten it with a using declaration (see the bottom of the header file below).
So, this is an easy-to-use solution that allows you to use the compiled-time value "made" from the run-time value. But remember that in the background the if-else cascade performs all the checks (in the order in which the channels and types are indicated). This means that for each combination of channel and type that is checked, one particular class of functors is generated. If it is big, it can be bad. Therefore, it should include only elements depending on the type. It can also be a factory functor that sets up a template class with a base without templates with virtual functions to reduce code size.
It follows a header file that contains the CvTypeTraits functions and template metaprograms. Below you can see that the CallFunctor type is just an abbreviation for "limiting" types and channels. You can also declare something similar with other restrictions.
#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>;