zoukankan      html  css  js  c++  java
  • C++

      踩了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 算法~

  • 相关阅读:
    450. 删除二叉搜索树中的节点
    958. 二叉树的完全性检验
    5211. 概率最大的路径(197)
    5447. 石子游戏 IV
    174. 地下城游戏
    Codeforces Round #622 (Div. 2).C2
    Codeforces Round #624 (Div. 3) F. Moving Points 题解
    竞赛头
    离散化
    线段树
  • 原文地址:https://www.cnblogs.com/darkchii/p/12677412.html
Copyright © 2011-2022 走看看