OpenCV 2.4.7 Mat::convertTo() 函数:32位转16位截断

3
我注意到使用convertTo将矩阵从32位转换为16位时,“四舍五入”会将数字舍入到上限。 所以,在源矩阵中大于0x0000FFFF的值将设置为目标矩阵中的0xFFFF。
我的应用程序需要的是遮罩值,仅在目标值中设置2个LSB的值。
以下是示例:
Mat mat32;
Mat mat16;

mat32 = Mat(2,2,CV_32SC1);

for(int y = 0; y < 2; y++)
  for(int x = 0; x < 2; x++)
    mat32.at<unsigned int>(cv::Point(x,y)) = 0x0000FFFE + (y*2+x);

mat32.convertTo(mat16, CV_16UC1);

矩阵具有以下值:
32 bits matrix:
0000FFFE        0000FFFF
00010000        00010001

16 bits matrix:
0000FFFE        0000FFFF
0000FFFF        0000FFFF

在16位矩阵的第二行,我想要有:
00000000  00000001

我可以通过逐个扫描源矩阵的值并进行掩码处理来完成这个任务,但是性能较低。

是否有一个OpenCV函数可以实现这个功能?

感谢大家!

MIX


1
你的问题根本原因在于OpenCV在cv::Mat::convertTo()方法中应用了cv::saturate_cast。这在文档中明确说明了。 - sansuiso
2个回答

3
这是可以实现的,但需要使用一些比较“黑科技”的方法,因此使用这种方法取决于你自己。具体怎么做如下:
以创建一个1000x1000的32位矩阵为例,并将所有值设置为65541(=256*256+5)。 因此,在转换后,我们期望得到一个填充有数字5的矩阵。
Mat M1(1000, 1000, CV_32S, Scalar(65541));

以下是诀窍:

Mat M2(1000, 1000, CV_16SC2, M1.data);

我们在与M1相同的内存缓冲区上创建了矩阵M2,但是M2“认为”这是一个2通道16位图像的缓冲区。现在要做的最后一件事是将您需要的通道复制到所需位置。可以通过split()或mixChannels()函数来完成此操作。例如:

Mat M3(1000, 1000, CV_16S);
int fromto[] = {0,0};
Mat inpu[] = {M2}, outpu[] = {M3};
mixChannels(inpu, 1, outpu, 1, fromto, 1);
cout << M3.at<short>(10,10) << endl;

我知道mixChannels函数的格式看起来很奇怪,使得代码更难读懂,但它可以正常工作。如果你更喜欢split()函数:

vector<Mat> v;
split(M2,v);
cout << v[0].at<short>(10,10) << " " << v[1].at<short>(10,10) << endl;

我不确定你在评论中的意思。如果你的意思是解释不清楚,我可以尝试重新表述一下。 - Michael Burdinov
不,没关系。只是它跟我的通常做法相去甚远,所以我不得不阅读多次才能明白发生了什么。 - il_mix
不错的方法,但这个字节序是否不变?在小端或大端机器上会产生相同的结果吗? - MaartenVds

1
我不知道有没有OpenCV函数可以像您想要的那样进行转换,所以您要么自己编写代码,要么像您说的那样先通过掩码步骤去除16个高位。
掩码可以使用C++中的bitwise_and或C中的cvAndS来应用。请参见here
您还可以使手写代码更加高效。一般来说,应避免在循环中使用OpenCV像素访问器,因为它们的性能较差。我手头没有OpenCV安装,所以这可能略有偏差--思路是直接使用data字段和step,它是每行的字节数:
for(int y = 0; y < mat32.height; ++) {
  int* row = (int*)( (char*)mat32.data + y * mat32.step);
  for(int x = 0; x < mat32.step/ 4)
    row[x] &= 0xffff;

一旦应用了掩码,所有的值都适合16位,convertTo将只截断16个高位。

另一种解决方案是手动编写转换代码:

mat16.resize( mat32.size() );
for(int y = 0; y < mat32.height; ++) {
  const int* row32 = (const int*)( (char*)mat32.data + y * mat32.step);
  short*     row16 = (short*)    ( (char*)mat16.data + y * mat16.step);
  for(int x = 0; x < mat32.step/ 4)
    row16[x] = short(row32[x]);

好的,对我来说没有内置函数...我已经创建了转换函数。使用data字段似乎更有效(但不太可读)。我没有使用它,因为我在Mat类中找不到widthStep参数,就像我迄今为止在OpenCV 1.x中使用的IplImage(类)一样;然后我注意到了step参数。我真是太蠢了! - il_mix
确实,在Mat类中,它被称为step,我会更正我的答案。 - Antoine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接