首页 > 解决方案 > 将 OpenCV 原始指针和 lambda 用于直方图的不同结果

问题描述

尝试使用 lambda 重写 OpenCV 代码,因为它们非常快。但结果显示直方图值并不完全相同,影响了之后的计算。

这是我看到的一些日志。我期待相同的颜色通道(r,g,b),它们的直方图值应该相同。

hist b 255= 274163
hist2 b255= 271049
hist g 255= 260360
hist2 g255= 258447
hist r 255= 257104
hist2 r255= 255348
Elapsed time:136759us (136.759ms)
hist b 255= 274289
hist2 b255= 271266
hist g 255= 260346
hist2 g255= 258108
hist r 255= 257084
hist2 r255= 255236
Elapsed time:135269us (135.269ms)
hist b 255= 274294
hist2 b255= 271183
hist g 255= 260342
hist2 g255= 258242
hist r 255= 257099
hist2 r255= 255197
Elapsed time:142417us (142.417ms)
hist b 255= 274218
hist2 b255= 271021
hist g 255= 260375
hist2 g255= 258768
hist r 255= 257039
hist2 r255= 255643
Elapsed time:138296us (138.296ms)

这是我的代码

// use raw pointer access
std::vector<std::vector<int>> hists(3,std::vector<int>(256,0));
for (int y = 0; y < _opencvImage.rows; ++y)
{
    uchar *ptr = _opencvImage.ptr<uchar>(y);
    for (int x = 0; x < _opencvImage.cols; ++x) 
    {
        for (int j = 0; j < 3; ++j)
        {
            hists[j][ptr[x * 3 + j]] += 1;
        }
    }
}

// use lambda
std::vector<std::vector<int>> hist_test(3,std::vector<int>(256,0));
_opencvImage.forEach<Pixel>(
[&hist_test] (Pixel &pixel, const int *pos) -> void {
            hist_test[0][pixel.x]++;
            hist_test[1][pixel.y]++;
            hist_test[2][pixel.z]++;
    }
);

printf("hist b 255= %d\r\n", hists[0][255]);
printf("hist2 b255= %d\r\n", hist_test[0][255]);
printf("hist g 255= %d\r\n", hists[1][255]);
printf("hist2 g255= %d\r\n", hist_test[1][255]);
printf("hist r 255= %d\r\n", hists[2][255]);
printf("hist2 r255= %d\r\n", hist_test[2][255]);

标签: c++opencvlambda

解决方案


您遇到的问题是竞争条件,我简化了您的示例,并尝试了解您使用的类型Pixel

int main()
{
    using Pixel = cv::Point3_<uint8_t>;
    cv::Mat _opencvImage(120, 120, CV_8UC3, cv::Scalar(0, 0, 0));

    std::vector<std::vector<int>> hist_test(3, std::vector<int>(256, 0));
    _opencvImage.forEach<Pixel>(
        [&hist_test](Pixel& pixel, const int* pos) -> void {
            hist_test[0][pixel.x]++;
            hist_test[1][pixel.y]++;
            hist_test[2][pixel.z]++;
        }
    );


    printf("hist2 b255= %d\r\n", hist_test[0][0]);
    printf("hist2 g255= %d\r\n", hist_test[1][0]);
    printf("hist2 r255= %d\r\n", hist_test[2][0]);
}

我们知道我打印的三个值应该是 120x120=14400,但是当您运行代码时,您很可能会看到不同的值。这是由于竞争条件,因为forEach根据文档

使用参数调用仿函数,并在所有矩阵元素上运行仿函数。

这些方法并行运行。

因此,当您有 2 个线程在其中一个通道中查看具有相同值的两个像素时,您就会遇到会产生未定义行为的竞赛,从而破坏您的结果。

为了说服自己(这不是您应该使用的解决方案),您可以像这样重写程序:

#include <mutex>
int main()
{
    using Pixel = cv::Point3_<uint8_t>;
    cv::Mat _opencvImage(120, 120, CV_8UC3, cv::Scalar(0, 0, 0));

    std::vector<std::vector<int>> hist_test(3, std::vector<int>(256, 0));
    std::mutex mx;
    _opencvImage.forEach<Pixel>(
        [&hist_test, &mx](Pixel& pixel, const int* pos) -> void {
            std::lock_guard<std::mutex> lck{ mx };
            hist_test[0][pixel.x]++;
            hist_test[1][pixel.y]++;
            hist_test[2][pixel.z]++;
        }
    );


    printf("hist2 b255= %d\r\n", hist_test[0][0]);
    printf("hist2 g255= %d\r\n", hist_test[1][0]);
    printf("hist2 r255= %d\r\n", hist_test[2][0]);
}

序列化对直方图的访问可以让您获得正确的结果,但显然会抵消使用更多线程带来的性能提升。

现在,有很多方法可以获得您想要的性能,例如您可以并行计算不相交区域中的直方图,然后将它们相加。但我建议使用 OpenCV 的calcHist函数,它完全符合您的要求,您可以在此处找到官方教程。我浏览了我可以看到已经利用加速的实现(我可以看到对 OpenVX 和 IPP 实现的调用)。所以,我认为你的代码不太可能比 OpenCV 已经提供的代码执行得更好,除非你有一个非常特殊的用例并且你愿意投入大量时间。calcHistcalcHist


推荐阅读