Разработка приложения с использованием библиотеки OpenCV

Обзор различных сфер применения компьютерного зрения. Работа с потоком видео. Особенности построения приложений на языке C++. Доступные функции библиотеки OpenCV для детектирования объектов. Захват видео с камеры. Алгоритм детектирования 4-х точек.

Рубрика Программирование, компьютеры и кибернетика
Вид дипломная работа
Язык русский
Дата добавления 07.08.2018
Размер файла 1,8 M

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru/

Федеральное агентство связи

Федеральное государственное образовательное бюджетное учреждение высшего профессионального образования

«Поволжский государственный университет телекоммуникаций и информатики»

Факультет Заочного обучения

Направление (специальность) Информационные системы и технологии

Кафедра Информационных систем и технологий

Выпускная квалификационная работа (бакалаврская работа)

Разработка приложения с использованием библиотеки OpenCV

Разработал 36И А.В. Кириллов

Руководитель старший преподаватель А.С. Тучкова

Н. контролер ассистент А.С. Лошкарев

Самара 2017

Введение

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

Все выше сказанное определило актуальность темы работы - Разработка приложения с использованием библиотеки OpenCV

Целью дипломной работы являетсяразработка приложения для работы компьютерным зрением

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

• Исследовать сферу применения компьютерного зрения ;

• Получить навыки владения языком C++;

• Разработать приложение используя библиотеку OpenCV;

• Провести анализ разработанного приложения.

Объектом исследования является определение объекта на заданном изображение.

Предметом исследования контурный анализ заранее выбранных объектов.

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

Цели и задачи определили структуру работы, которая состоит из введения, трех глав и заключения.

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

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

В следующей главе работы представлена библиотека OpenCVи компоненты, из которых она состоит. Также приведены прикладные области применения. Также рассказана история среды и описаны некоторые практические аспекты.

В последней главе идет описание процесса разработки. Здесь рассказывается об основных идеях и алгоритме реализации ввода информации с помощью библиотеки OpenCV. В заключении сделаны основные выводы по работе.

видео камера захват детектирование

1. Библиотека OpenCV

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

После установки OpenCV, первым делом, естественно, хочется сделать что-то интересное. Для этого прежде всего необходимо настроить среду для программирования.

В VisualStudio необходимо в свойствах проекта указать библиотеки (для debug, необходимо использовать библиотеки с d на конце) highgui.lib, cxcore.lib, ml.lib, cv.lib и путь к заголовочным файлам OpenCV.../opencv/* /include. Какправило, "include" - эточто-тотипаC:/program files/opencv/cv/include, .../opencv/cxcore/include,.../opencv/ml/include и .../opencv/otherlibs/highgui.

После выполнения этих настроек,всеготово для написания первой программы[1].

Некоторые заголовочные файлы могут существенно облегчить вашу жизнь. Множество полезных макросов можно найти в .../opencv/cxcore/include/cxtypes.h и cxmisc.h. При помощи них возможно делать такие вещи, как: инициализацию структур и массивов, сортировку списка и т.д. Наиболее важные заголовочные файлы: .../cv/include/cv.h и .../cxcore/include/cxcore.hдля компьютерного зрения, .../otherlibs/highgui/highgui.hдляввода/вывода, .../ml/include/ml.h для машинного обучения

OpenCV содержит средства для чтения широкого спектра типов изображений, а также видеофайлов с диска и видеопотока с камеры. Эти средства являются частью компонента HighGUI, который включен в OpenCV.

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

IplImage* img = cvLoadImage(argv[1] );

Эта строка загружает изображение. Функция cvLoadImage() является высокоуровневой: определяет формат файла по его имени; автоматически выделяет память, необходимую для изображения. Обратите внимание, функция cvLoadImage() поддерживает широкий спектр типов изображений: BMP, DIB, JPEG, JPE, PNG, PBM, PGM, PPM, SR, RAS и TIFF. Функция возвращает указатель типа IplImage. Это наиболее часто используемый тип данных в OpenCV. Используется для обработки всех видов изображений: одноканальные, многоканальные, целочисленные, вещественные и т.д. В дальнейшем манипуляция изображением производиться через созданный указатель IplImage. cvNamedWindow( "DisplayPicture", CV_WINDOW_AUTOSIZE );

Еще одна высокоуровневая функция cvNamedWindow(), открывает окно для размещения и отображения изображения. Функция из библиотеки HighGUI, присваивает имя окну (в нашем случае "DispalyPicture"). Последующие вызовы HighGUI будут взаимодействовать с созданным окном по его имени.

Второй аргумент функции cvNameWindow() определяет свойства окна. Может быть установлен как 0 (значение по умолчанию) или как CV_WINDOW_AUTOSIZE. В первом случае, размер окна не будет зависеть от размера загружаемого изображения, изображение будет подстраиваться под размеры окна. Во втором случае, окно будет подстраиваться под размеры загружаемого изображения.

cvShowImage( "DisplayPicture", img );

Всякий раз, когда есть изображение в виде указателя IplImage, его можно отобразить в существующем окне с помощью функции cvShowImage(). Функция требует, чтобы имя окна уже существовало (создано с помощью cvNamedWindow()). Вызов cvShowImage() перерисовывает окно, с соответствующим изображением в нем, и изменяет его размеры, если при создании был указан флаг CV_WINDOW_AUTOSIZE.

cvWaitKey(0);

Функция cvWaitKey() заставляет программу ожидать нажатие любой клавиши. Если аргумент - положительное число, программа будет ожидать заданное количество миллисекунд, а затем продолжит выполнение программы, даже если ничего не будет нажато. Если параметр - 0 или отрицательное число, программа будет бесконечно ожидать нажатие клавиши[2].

cvReleaseImage(&img );

Когда изображение больше не требуется, нужно освободить выделенную память. Для выполнения этой операции необходимо передать указатель типа IplImage*. После выполнения операции, указатель img будет установлен в NULL.

cvDestroyWindow( "DisplayPicture" );

В завершении, нужно уничтожить окно. Функция cvDestroyWindow() закрывает окно и высвобождает любую, связанную с этим окном, память (внутренний буфер для изображения, который содержит копию информации о пикселях из img). Для столь простой программы можно было и не использовать cvDestroyWindow() или cvReleaseImage(), т.к. все ресурсы и окна автоматически уничтожаются операционный системой при выходе, однако использование этих функций хорошая привычка.

В итоге мы описали набор функций самой простой программы, перейдем дальше к более сложным функциям.

1.2 Работа с потоком видео

Воспроизводить видео в OpenCV почти также легко, как и отображать одно изображение. Разница лишь в том, что нужно организовать некий цикл для обработки последовательности кадров; также нужен способ для выхода из цикла, если фильм скучный.

Простая программа для воспроизведения видеофайла с диска при помощи OpenCV

cvReleaseCapture( &capture ); // Закрытие файла cvDestroyWindow( "PlayVideo" ); // Уничтожение окна

}

Функция main() начинается с создания окна, в примере имя окна "PlayVideo". После начинается самое интересное.

CvCapture* capture = cvCreateFileCapture( argv[1] );

Функция cvCreateFileCapture() принимает в качестве аргумента имя видеофайла, а возвращает указатель на структуру типа CvCapture. Эта структура содержит всю информацию из видеофайла, включая информацию о состоянии[3].

frame = cvQueryFrame( capture );

После, в цикле while(1), происходит чтение видеофайла кадр за кадром. Функция cvQueryFrame() принимает в качестве аргумента указатель на структуру CvCapture и помещает последующий кадр в память (которая на самом деле часть структуры CvCapture). Возвращает указатель полученного кадра. В отличии от cvLoadImage(), которая выделяет память под изображение, cvQueryFrame() использует выделенную память в структуре CvCapture. Поэтому не требуется использовать cvReleaseImage() для указателя на "кадр". Вместо этого память из-под кадра будет освобождена, когда произойдет уничтожение структуры CvCapture.

c = cvWaitKey(33);

if( c == 27 ) { break;

}

После отображения кадра, программа будет ожидать нажатие клавиши в течении 33 мс. Если пользователь нажмет клавишу, то будет получен ASCII код этой клавиши; иначе будет получена -1. Если пользователь нажмет Esc (ASCII 27), то чтение кадров прекратиться. Если в течении 33 мс пользователь не нажмет клавишу, то произойдет чтение последующего кадра.

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

После окончания воспроизведения видеофайла - обработаны все кадры или пользователь нажал Esc - необходимо освободить память, связанную со структурой CvCapture.

Инструментарий HighGUI предоставляет ряд простых средств для работы с изображениями и видео. Одним из таких механизмов является слайдер, который с лёгкостью перемещает с одного конца видео в другой. Для создания ползунка необходимо вызвать функцию cvCreateTrackBar(), указать окно, в котором этот ползунок появится и установить функцию-обработчик.

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

Давайте взглянем на детали.

Int g_slider_position = 0;

CvCapture* g_capture = NULL;

Во-первых, нужно определить глобальную переменную для позиции ползунка.

Функции-обработчик требуется объект захвата, поэтому соответствующая переменная также объявлена глобально. Поступим как хорошие люди и сделаем наш код легко читаемым и понятным. Для этого добавим ведущую приставку g_ к нашим переменным.

voidonTrackbarSlide(intpos) {

cvSetCaptureProperty(

g_capture

,CV_CAP_PROP_POS_FRAMES

,pos

);

}

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

Вызов функции cvSetCaptureProperty(), наряду с функцией cvGetCaptureProperty(), является наиболее используемой функцией. Эти функции позволяют настроить различные свойства объекта CvCapture. В нашем случае, флаг

CV_CAP_PROP_POS_FRAMES указывает на то, что устанавливается позиция для чтения кадров в единицах (также можно использовать AVI_RATIO вместо FRAMES, если потребуется установить позицию в процентах). Как результат - новое положение позиции ползунка. Библиотека HighGUI довольно-таки умна и потому такие ситуации, как запрос к не ключевому кадру будут обрабатываться автоматически; произойдет откат назад к ближайшему ключевому кадру и уже от него произойдет перемотка вперед до нужного кадра.

int frames = (int) cvGetCaptureProperty(

g_capture

,CV_CAP_PROP_FRAME_COUNT

);

Как было отмечено ранее, для получения данных из структуры CvCapture используется функция cvGetCaptureProperty(). В нашем случае, требуется узнать количество кадров в видео для откалиброки ползунка.

Для создания ползунка используется функция cvCreateTrackbar(), которая принимает в качестве аргументов: имя объекта ползунка, имя окна, переменную положения ползунка, максимальное значение ползунка и функцию-обработчик (или NULL).

Ползунок не будет создан, если cvGetCaptureProperty() возвратит ноль кадров. Такое возможно, так как общее количество кадров может быть не доступно из-за используемого метода кодирования видеофайла. В таком случае прокрутить видео будет не возможно.

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

И в заключении, не был рассмотрен кусок кода, необходимый для отображения авто перемещения ползунка поверх видео. Эта задача остается в качестве упражнения[4].

Отлично, теперь с помощью OpenCV можно создать собственный видеоплеер, который не будет сильно отличатся от бесчисленного множества уже существующих плееров. Однако, предметом нашего обсуждения является компьютерное зрение и потому хочется сделать что-то большее, чем просто видеоплеер. Многие базовые задачи компьютерного зрения включают применение различного вида фильтров для обработки видео потока. Давайте попробуем модифицировать уже существующую программу путем добавления простой операции преобразования каждого кадра из видео потока.

Одна из самых простых операций это сглаживание изображения, которая эффективно снижает информативность изображения, сворачивая его гауссовой или другой аналогичной функцией ядра. Сделать нечто подобное довольно таки легко. Начнем с создания нового окна "Transform-out", где будем отображать результат преобразований. После отображения захваченного исходного кадра (функция cvShowImage()), произведем сглаживание изображения из кадра и отобразим в окне результата[5].

Ранее, новый кадр создавался при содействии функции cvCreateFileCapture().

Происходил покадровый перебор всех кадров из структуры CvCapture, один за другим, с размещением их в одном и том же указателе. В нашем случае использование данного указателя не приемлемо и потому создается собственная структура под результат сглаживания. Для этого используется новая функция cvCreateImage(). Аргументы функции cvCreateImage( CvSizesize, intdepth, intchannels ):

size - размер существующей структуры изображения (удобно получать при помощи функции cvGetSize())

depth - тип данных для каждого канала channels - количество каналов

В примере 2-3 создается 8-ми битное 3-х канальное изображение того же размера, что и исходное изображение.

Операция сглаживания это всего лишь вызов функции из OpenCV, которой передается указатель на исходное изображение, указатель на результирующее изображение, тип и параметры сглаживания. В примере 2-3 используется размытие по Гауссу с ядром 3х3.

Не забывайте освобождать ресурсы, выделенные под результирующее изображение (используйте cvReleaseImage()).

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

В таких случаях, зачастую полезно использовать функции-обертки, чтобы произвести все необходимые преобразования. Рассмотрим пример сжатия изображения в 2 раза.

OpenCV это достигается при помощи функции cvPyrDown(), которая выполняет гауссово сглаживание, а затем удаляет каждую вторую строку из изображения. Это полезно в самых разнообразных и важных алгоритмах компьютерного зрения

IplImage* doPyrDown(IplImage* in, intfilter = IPL_GAUSSIAN_5x5 ) {

// Проверка деления исходного изображения на 2

//

assert( 0 == in->width%2 && 0 == in->height%2 );

// Создание контейнера с размером вдвое меньшим, чем исходное изображение.

// В остальном параметры те же

//

IplImage* out = cvCreateImage(

cvSize( in->width/2, in->height/2 ) ,in->depth

,in->nChannels

);

// Сжатиеисходногоизображения

cvPyrDown( in, out );

return( out );

};

Обратите внимание, что под преобразованное изображение создается новая структура на основе параметров исходного изображения. В OpenCV все важные типы данных реализованы как структуры, в которых нет private данных, и все крутится вокруг указателей на эти структуры.

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

В итоге, можно довольно-таки легко соединять несколько различных операторов.

Например, требуется уменьшить изображение в два раза, а затем найти контур в уменьшенном еще раз вдвое изображении.

Важно отметить, что создание дополнительных переменных под каждое преобразование не является хорошей идеей. Зачастую, из-за своей лени, происходит утечка памяти в связи с отсутствием соответствующей функции освобождения памяти cvReleaseImage() в функции-обертки.

Механизм "само очистки" будет более аккуратным, если использовать эту функцию, однако существует еще одна проблема: что, если нам потребуется что-то сделать с промежуточным изображением? Ничего может не получиться из-за возможного отсутствия доступа к этому изображению[6].

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

указатель IplImage, возвращаемый cvCreateFileCapture(), указывает на структуру типа

CvCapture и инициализируется только в момент загрузки видеофайла. Освобождение памяти, вызовом функции cvRealeaseImage(), приведет к некоторым неожиданным проблемам. Мораль сей басни такова: очистка мусора важна, но только мусора, которой создавали сами!

2. Захват видео с камеры

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

OpenCV - а точнее ее часть, HighGUI - помогает с легкостью справиться с подобного рода ситуациями. Подход схож с тем, как происходило чтение AVI файла. Вместо вызова cvCreateFileCapture(), используется cvCreateCameraCapture(). В качестве аргумента используется не имя файла, как ранее, а идентификационный номер камеры, и то имеет смысл его использовать, когда доступно сразу несколько камер.

Значение по умолчанию равно -1, что означает "просто выбрать одну"; естественно, это работает, когда доступна только одна камера (см. Главу 4 для получения более подробной информации).

Функция cvCreateCameraCapture() возвращает указатель на структуру CvCapture, которая обрабатывается так же, как и в примере с обработкой видео файла с диска.

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

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

Во многих приложениях требуется записывать потоковое видео в файл и OpenCV предоставляет простые в использовании средства для этого. Просто нужно создать устройство захвата, прочитать из него кадры один за одним и при помощи созданного устройства записи поместить кадры один за другим в видео файл. Устройство записи создается при помощи функции cvCreateVideoWriter()[8].

Покадровое чтение производится при помощи функции cvWriteFrame(). Освобождение памяти, занимаемая устройством записи, производится при помощи функции cvReleaseVideoWriter(). Ниже представлена простая программа, в которой происходит чтение содержимого видео файла с диска, преобразование его в формат logpolar (см Главу 6 для более подробной информации) и запись в новый видео файл.

#include <cv.h>

#include <highgui.h>

int main( intargc, char* argv[] )

CvCapture* capture = 0; // Переменнаязахвата

capture = cvCreateFileCapture( argv[1] ); // Чтениевидеофайласдиска

// Не удалось захватить видео поток с диска

if( !capture ) { return -1;

}

// Получение первого кадра из видео потока с диска

//

IplImage *bgr_frame=cvQueryFrame( capture );

// Частотакадров

//

double fps = cvGetCaptureProperty( capture, CV_CAP_PROP_FPS );

// Размеркадра

//

CvSize size = cvSize(

(int)cvGetCaptureProperty( capture, CV_CAP_PROP_FRAME_WIDTH ) ,(int)cvGetCaptureProperty( capture, CV_CAP_PROP_FRAME_HEIGHT )

);

Все начинается с открытия видео файла с диска; затем берется первый кадр при помощи функции cvQueryFrame() для определения необходимых свойств при помощи функции cvGetCaptureProperty(). Потом создается устройство для записи и покадрово записываются преобразованные кадры в формате log-polar в новый видео файл. В заключении происходит освобождение занимаемой памяти.

Вызов функции cvCreateVideoWriter() сопровождается передачей в нее нескольких параматров:

cvCreateVideoWriter(const char* filename, intfourcc, double fps, CvSizeframe_size, int

fileName - имя нового видео файла

foutcc - видео кодек, которым будет сжиматься видеопоток. fps - частота кадров созданного видеопотока

frame_size - размер кадра

is_color - 1: кодирование цветных кадров; 0: кодирование кадров в оттенках серого

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

Эти символы составляют "четырехзначный код" кодека, каждый кодек имеет такой код.

Четырехзначный код движущейся jpg-картинки и есть MJPG (motionjpeg), потому и указано CV_FOURCC('M','J','P','G').

Список кодеков, включённых в OpenCV:

'X', 'V', 'I', 'D' - кодек XviD 'P', 'I', 'M', '1' - MPEG-1 'M', 'J', 'P', 'G' - motion-jpeg 'M', 'P', '4', '2' - MPEG-4.2 'D', 'I', 'V', '3' - MPEG-4.3 'D', 'I', 'V', 'X' - MPEG-4 'U', '2', '6', '3' - H263

'I', '2', '6', '3' - H263I 'F', 'L', 'V', '1' - FLV1

Прежде чем перейти к следующей главе, подведем итоги. Уже известно, как API OpenCV предоставляет множество простых в использовании инструментов для загрузки изображений из файлов, чтения видео файлов с жёсткого диска и захвата видео с камер. Так же известно, что библиотека предоставляет множество простых функций для работы с этими изображениями. Однако, всё ещё не были представлены более мощные инструменты OpenCV, которые позволяют производить более сложные манипуляции с абстрактными типами данных[9].

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

3 .Простые функции для преобразования изображений

3.1 Примитивные типы данных в OpenCV

OpenCV включает в себя множество примитивных типов данных. Эти данные не являются примитивными с точки зрения C, но являются самыми элементарными с точки зрения OpenCV. Посмотреть, как устроены все эти структуры, можно в файле cxtypes.h в директории .../OpenCV/cxcore/include.

Самый простейший тип - CvPoint. Это простая структура состоит только из двух полей x и y типа int. CvPoint2D32f содержит два поля x и y типа float. CvPoint3D32f содержит три поля x, y и z типа float.

CvSize содержит два поля width и height типа int. CvSize2D32f содержит два поля width и height типа float.

CvRect содержит четыре поля x, y, width и height типа int.

CvScalar содержит четыре переменные типаdouble. На самом деле CvScalar включает в себя одно поле val, которое является указателем на массив, содержащий четыре числа типа double.

Все эти типы данных имеют конструкторы с именами похожими на cvSize (обычно именуются так же, как и структуры, только первая буква - строчная). Помните, что это C, а не C++, и все эти "конструкторы" всего лишь inline функции, принимающие список аргументов и возвращающие желаемую структуру со значениями установленными соответствующим образом[10].

Конструкторы для типов данных из таблицы 3-1 - cvPointXXX(), cvSize(), cvRect(), cvScalar() - чрезвычайно полезны, потому что упрощают ваш код. Например, чтобы нарисовать белый прямоугольник с координатами углов (5, 10) и (20, 30), достаточно написать следующий код:

cvRectangle(

myImg // Изображение

,cvPoint(5,10) // Верхний левый угол

,cvPoint(20,30) // Нижний правый угол

,cvScalar(255,255,255) // Цвет

);

cvScalar в отличии от всех остальных структур содержит три конструктора. Первый может принимать один, два, три или четыре аргумента и присваивать их соответствующим элементам val[]. Второй конструктор cvRealScalar() принимает один аргумент и устанавливает соответствующее значение val[0], остальные элементы устанавливаются в 0. Третий конструктор cvScalarAll() так же принимает один аргумент и инициализирует все элементы val[] этим значением.

При использовании OpenCV, неоднократно будет использоваться тип IplImage. IplImage это базовая структура, используемая для кодирования того, что мы называем "изображение". Эти изображения могут быть чёрно-белыми, цветными, 4-х канальными (RGB+Alpha), и каждый канал может содержать либо целые, либо вещественные значения. Следовательно, этот тип является более общим, чем вездесущие 3-х канальные 8-битные изображения, которые сразу приходят на ум.

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

Несмотря на то, что OpenCV написана на C, структуры, используемые в OpenCV, объектно-ориентированные; в действительности, IplImage происходит от CvMat, которая является производной от CvArr.

Прежде, чем перейти к деталям, необходимо взглянуть на другой тип данных: CvMat, структура для матриц. Хотя OpenCV полностью написана на C, относительная взаимосвязь между CvMat и IplImage похожа на наследование в C++. IplImage можно рассматривать как производную от CvMat. Класс CvArr можно рассматривать как абстрактный базовый класс. В прототипах функций зачастую будет указано CvArr (точнее указатель CvArr). В таких случаях вместо CvArr можно использовать и указатель CvMat и указатель IplImage. LearningOpenCV (перевод на русский)

Прежде, чем приступить к обсуждению данной темы, уточним несколько вещей относительно матриц. Во-первых, в OpenCV нет конструкции "vector". Когда требуется вектор, просто воспользуйтесь матрицей с одной колонкой (или одной строкой, если требуется транспонированный или сопряженный вектор). Во-вторых, понятие матрицы в OpenCV несколько абстрактно, нежели в линейной алгебре. В частности, элементы матрицы не обязательно должны быть просто числами. К примеру, создание новой двумерной матрицы имеет следующий прототип:

cvMat* cvCreateMat ( introws, intcols, inttype );

Здесь type может быть любым из длинного списка предопределенных типов; тип задаётся так: CV_<глубина в битах>(S|U|F)C<число каналов>. Например, матрица может состоять из 32-битных чисел с плавающей точкой(CV_32FC1), 8-битных без знаковых триплетов(CV_8UC3) и из множества других. Элементами CvMat могут быть не только числа. Возможность представить один элемент составным значением позволяет делать такие вещи, как представление нескольких цветовых каналов в изображении RGB. С простыми изображениями, содержащие красную, зелёную и синюю составляющую, большинство операторов будут работать с каждым каналом по отдельности (если не указано обратное).

Внутренняя структура CvMat является довольно таки простой.

Матрицы имеют ширину, высоту, тип, шаг (длина строки в байтах, а не в int или float) и указатель на массив данных (и еще несколько вещей, о которых пока не будет рассказано). Получить доступ к элементам матрицы можно непосредственно через указатель на CvMat или через специальные функции. Например, для получения размера матрицы можно либо вызвать функцию cvGetSize(CvMat*), либо непосредственно обратиться к соответствующим полям через указатель как matrix->height и matrix->width.

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

Матрицы могут быть созданы несколькими способами. Наиболее распространенным является подход с использованием cvCreateMat(), которая по существу использует более элементарные функции cvCreateMatHeader() и cvCreateData(). cvCreateMatHeader() создает структуру CvMat, без выделения памяти под данные, в то время, как cvCreateData() выделяет память под данные. По тем или иным причинам, порой, требуется создание лишь заголовка матрицы. Ещё один метод заключается в вызове функции cvCloneMat(CvMat*), которая создает новую матрицу на основе существующей. Когда матрица больше не нужна, память из под неё может быть освобождена вызовом функции cvReleaseMat(CvMat**).

По аналогии с другими структурами OpenCV для CvMat также есть конструктор cvMat(). Конструктор не выделяет память под данные, а лишь создаёт заголовок (по аналогии с cvInitMatHeader()). Хороший способ создать матрицу из уже имеющихся данных показан в примере 3-3.

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

cvGetElemType(constCvArr* arr )

cvGetDims(constCvArr* arr, int* sizes = NULL ) cvGetDimSize( constCvArr* arr, int index )

Первая функция возвращает тип элемента матрицы (например, CV_8UC1, CV_64_FC4 и т.д.). Вторая принимает указатель на массив и дополнительный указатель на целое число, а возвращает количество измерений (в примере матрица 2х2). Если указатель на число не NULL, то будет возвращена размерность принимаемого массива.

Последняя функция принимает целое число, указывающее размер в процентах и просто возвращает степень матрицы в указанном измерении.

Существует три варианта получения данных матрицы: простой, сложный и правильный.

Простой способ

Самый простой способ получить данные матриц это воспользоваться макросом CV_MAT_ELEM(). Этот макрос принимает в качестве аргументов указатель на матрицу, тип элементов, сроку и столбец, а возвращает запрашиваемый элемент.

Доступ к данным матрицы через макрос CV_MAT_ELEM

CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 ); // Создание матрицы

float element_3_2 = CV_MAT_ELEM( *mat, float, 3, 2 );

"Под капотом" этот макрос просто вызывает другой макрос CV_MAT_ELEM_PTR().

Этот макрос (пример 3-5) принимает в качестве аргументов указатель на матрицу, номер строки и столбца запрашиваемого элемента и возвращает указатель на нужный элемент. Одно важное отличие CV_MAT_ELEM() от CV_MAT_ELEM_PTR() в том, что CV_MAT_ELEM() преобразует указатель в соответствии с типом. Если требуется задать значение элементу матрицы, то нужно непосредственно вызвать CV_MAT_ELEM_PTR(); при этом, однако, необходимо сделать приведение типов в явном виде.

CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 ); // Создание матрицы

float element_3_2 = 7.7; // Значение элемента в строке

*( (float*)CV_MAT_ELEM_PTR( *mat, 3, 2 ) ) = element_3_2;

К сожалению эти макросы пересчитывают смещение указателя каждый раз при их вызове. Это означает, что указатель каждый раз указывает на первый элемент матрицы; происходит вычисление смещения и добавление полученного значения смещения к указателю на первый элемент матрицы. Таким образом, хоть эти макросы и просты в использовании, это не лучший способ получения доступа к данным матрицы. Лучшим примером "против" использования данного подхода является вариант последовательного перебора элементов матрицы.

Сложный способ

Два макроса, которые были рассмотрены в простом способе, могут работать только с одно- и двумерными матрицами (одномерные массивы или вектора, на самом деле, просто 1xn матрица). OpenCV предоставляет механизмы для обработки многомерных массивов. Фактически OpenCV позволяет обрабатывать n-мерные матрицы "условно любого" размера.

Для получения доступа к элементам матрицы используется семейство функций cvPtr*D и cvGet*D описанные в примере 3-6 и 3-7. Семейство cvPtr*D содержит функции cvPtr1D(), cvPtr2D(), cvPtr3D() и cvPtrND(). Каждая из первых трех принимает указатель на матрицу CvArr, соответствующий количеству целых индексов, и необязательный параметр, указывающий на тип выходного параметра. Все эти процедуры возвращают указатель на необходимый элемент. cvPtrND() вторым аргументом принимает указатель на массив, содержащий соответствующее количество индексов.

Для простого чтения данных есть и другое семейство функций cvGet*D, описанные ниже и возвращающие фактическое значение элемента матрицы.

double cvGetReal1D( constCvArr* arr, int idx0 ); // Дляодно-

double cvGetReal2D( constCvArr* arr, int idx0, int idx1 ); // двух-

double cvGetReal3D( constCvArr* arr, int idx0, int idx1, int idx2 ); // трёх-

doublecvGetRealND( constCvArr* arr, int* idx ); // N-канальных

CvScalarcvGet1D(constCvArr* arr, int idx0 );

CvScalarcvGet2D(constCvArr* arr, int idx0, int idx1 );

CvScalarcvGet3D(constCvArr* arr, int idx0, int idx1, int idx2 );

CvScalarcvGetND(constCvArr* arr, int* idx );

Тип возвращаемого значения для первых четырех функций - число типа double, для других четырех CvScalar. Это означает, что имеет место значительное увеличение ненужных расходов при использовании этих функций. Они должны быть использованы только там, где это уместно и эффективно; иначе лучше использовать cvPtr*D.

Причин, по которой лучше использовать cvPtr*D() заключается в том, что эти функции возвращают указатель на необходимый элемент, что в свою очередь позволяет использовать арифметику указателей для перемещения по матрице. Важно помнить, что каналы в многоканальной матрице являются смежными. Например, трехканальная двумерная матрица, представляющая байты для красного, зеленого, синего цветов (RGB), хранит данные как: rgbrgbrgb .... Поэтому для перемещения указателя на следующий канал, необходимо увеличить указатель на единицу. Если потребуется перейти к следующему "пикселю" или набору элементов, увеличьте указатель на число равное числу каналов (в приведенном примере на 3).

Иной способ узнать шаг элемента матрицы заключается в получении длины строки матрицы в байтах. В структуре CvMat, колонок или ширины не достаточно для перемещения между строками матрицы, т.к. эффективное выделение памяти под матрицы или изображения выполняется до ближайшей границы в четыре байта. Таким образом под матрицы шириной в три байта будет выделено четыре байта и последний байт не будет использован. По этой причине, после получения указателя на элемент, необходимо увеличить его на соответствующий шаг. Если имеется матрица типа int или float и соответствующий указатель на элемент, то шаг для получения следующей строки: для int - step/4, для double - step/8 (при этом C будет автоматически умножать смещение в соответствии с типом данных).

Для удобства также имеются функции cvmSet() и cvmGet(), которые имеют дело с числами с плавающей точкой одно-канальных матриц.

doublecvmGet( constCvMat* mat, int row, int col )

voidcvmSet( CvMat* mat, int row, int col, double value )

Так, вызов функции cvmSet()

cvmSet( mat, 2, 2, 0.5000 );

эквивалентен вызову функции cvSetReal2D

cvSetReal2D( mat, 2, 2, 0.5000 );

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

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

floatsum( constCvMat* mat ) { floats = 0.0f;

// Переборстрокматрицы

for(int row=0; row<mat->rows; row++ ) {

// Указательнаначалосоответствующейстроки

const float* ptr = (const float*)(mat->data.ptr + row * mat->step);

// Переборэлементовстроки

for( col=0; col<mat->cols; col++ ) { s += *ptr++;

}

}

return( s );

}

На первом шаге происходит получение указателя данных и изменение его в соответствии со смещением. Как было сказано ранее, смещение в байтах. Для обеспечения безопасности, лучше всего сначала произвести арифметику над указателем, а затем произвести приведение типов (в указанном примере к типу float).

Хотя структура CvMat и содержит поля width и height, для совместимости со старой структурой IplImage, предпочтительней использовать поля rows и cols. В заключении, обратите внимание на то, что ptr пересчитывается для каждой строки, а не просто берется с самого начала и затем смещается. Это может показаться лишним, но, указывая на ROI внутри большого массива, нет никакой гарантии, что данные будут непрерывны по строкам.

Один вопрос который может часто возникать - и который важно понимать - сводится к разнице между многомерным массивом (или матрицей) из многомерных элементов, и многомерным массивом с одномерными элементами. Предположим есть n точек в трехмерном измерении, которые необходимо передать некоторой функции OpenCV в виде CvMat* (скорее CvArr*). Есть четыре очевидных способа сделать это, при этом не все эквиваленты друг другу. Первый способ: использовать двумерный массив типа CV32FC1 из n строк и трех столбцов (nx3). Второй способ: использовать двумерный массив типа CV32FC1 с 3 строками и n столбцов (3xn). Третий и четвертый способы: использовать массив типа CV32FC3 с n строк и одного столбца (nx1) или массив с одной строкой и n столбцов (1xn). В некоторых случаях эти способы взаимозаменяемы.

В заключении пару слов о типах CvPoint2D и CvPoint2D32f. Эти типы данных определены как структуры C и, следовательно, имеют строго определенную компоновку памяти. В частности, числа типа int или float, которые содержат эти структуры образуют последовательный "канал". Как результат одномерный массив в стиле C этих объектов имеет такое же распределение памяти, как и n-by-1 или 1-by-n массивы типа CV32FC2. Тоже самое справедливо и для массивов структур типа CvPoint3D32f.

Обычно требуется обрабатывать данные изображения быстро и эффективно. Это означает, что не стоит использовать функции вида cvSet*D или аналогичные. На самом деле, лучший путь получения данных изображения, получать данные на прямую.

Теперь, зная внутреннее устройство структуры IplImage, можно получать данные изображения быстрее и эффективнее.

Однако, даже при наличии в OpenCV хорошо оптимизированных процедур, выполняющих большой спектр задач, всегда найдутся такие задачи, с которыми эти процедуры не справятся. Рассмотрим случай трехканального HSV изображения, в котором будем изменять насыщенность и значение цвета от 0 до 255 (максимальное значение для 8-битного изображения), оставляя оттенок неизменным. Лучше всего сделать это с использованием указателя, также как это было сделано с матрицами в примере 3-9. Тем не менее есть незначительные отличия, которые связаны с различиями в устройстве структур IplImage и CvMat. Пример 3-11 отражает наиболее быстрый способ обработки трехканального HSV изображения.

. Установка "S" и "V" составляющих HSV изображения в 255:

voidsaturate_sv( IplImage* img ) { for( int y=0; y<img->height; y++ ) {

uchar* ptr = (uchar*) (

img->imageData + y * img->widthStep

);

for(int x=0; x<img->width; x++ ) { ptr[3*x+1] = 255;

ptr[3*x+2] = 255;

}

}

}

В начале происходит вычисление указателя ptr, который указывает на начало соответствующей строки y. Затем значение насыщенности устанавливается в 255. Так, как это трехканальное изображение, расположение канала c в колонке x вычисляется как 3x+c*.

Одно важное отличие IplImage от CvMat заключается в поведении imageData. Данные

CvMat являются объединением, поэтому существует возможность задать тип указателя. Указатель imageData задается жестко как uchar*. Уже известно, что указатель данных не обязательно может быть типа uchar, в случае же с изображением арифметика над указателем сводится к добавлению к указателю значения widthStep, который так же в байтах, и потому уже не нужно беспокоиться о приведении типов, после получения результата. При работе с матрицами, необходимо уменьшать смещение, так как указатель данных не всегда может быть типа byte, в то время, как указатель данных изображения всегда типа byte, смещение можно использовать "как есть".

ROI и widthStep имеют большое практическое значение, так как во многих ситуациях ускоряют операции компьютерного зрения, за счет обработки части изображения. Все функции OpenCV поддерживают ROI и widthStep. Для включения или выключения поддержки ROI, используются функции cvSetImageROI() и cvResetImageROI(). Т.к. выделение части изображения имеет вид прямоугольника, нужно использовать структуру CvRect, которую можно передать в функцию cvSetImageROI() для "включения ROI"; для "выключения ROI" необходимо передать указатель на изображение в функцию cvResetImageROI().

voidcvSetImageROI( IplImage* image, CvRectrect );

voidcvResetImageROI( IplImage* image );

Чтобы увидеть, как используется ROI, рассмотрим пример загрузки и выделения некоторой области изображения. Код из примера 3-12 загружает изображение, устанавливает x, y, width, height предполагаемого ROI и объявляет целое число, для изменнеия области ROI. Затем задается область ROI, при помощи конструктора cvRect(). Важно выключить область ROI с помощью функции cvResetImageROI перед выводом изображения на экран, иначе в вывод попадет часть (область ROI) изображения:

#include <cv.h>

#include <highgui.h>

int main( intargc, char** argv ) { IplImage* src;

// argv[1] - путьдоизображения

if(argc == 7 && ((src=cvLoadImage(argv[1],1)) != 0 )) {

int x = atoi(argv[2]); // Отступ от левого верхнего угла изображения по оси int y = atoi(argv[3]); // Отступ от левого верхнего угла изображения по оси

int width = atoi(argv[4]); // Ширина ROI

int height = atoi(argv[5]);// Высота ROI

intadd = atoi(argv[6]) // Значение увеличения

//Захват области изображения. Установка ROI

cvSetImageROI(src, cvRect(x,y,width,height) );

//Сложение скаляра с изображением

cvAddS(src, cvScalar(add),src );

// Сброс ROI

cvResetImageROI( src );

cvNamedWindow( "Roi_Add", 1 ); // Создание окна cvShowImage( "Roi_Add", src ); // Вывод результата cvWaitKey();

}

return 0;

}

}

4. Разработка приложения

4.1 Бинарный объект

Бинарный объект - это объект, созданный человеком, и находящийся в поле зрения камеры. К таким объектам относятся дорожные знаки, автомобильные номера, баркоды и т.п. Часто эти объекты имеют контур, по которому они достаточно хорошо детектируются.

Однако возникают ситуации, когда объекты серьезно наклонены к оси камеры в нескольких плоскостях, а при этом на них накладывается шум:

Рис. 4.1 - Тестовые объекты

На рисунке 4.1 отображены тестовые объекты: (а) исходный объект, (б) искаженный объект в результате поворота к камере, (в) зашумленный объект.

Для правильного распознавания объекта необходимо провести перспективное преобразование. Но для этого необходимо получить 4 точки бинарного объекта.

Цель данной работы заключается в определении четырех точек в зашумленном объекте изначальной прямоугольной формы.

4.2 Доступные функции OpenCV для детектирования объектов

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

approxPoly - позволит аппроксимировать контур, и свести контур, например, к 4-м точкам.

boundingRect - получить ограничивающий Rect.

minAreaRect - получить ограничивающий CvBox

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

#include "opencv2/core/core_c.h"

#include "opencv2/imgproc/imgproc_c.h"

#include "opencv2/highgui/highgui_c.h"

int main( intargc, char** argv )

{

IplImage *image = cvLoadImage( "test.png" ); // 24-битноеизображений

IplImage *gray = cvCreateImage(cvGetSize( image ), 8, 1 ); // Пустое 8-битноеизображение

cvCvtColor( image, gray, CV_BGR2GRAY ); // Переводвградациисерогоc

vThreshold( gray, gray, 128, 255, CV_THRESH_BINARY_INV ); // Бинаризация

cvSaveImage( "binary.png", gray ); cvReleaseImage( &image ); cvReleaseImage( &gray ); return 0;}

Результатом будет инвертированное изображение (Рис 4.2):

Рис. 4.2 - Бинаризация изображения

Далее сделаем нахождение контуров и минимального ограничивающего прямоугольника.

boundingRect

Для этого после бинаризации добавим следующий код:

CvMemStorage* storage = cvCreateMemStorage(0);

CvSeq* contours = 0;

cvFindContours( gray, storage, &contours, sizeof(CvContour),

CV_RETR_TREE, CV_CHAIN_APPROX_NONE, cvPoint(0,0) ); // Поискконтуров

for(CvSeq* c=contours; c!=NULL; c=c->h_next)

{ CvRectRect = cvBoundingRect( c ); // Поиск ограничивающего прямоугольника

if( Rect.width< 50 ) continue; // Маленькие контуры меньше 50 пикселей не нужны

cvRectangle( image, cvPoint( Rect.x, Rect.y ), cvPoint( Rect.x + Rect.width, Rect.y + Rect.height ), CV_RGB(255,0,0), 2 );

}

cvReleaseMemStorage(&storage);

cvSaveImage( "image24.png", image );

Теперь, результатомпрограммыбудет изображен на рисунке 4.3.

Рис. 4.3 следующий этап разработки приложения

Видно, чтодетектировалисьнужныеобъекты. Но для (a) точки все правильные, для (b) правильные только 2 точки, а дляС не найдено ни одной точки.

minAreaRect

Для получения ограничивающего Box модифицируем код, добавляя в цикл

CvBox2D b = cvMinAreaRect2( c );

DrawRotatedRect( image, b, CV_RGB(255,0,0), 2 );

При этом, определив ранее функцию вывода CvBox2D на экран:

voidDrawRotatedRect( IplImage * iplSrc,CvBox2D rect,CvScalar color, int thickness, intline_type = 8, int shift = 0 )

{

CvPoint2D32f boxPoints[4];

cvBoxPoints(rect, boxPoints);

cvLine(iplSrc,cvPoint((int)boxPoints[0].x, (int)boxPoints[0].y),cvPoint((int)boxPoints[1].x, (int)boxPoints[1].y),color,thickness,line_type,shift);

cvLine(iplSrc,cvPoint((int)boxPoints[1].x, (int)boxPoints[1].y),cvPoint((int)boxPoints[2].x, (int)boxPoints[2].y),color,thickness,line_type,shift);

cvLine(iplSrc,cvPoint((int)boxPoints[2].x, (int)boxPoints[2].y),cvPoint((int)boxPoints[3].x, (int)boxPoints[3].y),color,thickness,line_type,shift);

cvLine(iplSrc,cvPoint((int)boxPoints[3].x, (int)boxPoints[3].y),cvPoint((int)boxPoints[0].x, (int)boxPoints[0].y),color,thickness,line_type,shift);

}

Результат работы изображен ниже(Рис 4.4).

Рис 4.4 - промежуточный этап разработки приложения

Как видим, опят результат неудовлетворительный, используем approxPoly.

Вместо цикла в примере заменяем на код, а в findcontour - на CV_CHAIN_APPROX_SIMPLE:

cvApproxPoly( contours, sizeof(CvContour), storage, CV_POLY_APPROX_DP, 3, 1 );

cvDrawContours( image, contours, CV_RGB(255,0,0), CV_RGB(0,255,0),2, 1, CV_AA, cvPoint(0,0) );

Результат уже лучше, но все же еще не удовлетворяет условиям

Рис. 4.5 - Использование функции cvApproxPoly

Здесь представлены аппроксимированные контуры, которые не дают информации о 4-х точках. Попробуем аппроксимировать еще, но результата нужного нам нет.

Алгоритм детектирования 4-х точек

Поэтому приходим к тому, что нужен собственный алгоритм для детектирования этих 4-х точек. Он очень прост и сводится к принципу RANSAC. Т.е. берутся 2 точки из контура, по ним строится линия, и определяется сколько точек близки к данной линии. Таким образом определяются 4 линии, а на их пересечении будет находиться искомая точка. Естественно его нужно немного модифицировать, поскольку стороны - четыре. Но в целом функция, которая получает на вход контур может быть выполнена так:

bool Find4Points(CvSeq* contour,CvPoint*Points,CvRectRect)

{

CvPoint*v_points=newCvPoint[contour->total];

int step =(Rect.width/4);

intall_lines=0;

LINE_* lines =new LINE_[contour->total/step];

CvSeqReader reader;

cvStartReadSeq( contour,&reader,-1);

CvPoint p ={-1,-1};

// Кандидатына 4 линии

for(inti=0;i< contour->total;i++)

{

CV_READ_SEQ_ELEM(v_points[i], reader );

if(i% step ==0)

{

if(p.x!=-1)

{

lines[all_lines]=MakeLine( cvPointTo32f( p ), cvPointTo32f(v_points[i]));

all_lines++;

}

p =v_points[i];

}

}

LINE_ lines4[4];

int all_lines4 =0;

for(int j =0; j <all_lines; j++)

{

int k =0;

for(int it =0; it < all_lines4; it++)

{

if( lines[j].b == lines4[it].b &&absf( lines[j].b1 - lines4[it].b1 )<0.1f&&
absf( lines[j].b2 - lines4[it].b2 )<2.0f)

{

k =1;

break;

}

}

if( k ==1)continue;

k =0;

for(inti=0;i< contour->total;i++)

if(PointInLine( lines[j],v_points[i]))

k++;

if( k > contour->total /8)

{

lines4[all_lines4]= lines[j];

all_lines4++;

if( all_lines4 ==4)break;

}

}//for(int j = 0; j <all_lines; j++ )

bool result =false;

if( all_lines4 ==4)

{

float x, y;

for(inti=0;i<4;i++)

{

Intersection( lines4[i], lines4[(i+1)%4], x, y );

Points[i].x =int( x+0.5f);

Points[i].y =int( y+0.5f);

}

result=true;

}

delete[]v_points;

delete[] lines;

return result;

}

Для того, чтобы не перебирать все возможные точки берутся точки через шаг step и формируются только кандидаты из соседних точек. Кандидаты - это линии. Затем линии перебираются и первые 4, которые пересекают достаточное количество точек (if( k >contour->total / 8 )) считаются линиями сторонами четырехугольника. После этого находятся вершины четырехугольника путем нахождения пересечений линий.

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

LINE_ -- структура, описывающая линию.

MakeLine - функция, создающая линию.

PointInLine -- функция определяющая сколько точек пересекает линия.

Intersection - функция, находящая точку пересечения линий.

Эту функцию Find4Points можно вызвать так в том же цикле перебора контуров:

CvPointp[4];

if ( Find4Points( c, p, Rect ) )

{

for(inti = 0; i< 4; i++ )

cvLine( image, p[i], p[(i+1)%4], CV_RGB(255,0,0), 2 );

}

Результат будет следующий (Рис. 4.6):

Рис. 4.7 - конечный правильный результат приложения

Итогом работы стал о разработанное приложение, позволяющее определить контуры объектов, обладающих некоторыми дефектами : наклон, или шумы.

Заключение

...

Подобные документы

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.