C++ - OpenCV 大坑

  踩了1天的opencv坑,忍不住吐槽了。。。

  正常两个矩阵运算操作不能适用于一系列的 8U 图像,这是最大的坑,比如你想要将两个图像矩阵相加:

Mat im1 = imread("../opencv/samples/data/pic5.png", CV_8UC1);
Mat im2 = imread("../opencv/samples/data/pic6.png", CV_8UC1);
Mat overlap = im1 + im2;

  如果你这么写,得到的图像绝对是错误的!!!你必须做一些特殊转换。

  嘛,首先运行后会看到结果是:

  这里 im1 和 im2 是这样的(opencv 自带的示例图像):

 

  (im1)

  (im2)

  诈一看觉得好像是对的,但实际上正确的是这样的(运行于 python opencv):

  而 C++ 会出现这样的原因是因为 8U 图像的值范围是 0~255,Mat 在做相加操作的时候大于255的那些就溢出了。。然后那些因为相加而溢出的像素的值就会是 255,起初我写了这个方法来修正:

Mat matr_add(Mat im1, Mat im2)
{
    CV_Assert(im1.size() == im2.size());
    Mat uxx = Mat::zeros(im1.size(), CV_8UC1);
    for (int i = 0; i < uxx.rows; i++)
    {
        for (int j = 0; j < uxx.cols; j++)
        {
            uxx.at<uchar>(i, j) = (im1.at<uchar>(i, j) + im2.at<uchar>(i, j)) % 256;
        }
    }
    return uxx;
}

  运行结果:

  正确了。

  执行矩阵相乘的时候就更明显了(C++ opencv):

  而正确的(运行于 python opencv):

  同样用取模的方法修正:

Mat multiply_mod(Mat im1, Mat im2)
{
    CV_Assert(im1.size() == im2.size());
    Mat uxx = Mat::zeros(im1.size(), CV_8UC1);
    for (int i = 0; i < uxx.rows; i++)
    {
        for (int j = 0; j < uxx.cols; j++)
        {
            uxx.at<uchar>(i, j) = (im1.at<uchar>(i, j) * im2.at<uchar>(i, j)) % 256;
        }
    }
    return uxx;
}

  运行结果:

  修正了。

  同理,相减也是一样需要修复:

Mat matr_sub(Mat im1, Mat im2)
{
    CV_Assert(im1.size() == im2.size());
    Mat uxx = Mat::zeros(im1.size(), CV_8UC1);
    for (int i = 0; i < uxx.rows; i++)
    {
        for (int j = 0; j < uxx.cols; j++)
        {
            uxx.ptr<uchar>(i)[j] = (im1.ptr<uchar>(i)[j] - im2.ptr<uchar>(i)[j]) < 0 ? (im1.ptr<uchar>(i)[j] - im2.ptr<uchar>(i)[j]) % 256 : (im1.ptr<uchar>(i)[j] - im2.ptr<uchar>(i)[j]);
        }
    }
    return uxx;
}

  很美好,看起来似乎解决了所有问题。

  但实际上问题没这么简单,还有个问题在于相除的情况怎么才能修复,因为一些情况下对图像做了一些操作后会得到一个含有0(原因也是因为用 8UC1 类型的 Mat 存储像素,所以导致 0.xxx 的值直接按 0 处理)的矩阵,这时我没办法再修复了,但我又想要获得或者设置正确的浮点像素值,这个问题直接导致了我写的图像相似度检测算法没法正常用...

  于是,重新冷静考虑一下。

  首先要清楚的是,直接使用 CV_32FC1 读取图像是不行的,不出意外会得到很多 Nan 或 inf 值,从而图像也无法正常显示。

  以及我们千万别使用下面几种情况的代码访问像素元素(以下皆假设使用 C1 单通道图像):

  情况1:假设 im 类型为 CV_8UC1

im.at<int>(row, col);
im.at<float>(row, col);
//...

  不然,你会发现明明你的图像是 600 x 600,却读到小部分,甚至一进循环就报 opencv_assert 的断言中断错误。参见:https://github.com/kinchungwong/kinchungwong.github.io/blob/master/opencv_issues_11370/explain_opencv_non_issue_11370.md

  对于 8U 系列,你只能使用这种方式:

im.at<uchar>(row, col)
im.ptr<T>(row)[col];

  其他情况,对于其他类型的 Mat,最好是用这种方式获取或设置像素元素信息:

im.ptr<T>(row)[col];

  绝不能使用 at<T>() 去访问,因为它是靠类型进行内存地址计算的,所以一定会超出范围。程序会立即中断。

  回到主题。

  实际上,我们想保证正确且方便的进行图像的矩阵运算,要做的其实只有一步,首先,我们使用 CV_8UC1 (以单通道图像为例)读取图像,然后将其矩阵类型转换为 CV_32FC1 就可以了!就是这么简单却搞了我好久:

Mat im1 = imread("../opencv/samples/data/lena.jpg", CV_8UC1);
Mat im1f;

im1.convertTo(im1f, CV_32FC1);
// ...

  于是,我的写的 SSIM 算法可以用上了,下一篇我会完整介绍 SSIM 算法~

猜你喜欢

转载自www.cnblogs.com/darkchii/p/12677412.html
今日推荐