Convert 16-bit-depth CvMat* to 8-bit-depth
Asked Answered
B

2

11

I'm working with Kinect and OpenCV. I already search in this forum but I didn't find anything like my problem. I keep the raw depth data from Kinect (16 bit), I store it in a CvMat* and then I pass it to the cvGetImage to create an IplImage* from it:

CvMat* depthMetersMat = cvCreateMat( 480, 640, CV_16UC1 );
[...]
cvGetImage(depthMetersMat,temp);

But now I need to work on this image in order to do cvThreshdold and find contours. These 2 functions need an 8-bit-depth-image in input. How can I convert the CvMat* depthMetersMat in an 8-bit-depth-CvMat* ?

Bomar answered 2/8, 2011 at 8:50 Comment(0)
P
21

The answer that @SSteve gave almost did the trick for me, but the call to convertTo seemed to just chop off the high order byte instead of actually scaling the values to an 8-bit range. Since @Sirnino didn't specify which behavior was wanted, I thought I'd post the code that will do the (linear) scaling, in case anyone else is wanting to do that.

SSteve's original code:

 CvMat* depthMetersMat = cvCreateMat( 480, 640, CV_16UC1 );  
 cv::Mat m2(depthMetersMat, true);  
 m2.convertTo(m2, CV_8U);

To scale the values, you just need to add the scale factor (1/256, or 0.00390625, for 16-bit to 8-bit scaling) as the third parameter (alpha) to the call to convertTo

m2.convertTo(m2, CV_8U, 0.00390625);

You can also add a fourth parameter (delta) that will be added to each value after it is multiplied by alpha. See the docs for more info.

Plumbiferous answered 2/5, 2012 at 19:57 Comment(7)
What does mean "Scale the values"?Seer
@Maystro, an unsigned 16-bit number can have a value anywhere from 0 to 65,535, but we need the values to fit into an 8-bit number, which can only hold values of 0 to 255. So we apply a scale factor, which just means that you multiply each value by a constant, in this case, by 0.00390625. Any number in the original matrix that has a value of 0 to 255 gets a new value of 0, 256 to 511 becomes 1, 512 to 767 becomes 2, etc.Plumbiferous
So it is like m2.convertTo(m2/256,CV_8U) right? Another question, what if we didn't pass a scale factor parameter? how it will be calculated?Seer
Correct. You could just apply the scale factor to the matrix before passing it to the function. If you don't scale the values, then it appears that the convertTo() function just chops off the extra bits of the 16-bit value to make it fit into 8-bits. You'll have to check the link to the docs that I reference in my answer to see exactly what happens.Plumbiferous
The scale factor seem wrong to me. We want to map the range of [0-65,535] ushort (CV_16U) to [0-255] of uchar (CV_8U). Therefore the equation is 255=65,535/x. That x happens to be 257, so the scale factor is 1/257, right?Pottage
@John That depends on if the convertTo() method truncates or rounds the result after it scales. My guess is that it just truncates, in which case, scaling by 256 would be correct. (65,535 / 256 = 255.99609375, which gets truncated to 255).Plumbiferous
@Plumbiferous you are correct saturated_cast<uchar>(65,535/256) does return 255. However it seems weird to rely on truncation when the mathematically accurate equation can be evaluated just fine. This is especially relevant if we want to convert from e.g. uchar to ushort. Then you would want to multiply by 257 and not 256 otherwise you never reach 65,535.Pottage
V
2

This should work:

CvMat* depthMetersMat = cvCreateMat( 480, 640, CV_16UC1 );
cv::Mat m2(depthMetersMat, true);
m2.convertTo(m2, CV_8U);

But according to the OpenCV docs, CvMat is obsolete. You should use Mat instead.

Vulcanize answered 2/8, 2011 at 16:7 Comment(1)
How would this look in python?Dentate

© 2022 - 2024 — McMap. All rights reserved.