OpenCV и переупорядочение байтов из разных каналов

В OpenCV при использовании таких функций, как cv::imread() для чтения файлов JPEG, вы получаете cv::Mat, где данные хранятся как B, G, R, B, G, R. , и т. д...

Мне нужно, чтобы каналы были вместе, а не... «переплетались» значения. Поэтому вместо B, G, R, ... мой объект cv::Mat должен быть отформатирован как B, B, B, ..., G, G, G, ..., R, R, R, ... (Из-за библиотеки, которую я вызываю, которая ожидает данные изображения в таком порядке.)

Проблема в том, что я даже не знаю, как это называется в отрасли, поэтому не знаю, что гуглить. «Чересстрочная развертка» и «деинтерлейсинг» — единственное, о чем я могу думать, но я не думаю, что это правильные термины.

Независимо от отраслевого термина, я могу придумать два способа сделать это:

  1. Вызов cv::split() для разделения 3 каналов BGR на разные cv::Mat объекты, а затем объединение результатов. Поскольку мне нужно проделать это со многими большими изображениями, я решил посмотреть, есть ли у OpenCV другое решение, которое не требует трех ненужных копий во временные объекты мата.
  2. Я мог бы for() перебирать cv::Mat объекты и копировать байты по мере необходимости, чтобы «деинтерлейсить» каналы. Но опять же, если в OpenCV уже есть функция для выполнения такого рода работы, я бы предпочел вызвать существующие функции OpenCV.

(Для ясности: речь идет не о cv::cvtColor(src, dst, cv::BGR2RGB). Я не хочу поменять местами два канала, я хочу, чтобы каналы были вместе.)

Я думаю, что вам нужен термин «упакованный в плоскости» (или «плоский»), а не «упакованный в пикселях».

G.M. 23.08.2024 12:02

«3 ненужных копии во временные объекты мата» — сделайте местом назначения split 3 представления подходящего размера (созданные, скажем, cv::Mat::rowRange) полного результата Mat. Временники не нужны. Если целевой массив имеет правильную форму и тип данных, перераспределение не выполняется.

Dan Mašek 23.08.2024 12:23

@DanMašek Хотите объяснить? Как бы cv:split() не копировал 3 чересстрочных байта BGR по всему изображению для создания различных объектов канала cv::Mat?

Stéphane 23.08.2024 12:27

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

Dan Mašek 23.08.2024 12:29

Пример: pastebin.com/tDHH3TBQ

Dan Mašek 23.08.2024 12:45

@DanMašek, это хорошее решение, которое стоит опубликовать в качестве ответа, ИМХО (я обязательно поддержу).

wohlstad 23.08.2024 12:51

@wohlstad Хорошо, я напишу это сегодня позже.

Dan Mašek 23.08.2024 15:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

На основании вашего описания желаемого результата

в формате B, B, B, ..., G, G, G, ..., R, R, R, ...

Я предполагаю, что выходное изображение должно быть одноканальным Mat той же ширины, но в 3 раза больше высоты входного изображения. По сути, это 3 кадра в оттенках серого, наложенных друг на друга: первый представляет синий канал, второй — зеленый, третий — красный.

Ваша первоначальная идея использования cv::split() хороша, но вы упустили способ реализовать ее так, чтобы «не требовалось 3 ненужных копирования во временные объекты мата». На самом деле этого довольно легко добиться, и я объясню, как, но сначала давайте пробежимся по некоторым предысториям.


Прежде всего, большинство функций OpenCV предоставляют массив назначения. Часто это может быть пустой общий cv::Mat (или, возможно, их вектор), и функция будет выделять Mat подходящей формы и типа, необходимые для хранения вывода. Однако, если предоставлен уже выделенный Mat и он имеет правильную форму и тип данных, он будет повторно использован (выходные данные будут записаны в него Mat).

Во-вторых, важно понимать, как работает cv::Mat. По сути, это интеллектуальный указатель с некоторыми метаданными и указателем на массив байтов. Метаданные сообщают ему, как интерпретировать массив. Это означает, что у вас может быть несколько Mat, указывающих на одни и те же данные или подразделы этих данных (так называемые «представления»).


Учитывая это, стратегия ясна.

  1. Выделите одноканальное изображение result той же ширины и в 3 раза превышающее высоту входа BGR.
  2. Создайте последовательность представлений цветовой плоскости result, которую мы можем передать cv::split как OutputArrayOfArrays. В данном случае это означает std::vector<cv::Mat> с тремя элементами, каждый из которых представляет собой представление определенной цветовой плоскости result. Мы можем получить необходимые представления, используя cv::Mat::rowrange и простую арифметику.
  3. Звоните cv::split.

Вот тривиальный пример, демонстрирующий вышеупомянутую стратегию:

#include <opencv2/opencv.hpp>

int main()
{
    uint8_t data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
    cv::Mat bgr(2, 2, CV_8UC3, data);

    cv::Mat result = cv::Mat1b(bgr.rows * 3, bgr.cols);

    std::vector<cv::Mat> views = {
        result.rowRange(0, bgr.rows)
        , result.rowRange(bgr.rows, bgr.rows * 2)
        , result.rowRange(bgr.rows * 2, bgr.rows * 3)
    };
    cv::split(bgr, views);

    std::cout << bgr << '\n';

    std::cout << result << '\n';

    return 0;
}

Мы создаем BGR-изображение 2x2 со значениями пикселей:

  • вверху слева B = 0, G = 1, R = 2
  • вверху справа B = 3, G = 4, R = 5
  • внизу слева B = 6, G = 7, R = 8
  • внизу справа B = 9, G = 10, R = 11

Затем разделяем его по вашим требованиям, и у нас должно получиться:

  • 0, 3; 6, 9 для самолета B
  • 1, 4; 7, 10 для плоскости G
  • 2, 5; 8, 11 для самолета R

Приведенная выше программа выводит следующее:

[  0,   1,   2,   3,   4,   5;
   6,   7,   8,   9,  10,  11]
[  0,   3;
   6,   9;
   1,   4;
   7,  10;
   2,   5;
   8,  11]

Это именно то, на что я надеялся, но не мог понять, как это сделать. Спасибо.

Stéphane 24.08.2024 05:29

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