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* ?

gen
  • 9,528
  • 14
  • 35
  • 64
Sirnino
  • 111
  • 1
  • 1
  • 3

2 Answers2

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.

Gillfish
  • 717
  • 1
  • 9
  • 29
  • 2
    What does mean "Scale the values"? – Maystro Jan 19 '15 at 11:13
  • 3
    @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. – Gillfish Jan 19 '15 at 15:11
  • 1
    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? – Maystro Jan 19 '15 at 15:18
  • 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. – Gillfish Jan 19 '15 at 19:30
  • 1
    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? –  Oct 21 '16 at 16:37
  • @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). – Gillfish Oct 24 '16 at 22:42
  • @Gillfish you are correct saturated_cast(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. –  Oct 27 '16 at 15:38
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.

SSteve
  • 10,550
  • 5
  • 46
  • 72