opencv中的cv :: imshow仅显示合成图像的一部分,但是单独显示这些部分是可行的。为什么?

时间:2019-01-17 23:54:59

标签: c++ image opencv highgui

目标与问题

我正在尝试使用OpenCV 3.4.1快速处理视频文件,方法是抓取每一帧,转换为灰度,然后对其进行Canny边缘检测。为了实时显示图像,我创建了一个Mat类,其中包含3个额外的标题,其宽度是原始帧的三倍。这3个额外的标题代表了我想在合成中显示的图像,并位于合成的第1,第2和第3水平段。

但是,在图像处理之后,合成图像的显示不符合预期:the first segment (where the original frame should be) is completely black,而其他部分(已处理图像)的显示效果很好。另一方面,如果我display the ROIs one by one in separate windows,则所有图像看起来都很好。

这些是我试图克服此问题的方法:

  1. 使用.copyTo将数据实际复制到适当的图像段中。结果是一样的。
  2. 我将Canny图像放置到compOrigPart ROI中,并且确实在第一段中显示了图像,因此ROI的定义不是问题。
    • 将合成图像定义为三通道图像
    • 在循环中将其转换为灰度
    • 将处理后的图像放入其中
    • 转换回BGR
    • 放入原件。

这次,整个复合材料都是黑色的,什么也没显示。

  1. 按照gameon67的建议,我也尝试创建一个namedWindow,但这也无济于事。

代码:

int main() {

    cv::VideoCapture vid("./Vid.avi");
    if (!vid.isOpened()) return -1;

    int frameWidth = vid.get(cv::CAP_PROP_FRAME_WIDTH);
    int frameHeight = vid.get(cv::CAP_PROP_FRAME_HEIGHT);
    int frameFormat = vid.get(cv::CAP_PROP_FORMAT);

    cv::Scalar fontColor(250, 250, 250);
    cv::Point textPos(20, 20);

    cv::Mat frame;

    cv::Mat compositeFrame(frameHeight, frameWidth*3, frameFormat);
    cv::Mat compOrigPart(compositeFrame, cv::Range(0, frameHeight), cv::Range(0, frameWidth));
    cv::Mat compBwPart(compositeFrame, cv::Range(0, frameHeight), cv::Range(frameWidth, frameWidth*2));
    cv::Mat compEdgePart(compositeFrame, cv::Range(0, frameHeight), cv::Range(frameWidth*2, frameWidth*3));


    while (vid.read(frame)) {
        if (frame.empty()) break;

        cv::cvtColor(frame, compBwPart, cv::COLOR_BGR2GRAY);
        cv::Canny(compBwPart, compEdgePart, 100, 150);
        compOrigPart = frame;

        cv::putText(compOrigPart, "Original", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);
        cv::putText(compBwPart, "GrayScale", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);
        cv::putText(compEdgePart, "Canny edge detection", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);

        cv::imshow("Composite of Original, BW and Canny frames", compositeFrame);
        cv::imshow("Original", compOrigPart);
        cv::imshow("BW", compBwPart);
        cv::imshow("Canny", compEdgePart);
        cv::waitKey(33);
    }
}

问题

  • 为什么不能在单个窗口中显示整个合成图像,而分别显示可以吗?
  • 这些显示之间有什么区别?数据显然在那里,如单独的窗口所示。
  • 为什么只有原始帧出现异常?

2 个答案:

答案 0 :(得分:0)

您的compBwPart和compEdgePart是灰度图像,因此Mat类型是CV8UC1-单通道,因此您的CompositeFrame也是灰度的。如果要将这两个图像与彩色图像组合在一起,则必须先将其转换为BGR,然后填充compOrigPart。

while (vid.read(frame)) {
  if (frame.empty()) break;

  cv::cvtColor(frame, compBwPart, cv::COLOR_BGR2GRAY);
  cv::Canny(compBwPart, compEdgePart, 100, 150);
  cv::cvtColor(compositeFrame, compositeFrame, cv::COLOR_GRAY2BGR);
  frame.copyTo(compositeFrame(cv::Rect(0, 0, frameWidth, frameHeight)));

  cv::putText(compOrigPart, "Original", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor); //the rest  of your code

答案 1 :(得分:0)

这是几个问题的组合。

第一个问题是将compositeFrame的类型设置为vid.get(cv::CAP_PROP_FORMAT)返回的值。不幸的是,该属性似乎并不完全可靠-我只是在打开彩色视频后获得3通道(CV_8UC1)帧后才返回0(表示CV_8UC3)。由于您想让compositeFrame与输入框具有相同的类型,因此无法使用。

要解决此问题,而不是使用这些属性,我会在收到第一帧(根据其尺寸和类型)后懒惰初始化compositeFrame和3个ROI。


下一组问题在于这两个语句:

cv::cvtColor(frame, compBwPart, cv::COLOR_BGR2GRAY);
cv::Canny(compBwPart, compEdgePart, 100, 150);

在这种情况下,假设frame是BGR(因为您要进行转换),这意味着compositeFrame及其ROI也是BGR。不幸的是,在两种情况下,您都将灰度图像写入ROI。这将导致重新分配,并且目标Mat将不再是ROI。

要解决此问题,请为灰度数据使用临时Mat,然后使用cvtColor将其恢复为BGR以写入ROI。


类似的问题在于以下陈述:

compOrigPart = frame;

这是一个浅表副本,这意味着它只会使compOrigPart再次引用frame(因此它将不再是compositeFrame的ROI)。

您需要的是使用copyTo的深层副本(请注意,数据类型仍需要匹配,但是之前已解决)。


最后,即使您尝试在输入视频的类型上保持灵活性(根据vid.get(cv::CAP_PROP_FORMAT)判断),其余代码也确实假定输入为3通道,否则将中断输入。

至少应该有一些断言来满足这一期望。


将所有内容放在一起:

#include <opencv2/opencv.hpp>

int main()
{
    cv::VideoCapture vid("./Vid.avi");
    if (!vid.isOpened()) return -1;

    cv::Scalar fontColor(250, 250, 250);
    cv::Point textPos(20, 20);

    cv::Mat frame, frame_gray, edges_gray;
    cv::Mat compositeFrame;
    cv::Mat compOrigPart, compBwPart, compEdgePart; // ROIs

    while (vid.read(frame)) {
        if (frame.empty()) break;

        if (compositeFrame.empty()) {
            // The rest of code assumes video to be BGR (i.e. 3 channel)
            CV_Assert(frame.type() == CV_8UC3);
            // Lazy initialize once we have the first frame
            compositeFrame = cv::Mat(frame.rows, frame.cols * 3, frame.type());
            compOrigPart = compositeFrame(cv::Range::all(), cv::Range(0, frame.cols));
            compBwPart = compositeFrame(cv::Range::all(), cv::Range(frame.cols, frame.cols * 2));
            compEdgePart = compositeFrame(cv::Range::all(), cv::Range(frame.cols * 2, frame.cols * 3));
        }

        cv::cvtColor(frame, frame_gray, cv::COLOR_BGR2GRAY);
        cv::Canny(frame_gray, edges_gray, 100, 150);

        // Deep copy data to the ROI
        frame.copyTo(compOrigPart);
        // The ROI is BGR, so we need to convert back
        cv::cvtColor(frame_gray, compBwPart, cv::COLOR_GRAY2BGR);
        cv::cvtColor(edges_gray, compEdgePart, cv::COLOR_GRAY2BGR);

        cv::putText(compOrigPart, "Original", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);
        cv::putText(compBwPart, "GrayScale", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);
        cv::putText(compEdgePart, "Canny edge detection", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);

        cv::imshow("Composite of Original, BW and Canny frames", compositeFrame);
        cv::imshow("Original", compOrigPart);
        cv::imshow("BW", compBwPart);
        cv::imshow("Canny", compEdgePart);
        cv::waitKey(33);
    }
}

复合窗口的屏幕截图(使用网络上的一些随机测试视频):

Example Composite frame