Взгляните Opencv: отсканировать изображение

I. Обзор

Рассмотрим простой метод восстановления цвета. Используя типы unsigned char C и C++ для хранения записей матрицы, канал пикселя может иметь до 256 различных значений. Для трехканального изображения это может привести к появлению слишком большого количества цветов (точнее, 16 миллионов). Обработка такого большого количества цветностей может серьезно повлиять на производительность нашего алгоритма. Однако иногда использования меньшего количества элементов достаточно для достижения того же конечного результата.

В этом случае мы обычно уменьшаем цветовое пространство. Это означает, что мы делим текущее значение цветового пространства на новое входное значение, в результате чего цветов становится меньше. Например, каждое значение от 0 до 9 принимает новое значение 0, каждое значение от 10 до 19 принимает новое значение 10 и так далее.

Когда мы делим значение uchar (беззнаковый символ, то есть значение от 0 до 255) на значение int, результатом также будет символ. Эти значения могут быть только символьными значениями. Поэтому любые дроби будут округлены. Воспользовавшись этим фактом, операцию up в домене uchar можно выразить следующим образом:

Inew = (Iold10)∗10

Простой алгоритм уменьшения цветового пространства просто должен пройти по каждому пикселю матрицы изображения и применить эту формулу. Стоит отметить, что мы проделали деление и умножение. Эти операции очень дороги для системы. Если возможно, их можно избежать, используя какую-нибудь более дешевую операцию, например, вычитание, сложение или, в лучшем случае, простое присваивание. Также обратите внимание, что у нас есть только ограниченное количество входных значений для вышеуказанной операции. В системе uchar это 256, если быть точным.

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

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

 int divideWith = 0; // convert our input string to number - C++ style
 stringstream s;
 s << argv[2];
 s >> divideWith;
 if (!s || !divideWith)
 {
    
    
 cout << "Invalid number entered for dividing. " << endl;
 return -1;
 }
 uchar table[256];
 for (int i = 0; i < 256; ++i)
 table[i] = (uchar)(divideWith * (i/divideWith));

Здесь мы сначала преобразуем третий аргумент командной строки из текстового формата в целочисленный, используя класс stringstream C++. Затем мы используем простое представление и приведенную выше формулу для расчета таблицы поиска. Здесь нет ничего специфичного для OpenCV.

Другой вопрос: как мы измеряем время? OpenCV предоставляет две простые функции для реализации cv::getTickCount() и cv::getTickFrequency(). Первый возвращает количество тактов ЦП системы с момента события (например, с момента загрузки системы). Второй возвращает количество тиков, выданных ЦП за одну секунду. Следовательно, измерить время, прошедшее между двумя операциями, так же просто, как:

double t = (double)getTickCount();
// do something ...
t = ((double)getTickCount() - t)/getTickFrequency();
cout << "Times passed in seconds: " << t << endl;

2. Как матрица изображений хранится в памяти?

Размер матрицы зависит от используемой системы цветности. Точнее, это зависит от количества используемых каналов. В случае изображений в оттенках серого мы имеем что-то вроде этого:
учебник_how_matrix_stored_1.png

Для многоканальных изображений столбец содержит столько подстолбцов, сколько каналов. Например, в случае системы цвета BGR:
учебник_how_matrix_stored_2.png

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

3. Эффективный способ

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

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    
    
	 // accept only char type matrices
	 CV_Assert(I.depth() == CV_8U);
	 int channels = I.channels();
	 int nRows = I.rows;
	 
	 int nCols = I.cols * channels;	//单通道就是灰度图、三通道一般就是GBR色系
	 if (I.isContinuous())
	 {
    
    
		 nCols *= nRows;
		 nRows = 1;
	 }
	 int i,j;
	 uchar* p;
	 for( i = 0; i < nRows; ++i)
	 {
    
    
		 p = I.ptr<uchar>(i);
		 for ( j = 0; j < nCols; ++j)
			 {
    
    
			 	p[j] = table[p[j]];
			 }
	 }
	 return I;
}

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

Есть другой способ. Элемент данных объекта Mat возвращает указатель на первую строку и столбец. Если указатель равен нулю, в объекте нет допустимых входных данных. Проверка. Это самый простой способ проверить, успешно ли загрузилось изображение. Если хранилище является смежным, мы можем использовать это для перебора всего указателя данных. В случае изображения в оттенках серого это будет выглядеть так:

uchar* p = I.data;
for( unsigned int i = 0; i < ncol*nrows; ++i)
 *p++ = table[*p];

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

4. Итераторный (безопасный) метод

Если допустимо, мы несем ответственность за то, чтобы мы передали соответствующее количество полей uchar и пропустили любые пробелы, которые могут возникнуть между строками. Метод итератора считается более безопасным подходом, поскольку он берет на себя эти задачи пользователя. Все, что вам нужно сделать, это запросить у матрицы изображения начало и конец, а затем просто увеличивать начальный итератор, пока не дойдете до конца. Чтобы получить значение, на которое указывает итератор, используйте оператор (перед ним стоит ).

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    
    
 // accept only char type matrices
 CV_Assert(I.depth() == CV_8U);
 const int channels = I.channels();
 switch(channels)
 {
    
    
 case 1:
 {
    
    
 MatIterator_<uchar> it, end;
 for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
 *it = table[*it];
 break;
 }
 case 3:
 {
    
    
 MatIterator_<Vec3b> it, end;
 for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
 {
    
    
 (*it)[0] = table[(*it)[0]];
 (*it)[1] = table[(*it)[1]];
 (*it)[2] = table[(*it)[2]];
 }
 }
 }
 return I;
}

Для цветных изображений у нас есть три элемента uchar в каждом столбце. Это можно рассматривать как короткий вектор терминов uchar, названный Vec3b в OpenCV. Для доступа к n-му подстолбцу мы используем простой оператор доступа []. Важно помнить, что итераторы OpenCV перебирают столбцы и автоматически переходят к следующей строке. Таким образом, для цветных изображений, если вы используете простой итератор uchar, вы можете получить доступ только к значению синего канала.

5. Динамический расчет адреса с возвратом ссылки.

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

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
    
    
 // accept only char type matrices
 CV_Assert(I.depth() == CV_8U);
 const int channels = I.channels();
 switch(channels)
 {
    
    
 case 1:
 {
    
    
 for( int i = 0; i < I.rows; ++i)
 for( int j = 0; j < I.cols; ++j )
 I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
 break;
 }
 case 3:
 {
    
    
 Mat_<Vec3b> _I = I;
 for( int i = 0; i < I.rows; ++i)
 for( int j = 0; j < I.cols; ++j )
 {
    
    
 _I(i,j)[0] = table[_I(i,j)[0]];
 _I(i,j)[1] = table[_I(i,j)[1]];
 _I(i,j)[2] = table[_I(i,j)[2]];
 }
 I = _I;
 break;
 }
 }
 return I;
}

Эта функция принимает тип ввода и координаты и вычисляет адрес элемента запроса. Затем возвращается ссылка на него. Когда вы получаете значение, оно может быть константой, а когда вы устанавливаете значение, оно может быть непостоянным. В качестве меры безопасности в режиме отладки* проверяйте, действительны ли входные координаты и действительно ли они существуют. Если это не так, мы получим хорошее выходное сообщение в стандартном потоке вывода ошибок. Единственная разница в использовании этого способа по сравнению с эффективным способом в режиме выпуска заключается в том, что для каждого элемента изображения мы получаем новый указатель строки, тогда как мы используем оператор C [] для получения элементов столбца.

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

6. Основные функции

Это дополнительный способ реализации изменений таблицы поиска в изображениях. При обработке изображений очень часто требуется изменить все заданные значения изображения на другие значения. OpenCV предоставляет функцию для изменения значений изображения без написания логики сканирования изображения. Мы используем функцию cv::LUT() основного модуля. Сначала мы строим справочную таблицу типа Mat:

 Mat lookUpTable(1, 256, CV_8U);
 uchar* p = lookUpTable.ptr();
 for( int i = 0; i < 256; ++i)
 p[i] = table[i];

Наконец, вызовите функцию (I — наше входное изображение, J — выходное изображение):

 LUT(I, lookUpTable, J);

7. Различия в производительности

Для достижения наилучших результатов скомпилируйте программу и запустите ее самостоятельно. Чтобы разница была более четкой, я использовал довольно большое изображение (2560 X 1600). Показанные здесь характеристики относятся к цветным изображениям. Чтобы получить более точное значение, я усреднил значения, полученные от вызовов функций, 100 раз.

метод время
эффективный способ 79,4717 миллисекунд
Итератор 83,7201 миллисекунды
Динамический РА 93,7878 миллисекунд
ЛУТ-функция 32,5759 миллисекунды

Мы можем сделать некоторые выводы. Если возможно, используйте функции, которые уже есть в OpenCV (вместо того, чтобы изобретать их заново). Самый быстрый метод — функция LUT. Это связано с тем, что библиотека OpenCV является многопоточной с помощью строительных блоков Intel Threading Building Blocks. Однако, если нам нужно написать простое сканирование изображения, метод указателя предпочтителен. Итераторы — более безопасная альтернатива, но они довольно медленные. В режиме отладки сканирование голограммы с использованием метода доступа к динамическим ссылкам является наиболее затратным. В режиме выпуска он может быть лучше, а может и не быть лучше, чем методы итераторов, но ради этого он определенно жертвует свойствами безопасности итераторов.

おすすめ

転載: blog.csdn.net/qq_43680827/article/details/134070139