cvtColor ignores dstCn argument OpenCV - c++

I am trying to create a program which imports an RGB image and converts it to grayscale. I would like the output image to consist of 3 channels. To achieve that I use cv::cvtColor function with dstCn parameter set to 3:
cv::Mat mat = cv::imread("lena.bmp");
std::cout << CV_MAT_CN(mat.type()) << "\n"; // prints "3", OK
cv::cvtColor(mat, mat, cv::COLOR_BGR2GRAY, 3);
std::cout << CV_MAT_CN(mat.type()) << "\n"; // prints "1" regardless of dstCn
but it looks like dstCn isn't taken into account, and the output array has only 1 channel.
The OpenCV documentation says:
dstCn - number of channels in the destination image; if the parameter is 0, the number of the channels is derived automatically from src and code.
It's a very basic case and I am aware there are plenty of workarounds, but I would like to know whether it is a bug or my incomprehension.

The answer can be found in the OpenCV source code. Let's have a look at cvtColor function in imageproc/src/color.cpp file. There is a very long switch-case, so I only post here the most interesting part:
void cvtColor( InputArray _src, OutputArray _dst, int code, int dcn )
{
...
switch( code )
{
...
case COLOR_BGR2GRAY: case COLOR_BGRA2GRAY:
case COLOR_RGB2GRAY: case COLOR_RGBA2GRAY:
cvtColorBGR2Gray(_src, _dst, swapBlue(code));
break;
}
}
The code from my question uses COLOR_BGR2GRAY. Nothing special is done before the switch statement. Invoking swapBlue does not do anything interesting too. We can see that this case completely ignores dcn (aka dstCn). So it seems to be fully intentional and my idea was wrong from the start.
I have also found a similar post on OpenCV forum where Doomb0t pointed that:
the concept of greyscale is that you have one channel describing the intensity on a gradual scale between black and white. So, it is not clear why would you need a 3 channels greyscale image (...)

Yes, grayscale is one channel and what you ask doesn't make sense at first sight, however there could be a legit reason if you want it copied in three channels in one operation and then manipulate each of the copies, while they are kept in the same container.
swapBlue is because default format is BGR.
BTW, you can also read it directly in blackwhite and merge it into new 3 channel image:
cv::Mat bw = cv.imread("lena.bmp",0);
vector<Mat> ch(3);
bw3 = Mat::zeros(Size(bw.cols, bw.rows), CV_8UC3); //3 channels 8 bit unsigned
for(i=0; i<3; i++) ch.push_back(bw)
cv.merge(ch, bw3)
(Maybe there's a shorter way, I don't know.)
More examples with merge

Related

OpenCV masking/split function

I have a question for us. I'm a newbe of OpenCV and I need to understand if that lib can help me to reach my goals.
I need to use OpenCV to open a Tiff file (big Tiff file) and split it on two different file with a mask like that Mask, in the end the file 1 have pixel black and the file 2 have the negative - pixel white of the original image.
Any ideas or example for me?
Thank you all!
To read the file, you can use the function imread. This stores it in a cv::Mat object. Since your mask is black and white, I would read the mask-image as a grayscale using IMREAD_GRAYSCALE. This gives you each pixel with a value from 0-255. That should cover the first part of your question.
I have to admit I am having trouble understandig your question, but I expect you want to create two images. The first contains all the pixels where your mask has a black pixel. The second one contains an image where in the mask all the pixels are white.
You could look at this thread. Additionally I would like to give you the way that I would do it.
The problem you would run in to is that your .tiff-image has a different type than your chessboard. Tiff is probably CV_8UC3 and chessboard is probably CV_8UC1. But this should be easily solvable.
I think you would probably want to look at each individual pixel and leave the be if, at that same pixel of the chessboard, your color is white. Then if it is not, make that pixel from your original pixel black. I have not tested this, but it would look something like this.
for (int i = 0; i < originalImage.rows; i++) {
for (int j = 0; j < originalImage.cols; j++) {
if (chessboard.at<uchar>(Point(j, i)) != 255) {
originalImage.at<Vec3b>(Point(j, i)) = Scalar(0, 0, 0);
}
else {
// Do nothing.
}
}
}
Scalar is used, since the originalImage has three channels instead of one. I hope this helps!
Try this to create the mask:
cv::Mat tiff;
cv::Mat maskDark = tiff == 0; // comparison like '< 10' also works
cv::Mat maskDark = tiff == 255;

Blocky behavior when converting bgr to hsv in opencv

I'm trying to convert an bgr mat to an hsv mat for some detection, but the hsv image keeps coming out blocky. Here is my code in c++:
int main() {
const int device = 1;
VideoCapture capture(device);
Mat input;
int key;
if(!capture.isOpened()) {
printf("No video recording device under device number %i found. Aborting program...\n", device);
return -1;
}
namedWindow("Isolation Test", CV_WINDOW_AUTOSIZE);
while(1) {
capture >> input;
cvtColor(input, input, CV_BGR2HSV);
imshow("Isolation Test", input);
key = static_cast<int>(waitKey(10));
if(key == 27)
break;
}
destroyWindow("Isolation Test");
return 0;
}
Here is a snapshot of what the output looks like. the input does not look blocky when I comment out the cvtColor. What is the problem and what should I do to fix it?
I suggested an explanation in the comments part, but decided to actually verify my assumption and explain a little bit about the HSV color space.
There is no problem in the code nor in OpenCV's cvtColor. the "blocky" artifacts exist in the RGB image, but are not noticeable. All of the JPEG family compression algorithms produce these artifacts. The reason we usually don't see them is that the algorithms "exploit" weaknesses in our visual system and compress more stuff that we are not very sensitive to.
I converted the image back to RGB using OpenCVscvtColor` and the artifacts magically disappeared (images are below).
The HSV color space in particular has several characteristics that exaggerate these artifacts. The important of which is probably the fact that wherever the V channel (Value/Luminance) is very low, the H & S channels are very unstable and are quite meaningless. In the extreme: [128,255,0] == [0,0,0].
So very small and unnoticeable compression artifacts in the dark areas of the image become very prominent with the false colors of the HSV color space.
If you want to use the HSV color space as feature space for color comparison keep in mind that if V is very low, H & S are quite meaningless. That is also true for very low S values that make the H value meaningless ([0,0,100] == [128,0,100]).
BTW. also keep in mind that the H channel is cyclic and the difference between H == 0 and H == 255 is only one gray level.
False colors "blocky" HSV image posted in the question
Image converted back to RGB using cvtColor
I think this happen because the imshow function will always interpret the image as a simple RGB or BGR image. So you need to change back HSV to BGR using cvtColor(input,input,CV_HSV2BGR) before show image.

How to access single channel matrix in OpenCV

I want to ask if it is possible to access single channel matrix using img.at<T>(y, x) instead using img.ptr<T>(y, x)[0]
In the example below, I create a simple program to copy an image to another
cv::Mat inpImg = cv::imread("test.png");
cv::Mat img;
inpImg.convertTo(img, CV_8UC1); // single channel image
cv::Mat outImg(img.rows, img.cols, CV_8UC1);
for(int a = 0; a < img.cols; a++)
for(int b = 0; b < img.rows; b++)
outImg.at<uchar>(b, a) = img.at<uchar>(b, a); // This is wrong
cv::imshow("Test", outImg);
The shown result was wrong, but if I change it to
outImg.ptr<uchar>(b, a)[0] = img.ptr<uchar>(b, a)[0];
The result was correct.
I'm quite puzzled since using img.at<T>(y, x) should also be okay. I also tried with 32FC1 and float, the result is similar.
Although I know you already found it, the real reason - buried nicely in the documentation - is that cv::convertTo ignores the number of channels implied by the output type, so when you do this:
inpImg.convertTo(img, CV_8UC1);
And, assuming your input image has three channels, you actually end up with a CV_8UC3 format, which explains why your initial workaround was successful - effectively, you only took a single channel by doing this:
outImg.ptr<uchar>(b, a)[0] // takes the first channel of a CV_8UC3
This only worked by accident as the pixel should have been accessed like this:
outImg.ptr<Vec3b>(b, a)[0] // takes the blue channel of a CV_8UC3
As the data is still packed uchar in both cases, the effective reinterpretation happened to work.
As you noted, you can either convert to greyscale on loading:
cv::imread("test.png", CV_LOAD_IMAGE_GRAYSCALE)
Or, you can convert explicitly:
cv::cvtColor(inpImg, inpImg, CV_BGR2GRAY);

obtaining cv::Scalar from cv::Mat of unknown type

after reading an image of unknown depth and channel number i want to access its pixels one by one.
on opencv 1.x the code goes:
IplImage * I = cvLoadImage( "myimage.tif" );
CvScalar pixel = cvGet2D( I, y, x );
but on opencv 2.x the cv::Mat.at() method demands that i know the image's type:
cv::Mat I = cv::imread( "myimage.tif" );
if( I.depth() == CV_8U && I.channels() == 3 )
cv::Vec3b pixel = I.at<cv::Vec3b>( x, y );
else if( I.depth() == CV_32F && I.channels() == 1 )
float pixel = I.at<cv::float>( x, y );
is there a function resembling cvGet2D that can receive cv::Mat and return cv::Scalar without knowing the image's type in compile time?
For someone who is really a beginner in C++ ...
... and/or a hacker who just need to save mere seconds of code typing to finish off the last project
cv::Mat mat = ...; // something
cv::Scalar value = cv::mean(mat(cv::Rect(x, y, 1, 1)));
(Disclaimer: This code is only slightly less wasteful than a young man dying for a revolutionary cause.)
The short answer is no. There's no such function in the C++ API.
The rationale behind this is performance. cv::Scalar (and CvScalar) is the same thing as cv::Vec<double,4>. So, for any Mat type other than CV_64FC4, you'll need a conversion to obtain cv::Scalar. Moreover, this method would be a giant switch, like in your example (you have only 2 branches).
But I suppose quite often this function would be convenient, so why not to have it? My guess is that people would tend to overuse it, resulting in really bad performance of their algorithms. So, OpenCV makes it just a tiny bit less convenient to access individual pixels, in order to force client code to use statically typed methods. This isn't such a big deal convenient-wise, since more often than not, you actually know the type statically and it's a really big deal performance-wise. So, I consider it a good trade-off.
I had the same issue, I just wanted to test something quickly and performance was not an issue. But all parts of the code uses cv::Mat(). What I did was the following
Mat img; // My input mat, initialized elsewhere
// Pretty fast operation, Will only create an iplHeader pointing to the data in the mat
// No data is copied and no memory is mallocated.
// The Header resides on the stack (note its type is "IplImage" not "IplImage*")
IplImage iplImg = (IplImage)img;
// Then you may use the old (slow converting) legacy-functions if you like
CvScalar s = cvGet2D( &iplImg, y, x );
Just a warning: you are using cvLoadImage and imread with default flags. This means that any image you read will be a 8-bit 3-channel image. Use appropriate flags (IMREAD_ANYDEPTH / IMREAD_ANYCOLOR) if you want to read image as is (which seems to be your intention).

Understanding openCV code snippet

I have a question about this peace of code.
...............
cv::Mat image;
image = cv::imread(filename.c_str(), CV_LOAD_IMAGE_COLOR);
if (image.empty()) {
std::cerr << "Couldn't open file: " << filename << std::endl;
exit(1);
}
cv::cvtColor(image, imageRGBA, CV_BGR2RGBA);
imageGrey.create(image.rows, image.cols, CV_8UC1);
*inputImage = (uchar4 *)imageRGBA.ptr<unsigned char>(0);
*greyImage = imageGrey.ptr<unsigned char>(0);
As I understand we create a openCV mat object. Read the image into it. But why we use filename.c_str()? instead of just filename? And why we convert from BGR to RGBA?
cv::cvtColor(image, imageRGBA, CV_BGR2RGBA); I read in the documentation that imread reads the image as RGB not BGR.
The most confusing for we is this part:
*inputImage = (uchar4 *)imageRGBA.ptr<unsigned char>(0);
*greyImage = imageGrey.ptr<unsigned char>(0);
What's happening here? why we need all this casts?
I know this is a lot of question, but I really want to know whats happening here.)
imread takes a const char* as first argument and you cannot pass a std::string directly to it
OpenCV stores matrices as BGR. So also imread adheres to this channel order (documentation might be misleading, don't confuse image format read (RGB) versus internal representation (BGR)). Based on your cuda tag I guess somebody wants to pass the image data to the GPU. GPUs typically work with RGBA format. It is not only about BGR<->RGB but also about having four channels in the interleaved format.
The Mat::ptr() is templated (it is not casting!) because Mat hides the datatype from you. The code is risky, as it just assumes imread would create a Mat_<uchar> and so this is the right type to access. It would be better to start with a cv::Mat_<uchar> in the first place, then use Mat_<T>::operator[] to get a pointer to the first row etc.
I don't know what comes next in your code but there might be a bug if the stride (step) is not considered.