Распознавание образов
Пока писатели-фантасты пытаются напугать нас страшилками о разрушениях, вызванных искусственным разумом, ученые продолжают создавать наше будущее.
Важнейшим вектором развития искусственного интеллекта является распознавание образов, которое уже не первое десятилетие продолжает подминать под себя все вычислительные мощности, предоставляемые ему человечеством. Точность продолжает расти, а это требует дополнительных (и порой немалых) ресурсов. Казалось бы, что тут сложного?  Ведь человек, посмотрев на любой объект, сразу отличает его от всех остальных и чаще всего понимает, что это такое. Однако мы не до конца понимаем, как функционирует наш мозг, и потому нам не удается перенести наше поведение на машину. И тогда приходится искать варианты, которые устроили бы всех.
Определение лиц – наиболее развитая область распознавания образов. Любая современная «мыльница» или веб-камера имеет в своем распоряжении какой-либо алгоритм для такой цели. Наша статья посвящена теме определения лиц, и потому все алгоритмы будут рассмотрены применительно к ней.

Как объяснить компьютеру
Согласно одному из главных исследований в области распознавания лиц (Yang M.-H., Kriegman D.J., and Ahuja N. (2002). Detecting faces in images: A survey), все алгоритмы делятся на четыре категории, основанные на знаниях, на особенностях, на шаблонах, на внешности. 
У человека есть два глаза, два уха, рот, нос и т.д. Причем расположены все органы на определенном расстоянии друг от друга. Именно знания о них и решили использовать ученые, занимающиеся построением алгоритмов, базирующихся на таких сведениях. Но при этом возникла проблема с выбором способа формализации полученной информации. Если знания сильно детализировать, то очень многие лица опознаны не будут, если слабо – получится много ложных срабатываний.
Алгоритмы, основанные на особенностях (features) лица, более интересны и сложны. Они строятся на том, что человек легко опознает лицо по признакам, например по очертаниям, яркости, цвету и форме. Распознавание лиц по шаблону означает, что каждую область фотографии сравнивают с некоторым шаблоном, заданным разработчиком. Он представляет собой небольшой набор «эталонных» лиц.
Наконец, методики, основанные на внешности. Сейчас  в наиболее интересных работах по распознаванию лиц используются именно эти методики.  Понять, как это работает, очень просто. Достаточно посмотреть в зеркало на свое лицо. Оно имеет вполне определенную структуру, легко раскладывающуюся на части. Например, переносица – светлая полоса между темными глазницами, а щеки и нос, как правило, гораздо светлее, чем глаза. Вот на этом и строится работа алгоритма. Зная, где на лице должны быть темные или светлые места, можно определить его положение.

Определение и описание облика
Конечно, на деле все гораздо сложнее. Сначала необходимо собрать определенную базу каскадов, т. е.  шаблонов распознаваемых объектов (в данном случае -- лиц). Как же это делается? Представьте себе, что вы идете по лесу и рассматриваете то кроны деревьев, мерно раскачивающиеся на ветру, то опавшие листья, шелестящие под ногами. А если снять камерой всю эту красоту, то разве где-нибудь на фотографии не попадется фрагмент, схожий по цветовым пятнам с лицом? И чтобы таких ошибок (а ошибки в распознавании образов – главный враг) было как можно меньше, создаются каскады. Такие файлы содержат шаблоны не только  лиц, но и объектов, ими не являющихся, но принимаемых за них. В каскадах описаны цветовые пятна и их пересечения таким образом, чтобы программа поняла, нужно ей это или нет.
Обучение алгоритма – довольно трудоемкий и нудный процесс. Необходимо скармливать ему тысячи фотографий, подходящих для намеченных целей, причем каждую из них он должен масштабировать и вращать, чтобы не ошибиться в самый ответственный момент.
Для ускорения работы и упрощения алгоритма все фотографии и каскады делаются в оттенках серого, поскольку работать с таким изображением гораздо легче.
А теперь, после обсуждения теории, перейдем к практике. Рассмотрим алгоритм распознавания лиц по внешности, а точнее, метод  Viola-Jones. Прочитать про этот метод более подробно можно, например, на  "Википедии".

Пробуем сами

Начинать нужно с самого главного – с инструментария. Писать программу будем на  Java, а в качестве  IDE  возьмем Eclipse. Впрочем, в данном случае это совершенно все равно, можно использовать даже свой любимый блокнот.
В качестве библиотеки компьютерного зрения выберем наиболее распространенную сейчас OpenCV версии 2.1, скачав ее с сайта проекта sourceforge.net/projects/opencvlibrary.  Основу этой библиотеки заложили в компании Intel, но потом она стала  OpenSource–проектом и распространяется по лицензии BSD license.
Также потребуется библиотека JavaCV – java-обертка для OpenCV (code.google.com/p/javacv), с которой и предстоит работать, а также JNA (java.net/projects/jna) – библиотека, предназначенная для взаимодействия с native-программами. Так как JavaCV немного отстает от релизного плана OpenCV, то поддержки версии 2.2 еще пока нет. Все эти программы бесплатны и открыты, так что разрешается устанавливать и модифицировать их в свое удовольствие.
Теперь, когда все необходимые компоненты лежат у вас в папке Downloads, нужно установить OpenCV, с чем легко справиться.
Далее мы запускаем Eclipse и создаем новый проект под названием faceDetection. В нем создан package org.narsereg.facedetection, где и будут располагаться исходники нашей программы. В свойства проекта добавим библиотеки javacv.jar и jna.jar и приступим к написанию кода программы.
Создаем класс Main, который будет просто запускать остальную программу. Кроме инициализации класса единственной формы, здесь ничего нет.

 

Теперь перейдем к содержательной части нашей программы. Вот ее примерный алгоритм:
1. Открываем изображение.
2. Программа находит на фотографии лица и выводит их списком на форму приложения.
3. Подписываем каждое из них.
4. Вставляем подписи и рамки вокруг лиц на снимке.
Итак, нажимаем кнопку «Открыть фотографию», работа которой показана в листинге 1. 

Листинг 1
1 JFileChooser jfs = new JFileChooser();
2 jfs.setFileFilter(new FaceDetectionFileFilter());
3 int returnVal = jfs.showOpenDialog(null);
4 String openedFileName = jfs.getSelectedFile().getAbsolutePath();
5 panel.removeAll();

Сначала мы создаем JFileChooser – модальное окно, позволяющее выбирать фотографии в файловой системе. При этом создается фильтр FileFilter, благодаря которому можно видеть только файлы с расширениями *.jpg, *.png и *.gif. Также очищается список компонентов панели на форме.
А сейчас рассмотрим более подробно то, что будет происходить далее.

Листинг 2
1 FaceDetectionImplementation fd = new FaceDetectionImplementation(openedFileName);
2 BufferedImage[] bi = fd.run().clone();

В листинге 2 запускается алгоритм получения лиц с фотографии. Результаты складываются в массив bi.
Теперь обратимся к классу FaceDetectionImplementation, содержащему основную  часть этой программы (листинг 3).

Листинг 3
1 BufferedImage[] run(){
2 new JavaCvErrorCallback().redirectError();
3 storage = CvMemStorage.create();
4 return  cutFaces();
5 }

При использовании метода run сперва запускается JavaCvErrorCallback().redirectError(), перенаправляя ошибки в java-консоль. А потом вызывается CvMemStorage, создавая структуру данных для хранения необходимых областей. Этот метод возвращает массив BufferedImage, состоящий из  элементов, получаемых из cutFaces().

Листинг 4
1 private IplImage loadOriginalImageAndConvertToGrayScale(){
2 originalImage = cvLoadImage(imageToDetectFaces, 1);
3 IplImage grayscaleImage = IplImage.create(originalImage.width, originalImage.height, IPL_DEPTH_8U, 1);
4 cvCvtColor(originalImage, grayscaleImage, CV_BGR2GRAY);
5 return grayscaleImage;
6 }
 

Название метода loadOriginalImageAndConvertToGrayScale() листинга 4 говорит само за себя. Здесь мы превращаем originalImage, полученный из открытого нами файла в grayscaleImage, в тот же файл, но в черно-белом варианте (как уже было указано, для увеличения скорости и качества распознавания образов). Делается это созданием пустого изображения и присваивания ему методом cvCvtColor нового значения в гамме CV_BGR2GRAY.

Листинг 5
1 private CvSeq detectFaces(){
2 CvSeq listOfFaces = cvHaarDetectObjects(loadOriginalImageAndConvertToGrayScale(), new CvHaarClassifierCascade(cvLoad(CASCADE_FRONTALFACE_FILE)), storage, 1.1, 1, 0);
3 return  listOfFaces;
4 }

В detectFaces() , представленном на листинге 5, совершается главное действие – на основе каскада CASCADE_FRONTALFACE_FILE мы получаем последовательность найденных лиц.

Теперь перейдем к константе CASCADE_FRONTALFACE_FILE. Для нас это будет всего лишь файл haarcascade_frontalface_alt.xml, а для компьютера – критерий отбора лиц на фотографии. Каскады для лиц, носов, ртов и т.д. имеются в папке с OpenCV. А также допустимо их создать самостоятельно с помощью программ, находящихся в папке bin директории, куда был установлен OpenCV. Следовательно, можно натренировать каскад как на свою собаку, так и на любой фрукт, а потом искать именно их. Однако дело это не простое и не быстрое.

Листинг 6
1 private BufferedImage[] cutFaces(){
2 CvSeq faces = detectFaces();
3 BufferedImage[] facesAsPictures = new BufferedImage[faces.total];
4 for (int i = 0; i < faces.total; i++){
5 CvRect faceRect = new CvRect(cvGetSeqElem(faces, i));
6 if (names == null){
7 cvSetImageROI(originalImage, faceRect.byValue());
8 IplImage face = cvCreateImage(cvGetSize(originalImage), originalImage.depth, originalImage.nChannels);
9 cvCopy(originalImage, face, null);
10 facesAsPictures[i] = face.getBufferedImage();
11 cvResetImageROI(originalImage);
12 } else {
13 if (!names[i].equals("")) {
14 cvRectangle(originalImage, cvPoint(faceRect.x, faceRect.y), cvPoint(faceRect.x + faceRect.width, faceRect.y + faceRect.height), CvScalar.YELLOW, 1, CV_AA, 0);
15 CvFont font = new CvFont(CV_FONT_HERSHEY_PLAIN, 1, 1);
16 cvPutText(originalImage, names[i], cvPoint(faceRect.x, faceRect.y + faceRect.height + 15), font, CvScalar.YELLOW);
17 }
18 }
19 }
20 if (names != null){
21 CanvasFrame frame = new CanvasFrame("Picture");
22 frame.showImage(originalImage);
23 }
24 return facesAsPictures;
25 }
 

Осталось рассмотреть еще один метод, в котором происходит обработка полученных лиц (листинг 6).
Мы проходим по всему списку обнаруженных лиц (листинг 6, строка 4) и применяем к ним задуманные действия. Например, в нашем случае это будет прямоугольник с лицом, полученный методом CvRect (строка 5). Находим его на самом изображении методом cvSetImageROI() (Region Of Interest (ROI) -- интересующая область). Потом создается новое изображение, в которое мы и копируем результат работы программы (строки 8--9) и помещаем в выходной массив (строка 10).

Так выглядит список лиц, выделенных программой на фотографии. В этом же окне их можно подписать

На экране возникнет список картинок, принятых программой за лица. Кстати, там могут быть не только лица -- это и есть ложные срабатывания. Под каждым из них есть поле, в которое вы можете добавить какую-нибудь подпись по своему усмотрению. Правда, к сожалению, лишь транслитерацией, так как в OpenCV нет поддержки русского языка, точнее кодировки  UTF-8. После нажатия кнопки «Вставить текст в фото» под каждым из лиц появятся желтые рамки с подписями. Но если не был указан текст для лица на форме, то оно не будет окружено рамкой. Рассмотрим, как это работает. Вот краткий алгоритм:
1. Как и в первом случае, мы получаем список лиц.
2. Методом cvRectangle() в строке 14 окружаем эти лица прямоугольником. Здесь: originalImage – изображение для редактирования; faceRect – прямоугольник обнаруженного лица; CvScalar.YELLOW – цвет рамки.
3. Создаем экземпляр класса CvFont для текста;
4. Вставляем текст методом cvPutText() (строка 16), где names[i] – текст.
 

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

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