В 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, ... (Из-за библиотеки, которую я вызываю, которая ожидает данные изображения в таком порядке.)
Проблема в том, что я даже не знаю, как это называется в отрасли, поэтому не знаю, что гуглить. «Чересстрочная развертка» и «деинтерлейсинг» — единственное, о чем я могу думать, но я не думаю, что это правильные термины.
Независимо от отраслевого термина, я могу придумать два способа сделать это:
cv::Mat
объекты, а затем объединение результатов. Поскольку мне нужно проделать это со многими большими изображениями, я решил посмотреть, есть ли у OpenCV другое решение, которое не требует трех ненужных копий во временные объекты мата.for()
перебирать cv::Mat
объекты и копировать байты по мере необходимости, чтобы «деинтерлейсить» каналы. Но опять же, если в OpenCV уже есть функция для выполнения такого рода работы, я бы предпочел вызвать существующие функции OpenCV.(Для ясности: речь идет не о cv::cvtColor(src, dst, cv::BGR2RGB). Я не хочу поменять местами два канала, я хочу, чтобы каналы были вместе.)
«3 ненужных копии во временные объекты мата» — сделайте местом назначения split
3 представления подходящего размера (созданные, скажем, cv::Mat::rowRange
) полного результата Mat
. Временники не нужны. Если целевой массив имеет правильную форму и тип данных, перераспределение не выполняется.
@DanMašek Хотите объяснить? Как бы cv:split() не копировал 3 чересстрочных байта BGR по всему изображению для создания различных объектов канала cv::Mat?
Очевидно, что произойдет одна итерация копирования для переупорядочения данных, я говорю о том, чтобы избежать временного набора матов и «затем объединить результаты» (что является второй, ненужной итерацией копирования).
Пример: pastebin.com/tDHH3TBQ
@DanMašek, это хорошее решение, которое стоит опубликовать в качестве ответа, ИМХО (я обязательно поддержу).
@wohlstad Хорошо, я напишу это сегодня позже.
На основании вашего описания желаемого результата
в формате B, B, B, ..., G, G, G, ..., R, R, R, ...
Я предполагаю, что выходное изображение должно быть одноканальным Mat
той же ширины, но в 3 раза больше высоты входного изображения. По сути, это 3 кадра в оттенках серого, наложенных друг на друга: первый представляет синий канал, второй — зеленый, третий — красный.
Ваша первоначальная идея использования cv::split()
хороша, но вы упустили способ реализовать ее так, чтобы «не требовалось 3 ненужных копирования во временные объекты мата». На самом деле этого довольно легко добиться, и я объясню, как, но сначала давайте пробежимся по некоторым предысториям.
Прежде всего, большинство функций OpenCV предоставляют массив назначения. Часто это может быть пустой общий cv::Mat
(или, возможно, их вектор), и функция будет выделять Mat
подходящей формы и типа, необходимые для хранения вывода. Однако, если предоставлен уже выделенный Mat
и он имеет правильную форму и тип данных, он будет повторно использован (выходные данные будут записаны в него Mat
).
Во-вторых, важно понимать, как работает cv::Mat
. По сути, это интеллектуальный указатель с некоторыми метаданными и указателем на массив байтов. Метаданные сообщают ему, как интерпретировать массив. Это означает, что у вас может быть несколько Mat
, указывающих на одни и те же данные или подразделы этих данных (так называемые «представления»).
Учитывая это, стратегия ясна.
result
той же ширины и в 3 раза превышающее высоту входа BGR.result
, которую мы можем передать cv::split как OutputArrayOfArrays
. В данном случае это означает std::vector<cv::Mat>
с тремя элементами, каждый из которых представляет собой представление определенной цветовой плоскости result
. Мы можем получить необходимые представления, используя cv::Mat::rowrange и простую арифметику.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
для самолета B1, 4; 7, 10
для плоскости G2, 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]
Это именно то, на что я надеялся, но не мог понять, как это сделать. Спасибо.
Я думаю, что вам нужен термин «упакованный в плоскости» (или «плоский»), а не «упакованный в пикселях».