为什么任何 OpenCv (C++) 减法运算都会产生全黑图像?

Why does any OpenCv (C++) substract operation result in a full black image?

我想首先说明我当然不是使用 OpenCV 的初学者。但我一直主要在 C# 中使用它,现在我完全迷失在一个我似乎无法解决的 C++ 变体的简单问题上......

问题:每当我使用任何从一个图像中减去另一个图像的操作时,输出图像都是 100% 黑色。每 50 张图像中有 1 张部分显示预期结果,但也部分显示全黑(最后我将 link 示例图像)。澄清一下,全黑是指每个像素值都为 0。在此操作后我预计会有相当多的可见噪声,但 none 存在。

这是我的一段简单的调试代码:

Mat moveImage, stillImage, subst;

while(capture) {
  moveImage = CameraClass::cameraVector[0]->imageList.front().clone();
  stillImage = CameraClass::cameraVector[0]->imageList.back().clone();

  cv::resize(moveImage, moveImage, Size(moveImage.cols * 0.5, moveImage.rows * 0.5), INTER_LINEAR);
  cv::resize(stillImage, stillImage, Size(stillImage.cols * 0.5, stillImage.rows * 0.5), INTER_LINEAR);

  cv::addWeighted(moveImage, 1, stillImage, -1, 0, subst, CV_8UC1);

  stringstream ss, ss2, ss3;

  ss << path + "/camtest/image" + to_string(ui32FrameCount) + ".png";
  ss2 << path + "/camtest2/image" + to_string(ui32FrameCount) + ".png";
  ss3 << path + "/camtest3/image" + to_string(ui32FrameCount) + ".png";

  string filepath1 = ss.str();
  string filepath2 = ss2.str();
  string filepath3 = ss3.str();

  cv::imwrite(filepath1, moveImage);
  cv::imwrite(filepath2, stillImage);
  cv::imwrite(filepath3, subst);
}

我的结果是从 moveImagestillImage 保存的 2 张非常漂亮的图像,而 subst 完全是黑色的。

addWeighted 选项已经是尝试从我这边解决它。我也试过:

cv::subtract(moveImage, stillImage, subst);

还有C++操作:

subst = moveImage - stillImage;

所有结果都是一样的,黑色图像。我尝试以所有这些不同的方式添加图像,输出结果完全没问题。所以它一定是减法运算的结果,也许值低于 0?但是 CV_8UC1 MatType 应该自动截断低于 0 的值吗?

以下是一些示例图像和结果:

Partly black, partly noise (Had to amplify the noise or else it got lost in upload compression)

100% black image result

moveImage frame 1

stillImage frame 1

可能重要的其他信息:

  1. 我的密码是Threaded/Asynchronous。这段代码示例在一个线程中并且是独立的。由于 moveImage 和 stillImage 都很好,我不明白为什么其他线程或代码会严重影响我的减法操作。 Subst 仅存在于这段代码中,在其他任何地方都无法使用或访问。
  2. 目前我不会释放我的 Mat 资源。我在调试过程中尝试这样做,但结果没有任何区别。
  3. 现在所有 3 个 Mat 变量都在这段代码的循环之前声明一次,然后一遍又一遍地使用。我不知道这是否是个问题,但我也已经尝试在每个循环中再次声明它们。
  4. 此代码的每个循环 stillImage 和 moveImage 都使用来自相机的新帧进行更新。也许减法操作以某种方式在某处保留了引用而不是副本?
  5. moveImage 和 stillImage 的大小是完全一样的,在整个运行过程中不会改变。

很容易发生 cv::Mat 在列表或数组中共享它们的数据内存,这样如果更新了一个图像,所有图像仍然指向相同的内存。例如,如果捕获在每次迭代中更新相同的 cv::Mat 元素,并且此 cv::Mat 被推回到没有 deep-copying 像素的列表或向量(使用 .clone().copyTo()).

std::vector<cv::Mat> buffer;
cv::Mat img;
while(true)
{
    bool success = capture.read(img);
    //buffer.push_back(img); // in this case Mat objects will share their memory!
    buffer.push_back(img.clone()); // this one is ok
}

另一种不太常见的情况是使用已知图像大小初始化 cv::Mat 并创建具有特定大小和 default-object 的容器。在这种情况下,所有 objects 将共享它们的数据内存。

cv::Mat img = cv::Mat::zeros(cv::Size(knownWidth, knownHeight), CV_8UC3);
// WARNING: this initialized element creation will lead to shared memory between different Mat objects in the buffer!
std::vector<cv::Mat> buffer(BUFFER_SIZE, img);

这两种情况的原因是 cv::Mat 基本上是一些引用计数智能 header 可以被复制而不需要复制底层像素内存 space,所以显式 deep-copying 如果不共享内存,则需要数据。

对于此类情况的调试:

确保在您的 function/loop 中,两张图片不相同。您可以使用 cv::absdiff 并通过 > 0 对结果进行阈值处理。如果有任何白色像素,则图像内容不相同。