Компьютерное зрение на openCV. Пишем скрипт для поиска книг на изображениях с помощью Python и OpenCV Opencv поиск и сопоставление характерных точек

В этой статье использован C++ интерфейс, FREAK и детектирование множества объектов. Надежность детектирования объектов с помощью FREAK ниже, чем SURF, однако его работа намного быстрее, что позволяет использовать алгоритм на мобильных и встроенных системах. Пример работы представлен на рисунке:

Рассмотрим исходный код, который позволяет этого достигнуть. Код приведен полностью для желающих быстро вставить его в свой проект.
#include #include #include #include #include #include #include #include keypointsImageTemple, keypointsImage; Mat descriptorsImageTemple, descriptorsImage; std::vector matches; // Инициалищация класса детектора особенностей, 1000 - пороговое значение для отсеивания // малозначимых особенностей SurfFeatureDetector detector(1000); // Класс для FREAK особенностей. Можно настраивать режимы сравнения особенностей: // FREAK extractor(true, true, 22, 4, std::vector matcher; // Детектирование double t = (double)getTickCount(); detector.detect(ImageTemple, keypointsImageTemple); detector.detect(Image, keypointsImage); t = ((double)getTickCount() - t)/getTickFrequency(); std::cout << "detection time [s]: " << t/1.0 << std::endl; // Извлечение особенностей t = (double)getTickCount(); extractor.compute(ImageTemple, keypointsImageTemple, descriptorsImageTemple); extractor.compute(Image, keypointsImage, descriptorsImage); t = ((double)getTickCount() - t)/getTickFrequency(); std::cout << "extraction time [s]: " << t << std::endl; // Сравнение t = (double)getTickCount(); matcher.match(descriptorsImageTemple, descriptorsImage, matches); t = ((double)getTickCount() - t)/getTickFrequency(); std::cout << "matching time [s]: " << t << std::endl; // Отобразить на изображении Mat imgMatch; drawMatches(ImageTemple, keypointsImageTemple, Image, keypointsImage, matches, imgMatch); imwrite("matches.jpeg", imgMatch); std::vectorObj; std::vectorScene; for(int i = 0; i < matches.size(); i++) { obj.push_back(keypointsImageTemple[ matches[i].queryIdx ].pt); scene.push_back(keypointsImage[ matches[i].trainIdx ].pt); } Mat H = findHomography(obj, scene, CV_RANSAC); std::vector Scene_corners(4); perspectiveTransform(obj_corners, scene_corners, H); //-- Draw lines between the corners (the mapped object in the scene - image_2) line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255, 0), 4); line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255, 0), 4); line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255, 0), 4); line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255, 0), 4); imwrite("matches3.jpeg", imgMatch); return 0; }

Для любых особенностей в OpenCV необходимо инициализировать класс SurfFeatureDetector. Первое действие после различных инициализаций – это детектирование особенностей detector.detect для эталонного изображения и изображения сцены. После чего для каждого изображения по результатам работы детектора вычисляются FREAK особенности: extractor.compute.
Сравнение схожести особенностей осуществляется с помощью matcher.match.
Далее присутствует цикл с формированием точек из особенностей для обоих изображений. На основании точек вычисляется гомография изображений findHomography. Положение и поворот объекта вычисляется с помощью функции perspectiveTransform. Ну а затем – вывод на изображение.
Эталонное изображение:

Изображение сцены:


Результат представлен вначале.
Однако здесь возникает вопрос, как рассчитывать оптимальный порог особенностей: SurfFeatureDetector detector(1000);. Ответ – экспериментально. Некоторую информацию по данному вопросу вы можете получить .
Предположим, что у нас на изображении несколько объектов:


Результат работы программы будет следующий:


Естественно, что такая ситуация не устраивает. Для того, чтобы детектировать все объекты, необходимо разделить изображение на несколько частей. Однако здесь следует помнить, что если изображение разделить на непересекающиеся блоки (пример изображение 100x100 разделить на 4 блока по 50x50), то может возникнуть ситуация, когда объект будет частично находиться в нескольких блоках и не будет детектирован. Для избегания этого необходимо делать пересекающиеся блоки, что несколько замедлит работу, но улучшит качество (пример изображение 100x100 разделить на 9 блоков по 50x50 так, как показано в примере). Пример программы детектирующий множество объектов ниже:
#include #include #include #include #include #include #include #include using namespace cv; int main(int argc, char** argv) { if(argc != 3) return 1; Mat ImageTemple = imread(argv, CV_LOAD_IMAGE_GRAYSCALE); if(!ImageTemple.data) return 2; // Ошибка Mat Image = imread(argv, CV_LOAD_IMAGE_GRAYSCALE); if(!Image.data) return 3; // Ошибка std::vector keypointsImageTemple; Mat descriptorsImageTemple; std::vector matches; // Инициалищация класса детектора особенностей, 1000 - пороговое значение для отсеивания // малозначимых особенностей SurfFeatureDetector detector(1000); detector.detect(ImageTemple, keypointsImageTemple); int maxy = 3; int maxx = 3; Mat Draw_mat = imread(argv, 1); for(int y = 0; y < maxy; y++) for(int x = 0; x < maxx; x++) { // Класс для FREAK особенностей. Можно настраивать режимы сравнения особенностей: // FREAK extractor(true, true, 22, 4, std::vector()); FREAK extractor; // Используется для определение совпадений особенностей - мера Хемминга BruteForceMatcher matcher; std::vector keypointsImage; Mat descriptorsImage; CvRect Rect = cvRect(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1)), 2 * (Image.cols / (maxx + 1)), 2 * (Image.rows / (maxy + 1))); Mat ImageROI(Image, Rect); detector.detect(ImageROI, keypointsImage); extractor.compute(ImageTemple, keypointsImageTemple, descriptorsImageTemple); extractor.compute(ImageROI, keypointsImage, descriptorsImage); matcher.match(descriptorsImageTemple, descriptorsImage, matches); // Отброс слишком расходящихся значений for (int i = 0; i < matches.size(); i++) { if(matches[i].distance > 150) { matches.erase (matches.begin() + i); } } std::vectorObj; std::vectorScene; for(int i = 0; i < matches.size(); i++) { obj.push_back(keypointsImageTemple[ matches[i].queryIdx ].pt); scene.push_back(keypointsImage[ matches[i].trainIdx ].pt); } Mat H = findHomography(obj, scene, CV_RANSAC); std::vectorObj_corners(4); obj_corners = cvPoint(0,0); obj_corners = cvPoint(ImageTemple.cols, 0); obj_corners = cvPoint(ImageTemple.cols, ImageTemple.rows); obj_corners = cvPoint(0, ImageTemple.rows); std::vectorScene_corners(4); perspectiveTransform(obj_corners, scene_corners, H); //-- Draw lines between the corners (the mapped object in the scene - image_2) line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalar(0, 255, 0), 4); line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalar(0, 255, 0), 4); line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalar(0, 255, 0), 4); line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalar(0, 255, 0), 4); } imwrite("draw_mat.jpeg", Draw_mat); return 0; }

Результат работы следующий:


Видно, что все объекты детектированы. Причем некоторые дважды (из-за того, что попали в два блока).

Библиотека компьютерного зрения и машинного обучения с открытым исходным кодом. В неё входят более 2500 алгоритмов, в которых есть как классические, так и современные алгоритмы для компьютерного зрения и машинного обучения. Эта библиотека имеет интерфейсы на различных языках, среди которых есть Python (в этой статье используем его), Java, C++ и Matlab.

Установка

Инструкцию по установке на Windows можно посмотреть , а на Linux - .

Импорт и просмотр изображения

import cv2 image = cv2.imread("./путь/к/изображению.расширение") cv2.imshow("Image", image) cv2.waitKey(0) cv2.destroyAllWindows()

Примечание При чтении способом выше изображение находится в цветовом пространстве не RGB (как все привыкли), а BGR. Возможно, в начале это не так важно, но как только вы начнёте работать с цветом - стоит знать об этой особенности. Есть 2 пути решения:

  1. Поменять местами 1-й канал (R - красный) с 3-м каналом (B - синий), и тогда красный цвет будет (0,0,255) , а не (255,0,0) .
  2. Поменять цветовое пространство на RGB: rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    И тогда в коде работать уже не с image , а с rgb_image .

Примечание Чтобы закрыть окно, в котором отображается изображение, нажмите любую клавишу. Если использовать кнопку закрытия окна, можно наткнуться на подвисания.

На протяжении статьи для вывода изображений будет использоваться следующий код:

Import cv2 def viewImage(image, name_of_window): cv2.namedWindow(name_of_window, cv2.WINDOW_NORMAL) cv2.imshow(name_of_window, image) cv2.waitKey(0) cv2.destroyAllWindows()

Кадрирование

Пёсик после кадрирования

Import cv2 cropped = image viewImage(cropped, "Пёсик после кадрирования")

Где image - это image .

Изменение размера

После изменения размера на 20 %

Import cv2 scale_percent = 20 # Процент от изначального размера width = int(img.shape * scale_percent / 100) height = int(img.shape * scale_percent / 100) dim = (width, height) resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA) viewImage(resized, "После изменения размера на 20 %")

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

Поворот

Пёсик после поворота на 180 градусов

Import cv2 (h, w, d) = image.shape center = (w // 2, h // 2) M = cv2.getRotationMatrix2D(center, 180, 1.0) rotated = cv2.warpAffine(image, M, (w, h)) viewImage(rotated, "Пёсик после поворота на 180 градусов")

image.shape возвращает высоту, ширину и каналы. M - матрица поворота - поворачивает изображение на 180 градусов вокруг центра. -ve - это угол поворота изображения по часовой стрелке, а +ve , соответственно, против часовой.

Перевод в градации серого и в чёрно-белое изображение по порогу

Пёсик в градациях серого

Чёрно-белый пёсик

Import cv2 gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ret, threshold_image = cv2.threshold(im, 127, 255, 0) viewImage(gray_image, "Пёсик в градациях серого") viewImage(threshold_image, "Чёрно-белый пёсик")

gray_image - это одноканальная версия изображения.

Функция threshold возвращает изображение, в котором все пиксели, которые темнее (меньше) 127 заменены на 0, а все, которые ярче (больше) 127, - на 255.

Для ясности другой пример:

Ret, threshold = cv2.threshold(im, 150, 200, 10)

Здесь всё, что темнее, чем 150, заменяется на 10, а всё, что ярче, - на 200.

Остальные threshold-функции описаны .

Размытие/сглаживание

Размытый пёсик

Import cv2 blurred = cv2.GaussianBlur(image, (51, 51), 0) viewImage(blurred, "Размытый пёсик")

Функция GaussianBlur (размытие по Гауссу) принимает 3 параметра:

  1. Исходное изображение.
  2. Кортеж из 2 положительных нечётных чисел. Чем больше числа, тем больше сила сглаживания.
  3. sigmaX и sigmaY . Если эти параметры оставить равными 0, то их значение будет рассчитано автоматически.

Рисование прямоугольников

Обводим прямоугольником мордочку пёсика

Import cv2 output = image.copy() cv2.rectangle(output, (2600, 800), (4100, 2400), (0, 255, 255), 10) viewImage(output, "Обводим прямоугольником лицо пёсика")

Эта функция принимает 5 параметров:

  1. Само изображение.
  2. Координата верхнего левого угла (x1, y1) .
  3. Координата нижнего правого угла (x2, y2) .
  4. Цвет прямоугольника (GBR/RGB в зависимости от выбранной цветовой модели).
  5. Толщина линии прямоугольника.

Рисование линий

2 пёсика, разделённые линией

Import cv2 output = image.copy() cv2.line(output, (60, 20), (400, 200), (0, 0, 255), 5) viewImage(output, "2 пёсика, разделённые линией")

Функция line принимает 5 параметров:

  1. Само изображение, на котором рисуется линия.
  2. Координата первой точки (x1, y1) .
  3. Координата второй точки (x2, y2) .
  4. Цвет линии (GBR/RGB в зависимости от выбранной цветовой модели).
  5. Толщина линии.

Текст на изображении

Изображение с текстом

Import cv2 output = image.copy() cv2.putText(output, "We <3 Dogs", (1500, 3600),cv2.FONT_HERSHEY_SIMPLEX, 15, (30, 105, 210), 40) viewImage(output, "Изображение с текстом")

Функция putText принимает 7 параметров:

  1. Непосредственно изображение.
  2. Текст для изображения.
  3. Координата нижнего левого угла начала текста (x, y) .
  4. Лиц обнаружено: 2

    Import cv2 image_path = "./путь/к/фото.расширение" face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml") image = cv2.imread(image_path) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, scaleFactor= 1.1, minNeighbors= 5, minSize=(10, 10)) faces_detected = "Лиц обнаружено: " + format(len(faces)) print(faces_detected) # Рисуем квадраты вокруг лиц for (x, y, w, h) in faces: cv2.rectangle(image, (x, y), (x+w, y+h), (255, 255, 0), 2) viewImage(image,faces_detected)

    detectMultiScale - общая функция для распознавания как лиц, так и объектов. Чтобы функция искала именно лица, мы передаём ей соответствующий каскад.

    Функция detectMultiScale принимает 4 параметра:

    1. Обрабатываемое изображение в градации серого.
    2. Параметр scaleFactor . Некоторые лица могут быть больше других, поскольку находятся ближе, чем остальные. Этот параметр компенсирует перспективу.
    3. Алгоритм распознавания использует скользящее окно во время распознавания объектов. Параметр minNeighbors определяет количество объектов вокруг лица. То есть чем больше значение этого параметра, тем больше аналогичных объектов необходимо алгоритму, чтобы он определил текущий объект, как лицо. Слишком маленькое значение увеличит количество ложных срабатываний, а слишком большое сделает алгоритм более требовательным.
    4. minSize - непосредственно размер этих областей.

    Contours - распознавание объектов

    Распознавание объектов производится с помощью цветовой сегментации изображения . Для этого есть две функции: cv2.findContours и cv2.drawContours .

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

    Сохранение изображения

    import cv2 image = cv2.imread("./импорт/путь.расширение") cv2.imwrite("./экспорт/путь.расширение", image)

    Заключение

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

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

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

Что мы будем делать?

Взглянем на изображение, на котором будем искать книги:

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

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

Какие библиотеки нам понадобятся?

Чтобы написать систему для поиска и обнаружения книг на изображениях, мы будем использовать OpenCV для работы с компьютерным зрением и обработки изображений. Нам также необходимо установить NumPy для корректной работы OpenCV. Убедитесь, что у вас установлены эти библиотеки!

Поиск книг на изображениях с помощью Python и OpenCV

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

Откройте свой любимый редактор кода, создайте новый файл с именем find_books.py и начнем:

# -*- coding: utf-8 -*- # импортируйте необходимые пакеты import numpy as np import cv2 # загрузите изображение, смените цвет на оттенки серого и уменьшите резкость image = cv2.imread("example.jpg") gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (3, 3), 0) cv2.imwrite("gray.jpg", gray)

Начнем с импорта библиотеки OpenCV. Загрузка изображения с диска обрабатывается функцией cv2.imread . Здесь мы просто загружаем его с диска, а затем преобразуем цветовую гамму из RGB в оттенки серого.

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

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

Теперь давайте определим края (т.е. контуры) объектов на изображении:

# распознавание контуров edged = cv2.Canny(gray, 10, 250) cv2.imwrite("edged.jpg", edged)

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

Мы нашли контуры объектов на изображениях. Однако, как вы видите, некоторые из контуров не закрыты - между контурами существуют промежутки. Чтобы убрать промежутки между белыми пикселями изображения, мы применим операцию «закрытия»:

# создайте и примените закрытие kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) closed = cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel) cv2.imwrite("closed.jpg", closed)

Теперь пробелы в контурах закрыты:

Следующим шагом является фактическое обнаружение контуров объектов на изображении. Для этого мы будем использовать функцию cv2.findContours:

# найдите контуры в изображении и подсчитайте количество книг cnts = cv2.findContours(closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) total = 0

Рассмотрим геометрию книги.

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

Чтобы проверить, является ли контур книгой или нет, нам нужно выполнить цикл по каждому контуру:

# цикл по контурам for c in cnts: # аппроксимируем (сглаживаем) контур peri = cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, 0.02 * peri, True) # если у контура 4 вершины, предполагаем, что это книга if len(approx) == 4: cv2.drawContours(image, , -1, (0, 255, 0), 4) total += 1

Для каждого из контуров мы вычисляем периметр, используя cv2.arcLength , а затем аппроксимируем (сглаживаем) контур, используя cv2.approxPolyDP .

Причина, по которой мы аппроксимируем контур, заключается в том, что он может не быть идеальным прямоугольником. Из-за зашумления и теней на фото вероятность того, что у книги будет ровно 4 вершины, невелика. Аппроксимируя контур, мы решаем эту проблему.

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

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

# показываем результирующее изображение print("Я нашёл {0} книг на этой картинке".format(total) cv2.imwrite("output.jpg", image))

На данном этапе наше изображение будет выглядеть так:

Подведем итоги

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

Наш подход состоял в том, чтобы:

  1. Загрузить изображение с диска и преобразовать его в оттенки серого.
  2. Немного размыть изображение.
  3. Применить детектор контуров Canny для обнаружения объектов на изображении.
  4. Закрыть любые промежутки в контурах.
  5. Найти контуры объектов на изображении.
  6. Применить контурную аппроксимацию, чтобы определить, был ли контур прямоугольником и, следовательно, книгой.

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

Важнейшими источниками информации о внешнем мире для робота являются его оптические датчики и камеры. После получения изображения необходима его обработка для анализа обстановки или принятия решения. Как я говорил ранее, компьютерное зрение объединяет множество методов работы с изображениями. При функционировании робота предполагается, что видеоинформация с камер обрабатывается какой-либо программой, запущенной на контроллере. Чтобы не писать код с нуля, можно воспользоваться готовыми программными решениями. На текущий момент существует множество готовых библиотек компьютерного зрения:

  • Matrox Imaging Library
  • Camellia Library
  • Open eVision
  • HALCON
  • libCVD
  • OpenCV
  • и т.д…
Данные SDK могут сильно различаться по функциональности, условиям лицензирования, используемым языкам программирования. Мы же подробнее остановимся на OpenCV . Она бесплатна как для учебных целей, так и для коммерческого использования. Написана на оптимизированном C/C++, поддерживает интерфейсы C, C++, Python, Java и включает в себя реализации свыше 2500 алгоритмов. Помимо стандартных функций обработки изображений (фильтрация, размытие, геометрические преобразования и т.д…) данный SDK позволяет решать более сложные задачи, к которым относятся обнаружение объекта на фотографии и его «узнавание». Следует понимать, что задачи обнаружения и распознавания могут быть совершенно различными:
  • поиск и распознавание конкретного объекта,
  • поиск объектов одной категории (без распознавания),
  • только распознавание объекта (уже готовое изображение с ним).
Для обнаружения признаков на изображении и проверки на совпадение в OpenCV имеются следующие методы:
  • Гистограмма направленных градиентов HOG(Histogram of Oriented Gradients) — может применяться для обнаружения пешеходов
  • Алгоритм Виолы-Джонса — применяется для поиска лиц
  • Алгоритм обнаружения признаков SIFT (Scale Invariant Feature Transform)
  • Алгоритм обнаружения признаков SURF (Speeded Up Robust Features)
Например, SIFT обнаруживает наборы точек, которые можно использовать для идентификации объекта. Помимо приведенных методик в OpenCV имеются и другие алгоритмы для детектирования и распознавания, а также набор алгоритмов, относящихся к машинному обучению, таких как метод k ближайших соседей, нейронные сети, метод опорных векторов и т.д… В целом OpenCV предоставляет инструментарий, достаточный для решения подавляющего большинства задач компьютерного зрения. Если алгоритм не имеется в составе SDK, то, как правило, он может быть без проблем запрограммирован. Кроме того, существует множество авторских версий алгоритмов, написанных пользователями на основе OpenCV. Также следует отметить, что за последние годы OpenCV сильно расширилась и стала в некоторой степени «тяжеловесной». В связи с этим, разными группами энтузиастов создаются «облегченные» библиотеки, основанные на OpenCV. Примеры: SimpleCV, liuliu ccv, tinycv… Полезные сайты
  1. http://opencv.org/ — Основной сайт проекта
  2. http://opencv.willowgarage.com/wiki/ — Старый сайт проекта с документацией по старым версиям

Привет! Передо мной встала задача реализовать распознавание дорожных знаков с видео потока. Так как с задачами подобного рода я раньше не сталкивался, то процесс реализации само собой предполагает предварительное долгое «курение» форумов и безжалостные издевательства над чужими примерами. Поэтому решил собрать всё прочитанное в одном месте для будущих поколений, а так же, в ходе повествования, задать Хабру несколько вопросов.

Прелюдии.

Итак, после изучения всех средств, которые возможно использовать для реализации поставленной задачи, я остановился на среде разработки Microsoft Visual Studio© 2010 , с использованием чудесной библиотеки OpenCV .

Сам процесс работы с OpenCV предполагает предварительные танцы с бубном, о которых есть достаточно подробных описаний:

Второй акт танцев с бубном.

В итоге повернул в сторону тренировки каскадов. «Покурив» в этом направлении понял что мне нужны два инструмента createsampes и haartraining. Но их exe`шники у меня отсутствовали, а компилироваться отказывались. На тот момент версия OpenCV у меня была 2.4.4, настроенная по , во же статье я впервые прочитал про использование Cmake при установке. В итоге решил скачать версию 2.3.1 и переустановить библиотеку. После чего мне удалось запустить нужные инструменты через командную строку и встал вопрос как с ними работать. Все точки над «и» расставили статьи, в которых показаны параметры с которыми нужно запускать createsampes и haartraining с подробным описанием этих параметров.

Код с чистого листа.

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

Код 2.0

#include "stdafx.h" #include #include #include using namespace cv; int main(int argc, char** argv) { Mat frame, gray; string object_cascade = "haarustupi.xml"; CascadeClassifier haar(object_cascade); VideoCapture cap(0); namedWindow("Video", 1); vector objects; while (true) { cap >> frame; cvtColor(frame, gray, CV_BGR2GRAY); haar.detectMultiScale(gray, objects, 1.9, 10, 0,Size(50, 50)); for (vector::const_iterator r = objects.begin(); r != objects.end(); r++) rectangle(frame, r->tl(), r->br(), Scalar(0, 0, 255)); imshow("Video", frame); if (waitKey(33) >= 0) break; } return (EXIT_SUCCESS); }

Среду настраиваем точно так же как и в прошлом проекте.

ПовторениЯ - отцы учения.

Дело за «малым» обучить каскады.)
Тут начинается самое интересное. После чего я решил писать о всех этих мытарствах на хабр и просить совета.
Я заготовил 500 изображений размером 1600х1200. и одно изображение со знаком размером 80х80. Одного изображения будет достаточно, потому что мы детектируем определенный объект, а не огромное разнообразие лиц.

Итак, заготовив картинки и создав файл neg.dat со структурой

Negative/n (1).jpg negative/n (2).jpg negative/n (3).jpg negative/n (4).jpg ... negative/n (500).jpg

запускаем файл opencv_createsamples.exe через CMD со следующими параметрами

C:OpenCV2.3.1buildcommonx86opencv_createsamples.exe -vec C:OpenCV2.3.1buildcommonx86positive.vect -bg C:OpenCV2.3.1buildcommonx86neg.dat -img C:OpenCV2.3.1buildcommonx86ustupi.jpg -num 500 -w 50 -h 50 -bgcolor 0 -bgthresh 0 -show

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

маленькая

Т.е утилита обрезает bg-картинку под размер позитивной картинки. Изменение параметров -w и -h результата не дают и заднего фона все равно почти не видно. Если вдруг кто знает в чем тут дело, поделитесь соображениями . Размер негативных изображений уменьшал до 800х600 - результат тот же.

C:OpenCV2.3.1buildcommonx86opencv_haartraining.exe -data C:OpenCV2.3.1buildcommonx86haarustupi -vec C:OpenCV2.3.1buildcommonx86positive.vect -bg C:OpenCV2.3.1buildcommonx86neg.dat -npos 500 -nneg 500 -nstages 6 -nsplits 2 -w 20 -h 24 -mem 1536 -mode ALL -nonsym -minhitrate 0.999 -maxfalsealarm 0.5

после чего вы получите долгожданный xml-файл, который можно подгружать в исходный код программы.
В итоге каскад слегка обучается и, с большим количеством ложных срабатываний, реагирует на, полюбившуюся мне, картинку знака уступи дорогу.
Но я не могу добиться точных срабатываний, как мне кажется, из-за того что обрезается задний фон в позитивных изображениях. И никак не получаются картинки как в мануалах. Но остается еще вариант увеличить количество этапов обучения и, нагрузив свой компьютер на весь день, дождаться пока каскад будет более «образованным». Чем я и планирую заняться до появления других идей.

Эпилог

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