Skip to content

Каскад Хаара

Каскады Хаара — инструмент, позволяющий реализовывать поиск и детектирование на изображении интересующих объектов, например, дорожных знаков, лиц (то есть любых предметов, которые на первый взгляд выглядят различно, однако на самом деле имеют некие общие черты – такие черты называют features (признаки)). Каскады Хаара — это инструмент компьютерного зрения (CV) среднего уровня, алгоритмы, для реализации которых используется машинное обучение (machine learning, ML), в отличие от простых алгоритмов (нижнего уровня), которые используются, например, для изменения масштабов изображения, размытия, коррекции перспективы или детекции простых геометрических примитивов. Однако при этом в этих алгоритмах не используются нейросети (в отличие от алгоритмов высокого уровня). Посмотрим на то, как работает программа с использованием каскадов Хаара, а также разберем принцип работы каскадов Хаара на простом примере – детекторе лиц:

import cv2
#импорт библиотеки OpenCV

faceCascade= cv2.CascadeClassifier("Resources/haarcascade_frontalface_default.xml")
#выбор классификатора Хаара для лиц в анфас

img = cv2.imread('Resources/lena.png')
#создание объекта img с использованием команды чтения

imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#перевод изображения в оттенки серого

faces = faceCascade.detectMultiScale(imgGray,1.1,4)
#создание объекта, содержащего обводящие прямоугольники лиц на изображении

for (x,y,w,h) in faces:
#рисование обводящих прямоугольников для лиц
    cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)

cv2.imshow("Result", img)
#вывод результата

cv2.waitKey(0)
#команда для того, чтобы изображение не закрывалось

В библиотеке OpenCV уже реализованы инструменты для работы с каскадными классификаторами (к которым относятся каскады Хаара), поэтому в примере выше мы просто подгружаем уже обученный классификатор, haarcascade_frontalface_default.xml который находится в папке Resources (данная папка содержится в корневой директории проекта):

Папка resources

В этой же папке находится изображение lena.png:

Изображение из resources

В самой программе создается объект faceCascade, который по сути как раз является классификатором лиц в анфас. С помощью этого классификатора на изображении, переведенном в оттенки серого (каскады Хаара лучше работают именно с изображениями в оттенках серого), ищутся лица, а их местоположение сохраняется в объект faces, в котором они хранятся в виде координат и размеров обводящих прямоугольников. Далее обводящие прямоугольники выводятся на объект, содержащий оригинальное изображение, а этот объект выводится на экран монитора:

Lena с квадратом

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

faces = faceCascade.detectMultiScale(imgGray,1.1,4)

В качестве аргументов функции (метода) detectMultiScale выступают:

  • imgGray --- объект-изображение в оттенках серого;
  • 1.1 --- масштабный коэффициент (более подробная информация приведена далее);
  • 4 --- минимальное количество соседей, при наличии которого будет детектироваться интересующий объект (более подробная информация приведена далее).

Разберем основной принцип работы каскадов Хаара.

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

Матрица лиц

По сути, каскад Хаара — это набор четырехугольников-фильтров. Это может быть прямоугольник, который сам состоит из двух прямоугольников — черного и белого. А может быть прямоугольник из 3-х других — двух черных и одного белого, или прямоугольник из 4-х других — двух черных и двух белых. Эти фильтры находятся в определенной конфигурации внутри скользящего окна (а само окно скользит по изображению).

Скользящий квадрат

Скользящий квадрат

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

Скользящий квадрат

Сама операция наложения фильтров и проверки, является ли область потенциально пригодной, сводится к простой математике. Если предположить, что более темные пиксели имеют интенсивность, выражаемую числом, тем меньшим, чем темнее пиксель (и наоборот, чем число больше, тем пиксель светлее), то при конфигурации, показанной ниже, сумму фильтра можно выразить как: , то есть из интенсивности пикселей, находящихся в «светлой» зоне фильтра, вычитаются интенсивности пикселей, находящихся в «темной» зоне. Чем выше полученное значение, тем лучше фильтр «накладывается» на определенную зону (и наоборот, если это значение близко к 0 или отрицательное), то зона не подходит.

Принцип наложения

Сам фильтр со временем перемещается:

Принцип наложения

А размер фильтра также может меняться:

Принцип наложения

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

Из этого видео как раз и становятся понятными смыслы коэффициентов функции (метода) detectMultiScale:

  • imgGray --– входной объект-изображение для сканирования в оттенках серого
  • 1.1 --– (scaleFactor) коэффициент масштабирования. Это параметр, указывающий, насколько изменится размер сканирующего окна (а на самом деле – насколько уменьшается размер изображения при каждой итерации сканирования). По сути, масштабный коэффициент используется для создания масштабной пирамиды. Наша модель каскада (в формате .xml) имеет фиксированный размер, определенный во время обучения. Это означает, что в обычном случае (если бы сканирование не происходило по итерациям, с постепенным уменьшением входного изображения), могла бы возникнуть ситуация, когда лицо, которое есть на изображении, значительно больше скользящего окна (то есть той конфигурации фильтров, которая считается пригодной в каскаде Хаара):

Принцип наложения

Изменяя масштаб входного изображения, можно изменить размер большего лица на меньшее, что сделает его доступным для обнаружения:

Принцип наложения

Чем выше показатель масштаба (например, 1.5-2), тем быстрее будет работать алгоритм. Однако качество работы при этом будет ниже (т.к. возможны ситуации, когда размер лица на одной итерации больше, чем размер скользящего окна, а на другой – меньше). С другой стороны, если коэффициент сделать меньше (1.05-1.1), то качество обнаружения лиц будет выше, но в то же время итераций будет больше, а значит, и время работы алгоритма увеличится.

  • 4 --- (minNeighbors) минимальное количество соседей, при наличии которого будет детектироваться интересующий объект. Этот параметр указывает, сколько соседей должен иметь каждый прямоугольник-кандидат, чтобы сохранить его. Влияет на качество обнаруженных лиц. Более высокое значение параметра приводит к меньшему количеству обнаружений, но вероятность правильного обнаружения будет выше (т. е. качество работы алгоритма будет выше). И, наоборот, если значение сделать малым (1-3), то детектирование может сработать неправильно (в качестве лица случайно могут определиться похожие посторонние объекты — деревья или листва).

Ошибочное изображение

На самом деле каскады Хаара работают несколько более сложным образом, чем описано выше: при применении всей вышеперечисленной математики «в лоб» вычисления занимали бы слишком много времени, поэтому в реальности алгоритмы реализуются таким образом, который позволяет им очень быстро проводить первичную оценку области, на которой находится скользящее окно (а не проверять наличие расположения вообще всех отдельных фильтров в каскаде), а кроме того, запоминать бесперспективные области и не проводить их повторное сканирование при других масштабах (на итерациях с изменением масштаба входного изображения).

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

import cv2

cap = cv2.VideoCapture(0)

while 1:
    success, img = cap.read()
    imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
    image = img
    imgGRAY = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(
        imgGRAY,
        scaleFactor= 1.1,
        minNeighbors= 6,
        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)
    cv2.imshow('girls_img_new', image)
    cv2.waitKey(1)

По ссылке вы можете найти уже готовые хорошо натренированные модели каскадов Хаара.

Несмотря на то, что процесс обучения алгоритмов, основанных на каскадах Хаара, является относительно быстрым, а сам процесс детекции имеет относительно малую вычислительную сложность (например, по сравнению с нейросетевыми подходами), такой подход все же имеет недостатки, главным из которых является чувствительность к ориентации объекта на изображении: объект будет распознан хорошо только в том случае, если его ориентация совпадает с той, на которой учился детектор (в случае лиц в анфас — вертикальная):

Ограничения наложения

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

Тренировка собственного каскада

Наконец, рассмотрим тренировку собственного каскада для детекции. В качестве детектируемого объекта выберем знак «Искусственная неровность»:

Знак

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

Матрица знаков

Для тренировки каскадов будем использовать программу Cascade Trainer GUI .

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

Матрица знаков

Красным выделен путь к текущей рабочей директории, которая должна в себе содержать две папки: «n» и «p»:

Матрица знаков

В этих папках должны содержаться тренировочные изображения:

  1. В папке «p» --- «позитивные» изображения. Это такие изображения, на которых есть интересующий нас объект (знак «Искусственная неровность»):

Матрица знаков

  1. В папке «n» --- «негативные» изображения. То есть изображения, на которых интересующего объекта нет ни в каком виде (даже кусочно):

Матрица знаков

Сами наборы изображений можно снимать, например, на камеру мобильного телефона. При этом очень важно выбирать максимально разнообразные фоны для «позитивных» изображений (их же, но без самого объекта можно использовать для «негативных» изображений):

Матрица знаков

Предупреждение: При создании датасета разрешение изображений рекомендуется изменять на довольно малое (например, на 320x240 пикселей, с изначальных 4640x3472) – это необходимо для ускорения процесса обучения. Сделать это можно с помощью бесплатной программы XnViewMP, о ней подробнее рассказано в конце главы.

На первой вкладке также выставим параметр «Negative image count» - это количество негативных изображений в папке «n».

Матрица знаков

На второй вкладке можно выделить дополнительную оперативную память, что ускорит вычисления (если у вас всего 8Гб оперативной памяти, то стоит оставить значения в обеих графах 1024Мб; если у вас всего 16Гб оперативной памяти, то оба значения можно поднять в два раза до 2048Мб). Также на второй вкладке необходимо выбрать количество эпох, за которые будет проходить обучение (по умолчанию там стоит значение 20, однако в целом можно опускать до 15 для ускорения процесса и возвращать к 2030, когда результаты обучения начинают удовлетворять.

Матрица знаков

Параметры на четвертой вкладке трогать не будем, а на третьей выберем размер скользящего окна – по умолчанию он 24x24 пикселя, однако в целом можно его поднять до 32x32 пикселя (чем выше размер, тем точнее будет работать каскад; однако не стоит делать это значение слишком высоким, т.е. более 32x32 пикселя, так как в этом случае обучение не запустится, вычисления будут слишком сложными для компьютера). В нашем случае искомый знак можно вписать в прямоугольник с равными сторонами, то есть в квадрат, поэтому оба значения можно выставить равными 32. Однако если выбранный вами объект не вписывается в квадрат, а, скорее, в прямоугольник (например, вы хотите искать плату Ардуино), то можно выставить ширину 32 и высоту 24.

Матрица знаков

После выставления всех параметров необходимо нажать на кнопку Start и ждать окончания обучения.

Матрица знаков

Предупреждение: Если в процессе обучения появляются ошибки, это может быть связано с такими причинами, как малое разнообразие позитивных изображений (все они сделаны на одном фоне, поэтому у программы не получается «понять» что же именно на всех фото общее и выбивается из окружения), либо же сложность выделения объекта на позитивных изображениях (когда искомый знак лежит на других знаках, и программе тяжело найти, что же на изображении все-таки является объектом, т.к. очень много похожих элементов – в этом плане рекомендуется не располагать объект поверх других очень похожих). В любом случае, датасет стоит увеличивать постепенно (на 50-100 изображений), чтобы вовремя отловить появление ошибки и понять, какие тренировочные изображения вызвали ошибку (и исключить их). Таким образом, рекомендуется постепенно улучшать качество детектора путем добавления новых элементов (позитивных и негативных) в датасет. Чем больше будет итоговое разнообразие (разные фоны, разные освещения) – тем лучше.

После обучения каскада программа создает в рабочей папке дополнительные файлы, а также директорию «classifier», а в ней расположен файл «cascade.xml».

Матрица знаков

Этот файл можно (необязательно, но рекомендуется, чтобы не запутаться) переименовать в «cascade_sign_1.xml» и переместить в папку Resources, а далее запустить либо написанный ранее скрипт (в котором захватывалось изображение с веб-камеры), либо представленный ниже скрипт. В нем можно прямо во время работы программы изменить такие параметры, как минимальное количество соседей, минимальный размер детектируемого объекта в пикселях или масштаб, что в целом может быть довольно удобно для отладки (например, если детектор настроился неплохо и в целом распознает объект, но иногда срабатывают ложные распознавания – в качестве знака обводятся какие-то другие области изображения):

import cv2

################################################################
path = 'Resources/cascade_sign_1.xml'  #путь к расположению каскада
cameraNo = 1                       #номер камеры
objectName = 'Artificial bump'       #имя, которое будет подписываться
frameWidth= 640                     #ширина изображения в пикселях
frameHeight = 480                  #высота изображения в пикселях
color= (255,0,255)                  #цвет обводки
#################################################################

cap = cv2.VideoCapture(cameraNo)
cap.set(3, frameWidth)
cap.set(4, frameHeight)

def empty(a):
    pass

# CREATE TRACKBAR
cv2.namedWindow("Result")
cv2.resizeWindow("Result",frameWidth,frameHeight+100)
cv2.createTrackbar("Scale","Result",400,1000,empty)
cv2.createTrackbar("Neig","Result",8,50,empty)
cv2.createTrackbar("Min Area","Result",0,100000,empty)
cv2.createTrackbar("Brightness","Result",180,255,empty)

# LOAD THE CLASSIFIERS DOWNLOADED
cascade = cv2.CascadeClassifier(path)

while True:
    #установка яркости камеры по значению с ползунка
    cameraBrightness = cv2.getTrackbarPos("Brightness", "Result")
    cap.set(10, cameraBrightness)
    #получение изображения с камеры и конвертация в оттенки серого
    success, img = cap.read()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    #обнаружение объекта с использованием каскада
    scaleVal =1 + (cv2.getTrackbarPos("Scale", "Result") /1000)
    neig=cv2.getTrackbarPos("Neig", "Result")
    objects = cascade.detectMultiScale(gray,scaleVal, neig)
    #обводка найденных объектов
    for (x,y,w,h) in objects:
        area = w*h
        minArea = cv2.getTrackbarPos("Min Area", "Result")
        if area >minArea:
            cv2.rectangle(img,(x,y),(x+w,y+h),color,3)
            cv2.putText(img,objectName,(x,y-5),cv2.FONT_HERSHEY_COMPLEX_SMALL,1,color,2)
            roi_color = img[y:y+h, x:x+w]

    cv2.imshow("Result", img)

    if cv2.waitKey(1) & 0xFF == ord('q'):
         break

Для самой первой тренировки рекомендуется взять около 60 негативных изображений и 40 позитивных (изображения из примера доступны по ссылке), однако для обучения по-настоящему хорошего классификатора требуется от 200 негативных и 180 позитивных изображений (а то и от 1000), причем позитивные изображения должны иметь разнообразные фоны (пример будет по ссылке – сравните его с первым датасетом, разнообразие намного больше).

Использование XnViewMP

Рассмотрим процесс работы с программой XnViewMP .

В отдельную папку поместим изображения в высоком разрешении 4640x3472 (в примере – папка N). Найдем эту папку в навигаторе XnViewMP и с помощью сочетания клавиш Ctrl+A выделим все изображения:

XnViewMP

Далее в меню «Инструменты» выберем пункт «Пакетная обработка» и выставим на нем параметры, как показано ниже (добавляем действие «изменение разрешения», а также выбираем папку для сохранения отмасштабированных изображений). Далее нажимаем на кнопку «Преобразовать»:

XnViewMP

XnViewMP

XnViewMP

После конвертации негативных и позитивных изображений переместим их в папки «n» и «p» соответственно.

Матрица знаков