c++opencvopencv-mat

OpenCV Mat::convertTo(type) does not convert the type


GIVEN:

The following code fragment:

#include <opencv2/core.hpp>
#include <iostream>

int
main(int argc, char** argv)
{

    cv::Mat a = (cv::Mat_<double>(3,1) << 1,   2,   3); 
    cv::Mat b;

    std::cout << "a(before): " << cv::typeToString(a.type()) << std::endl;
    std::cout << "b(before): " << cv::typeToString(b.type()) << std::endl;

    std::cout <<                                             std::endl;
    std::cout << "Convert 'a' --> 'b' with type CV_64FC4" << std::endl;
    std::cout <<                                             std::endl;
    a.convertTo(b, CV_64FC4);

    std::cout << "a(after):  " << cv::typeToString(a.type()) << std::endl;
    std::cout << "b(after):  " << cv::typeToString(b.type()) << std::endl;

    return 0;
}

EXPECTATION:

Should produce, in my understanding, the following output:

a(before): CV_64FC1
b(before): CV_8UC1

Convert 'a' --> 'b' with type CV_64FC4

a(after):  CV_64FC1
b(after):  CV_64FC4

OUTPUT:

Instead, the output is as follows:

a(before): CV_64FC1
b(before): CV_8UC1

Convert 'a' --> 'b' with type CV_64FC4

a(after):  CV_64FC1
b(after):  CV_64FC1

QUESTION:

What is going on here? How can I actually convert to the specified target type?


Solution

  • Short answer:
    cv::Mat::convertTo does not support changing the number of channels.

    Longer answer:
    As you can see in the documentation regarding the rtype paremeter of cv::Mat::convertTo (the one you pass CV_64FC4 to):

    desired output matrix type or, rather, the depth since the number of channels are the same as the input has; if rtype is negative, the output matrix will have the same type as the input.

    I.e. convertTo does not handle the case of changing the number of channels (only the bit depth of each channel).
    Although it is not documented explicitly, I guess cv::Mat::convertTo extracts the bit depth from rtype and ignores the number of channels.

    In your example: a has a single channels, and therefore so is b after the conversion.
    In order to see the effect of convertTo, you can pass e.g. CV_32FC1 in order to convert 64 bit doubles to 32 bit floats.


    Update:
    According to the OP's request (in the comments below), here are examples of changing the number of channels using cv::mixChannels:

    cv::Mat img1c = (cv::Mat_<double>(3, 1) << 1, 2, 3);
    cv::Mat img4c(img1c.size(), CV_64FC4);
    cv::Mat img1c_2(img1c.size(), CV_64FC1);
    
    // Convert 1 channel into 4 (duplicate the channel):
    std::vector<int> fromTo1{ 0,0, 0,1, 0,2, 0,3 }; // 0->0, 0->1, 0->2, 0->3
    cv::mixChannels(img1c, img4c, fromTo1);
    
    // Convert back to 1 channel (pick one of the channels - the 1st one in this case):
    std::vector<int> fromTo2{ 0,0 };    // 0->0  (pick the 1st channel)
    cv::mixChannels(img4c, img1c_2, fromTo2);