cv::imshow в opencv отображает только части составного изображения, но работает отдельно. Зачем?

Цель и проблема

Я пытаюсь обработать видеофайл на лету, используя OpenCV 3.4.1, захватывая каждый кадр, преобразуя его в градации серого, а затем определяя края Канни на нем. Чтобы отобразить изображения (также на лету), я создал класс Mat с 3 дополнительными заголовками, который в три раза шире исходного кадра. 3 дополнительных заголовка представляют изображения, которые я хотел бы отобразить в композите, и расположены в 1, 2 и 3-м горизонтальных сегментах композита.

Однако после обработки изображения отображение составного изображения не соответствует ожидаемому: первый сегмент (где должен быть исходный кадр) полностью черный, а остальные сегменты (обработанных изображений) отображаются нормально. Если, с другой стороны, я отображаю области интереса по одному в отдельных окнах, все изображения выглядят нормально.

Вот то, что я пытался преодолеть эту проблему:

  1. используйте.copyTo для фактического копирования данных в соответствующие сегменты изображения. Результат был таким же.
  2. Я поместил изображение Canny в ROI compOrigPart, и оно отображалось в первом сегменте, так что это не проблема с определением 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 ответа

Ваши compBwPart и compEdgePart являются изображениями в градациях серого, поэтому тип Mat равен CV8UC1 - один канал, и поэтому ваш ComofFrame также находится в градациях серого. Если вы хотите объединить эти два изображения с цветным изображением, вы должны сначала преобразовать его в 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

Это сочетание нескольких вопросов.

Первая проблема заключается в том, что вы устанавливаете тип compositeFrame к значению, возвращенному vid.get(cv::CAP_PROP_FORMAT), К сожалению, это свойство не кажется полностью надежным - я только что вернул 0 (имеется в виду CV_8UC1) после открытия цветного видео, а затем получения 3 канала (CV_8UC3) кадры. Так как вы хотите иметь compositeFrame того же типа, что и входной кадр, это не сработает.

Чтобы обойти это, вместо использования этих свойств, я бы ленивый инициализировать compositeFrame и 3 области интереса после получения первого кадра (в зависимости от его размеров и типа).


Следующий набор проблем заключается в этих двух утверждениях:

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

В этом случае делается предположение, что frame это BGR (так как вы пытаетесь конвертировать), то есть compositeFrame и его ROI также являются BGR. К сожалению, в обоих случаях вы записываете изображение в градациях серого в ROI. Это приведет к перераспределению, и цель Mat перестанет быть ROI.

Чтобы исправить это, используйте временный Mat s для данных в градациях серого и используйте cvtColor чтобы вернуть его в BGR, чтобы записать в ROI.


Аналогичная проблема заключается в следующем утверждении:

compOrigPart = frame;

Это мелкая копия, то есть она просто сделает compOrigPart еще одна ссылка на frame (и, следовательно, он перестанет быть ROI compositeFrame).

Что вам нужно, это глубокая копия, используя 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);
    }
}

Снимок экрана составного окна (с использованием случайного тестового видео из Интернета):

Другие вопросы по тегам