优化获取 OpenCV Mat 通道的或运算结果

4

我希望将一个BGR cv::Mat转换成灰度图,使用这个公式:Gray=B OR G OR R; pixel-wise operation。我尝试了以下代码:

cv::Mat diff_channels[3];
cv::split(diff, diff_channels);
diff = diff_channels[0] | diff_channels[1] | diff_channels[2];

有更好的方法可以实现这个吗?

另外,如果我想要实现Gray=MAX(B,G,R); 像素级操作,有什么建议吗?


diff的声明是什么? - John Zwinck
请查看以下链接以获取最大值:https://dev59.com/M4Hba4cB1Zd3GeqPQ2t0#24688707。 - herohuyongtao
3个回答

6

OpenCV没有包含任何适合处理单独通道的内置函数。如果您想获得最佳性能,可以自己实现此过程。我建议您使用类似以下方式:

void calcOrChannels(const cv::Mat& src, cv::Mat& dst)
{
  CV_Assert(src.type() == CV_8UC3);
  int rows = src.rows, cols = src.cols;

  dst.create(src.size(), CV_8UC1);

  if (src.isContinuous() && dst.isContinuous())
  {
    cols = rows * cols;
    rows = 1;
  }

  for (int row = 0; row < rows; row++)
  {
    const uchar* src_ptr = src.ptr<uchar>(row);
    uchar* dst_ptr = dst.ptr<uchar>(row);

    for (int col = 0; col < cols; col++)
    {
      dst_ptr[col] = src_ptr[0] | src_ptr[1] | src_ptr[2]; // std::max(src_ptr[0], std::max(src_ptr[1], src_ptr[2]))
      src_ptr += 3;
    }
  }
}

请注意,在您的硬件上测试此功能的性能,因为它使用SIMD指令和并行实现(或者将来可能实现)而失去了一些好处。但是这个过程使用更少的额外内存和算术运算。我猜它在大多数系统上(尤其是嵌入式系统)可以更快地工作。它还取决于您的矩阵大小。
我的系统(Core i7-4790)上的计时结果如下:
| Matrix size | OpenCV (ms) | My (ms) |
|:-----------:|------------:|---------|
| 1280*720    | 4           | 1       |
| 1920*1080   | 8           | 2       |
| 4096*3112   | 41          | 17      |

谢谢,但在这种情况下,我不会失去OpenCV的优化,比如并行处理吗? - Humam Helfawi
1
@HumamHelfawi 很好的问题!我更新了答案。此外,您可以使用OpenCV的并行后端(cv::parallel_for)加速此函数。如果您在使用过程中遇到问题,请告诉我,我会帮助您。 - akarsakov
@akarsakov,我刚刚发布了一个带有parallel_for_的答案。总的来说,你的方法更好。 - Miki

6
你可以使用 cv::parallel_for_cv::ParallelLoopBody,来使用OpenCV并发API:
class ParallelBGRtoGrayOR : public ParallelLoopBody
{
    const Mat3b src;
    mutable Mat1b dst;

public:
    ParallelBGRtoGrayOR(const Mat3b& _src, Mat1b& _dst) : ParallelLoopBody(), src(_src), dst(_dst) {}

    virtual void operator()(const Range& range) const
    {
        int rows = range.end - range.start;
        int cols = src.cols;
        int len = rows * cols;

        const uchar* yS = src.ptr<uchar>(range.start);
        uchar* yD = dst.ptr<uchar>(range.start);

        for (int i = 0; i < len; ++i, yD++, yS += 3)
        {
            *yD = yS[0] | yS[1] | yS[2];
            //*yD = std::max(yS[0], std::max(yS[1], yS[2]));
        }
    }
};

void cvtBgrToGray_OR_Miki(const Mat3b& src, Mat1b& dst)
{
    dst.create(src.rows, src.cols);
    parallel_for_(Range(0, src.rows), ParallelBGRtoGrayOR(src, dst), -1);
}

测试

使用您和@akarsakov的方法进行测试,我得到了(以毫秒为单位的时间):

Size:           akarsakov       Humam Helfawi   Miki            OpenCV (not same operation)

[10 x 10]       0.00109963      0.0711094       2.60722         0.0934685
[100 x 100]     0.0106298       0.0373874       0.0461844       0.0395867
[1000 x 1000]   1.1799          3.30622         0.747382        1.61646
[1280 x 720]    1.07324         2.91585         0.520858        0.9893
[1920 x 1080]   2.31252         6.87818         1.11502         1.94011
[4096 x 3112]   14.3454         42.0125         6.79644         12.0754
[10000 x 10000] 115.575         321.145         61.1544         93.8846

注意事项

@akarsakov的方法(在原始数据上智能工作)通常是更好的方法,因为它非常快速且易于编写。只有在大型图像(至少在我的电脑上)上使用ParallelLoopBody才有一些优势。

我假设源图像是连续的。实际应该进行这个检查。

测试代码

您可以使用此代码在您的计算机上评估结果:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

class ParallelBGRtoGrayOR : public ParallelLoopBody
{
    const Mat3b src;
    mutable Mat1b dst;

public:
    ParallelBGRtoGrayOR(const Mat3b& _src, Mat1b& _dst) : ParallelLoopBody(), src(_src), dst(_dst) {}

    virtual void operator()(const Range& range) const
    {
        int rows = range.end - range.start;
        int cols = src.cols;
        int len = rows * cols;

        const uchar* yS = src.ptr<uchar>(range.start);
        uchar* yD = dst.ptr<uchar>(range.start);

        for (int i = 0; i < len; ++i, yD++, yS += 3)
        {
            *yD = yS[0] | yS[1] | yS[2];
            //*yD = std::max(yS[0], std::max(yS[1], yS[2]));
        }
    }
};

void cvtBgrToGray_OR_Miki(const Mat3b& src, Mat1b& dst)
{
    dst.create(src.rows, src.cols);
    parallel_for_(Range(0, src.rows), ParallelBGRtoGrayOR(src, dst), -1);
}


// credits to @akarsakov
void cvtBgrToGray_OR_akarsakov(const Mat3b& src, Mat1b& dst)
{
    int rows = src.rows, cols = src.cols;
    dst.create(src.size());
    if (src.isContinuous() && dst.isContinuous())
    {
        cols = rows * cols;
        rows = 1;
    }

    for (int row = 0; row < rows; row++)
    {
        const uchar* src_ptr = src.ptr<uchar>(row);
        uchar* dst_ptr = dst.ptr<uchar>(row);

        for (int col = 0; col < cols; col++)
        {
            dst_ptr[col] = src_ptr[0] | src_ptr[1] | src_ptr[2];
            //dst_ptr[col] = std::max(src_ptr[0], std::max(src_ptr[1], src_ptr[2]));
            src_ptr += 3;
        }
    }
}

// credits to @Humam_Helfawi
void cvtBgrToGray_OR_Humam_Helfawi(const Mat3b& src, Mat1b& dst)
{
    cv::Mat channels[3];
    cv::split(src, channels);
    dst = channels[0] | channels[1] | channels[2];
}



int main()
{
    vector<Size> sizes{ Size(10, 10), Size(100, 100), Size(1000, 1000), Size(1280, 720), Size(1920, 1080), Size(4096, 3112), Size(10000, 10000) };

    cout << "Size: \t\takarsakov \tHumam Helfawi \tMiki \tOpenCV (not same operation)" << endl;

    for (int is = 0; is < sizes.size(); ++is)
    {
        Size sz = sizes[is];

        cout << sz << "\t";

        Mat3b img(sz);
        randu(img, Scalar(0, 0, 0), Scalar(255, 255, 255));

        Mat1b gray_akarsakov;
        Mat1b gray_Miki;
        Mat1b gray_Humam;
        Mat1b grayOpenCV;


        double tic = double(getTickCount());

        cvtBgrToGray_OR_akarsakov(img, gray_akarsakov);

        double toc = (double(getTickCount()) - tic) * 1000. / getTickFrequency();
        cout << toc << " \t";



        tic = double(getTickCount());

        cvtBgrToGray_OR_Humam_Helfawi(img, gray_Humam);

        toc = (double(getTickCount()) - tic) * 1000. / getTickFrequency();
        cout << toc << " \t";



        tic = double(getTickCount());

        cvtBgrToGray_OR_Miki(img, gray_Miki);

        toc = (double(getTickCount()) - tic) * 1000. / getTickFrequency();
        cout << toc << " \t";


        tic = double(getTickCount());

        cvtColor(img, grayOpenCV, COLOR_BGR2GRAY);

        toc = (double(getTickCount()) - tic) * 1000. / getTickFrequency();
        cout << toc << endl;

    }


    getchar();


    return 0;
}

谢谢!第一次看到我的名字出现在别人的代码里 :D 非常棒的回答和努力,真的谢谢你。 - Humam Helfawi
干得好!感谢你的努力!只有一个小备注:int len = rows * cols; 只适用于连续矩阵(而不是子矩阵),但这种情况很少见。 - akarsakov
当然,我在@akarsakov的考虑中提到了这一点。 - Miki

1

只是想分享我的结果

Size:           akarsakov       Humam Helfawi   Miki    OpenCV (not same operation)
[10 x 10]       0.00733416      1.03216         1.15244         0.044005
[100 x 100]     0.078231        0.0816536       0.185799        0.043516
[1000 x 1000]   7.81039         5.89764         40.7481         3.49253
[1280 x 720]    7.61432         5.31824         8.74916         1.70397
[1920 x 1080]   16.0256         12.8186         9.32367         3.6045
[4096 x 3112]   97.7365         72.6287         49.3452         22.9545
[10000 x 10000] 763.509         575.718         402.729         197.01

编辑:我有一台新笔记本电脑,再次在它和旧的电脑上测试了代码。看起来结果的差异取决于OpenCV不同的构建配置。

1.Intel(R)_Core(TM)_i5-3230M_CPU_@_2.60GHz

Size:           akarsakov       Humam Helfawi   Miki    OpenCV (not same operation)
[10 x 10]       0.00276318      0.0627636       0.445661        0.0351318
[100 x 100]     0.0303949       0.0734216       0.0457898       0.0663162
[1000 x 1000]   3.01186         5.30727         2.11699         3.05805
[1280 x 720]    2.59975         4.91806         1.82014         2.69528
[1920 x 1080]   5.97478         11.5406         3.56213         5.52556
[4096 x 3112]   37.3076         64.1728         22.4575         35.0398
[10000 x 10000] 284.249         510.332         175.626         268.652

2.Intel(R)_Core(TM)2_Duo_CPU_____T6500__@_2.10GHz

Size:           akarsakov       Humam Helfawi   Miki    OpenCV (not same operation)
[10 x 10]       0.00586751      0.107571        24.1966         1.50844
[100 x 100]     0.0704101       0.154511        0.308044        0.119306
[1000 x 1000]   7.00825         11.072          3.44912         5.25778
[1280 x 720]    6.63322         9.88529         3.91999         5.0177
[1920 x 1080]   14.6199         21.8047         7.19357         10.9551
[4096 x 3112]   85.8226         133.165         42.4392         64.2184
[10000 x 10000] 675.604         1050.19         334.334         507.87

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