От мата к вектору <Vec3f> и наоборот без искажения изображения - PullRequest
0 голосов
/ 26 января 2019

Мне нужно использовать OpenCV, чтобы прочитать изображение, преобразовать его в вектор Vec3f, поработать с пикселями, а затем преобразовать его обратно в Mat для визуализации.

Я использую C ++ 17.

Вот код пока:

Mat* in = new Mat;
*in = imread(filepath);
int rows = in->rows;
int cols = in->cols;

//MAT -> VECTOR
vector<Vec3f>* src = new vector<Vec3f>(rows * cols);
if (in->isContinuous()) {
    src->assign(in->datastart, in->dataend);
} 
else {
    for (int i = 0; i < rows; ++i) {
        src->insert(src->end(), in->ptr<Vec3f>(i), in->ptr<Vec3f>(i)+cols);
    }
}

//---USE THE VECTOR TO TRASFORM EVERY PIXEL GRAY---

//SHOW 
imshow("out", cv::Mat(rows, cols, CV_8U, src, cv::Mat::AUTO_STEP));

В результате получается искаженное изображение, похожее на статический шум телевизора, даже если я не выполняю этап обработки пикселей

Спасибо за помощь

Ответы [ 2 ]

0 голосов
/ 27 января 2019

Вот версия с .assign и .insert, аналогичная заданному вами коду.Он также охватывает юнит-тест и путь от вектора до мата.И способ проверки на непостоянные коврики тоже.Я не знаю, какая версия быстрее, эта или та, что принадлежит Дэну Масеку.Не стесняйтесь попробовать.

int main()
{
cv::Mat in = cv::imread("C:/StackOverflow/Input/Lenna.png"); // this is a CV_8UC3 image, which is cv::Vec3b format

cv::Mat inFloat;
in.convertTo(inFloat, CV_32F);

// choose this line if you want to test non-continuous:
//inFloat = inFloat(cv::Rect(0, 0, 100, 100));

int rows = inFloat.rows;
int cols = inFloat.cols;

std::vector<cv::Vec3f> src;



if (inFloat.isContinuous())
{
    std::cout << "continuous image data" << std::endl;
    src.assign((cv::Vec3f*)inFloat.datastart, (cv::Vec3f*)inFloat.dataend);
}
else 
{
    std::cout << "non-continuous image data" << std::endl;
        for (int i = 0; i < inFloat.rows; ++i) 
        {
            src.insert(src.end(), inFloat.ptr<cv::Vec3f>(i), inFloat.ptr<cv::Vec3f>(i) + inFloat.cols);
        }
}

// UNIT TEST:
bool testSuccess = true;
//const float epsilon = 0.01;
for(int j=0; j<rows; ++j)
    for (int i = 0; i < cols; ++i)
    {
        cv::Vec3b & pixelIn = in.at<cv::Vec3b>(j, i);
        cv::Vec3f & pixelInFloat = inFloat.at<cv::Vec3f>(j, i);
        cv::Vec3f & pixelSrc = src.at(j*cols + i);

        if (pixelInFloat != pixelSrc)
        {
            std::cout << "different values in: [" << i << "," << j << "]: " << pixelInFloat << " vs. " << pixelSrc << std::endl;
            testSuccess = false;
        }
    }

if (testSuccess)
{
    std::cout << "conversion from imread to vector<cv::Vec3f> successful." << std::endl;
}
else
{
    std::cout << "Conversion failed." << std::endl;
}

// now test converting the vector back to a cv::Mat:
cv::Mat outFloat = cv::Mat(rows, cols, CV_32FC3, src.data());

// if you want to give the vector memory free later, choose this deep copy version instead:
// cv::Mat outFloat = cv::Mat(rows, cols, CV_32FC3, src.data()).clone();

cv::Mat out;
outFloat.convertTo(out, CV_8U);
cv::imshow("out", out);
cv::imshow("in", in);
cv::waitKey(0);


//std::cin.get();



return 0;
}
0 голосов
/ 27 января 2019

Давайте используем небольшое случайное изображение для демонстрации:

// Generate random input image
cv::Mat image(5, 5, CV_8UC3);
cv::randu(image, 0, 256);

Опция 1

Поскольку входное значение равно CV_8UC3 (то есть каждый элемент является cv::Vec3b), и мы хотим, чтобыэлементы как cv::Vec3f, нам сначала нужно использовать convertTo, чтобы преобразовать Mat в CV_32FC3.Мы сохраняем результат во временной матрице, и для удобства (поскольку мы знаем тип элемента) мы можем явно использовать cv::Mat3f.

// First convert to 32bit floats
cv::Mat3f temp;
image.convertTo(temp, CV_32FC3);

Теперь мы можем просто использовать Mat итераторы для инициализации вектора.

// Use Mat iterators to construct the vector.
std::vector<cv::Vec3f> v1(temp.begin(), temp.end());

Опция 2

Предыдущая опция заканчивается выделением временного массива.Приложив немного творческих усилий, мы можем избежать этого.

Как оказалось, можно создать заголовок cv:Mat, обертывающий вектор , совместно использующий хранилище данных.

Мы начнем с создания вектора соответствующего размера:

std::vector<cv::Vec3f> v2(image.total());

Mat, созданный из такого вектора, будет иметь 1 столбец и столько строк, сколько имеется элементов.Поэтому мы reshape нашей входной матрицы с идентичной формой, а затем будем использовать convertTo для прямой записи в вектор.

image.reshape(3, static_cast<int>(image.total())).convertTo(v2, CV_32FC3);

Вся программа:

#include <opencv2/opencv.hpp>

#include <vector>

template<typename T>
void dump(std::string const& label, T const& data)
{
    std::cout << label << ":\n";
    for (auto const& v : data) {
        std::cout << v << " ";
    }
    std::cout << "\n";

}

int main()
{
    // Generate random input image
    cv::Mat image(5, 5, CV_8UC3);
    cv::randu(image, 0, 256);

    // Option 1
    // ========

    // First convert to 32bit floats
    cv::Mat3f temp;
    image.convertTo(temp, CV_32FC3);

    // Use Mat iterators to construct the vector.
    std::vector<cv::Vec3f> v1(temp.begin(), temp.end());

    // Option 2
    // ========

    std::vector<cv::Vec3f> v2(image.total());
    image.reshape(3, static_cast<int>(image.total())).convertTo(v2, CV_32FC3);

    // Output
    // ======

    dump("Input", cv::Mat3b(image));
    dump("Vector 1", v1);
    dump("Vector 2", v2);

    return 0;
}

Пример вывода:

Input:
[246, 156, 192] [7, 165, 166] [2, 179, 231] [212, 171, 230] [93, 138, 123] [80, 105, 242] [231, 239, 174] [174, 176, 191] [134, 54, 234] [69, 25, 147] [24, 67, 124] [158, 203, 206] [89, 144, 210] [51, 31, 132] [123, 250, 234] [246, 204, 74] [111, 208, 249] [149, 234, 37] [55, 147, 143] [29, 214, 169] [215, 84, 190] [204, 110, 239] [216, 103, 137] [248, 173, 53] [221, 251, 29]
Vector 1:
[246, 156, 192] [7, 165, 166] [2, 179, 231] [212, 171, 230] [93, 138, 123] [80, 105, 242] [231, 239, 174] [174, 176, 191] [134, 54, 234] [69, 25, 147] [24, 67, 124] [158, 203, 206] [89, 144, 210] [51, 31, 132] [123, 250, 234] [246, 204, 74] [111, 208, 249] [149, 234, 37] [55, 147, 143] [29, 214, 169] [215, 84, 190] [204, 110, 239] [216, 103, 137] [248, 173, 53] [221, 251, 29]
Vector 2:
[246, 156, 192] [7, 165, 166] [2, 179, 231] [212, 171, 230] [93, 138, 123] [80, 105, 242] [231, 239, 174] [174, 176, 191] [134, 54, 234] [69, 25, 147] [24, 67, 124] [158, 203, 206] [89, 144, 210] [51, 31, 132] [123, 250, 234] [246, 204, 74] [111, 208, 249] [149, 234, 37] [55, 147, 143] [29, 214, 169] [215, 84, 190] [204, 110, 239] [216, 103, 137] [248, 173, 53] [221, 251, 29]

Проблемы с вашим кодом

  1. In src->assign(in->datastart, in->dataend);

    Элементы src равны Vec3f, однако datastart и dataend являются указателями на uchar.

    Это будет иметь несколько последствий.Прежде всего, поскольку in равно CV_8UC3, элементов будет в 3 раза больше.Кроме того, каждый из Vec3f экземпляров будет иметь только первый установленный набор, остальные 2 будут 0.

  2. In src->insert(src->end(), in->ptr<Vec3f>(i), in->ptr<Vec3f>(i)+cols);

    Напомним, что у вас естьsrc уже инициализирован как vector<Vec3f>(rows * cols); - т.е. вектор уже содержит столько элементов, сколько пикселей в исходном изображении.Тем не менее, в цикле вы продолжаете добавляя дополнительных элементов в конце.Это означает, что результирующий вектор будет иметь в два раза больше элементов, причем первая половина из них будет равна нулю.

    Кроме того, in равно CV_8UC3, но вы интерпретируете данные как cv::Vec3f.Это означает, что вы берете байтовые значения 4 последовательных пикселей и интерпретируете это как последовательность из 3 32-битных чисел с плавающей запятой.Результат не может быть ничем иным, как мусором.

    Это также означает, что вы в конечном итоге получите доступ к данным за пределами допустимой области, потенциально после конца буфера.

  3. В cv::Mat(rows, cols, CV_8U, src, cv::Mat::AUTO_STEP) ...

    Прежде всего, src содержит Vec3f элементы, но вы создаете Mat как CV_8U (что также является проблемой, так как вам нужноздесь также указывается количество каналов, поэтому оно фактически интерпретируется как CV_8UC1).Таким образом, вы будете иметь не только неправильное количество каналов, но и мусор из-за несоответствия типов.

    Еще большая проблема заключается в том, что вы передаете src в качестве 4-го параметра.Теперь это указатель на экземпляр std::vector, а не на реальные данные, которые он содержит.(Компилируется, так как 4-й параметр - void*).Это означает, что вы на самом деле интерпретируете метаданные vector вместе со множеством других неизвестных данных.Результат - в лучшем случае мусор (или, как вы узнали, SEGFAULTs или потенциально неприятные ошибки безопасности).


Назад к Mat

Обратите внимание, что этовозможно imshow с плавающей точкой Mat, при условии, что значения нормализованы в диапазоне [0,1].

Мы можем воспользоваться конструктором Mat, который принимает vector, ипросто измените результирующую матрицу обратно на исходную форму.

cv::Mat result(cv::Mat(v2).reshape(3, image.rows));

Обратите внимание, что в этом случае базовое хранилище данных используется совместно с источником vector, поэтому вам необходимо убедиться, что оно остается в области действия до тех пор, покаMat делает.Если вы не хотите делиться данными, просто передайте true в качестве второго параметра конструктору.

cv::Mat result(cv::Mat(v2, true).reshape(3, image.rows));

Конечно, если вы хотите вернуться к CV_8UC3, это так же просто, какдобавив convertTo.В этом случае нет необходимости копировать векторные данные, так как тип данных изменяется, и новый массив хранения будет выделяться автоматически.

cv::Mat result;
cv::Mat(v2).reshape(3, image.rows).convertTo(result, CV_8UC3);
...