c++ - 将 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]);
解决方案
您遇到的问题是竞争条件,我简化了您的示例,并尝试了解您使用的类型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 已经提供的代码执行得更好,除非你有一个非常特殊的用例并且你愿意投入大量时间。calcHist
calcHist
推荐阅读
- javascript - 在 React-datepicker 上禁用特定日期
- r - 计算宏观系列的年初至今百分比变化
- scala - 如何在一个查询中插入和更新 ReactiveMongo Scala
- lotus-domino - Lotus Notes 脚本
- bash - 当运算符存储在变量中时,如何在 bash 中进行算术运算?
- javascript - 使用 Javascript 解析 SOAP 响应
- c# - 当我尝试以一对一关系添加新对象时,EF Core 更新以前的对象 ID 值
- google-sheets - 我正在尝试通过 Appscript 将我的谷歌表格数据推送到 Mailchimp
- python - 单击重置按钮(重置在 OptionMenus 上所做的选择)后,OptionMenu 对其他 OptionMenus 的依赖不起作用
- python - 通过符号链接运行 python 脚本不允许编写小 L