Подглава 10.8 OpenCV: библиотека компьютерного зрения с открытым исходным кодом
(https://docs.opencv.org/2.4/modules/objdetect/doc/cascade_classification.html?highlight=cascade#cv2.CascadeClassifierOpenCV был разработан Intel в 1999 году для тестирования приложений, интенсивно использующих процессор, а код был выпущен для публики в 2000 году. В 2008 году Willow Garage занялась первичной разработкой. OpenCV не так прост в использовании, как некоторые графические пакеты на основе графического интерфейса, такие как RoboRealm для Windows. Однако функции, доступные в OpenCV, представляют собой множество современных алгоритмов визуализации, а также методы машинного обучения, такие как машины опорных векторов, искусственные нейронные сети и случайные деревья.
OpenCV может работать как отдельная библиотека в Linux, Windows, MacOS X и Android. Для новичков в OpenCV, обратите внимание, что мы затронем лишь небольшую часть возможностей OpenCV. Для полного знакомства с библиотекой и всеми ее функциями, пожалуйста, обратитесь к разделу «Learning OpenCV» от Гари Брадски и Адриана Келера. Вы также можете обратиться к полному онлайн-руководству, включая несколько вводных руководств.
10.8.1 Обнаружение лиц
OpenCV позволяет относительно легко обнаруживать лица в изображении или видеопотоке. И так как это популярный запрос для тех, кто интересуется роботизированным зрением, это хорошее место для начала.
Детектор лица OpenCV использует каскадный классификатор с функциями типа Хаара. Вы можете узнать больше о каскадных классификаторах и функциях Haar по предоставленной ссылке. Пока все, что нам нужно понять, - это то, что каскадный классификатор OpenCV может быть инициализирован различными XML-файлами, которые определяют объект, который мы хотим обнаружить. Мы будем использовать два из этих файлов, чтобы обнаружить лицо, если смотреть прямо спереди. Другой файл позволит нам обнаружить лицо, если смотреть со стороны (вид профиля). Эти файлы были созданы с помощью алгоритмов машинного обучения на сотнях или даже тысячах изображений, которые либо содержат лицо, либо не содержат его. Затем алгоритм обучения может извлекать признаки, которые характеризуют лица, и результаты сохраняются в формате XML. (Дополнительные каскадные файлы были обучены для обнаружения глаз и даже целых людей.)
Некоторые из этих файлов XML были скопированы из дерева исходных текстов OpenCV в каталог rbx1_vision / data / haar_detectors, и мы будем использовать их в наших сценариях ниже.
Наш узел обнаружения лиц ROS находится в файле face_detector.py в каталоге rbx1_vision / src / rbx1_vision. Прежде чем мы посмотрим на код, давайте попробуем.
Чтобы запустить детектор, сначала запустите соответствующий видеодрайвер:
Для Microsoft Kinect:
$ roslaunch freenect_launch freenect-registered-xyzrgb.launch
Для камер Asus Xtion, Xtion Pro или Primesense 1.08/1.09:
$ roslaunch openni2_launch openni2.launch depth_registration:=true
Или для веб-камеры:
$ roslaunch rbx1_vision uvc_cam.launch device:=/dev/video0
(При необходимости измените видеоустройство.)
Теперь запустите узел детектора лица:
$ roslaunch rbx1_vision face_detector.launch
Если вы поместите свое лицо в рамку камеры, вы увидите зеленую рамку вокруг вашего лица, когда каскадный детектор обнаружит ее. Когда детектор потеряет ваше лицо, окно исчезнет, и вы увидите сообщение "LOST FACE!" на экране. Попробуйте повернуть лицо из стороны в сторону, вверх и вниз. Также попробуйте двигать рукой перед лицом. Число «Hit Rate», также отображаемое на экране, представляет собой количество кадров, в которых было обнаружено ваше лицо, деленное на общее количество кадров на данный момент.
Как вы можете видеть, детектор довольно хорош, но у него есть ограничения - детектор теряет ваше лицо, когда вы поворачиваете голову слишком далеко от фронтального или бокового обзора. Тем не менее, поскольку люди (и роботы), как правило, взаимодействуют друг с другом, детектор может выполнять работу во многих ситуациях.
Давайте теперь посмотрим код.
Ссылка на источник: face_detector.py
Давайте посмотрим на ключевые строки сценария.
6. from rbx1vision.ros2opencv2 import ROS2OpenCV2
7.
8. class FaceDetector(ROS2OpenCV2):
9. def
init
(self, nodename):
10. super(FaceDetector, self).__init(node_name)
Сначала мы должны импортировать класс ROS2OpenCV2 из сценария ros2opencv2.py, который мы разработали ранее. Затем узел детектора лица определяется как класс, который расширяет класс ROS2OpenCV2. Таким образом, он наследует все служебные функции и переменные из скрипта ros2opencv2.py, такие как выбор пользователя с помощью мыши, отображая поле вокруг ROI и так далее. Всякий раз, когда мы расширяем класс, мы также должны инициализировать родительский класс, что делается с помощью функции super() Python, как показано в последней строке выше.
14. cascade_1 = rospy.get_param("~cascade_1", "")
15. cascade_2 = rospy.get_param("~cascade_2", "")
16. cascade_3 = rospy.get_param("~cascade_3", "")
Эти три параметра хранят имена путей к файлам XML, которые мы хотим использовать для каскадного детектора Haar. Пути указываются в файле запуска rbx1_vision/launch/face_detector.launch. Сами файлы XML не включены в пакеты OpenCV или ROS Indigo Debian, поэтому они были скопированы из источника OpenCV в репозиторий ros-by-example и находятся в каталоге rbx1_vision/data/haar_detectors.
19. self.cascade_1 = cv2.CascadeClassifier(cascade_1)
20. self.cascade_2 = cv2.CascadeClassifier(cascade_2)
21. self.cascade_3 = cv2.CascadeClassifier(cascade_3)
Эти три строки создают каскадные классификаторы OpenCV на основе трех файлов XML, две для фронтальных видов лица и одна для боковых профилей.
25. self.haar_minSize = rospy.get_param("~haar_minSize", (20, 20))
26. self.haar_maxSize = rospy.get_param("~haar_maxSize", (150, 150))
27. self.haar_scaleFactor = rospy.get_param("~haar_scaleFactor", 1.3)
28. self.haar_minNeighbors = rospy.get_param("~haar_minNeighbors", 1)
29. self.haar_flags = rospy.get_param("~haar_flags", cv.CV_HAAR_DO_CANNY_PRUNING)
30.
Для каскадных классификаторов требуется ряд параметров, определяющих их скорость и вероятность правильного обнаружения цели. В частности, параметры minSize и maxSize (указанные в пиксельных измерениях x и y) задают самую маленькую и самую большую цель (в нашем случае грани), которая будет принята. Параметр scaleFactor действует как множитель для изменения размера изображения при перемещении детектора от одного масштаба к другому. Чем меньше это число (должно быть> 1,0), тем тоньше масштабная пирамида, используемая для сканирования лиц, но тем дольше она будет занимать каждый кадр. Вы можете прочитать другие параметры в документации OpenCV).
32. self.haar_params = dict(minSize = self.haar_minSize,
33. maxSize = self.haar_maxSize,
34. scaleFactor = self.haar_scaleFactor,
35. minNeighbors = self.haar_minNeighbors,
36. flags = self.haar_flags)
Здесь мы вставляем все параметры в словарную переменную Python для удобства использования в дальнейшем в скрипте.
49. def process_image(self, cv_image):
50. # Create a grayscale version of the image
51. grey = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY)
52.
53. # Equalize the histogram to reduce lighting effects
54. grey = cv2.equalizeHist(grey)
Поскольку класс FaceDetector расширяет класс ROS2OpenCV2, функция process_image () переопределяет функцию, определенную в ros2opencv2.py. В этом случае мы начинаем с преобразования изображения в оттенки серого. Многие алгоритмы обнаружения признаков работают на версии изображения в градациях серого, включая каскадный детектор Хаара. Затем мы выровняем гистограмму изображения в градациях серого. Выравнивание гистограммы - это стандартная методика снижения влияния изменений общего освещения.
56. self.detect_box = self.detect_face(grey)
57.
58. # Did we find one?
59. if self.detect_box is not None:
60. self.hits += 1
61. else:
62. self.misses += 1
63.
64. # Keep tabs on the hit rate so far
65. self.hit_rate = float(self.hits) / (self.hits + self.misses)
Здесь мы отправляем предварительно обработанное изображение в функцию detect_face (), которую мы опишем ниже. Если лицо обнаружено, ограничительная рамка возвращается в переменную self.detect_box, которая в свою очередь рисуется на изображении базовым классом ROS2OpenCV2.
Если в этом кадре изображения обнаружено лицо, мы увеличиваем число попаданий на 1, в противном случае добавляем к числу пропусков. После этого коэффициент попадания обновляется соответствующим образом.
70. def detect_face(self, input_image):
71. # First check one of the frontal templates
72. if self.cascade_1:
73. faces = self.cascade_1.detectMultiScale(input_image, **self.haar_params)
Здесь мы начинаем суть скрипта - функцию detect_face (). Мы запускаем входное изображение через каскадный детектор, используя первый шаблон XML. Функция deteMultiScale () ищет изображение в нескольких масштабах и возвращает любые грани в виде списка прямоугольников OpenCV в форме (x, y, w, h), где (x, y) - координаты верхнего левого угла поля. и (w, h) - ширина и высота в пикселях.
76. if len(faces) == 0 and self.cascade_3:
77. faces = self.cascade_3.detectMultiScale(input_image,self.haar_params)
78.
79. # If that also fails, check a the other frontal template
80. if len(faces) == 0 and self.cascade_2:
81. faces = self.cascade_2.detectMultiScale(input_image, self.haar_params)
Если лицо не обнаружено с помощью первого каскада, мы пробуем второй детектор, а затем третий, если необходимо.
85. if len(faces) > 0:
86. face_box = faces[0]
Если одна или несколько граней найдены, переменная FaceS будет содержать список граней (cvRect). Мы будем отслеживать только первое найденное лицо, поэтому мы устанавливаем переменную face_box в face [0]. Если в этом кадре лица не найдены, мы устанавливаем для face_box значение Нет. В любом случае, результат возвращается вызывающей функции process_image ().
Это завершает сценарий. Функции process_image () и detect_face () применяются к каждому кадру видео. В результате окно обнаружения отслеживает ваше лицо, пока оно находится в текущем кадре. Не забывайте, что вы можете отслеживать тему / roi, чтобы увидеть координаты отслеживаемого лица:
$ rostopic echo /roi
Отслеживание объекта путем многократного запуска детектора по всему изображению является вычислительно дорогостоящим и может легко потерять отслеживание объекта в любом данном кадре. В следующих двух разделах мы покажем, как быстро обнаруживать и отслеживать набор ключевых точек для любой заданной области изображения. Затем мы объединим трекер с детектором лица, чтобы сделать трекер получше.
10.8.2 Определение ключевой точки с использованием GoodFeaturesToTrack
Детектор лица Haar сканирует изображение для определенного типа объекта. Другая стратегия предполагает поиск небольших изображений, которые довольно легко отслеживать от одного кадра к другому. Эти функции называются ключевыми точками или точками интереса. Ключевыми точками, как правило, являются регионы, в которых происходят значительные изменения интенсивности более чем в одном направлении. Рассмотрим, например, изображения, показанные ниже:
Изображение слева показывает пиксели от области левого глаза от изображения справа. Квадрат слева указывает область, где интенсивность изменяется наиболее во всех направлениях. Центр такой области является ключевой точкой изображения, и он, вероятно, будет повторно обнаружен в том же месте лица, независимо от его ориентации или масштаба.
OpenCV 2.4 включает в себя несколько детекторов ключевых точек, включая goodFeaturesToTrack(), cornerHarris() и SURF(). Мы будем использовать goodFeaturesToTrack() для нашего примера программирования. На изображении справа показаны ключевые точки, возвращаемые функцией goodFeaturesToTrack ().
Как вы можете видеть, ключевые точки сосредоточены в областях, где градиенты интенсивности являются самыми большими. Наоборот, области картины, которые являются довольно однородными, имеют мало или вообще не имеют ключевых точек. (Чтобы воспроизвести это изображение или найти ключевые точки на других изображениях, взгляните на программу на языке Python script_good_features.py в каталоге rbx1_vision/scripts.)
Теперь мы готовы обнаружить ключевые точки в прямом эфире видео. Наш узел ROS называется good_features.py, и его можно найти в подкаталоге rbx1_vision / src / rbx1_vision. Соответствующий файл запуска - good_features.launch в подкаталоге запуска. Файл запуска содержит ряд параметров, которые влияют на ключевые точки, возвращаемые функцией goodFeaturesToTrack ():
• maxCorners: устанавливает верхний предел количества возвращаемых ключевых точек.
• qualityLevel: отражает, насколько сильной должна быть угловая функция, прежде чем она будет считаться ключевой точкой. Установка более низких значений возвращает больше очков.
• minDistance: минимальное количество пикселей между ключевыми точками.
• blockSize: размер окрестности вокруг пикселя, используемый для вычисления наличия угла.
• useHarrisDetector: использовать или нет оригинальный детектор углов Harris или критерий минимального собственного значения.
• k: свободный параметр для детектора углов Harris.
Файл good_features.launch устанавливает разумные значения по умолчанию для этих параметров, но попробуйте поэкспериментировать с ними, чтобы увидеть их влияние.
Чтобы запустить детектор, сначала убедитесь, что драйвер для вашей камеры включен и работает, как описано ранее. Завершите запуск файла детектора лиц, если он все еще запущен из предыдущего раздела, затем выполните команду:
$ roslaunch rbx1_vision good_features.launch
Когда появится видео, нарисуйте прямоугольник с помощью мыши вокруг какого-либо объекта на изображении. Прямоугольник должен указывать на выбранную область, и вы должны увидеть ряд зеленых точек, обозначающих ключевые точки, найденные в этой области функцией goodFeaturesToTrack (). Попробуйте нарисовать рамку вокруг других областей изображения и посмотреть, сможете ли вы угадать, где появятся ключевые точки. Обратите внимание, что мы еще не отслеживаем эти ключевые точки - мы просто вычисляем их для любого патча сцены, находящегося в вашем окне выбора.
Вы, вероятно, заметите, что некоторые ключевые точки немного перемешиваются даже в неподвижной части сцены. Это связано с шумом в видео. Сильные ключевые точки менее восприимчивы к шуму, что можно увидеть, нарисовав тестовую рамку за углом стола или другой высококонтрастной точки.
Давайте теперь посмотрим на код.
Ссылка на источник: good_features.py
В целом, мы видим, что скрипт имеет ту же структуру, что и узел face_detector.py. Мы инициализируем класс GoodFeatures, который расширяет класс ROS2OpenCV2. Затем мы определяем функцию process_image (), которая выполняет большую часть работы. Давайте рассмотрим более важные строки скрипта:
20. self.gf_maxCorners = rospy.get_param("~gf_maxCorners", 200)
21. self.gf_qualityLevel = rospy.get_param("~gf_qualityLevel", 0.02)
22. self.gf_minDistance = rospy.get_param("~gf_minDistance", 7)
23. self.gf_blockSize = rospy.get_param("~gf_blockSize", 10)
24. self.gf_useHarrisDetector = rospy.get_param("~gf_useHarrisDetector", True)
25. self.gf_k = rospy.get_param("~gf_k", 0.04)
Как и в случае с детектором Haar, детектор Good Features принимает ряд параметров для точной настройки своего поведения. Вероятно, двумя наиболее важными параметрами, указанными выше, являются qualityLevel и minDistance. Меньшие значения для qualityLevel приведут к большому количеству характерных точек, но некоторые из них будут из-за шума и не очень согласованы от одного кадра изображения к другому. Установка более высокого значения, которое будет слишком высоким, даст только несколько ключевых точек в самых сильных углах. Кажется, что-то порядка 0,02 или около того обеспечивает хороший баланс для видео естественных сцен.
Параметр minDistance указывает наименьшее расстояние в пикселях, которое мы допустим между ключевыми точками. Чем больше это значение, тем дальше друг от друга ключевые точки должны приводить к их меньшему количеству.
40. def process_image(self, cv_image):
41. # If the user has not selected a region, just return the image
42. if not self.detect_box:
43. return cv_image
44.
45. # Create a grayscale version of the image
46. grey = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY)
47.
48. # Equalize the histogram to reduce lighting effects
49. grey = cv2.equalizeHist(grey)
50.
51. # Get the good feature keypoints in the selected region
52. keypoints = self.get_keypoints(grey, self.detect_box)
Как и в случае с узлом детектора лица, мы определяем функцию process_image (), которая сначала преобразует изображение в градации серого и выравнивает гистограмму. Полученное изображение передается в функцию get_keypoints (), которая выполняет всю работу по поиску полезных функций.
69. def get_keypoints(self, input_image, detect_box):
70. # Initialize the mask with all black pixels
71. self.mask = np.zeros_like(input_image)
72.
73. # Get the coordinates and dimensions of the detect_box
74. try:
75. x, y, w, h = detect_box
76. except:
77. return None
78.
79. # Set the selected rectangle within the mask to white
80. self.mask[y:y+h, x:x+w] = 255
81.
82. # Compute the good feature keypoints within the selected region
83. keypoints = list()
84. kp = cv2.goodFeaturesToTrack(input_image, mask = self.mask, **self.gf_params)
85. if kp is not None and len(kp) > 0:
86. for x, y in np.float32(kp).reshape(-1, 2):
87. keypoints.append((x, y))
88.
89. return keypoint
Функция get_keypoints () реализует детектор OpenCV GoodFeaturesToTrack. Так как мы хотим, чтобы ключевые точки в пределах блока были выбраны пользователем (dete_box), мы маскируем изображение с помощью поля, начиная с маски всех нулей (черного), а затем заполняя поле обнаружения всеми белыми пикселями (255). Выходные данные функции cv2.goodFeaturesToTrack () представляют собой вектор координат ключевых точек. Поэтому мы используем небольшое изменение формы, чтобы превратить его в список Python (x, y) пар. Полученный список возвращается в функцию process_image (), в которой точки нарисованы на изображении.
10.8.3 Отслеживание ключевых точек с помощью оптического потока
Теперь, когда мы можем обнаруживать ключевые точки на изображении, мы можем использовать их для отслеживания базового объекта от одного видеокадра к следующему с помощью функции оптического потока Лукаса-Канаде в OpenCV calcOpticalFlowPyrLK (). Подробное объяснение метода Лукаса-Канаде можно найти в Wikipedia. Основная идея заключается в следующем.
Мы начнем с текущего кадра изображения и набора ключевых точек, которые мы уже извлекли. Каждая ключевая точка имеет местоположение (координаты x и y) и окрестность пикселей изображения. В следующем кадре изображения алгоритм Лукаса-Канаде использует метод наименьших квадратов для поиска небольшого преобразования с постоянной скоростью, которое отображает данную окрестность пикселей из первого кадра в следующий. Если ошибка наименьших квадратов для данной окрестности не превышает некоторого порогового значения, мы предполагаем, что это та же самая окрестность, что и в первом кадре, и мы назначаем ей ту же ключевую точку для этого местоположения; в противном случае ключевая точка отбрасывается. Обратите внимание, что мы не извлекаем новые ключевые точки в последующих кадрах. Вместо этого calcOpticalFlowPyrLK () вычисляет новые позиции для исходных ключевых точек. Таким образом, мы можем извлечь ключевые точки в первом кадре, а затем следовать им от кадра к кадру, когда базовый объект или камера перемещаются во времени.
Через несколько кадров отслеживание будет ухудшаться по двум причинам: ключевые точки будут сброшены, если между треками слишком высока ошибка отслеживания, а соседние ключевые точки заменят их в исходном наборе, поскольку алгоритмы допускают ошибки в предсказаниях. Мы рассмотрим способы преодоления этих ограничений в следующих разделах.
Наш новый узел, lk_tracker.py, находится в каталоге rbx1_vision/src/rbx1_vision и объединяет наш более ранний детектор ключевых точек (с использованием goodFeaturesToTrack()) с этим оптическим отслеживанием потока. Прекратите запуск файла хороших функций, если он все еще работает, затем запустите:
$ roslaunch rbx1_vision lk_tracker.launch
Когда появится окно видео, нарисуйте прямоугольник с помощью мыши вокруг объекта, представляющего интерес. Как и в предыдущем разделе, ключевые точки будут отображаться над изображением в виде зеленых точек. Теперь попробуйте переместить объект или камеру, и ключевые точки должны следовать за объектом. В частности, попробуйте нарисовать рамку вокруг вашего лица. Вы должны увидеть точки привязки к различным частям вашего лица. Теперь переместите голову, и ключевые точки должны двигаться вместе с ней. Обратите внимание, насколько быстрее значение CPS сравнивается с работой детектора лица Haar. На моей машине отслеживание ключевых точек с использованием метода LK в два раза быстрее, чем при использовании детектора лица Haar. Это также намного более надежно в том, что он продолжает отслеживать ключевые точки лица при большем диапазоне движения. Лучше всего это увидеть, включив «ночной режим» (нажмите клавишу «n», когда окно видео находится на переднем плане.)
Помните, что базовый класс ROS2OpenCV2 публикует ограничивающий прямоугольник вокруг отслеживаемых точек в теме /roi. Итак, если вы запустите команду:
$ rostopic echo /roi
при использовании узла lk_tracker вы должны видеть движение ROI по мере движения точек. Это означает, что если у вас есть другой узел, которому нужно следить за местоположением отслеживаемых точек, ему нужно только подписаться на тему /roi, чтобы следить за ними в реальном времени.
Теперь давайте посмотрим на код:
Ссылка на источник: lk_tracker.py
Давайте посмотрим на ключевые строки сценария.
7. from rbx1vision.goodfeatures import GoodFeatures
8.
9. class LKTracker(GoodFeatures):
10. def
_init_
(self, node_name):
11. super(LKTracker, self).__init(node_name)
Общая структура скрипта снова похожа на узел face_detector.py. Однако на этот раз мы импортируем good_features.py и определяем класс LKTracker как расширение класса GoodFeatures, а не ROS2OpenCV2. Почему? Поскольку ключевые точки, которые мы будем отслеживать, это именно те, которые мы получили из класса GoodFeatures в предыдущем разделе. А так как сам класс GoodFeatures расширяет класс ROS2Opencv2, мы покрыты.
36. def process_image(self, cv_image):
. . .
53. self.keypoints = self.get_keypoints(self.grey, self.track_box)
. . .
61. self.track_box = self.track_keypoints(self.grey, self.prev_grey)
Функция process_image() очень похожа на ту, которую мы использовали в скрипте good_features.py. Ключевые строки 53 и 61 выше. В строке 53 мы используем функцию get_keypoints() из класса GoodFeatures, чтобы получить начальные ключевые точки. И в строке 61 мы отслеживаем эти ключевые точки, используя новую функцию track_keypoints(), которую мы сейчас опишем.
77. def track_keypoints(self, grey, prev_grey):
78. try:
79. # We are tracking points between the previous frame and the
80. # current frame
81. img0, img1 = prev_grey, grey
82.
83. # Reshape the current keypoints into a numpy array required
84. # by calcOpticalFlowPyrLK()
85. p0 = np.float32([p for p in self.keypoints]).reshape(-1, 1, 2)
Чтобы отслеживать ключевые точки, мы начинаем с сохранения предыдущего изображения в градациях серого и текущего изображения в градациях серого в нескольких переменных. Затем мы сохраняем текущие ключевые точки, используя формат массива, требуемый функцией calcOpticalFlowPyrLK().
89. p1, st, err = cv2.calcOpticalFlowPyrLK(img0, img1, p0, None, **self.lk_params)
В этой строке мы используем функцию OpenCV calcOpticalFlowPyrLK () для прогнозирования следующего набора ключевых точек из текущих ключевых точек и двух изображений в градациях серого.
93. p0r, st, err = cv2.calcOpticalFlowPyrLK(img1, img0, p1, None, **self. lk_params)
И в этой строке мы делаем обратный расчет: здесь мы прогнозируем предыдущие точки из будущих точек, которые мы только что вычислили. Это позволяет нам выполнять проверку согласованности, поскольку мы можем сравнивать фактические предыдущие точки (ключевые точки, с которых мы начали) с этими точками с обратным прогнозом.
97. d = abs(p0-p0r).reshape(-1, 2).max(-1)
Далее мы вычисляем расстояния между парами точек с обратным предсказанием (p0r) и нашими исходными точками (p0). Результат, d, является массивом этих расстояний. (Python иногда кажется ужасно компактным.)
101. good = d < 1
И здесь мы определяем новый массив (хороший), который представляет собой набор значений True или False в зависимости от того, меньше ли расстояние между парой точек или менее 1 пикселя.
108. for (x, y), good_flag in zip(p1.reshape(-1, 2), good):
109. if not good_flag:
110. continue
111. new_keypoints.append((x, y))
Finally, we drop any keypoints that are more than 1 pixel away from their reversepredicted counterpart.
117.
self.keypoints = new_keypoints
Результатом становится наш новый глобальный набор ключевых точек, которые мы отправляем через следующий цикл отслеживания.
10.8.4 Создание лучшего трекера лица
Теперь у нас есть ингредиенты, которые нам нужны для улучшения нашего оригинального детектора лица. Напомним, что узел face_detector.py пытается снова и снова обнаруживать лицо в каждом кадре. Это не только сильно нагружает процессор, но и может вообще не обнаружить лицо. Лучшая стратегия - сначала определить лицо, затем использовать goodFeaturesToTrack () для извлечения ключевых точек из области лица, а затем использовать calcOpticalFlowPyrLK () для отслеживания этих объектов от кадра к кадру. Таким образом, обнаружение выполняется только один раз, чтобы первоначально получить область лица.
Наш конвейер обработки выглядит так:
detect_face() → get_keypoints() → track_keypoints()
С точки зрения узлов, которые мы разработали до сих пор, конвейер становится:
face_detector.py() → good_features.py() → lk_tracker.py()
Наш новый узел face_tracker.py реализует этот конвейер. Чтобы проверить это, убедитесь, что вы запустили драйвер для вашей камеры, а затем запустите:
$ roslaunch rbx1_vision face_tracker.launch
Если вы переместите свое лицо в поле зрения камеры, детектор лица Haar должен найти его. После первоначального обнаружения ключевые точки вычисляются по области лица и затем отслеживаются в последующих кадрах с использованием оптического потока. Чтобы очистить текущие ключевые точки и вызвать повторное обнаружение лица, нажмите клавишу «c», когда видеоокно находится на переднем плане.
Давайте теперь посмотрим на код.
Ссылка на источник: face_tracker.py
Узел face_tracker по сути объединяет два уже разработанных узла: узел face_detector и узел lk_tracker. Узел lk_tracker, в свою очередь, зависит от узла good_features. Следующая разбивка объясняет, как мы объединяем эти классы Python.
8. from rbx1vision.facedetector import FaceDector
9. from rbx1vision.lktracker import LKTracker
10.
11.class FaceTracker(FaceDetector, LKTracker):
12. def __init(self, node_name):
13. super(FaceTracker, self).__init(node_name)
Чтобы использовать код face_detector и lk_tracker, который мы разработали ранее, мы сначала должны импортировать их классы FaceDetector и LKTracker. Затем мы определяем наш новый класс FaceTracker как расширение обоих классов. В Python это называется множественным наследованием. Как и раньше, мы используем функцию super() для инициализации нашего нового класса, который также заботится об инициализации родительских классов.
26. def process_image(self, cv_image):
27. # Create a grayscale version of the image
28. self.grey = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY)
29.
30. # Equalize the grey histogram to minimize lighting effects
31. self.grey = cv2.equalizeHist(self.grey)
Как и в случае с другими нашими узлами, мы начинаем функцию process_image () с преобразования изображения в оттенки серого и выравнивания гистограммы для минимизации световых эффектов.
33. # STEP 1: Detect the face if we haven't already
34. if self.detect_box is None:
35. self.detect_box = self.detect_face(self.grey)
Шаг 1 заключается в том, чтобы определить лицо, которое уже нет. Функция dete_face () происходит из импортированного нами класса FaceDetector.
38. # STEP 2: If we aren't yet tracking keypoints, get them now
39. if self.track_box is None or not self.is_rect_nonzero(self.track_box):
40. self.track_box = self.detect_box
41. self.keypoints = self.get_keypoints(self.grey, self.track_box)
После того как мы определили лицо, Шаг 2 должен получить ключевые точки из области лица с помощью функции get_keypoints (), которую мы импортировали из класса LKTracker, который, в свою очередь, фактически получает функцию из импортируемого им класса GoodFeatures.
43. # STEP 3: If we have keypoints, track them using optical flow
44. if len(self.keypoints) > 0:
45. # Store a copy of the current grey image used for LK tracking
46. if self.prev_grey is None:
47. self.prev_grey = self.grey
48.
49. self.track_box = self.track_keypoints(self.grey, self.prev_grey)
Когда у нас есть ключевые точки, шаг 3 начинает отслеживать их с помощью функции track_keypoints (), которую мы импортировали из класса LKTracker.
else:
2. # We have lost all keypoints so re-detect the face
3. self.detect_box = None
Если во время отслеживания количества ключевых точек уменьшается до нуля, мы устанавливаем для поля обнаружения значение Нет, чтобы мы могли повторно обнаружить лицо на шаге 1.
В конце вы можете увидеть, что общий сценарий по сути является просто комбинацией наших предыдущих узлы.
10.8.5 Динамическое добавление и удаление ключевых точек
Если вы поиграете с трекером лица немного, вы заметите, что ключевые точки могут дрейфовать на другие объекты, помимо вашего лица. Вы также заметите, что количество ключевых точек с течением времени уменьшается по мере того, как оптическая система отслеживания потока отбрасывает их из-за низкого показателя отслеживания.
Мы можем легко добавлять новые ключевые точки и отбрасывать плохие в процессе отслеживания. Чтобы добавить ключевые точки, мы запускаем goodFeaturesToTrack () время от времени в отслеживаемом регионе. Чтобы отбросить ключевые точки, мы можем запустить простой статистический кластеризационный тест для сбора ключевых точек и удалить выбросы.
Узел face_tracker2.py включает в себя эти улучшения. Если драйвер вашей камеры уже запущен, попробуйте новый трекер лица с помощью команды:
$ roslaunch rbx1_vision face_tracker2.launch
Теперь вы должны увидеть улучшенное отслеживание вашего лица, когда ключевые точки добавляются и опускаются, чтобы отражать движения вашей головы. Чтобы на самом деле увидеть точки, которые добавляются и удаляются, а также область вокруг грани, с которой рисуются новые ключевые точки, нажмите клавишу «d» над окном изображения. Область расширенной ключевой точки обозначена желтой рамкой. Добавленные ключевые точки будут мигать синим светом, а выпавшие точки будут мигать красным, пока они не исчезнут. Нажмите кнопку «d» еще раз, чтобы выключить дисплей. Вы также можете в любой момент нажать клавишу «c», чтобы очистить текущие ключевые точки и вызвать повторное обнаружение лица.
Код для face_tracker2.py почти такой же, как и первый скрипт face_tracker.py, поэтому мы не будем его подробно описывать. Полный исходный код можно найти здесь:
Ссылка на источник: face_tracker2.py
Две новые функции - add_keypoints () и drop_keypoints (), которые должны быть достаточно понятны из комментариев в коде. Однако стоит кратко описать новые параметры, которые контролируют, когда точки удаляются и добавляются. Их можно найти в файле запуска face_tracker2.launch в каталоге rbx1_vision/launch. Давайте посмотрим на эти параметры сейчас. Значения по умолчанию указаны в скобках:
• use_depth_for_tracking: (True) Если вы используете камеру глубины, установка этого значения в True приведет к удалению ключевых точек, которые падают слишком далеко от плоскости лица. (Этот параметр считается ложным, если используется веб-камера.)
• min_keypoints: (20) Минимальное количество ключевых точек, прежде чем мы добавим новые.
• abs_min_keypoints: (6) Абсолютное минимальное количество ключевых точек, прежде чем мы рассмотрим потерянное лицо и попытаемся повторно его обнаружить.
• add_keypoint_distance: (10) Новая ключевая точка должна находиться как минимум на этом расстоянии (в пикселях) от любой существующей ключевой точки.
• std_err_xy: (2.5) Стандартная ошибка (в пикселях) для определения того, является ли ключевая точка выбросом.
• pct_err_z: (1.5) Порог глубины (в процентах), который определяет, когда мы отбрасываем ключевую точку для падения слишком далеко от плоскости лица.
• max_mse: (10000) Максимальная общая среднеквадратичная ошибка в текущем кластере объектов, прежде чем мы начнем сначала и заново определим лицо.
• expand_roi: (1,02) При поиске новых ключевых точек коэффициент расширения будет увеличивать рентабельность инвестиций в каждом цикле.
• add_keypoints_interval: (1) Как часто мы пытаемся добавить новые ключевые точки. Значение 1 означает каждый кадр, 2 - каждый второй кадр и так далее.
• drop_keypoints_interval: (1) Как часто мы пытаемся отбрасывать ключевые точки. Значение 1 означает каждый кадр, 2 - каждый второй кадр и так далее.
Большинство значений по умолчанию должны работать достаточно хорошо, но, конечно, вы можете попробовать разные значения.
10.8.6 Отслеживание цветовых пятен (CamShift)
До сих пор мы не использовали информацию о цвете для отслеживания интересующего объекта. OpenCV включает в себя фильтр CamShift, который позволяет нам отслеживать выбранную область изображения на основе цветовой гистограммы этой области. Для превосходного объяснения того, как работает фильтр CamShift, обязательно ознакомьтесь со статьей Робина Хьюитта о том, как работает OpenCV Face Tracker. Короче говоря, фильтр CamShift сканирует последовательные кадры видеопотока и назначает каждому пикселю вероятность принадлежности к исходной цветовой гистограмме. Коллекция пикселей с наибольшей вероятностью «принадлежности» становится новой целевой областью, подлежащей отслеживанию.
Прежде чем мы посмотрим на код, вы можете попробовать его следующим образом. Отслеживание лучше всего работает с ярко окрашенными объектами. Поэтому сначала найдите такой объект, как пластиковый шар, который достаточно однороден по цвету и выделяется на фоне любых цветов.
Затем обязательно запустите соответствующий драйвер камеры для камеры, которую вы используете. Затем запустите узел CamShift с помощью следующей команды:
$ roslaunch rbx1_vision camshift.launch
Когда появится окно видео, удерживайте целевой объект перед камерой и нарисуйте прямоугольник вокруг него. Узел CamShift сразу же начнет следовать за объектом как можно лучше на основе цветовой гистограммы, вычисленной из выбранной области. Обратите внимание, что отслеживаемая область фактически заполняет объект, даже если вы выбираете только меньший его фрагмент. Это связано с тем, что алгоритм CamShift адаптивно сопоставляет диапазон цветов в выбранной области, а не только одно значение RGB.
Два других окна будут присутствовать на экране. Первый - это группа элементов управления ползунком, которые выглядят так:
Эти элементы управления определяют селективность фильтра CamShift. Для ярко окрашенных объектов, таких как зеленый теннисный мяч, значения по умолчанию должны работать достаточно хорошо. Однако для естественных цветов, таких как лица, вам, возможно, придется выключить настройки Saturation и Min Value и слегка настроить параметр Threshold. Как только вы найдете группу настроек, которые работают для вашей камеры, вы можете установить их в файле camshift.launch.
Второе окно показывает «обратную проекцию» вероятностей гистограммы обратно на изображение. В результате получается изображение в градациях серого, где белые пиксели представляют высокую вероятность принадлежности к гистограмме, а серые или черные пиксели представляют более низкие вероятности. Полезно иметь окно обратной проекции видимым при настройке элементов управления ползунком: цель состоит в том, чтобы над мишенью были в основном белые пиксели, а в другом - черные.
Следующее видео демонстрирует фильтр CamShift в действии:
Давайте теперь посмотрим на код.
Ссылка на источник: camshift.py
Давайте посмотрим на ключевые строки в сценарии.
20. self .smin = rospy.get_param("~smin", 85)
21. self .vmin = rospy.get_param("~vmin", 50)
22. self .vmax = rospy.get_param("~vmax", 254)
23. self .threshold = rospy.get_param("~threshold", 50)
Это параметры, которые управляют чувствительностью цвета алгоритма CamShift. Алгоритм не будет работать вообще без установки правильных значений для вашей камеры. Значения по умолчанию должны быть близкими, но вы захотите поиграть с ползунками (которые появляются после запуска программы), чтобы найти хорошие значения для вашей настройки. Как только вы будете удовлетворены своими числами, вы можете ввести их в файл запуска, чтобы переопределить значения по умолчанию.
Значение smin контролирует минимальную насыщенность изображения HSV (Hue, Saturation, Value). Это мера "богатства" цвета. Параметры vmin и vmax определяют минимальное и максимальное значение (яркость), которое должен иметь цвет. Наконец, пороговый параметр применяется после того, как обратное проецирование вычислено, чтобы отфильтровать пиксели низкой вероятности из результата.
35. cv.CreateTrackbar("Saturation", "Parameters", self.smin, 255, self.set_smin)
36. cv.CreateTrackbar("Min Value", "Parameters", self.vmin, 255, self.set_vmin)
37. cv.CreateTrackbar("Max Value", "Parameters", self.vmax, 255, self.set_vmax)
38. cv.CreateTrackbar("Threshold", "Parameters", self.threshold, 255, self.set_threshold)
Здесь мы используем функцию трекбара OpenCV для создания элементов управления ползунком в окне «Параметры». Последние три аргумента функции CreateTrackbar() определяют минимальные, максимальные и стандартные значения для каждого трекбара.
59. def process_image(self, cv_image):
60. # First blue the image
61. frame = cv2.blur(cv_image, (5, 5))
62.
63. # Convert from RGB to HSV spave
64. hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
Основной цикл обработки начинается с размытия изображения, а затем преобразования его из сине-зеленого (BGR) в значение насыщенности оттенка (HSV). Размытие помогает устранить некоторые цветовые шумы в типичных видеоизображениях. Работа в пространстве HSV является обычной практикой при обработке цветных изображений. В частности, измерение оттенка хорошо согласуется с тем, что мы (то есть люди) рассматриваем с разными цветами, такими как красный, оранжевый, желтый, зеленый, синий и т. Д. Измерение насыщенности отображает, насколько «богатым» и «размытым» кажется цвет нас, и измерение значения отображается в виде яркого цвета.
67. mask = cv2.inRange(hsv, np.array((0., self.smin, self.vmin)), np.array((180., 255., self.vmax)))
Функция OpenCV inRange() превращает наши пределы насыщенности и значений в маску, чтобы мы обрабатывали только те пиксели, которые попадают в наши параметры цвета. Обратите внимание, что мы не фильтруем по оттенку - поэтому мы все еще принимаем любой цвет в этой точке. Вместо этого мы выбираем только те цвета, которые имеют достаточно высокую насыщенность и ценность.
71. if self.selection is not None:
72. x0, y0, w, h = self.selection
73. x1 = x0 + w
74. y1 = y0 + h
75. self.track_window = (x0, y0, x1, y1)
76. hsv_roi = hsv[y0:y1, x0:x1]
77. mask_roi = mask[y0:y1, x0:x1]
В этом блоке мы берем выбор от пользователя (сделанный с помощью мыши) и превращаем его в интересующую область для вычисления цветовой гистограммы и маски.
78. self.hist = cv2.calcHist( [hsv_roi], [0], mask_roi, [16], [0, 180] )
79. cv2.normalize(self.hist, self.hist, 0, 255, cv2.NORM_MINMAX);
80. self.hist = self.hist.reshape(-1)
81. self.show_hist()
Здесь мы используем функцию OpenCV calcHist() для вычисления гистограммы по оттенкам в выбранной области. Обратите внимание, что регион также маскируется с помощью mask_roi. Затем мы нормализуем гистограмму таким образом, чтобы максимальное значение составляло 255. Это позволяет нам отображать результат в виде 8-битного цветного изображения, которое выполняется с двумя последними строками с помощью вспомогательной функции show_hist(), определенной позже в сценарии.
89. backproject = cv2.calcBackProject([hsv], [0], self.hist, [0, 180], 1)
90.
91. # Mask the backprojection with the mask created earlier
92. backproject &= mask
93.
94. # Threshold the backprojection
95. ret, backproject = cv2.threshold(backproject, self.threshold, 255, cv.CV_THRESH_TOZERO)
Получив гистограмму для отслеживания, мы используем функцию OpenCV calcBackProject(), чтобы присвоить вероятности каждому пикселю в изображении принадлежности к гистограмме. Затем мы маскируем вероятности с помощью нашей более ранней маски и пороговую ее для исключения пикселей с низкой вероятностью.
102. term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )
103.
104. # Run the CamShift algorithm
105. self.track_box, self.track_window = cv2.CamShift(backproject, self.track_window, term_crit)
Имея под рукой замаскированное и пороговое обратное проецирование, мы, наконец, можем запустить сам алгоритм CamShift, который преобразует вероятности в новое местоположение окна трека.
Весь процесс повторяется для каждого кадра (без какого-либо дополнительного выбора со стороны пользователя), и окно отслеживания следует за пикселями, которые имеют наибольшую вероятность принадлежности к исходной гистограмме. Если вы обнаружите, что за вашим целевым объектом не очень хорошо следят, начните с ярко окрашенного шара или другого однородно окрашенного объекта. Вам также, вероятно, придется настроить элементы управления ползунка насыщенности и значения, чтобы получить желаемый результат.
Last updated
Was this helpful?