Статьи

Автоматически помечать фотографии с помощью библиотеки распознавания лиц AS3

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


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

Нажмите здесь, чтобы посмотреть демо

Загрузите изображение, и демоверсия покажет вам, какие области Библиотека распознавания лиц распознает как лица. (Это не всегда идеально!)

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

Нужны фотографии, чтобы проверить это? Попробуйте Flickr !


Загрузите FaceRecognitionLib.swc со страницы проекта библиотеки. Вам также понадобится файл face.zip от руководства проекта. Перейдите на страницу http://code.google.com/p/face-recognition-library-as3/source/browse/trunk/FaceRecognitionLib/src/face.zip . Нажмите View raw file, чтобы загрузить его.

Вам понадобится Flex SDK (версия 3.5 или более поздняя) и должен быть настроен Flash Professional CS5 для целевой компиляции для Flash Player 10 или более поздней версии. Последнее, что вам нужно, это последняя версия TweenLite , которую мы будем использовать для добавления простых анимаций в наше приложение.


Откройте Flash CS5 Professional и создайте новый файл Actionscript 3.0.

новый проект

Установите имя класса документа в FaceTagger .

document_class

Нажмите « Параметры ActionScript» на панели « Свойства» . Нажмите на вкладку « Путь к библиотеке » в окне « Параметры ActionScript» . Перейдите к FaceRecognitionLib.swc, чтобы добавить его в проект. Повторите этот процесс для файла greensock.swc .

add_button_component
add_button_component

Нажмите на сцену. Измените цвет сцены на цвет по вашему выбору.

add_button_component
color_picker

Откройте панель « Компоненты» . Перетащите кнопку в верхний правый угол сцены.

components_panel

Установите имя экземпляра кнопки browseBtn на панели « Свойства» . На панели « Параметры компонента» измените метку кнопки на « Обзор» .

add_button_component

Добавьте еще одну кнопку на сцену прямо слева от browseBtn . Измените метку кнопки на « Добавить тег» и установите имя ее экземпляра на « addBtn .

add_button_component

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

Используя инструмент « Овал» ( нажмите клавишу «O», чтобы переключиться на инструмент «Овал» ), добавьте овал к сцене высотой 36 пикселей и шириной 36 пикселей (вы можете щелкнуть по сцене щелчком мыши, чтобы вызвать диалоговое окно для этого). У овала должен быть штрих 2,0, цвет белой линии и темно-серый цвет заливки, как показано ниже.

add_button_component

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

add_button_component

Используя инструмент « Линия» (нажмите клавишу N, чтобы переключиться на инструмент «Линия») , нарисуйте диагональную линию на сцене, удерживая клавишу Shift, чтобы придать линии фиксированный угол. Вставьте копию линии на сцену. В верхнем меню Flash выберите « Modify» , прокрутите вниз до « Transfrom» и выберите « Flip Horizontal» . Выровняйте новую линию поверх исходной линии, чтобы сформировать форму X.

add_button_component
add_button_component

Перетащите обе линии на овальную форму. Выберите овальную форму вместе с каждой линией. Нажмите клавишу F8, чтобы открыть окно « Преобразовать в символ» . Установите имя символа на RemoveButton и тип на Movie Clip . Убедитесь, что регистрация установлена ​​в верхнем левом углу и что папка установлена ​​в корневой каталог библиотеки . В разделе « Дополнительные параметры» выберите « Экспорт для ActionScript» . Поле Class должно иметь значение RemoveButton, а базовый класс — flash.display.MovieClip . Нажмите OK, чтобы создать символ. Экземпляр класса RemoveButton не должен существовать во время выполнения. Мы создадим его, когда потребуется, используя код, чтобы вы могли удалить его из Stage .

add_button_component

Если вы помните из шага 1, класс FaceTagger — это наш класс документов. Большая часть магии случится в этом классе.

Создайте новый класс и назовите его FaceTagger . Класс должен расширять класс flash.display.Sprite .

create_new_class
create_face_tagger_class

Начнем с импорта следующих классов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package {
 
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.text.TextField;
    import flash.text.TextFieldType;
    import flash.text.TextFieldAutoSize;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.net.FileReference;
    import flash.net.FileFilter;
    import flash.geom.Rectangle;
    import flash.display.Loader;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.MovieClip
    import flash.text.TextFormat;
    import flash.filters.DropShadowFilter;
     
    public class FaceTagger extends Sprite {

Создайте следующие переменные и константы после объявления класса.

01
02
03
04
05
06
07
08
09
10
11
12
13
private var statusTxt:TextField;
private var fileRef:FileReference;
private var fileFilter:FileFilter;
private var loader:Loader;
private var darkBox:Sprite;
private var bitmap:Bitmap;
private var image:MovieClip;
 
public static const MIN_WIDTH:Number = 50;
public static const MIN_HEIGHT:Number = 50;
public static const MAX_WIDTH:Number = 1000;
public static const MAX_HEIGHT:Number = 1000;
public static const FILE_TYPES:String = «*.jpg; *.jpeg; *.png»;

Нам нужен объект TextField для отображения сообщений пользователю. Объект FileReference позволяет пользователю загружать изображение с компьютера, а объект FileFilter ограничивает типы файлов, которые пользователь может загружать. Объект Loader необходим для фактического анализа данных изображения из объекта FileReference . Переменная darkBox — это darkBox который будет использоваться для затемнения экрана при редактировании тега. Мы сделаем это, чтобы добавить контраст к нашему приложению. Это позволит пользователю узнать, что он находится в каком-то режиме редактирования, когда экран темный и текущий тег находится в фокусе. Наконец, нам понадобится Bitmap объект для представления фактического изображения и MovieClip содержащий растровое изображение и все теги изображения.

MIN_WIDTH и MIN_HEIGHT представляют минимальные измерения, которыми должно быть изображение пользователя, чтобы процесс обнаружения лица происходил. MAX_WIDTH и MAX_HEIGHT представляют максимальные размеры. FILE_TYPES содержит типы файлов, которые мы позволим пользователю выбирать при загрузке изображения в наше приложение.

Давайте добавим немного кода в конструктор класса.

1
2
3
4
5
6
7
8
9
public function FaceTagger() {
 
    statusTxt = new TextField();
    fileRef = new FileReference();
    fileFilter = new FileFilter( «Image (» + FILE_TYPES + «)», FILE_TYPES );
    loader = new Loader();
    darkBox = new Sprite();
    init();
}

Мы передаем два параметра в конструктор FileFilter . Первый параметр — это строка, которая содержит описание фильтра, который в данном случае является изображением. Мы FILE_TYPES константу FILE_TYPES в скобках для отображения разрешенных типов файлов пользователю, когда он просматривает изображение для загрузки. Второй параметр — это String , содержащая разрешенные типы файлов. Мы передаем нашу константу FILE_TYPES в этот параметр как воля. Ласли, мы вызываем метод init который создадим позже, для инициализации приложения.


Давайте создадим метод init .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private function init():void {
         
    stage.align = StageAlign.TOP_LEFT;
    stage.scaleMode = StageScaleMode.NO_SCALE;
     
    stage.addEventListener( Event.RESIZE, onStageResize );
    browseBtn.addEventListener( MouseEvent.CLICK, browse );
    addBtn.addEventListener( MouseEvent.CLICK, addTag );
    fileRef.addEventListener( Event.SELECT, onFileSelected );
    fileRef.addEventListener( Event.COMPLETE, onFileComplete );
    loader.contentLoaderInfo.addEventListener( Event.COMPLETE, detectFaces );
     
    statusTxt.type = TextFieldType.DYNAMIC;
    statusTxt.selectable = false;
    statusTxt.autoSize = TextFieldAutoSize.CENTER;
    statusTxt.defaultTextFormat = new TextFormat( null, 22, 0xFFFFFF, true );
    statusTxt.text = «Choose an image to upload»;
    statusTxt.filters = [ new DropShadowFilter( 5, 45, 0, 1, 5, 5, 1, 3 ) ];
     
    darkBox.graphics.beginFill( 0, .5 );
    darkBox.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
    darkBox.visible = false;
    darkBox.addEventListener( MouseEvent.CLICK, exitEditMode );
     
    addChild( statusTxt );
    addChild( darkBox );
     
    positionContents();
}

Мы инициализируем stage , устанавливая его свойство align в TOP_LEFT и устанавливая его свойство scaleMode в NO_SCALE . Мы вызываем метод onStageResize при изменении размера stage . Мы вызываем метод browse , который позволит пользователю просматривать изображения для загрузки, когда browseBtn .

Метод addTag вызывается, когда пользователь нажимает addBtn . Нам нужно слушать, когда пользователь выбирает файл, чтобы мы могли загрузить файл в память. Мы также слушаем, когда файл был загружен и когда объект Loader завершил анализ загруженных данных в изображение.

Мы инициализируем наше текстовое поле и применяем хороший фильтр тени, чтобы добавить глубину нашему тексту. Наконец, мы рисуем графику для объекта darkBox , отображаем текстовое поле и вызываем метод positionContents который мы сейчас создадим.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function positionContents():void {
     
    browseBtn.x = stage.stageWidth — browseBtn.width — 10;
    addBtn.x = browseBtn.x — addBtn.width — 10;
    statusTxt.x = ( stage.stageWidth — statusTxt.width ) / 2;
    statusTxt.y = stage.stageHeight — statusTxt.height — 10;
    darkBox.width = stage.stageWidth;
    darkBox.height = stage.stageHeight;
     
    if ( image ) {
         
        image.x = ( stage.stageWidth — image.width ) / 2;
        image.y = ( stage.stageHeight — image.height ) / 2;
    }
}

Метод positionContents не требует пояснений. Он позиционирует каждый элемент в зависимости от размера сцены. Мы darkBox размер объекта darkBox до размера stage и darkBox объект image только в том случае, если он существует.

Мы хотим вызывать метод positionContents каждый раз, когда размер stage изменяется. Создайте метод onStageResize для обработки этого:

1
2
3
4
private function onStageResize( e:Event ):void {
  
    positionContents();
}

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

1
2
3
4
private function browse( e:MouseEvent ):void {
             
    fileRef.browse( [ fileFilter ] );
}

Мы просто вызываем метод browse объекта FileReference , и это открывает собственное окно, которое позволяет пользователю искать файл изображения. Нам нужно применить наш объект FileFilter чтобы ограничить типы файлов, которые пользователь может просматривать. Для этого мы передаем фильтр в первый параметр метода browse в объекте Array .

Теперь пользователь может просматривать изображение для загрузки. Мы должны будем загрузить файл во Flash, когда пользователь выберет файл. Для этого давайте создадим метод onFileSelected . Этот метод является функцией обработчика событий, которая вызывается, когда пользователь выбирает файл.

1
2
3
4
5
6
private function onFileSelected( e:Event ):void {
     
    browseBtn.enabled = false;
    statusTxt.text = «loading»;
    fileRef.load();
}

Мы прослушали событие SELECTED в методе init . Мы также прослушали событие COMPLETE события, которое отправляется объектом contentLoaderInfo когда данные изображения были проанализированы. Метод onFileComplete вызывается, когда происходит это событие. Давайте создадим этот метод также.

1
2
3
4
private function onFileComplete( e:Event ):void {
             
    loader.loadBytes( fileRef.data );
}

Первое, что мы хотим сделать после выбора файла, — отключить кнопку « Обзор» , чтобы пользователь не мог загрузить второй файл. Мы отображаем статус для пользователя и, наконец, вызываем метод load из объекта FileReference . Это загружает выбранный файл в память.

Метод onFileComplete просто сообщает объекту Loader для анализа байтов загруженного файла. Ранее мы прослушивали loader.contentLoaderInfo для отправки события COMPLETE . Когда отправляется это событие, detectFaces метод detectFaces . Давайте начнем кодировать этот метод.

1
2
3
4
5
private function detectFaces( e:Event ):void {
     
    bitmap = Bitmap( loader.content );
    addChild( bitmap);
}

На данный момент мы только назначим ссылку на loader.content для нашего объекта Bitmap . Мы используем приведение типов, чтобы указать Flash обрабатывать loader.content как объект Bitmap , а затем, наконец, отображаем изображение.


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

Давайте создадим статический метод с именем inRange .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private static function inRange( width:Number, height:Number ):Boolean {
     
   if ( width < MIN_WIDTH || width > MAX_WIDTH ) {
         
        return false;
    }
    else if ( height < MIN_HEIGHT || height > MAX_HEIGHT ) {
                 
        return false;
    }
    else {
         
        return true;
    }
}

Метод inRange возвращает true если указанные ширина и высота не выходят за пределы диапазона. В противном случае возвращается false . Мы собираемся вызвать этот метод в методе detectFaces . Удалить строку "addChild( bitmap );" из метода detectFaces . Затем добавьте следующие строки кода.

01
02
03
04
05
06
07
08
09
10
if ( !inRange( bitmap.width, bitmap.height ) ) {
         
    if ( !image ) image = new MovieClip();
    image.addChild( bitmap );
    addChildAt( image, 0 );
    image.alpha = 0;
    TweenLite.to( image, 1, { alpha:1 } );
    statusTxt.text = «image too large for face detection»;
    return;
}

Также нам нужно добавить com.greensock.TweenLite в путь к классу.

1
import com.greensock.TweenLite;

Мы передаем width и height объекта Bitmap в параметры метода inRange . Если метод inRange возвращает false , мы создаем экземпляр объекта image , добавляем bitmap к объекту image и отображаем изображение под пользовательским интерфейсом. Изображение исчезает при использовании TweenLite, и мы показываем пользователю сообщение об ошибке, что изображение выходит за пределы диапазона. Наконец, мы выходим из функции, используя оператор return так как мы не хотим, чтобы выполнялся код, который будет следовать за оператором if .

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


Включите следующие классы в путь к классу FaceTagger.

  • com.oskarwicha.images.FaceDetection.FaceDetector
  • com.oskarwicha.images.FaceDetection.Events.FaceDetectorEvent
1
2
import com.oskarwicha.images.FaceDetection.FaceDetector;
import com.oskarwicha.images.FaceDetection.Events.FaceDetectorEvent;

Создайте новую переменную под объявлением класса с именем detector . Объект detector должен иметь тип FaceDetector . Начало класса FaceTagger теперь должно выглядеть следующим образом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package {
     
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.text.TextField;
    import flash.text.TextFieldType;
    import flash.text.TextFieldAutoSize;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.net.FileReference;
    import flash.net.FileFilter;
    import flash.geom.Rectangle;
    import flash.display.Loader;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.MovieClip;
    import flash.text.TextFormat;
    import flash.filters.DropShadowFilter;
    import com.greensock.TweenLite;
    import com.oskarwicha.images.FaceDetection.FaceDetector;
    import com.oskarwicha.images.FaceDetection.Events.FaceDetectorEvent;
     
    public class FaceTagger extends Sprite {
 
        private var statusTxt:TextField;
        private var fileRef:FileReference;
        private var fileFilter:FileFilter;
        private var loader:Loader;
        private var bitmap:Bitmap;
        private var image:MovieClip;
        private var darkBox:Sprite;
        private var detector:FaceDetector;
         
        public static const MIN_WIDTH:Number = 50;
        public static const MIN_HEIGHT:Number = 50;
        public static const MAX_WIDTH:Number = 1000;
        public static const MAX_HEIGHT:Number = 1000;
        public static const FILE_TYPES:String = «*.jpg; *.jpeg; *.bmp; *.png»;
     
        public function FaceTagger() {

Добавьте следующие строки кода в метод detectFaces .

1
2
3
4
5
statusTxt.text = «detecting faces… please wait»;
 
detector.addEventListener( FaceDetectorEvent.FACE_CROPED, onFacesDetected );
detector.addEventListener( FaceDetectorEvent.NO_FACES_DETECTED, onNoFaces );
detector.loadFaceImageFromBitmap( bitmap );

Сначала мы добавляем прослушиватели событий в FaceDetector . Есть два события, которые мы должны слушать. Событие NO_FACES_DETECTED событие NO_FACES_DETECTED .

( Примечание: FACE_CROPED — не опечатка. Вероятно, это ошибка орфографии автора библиотеки. )

Мы хотим вызывать метод onFacesDetected всякий раз, когда лицо было обнаружено, и метод onNoFaces когда лица не были обнаружены на изображении. Последняя строка кода загружает изображение лица из нашего bitmap объекта, содержащего загруженное изображение. Мы вызываем метод FaceDetector объекта loadFaceImageFromBitmap используя bitmap объект в качестве параметра Bitmap , для которого вызывается метод.


Когда FaceDetector объекта objectDetector обнаруживает лица на изображении, сам FaceDetector только одно из этих лиц. Если мы хотим получить доступ ко всем обнаруженным лицам, нам нужно получить прямой доступ к objectDetector . Создайте метод onFacesDetected метод onNoFaces содержащий следующие строки кода.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private function onFacesDetected( e:Event ):void {
     
    var faces:Array = detector.objectDetector.detected;
    if ( !image ) image = new MovieClip()
    else return;
    image.addChild( bitmap );
    addChildAt( image, 0 );
    positionContents();
    image.alpha = 0;
    TweenLite.to( image, 1, { alpha:1 } );
    statusTxt.text = «»;
     
    for each( var face:Rectangle in faces ) {
         
        trace( face );
        var rect:Rectangle = face;
        newTag( rect );
    }
     
    positionContents();
}
 
private function onNoFaces( e:Event ):void {
     
    statusTxt.text = «no faces were detected»;
    if ( !image ) image = new MovieClip();
    image.addChild( bitmap );
    addChildAt( image, 0 );
    positionContents();
    image.alpha = 0;
    TweenLite.to( image, 1, { alpha:1 } );
}

Давайте посмотрим на метод onFacesDetected . FaceDetector объекта objectDetector содержит массив обнаруженных объектов. Эти объекты являются объектами Rectangle , каждый из которых представляет местоположение и размер каждой обрезанной грани, которая была успешно найдена на изображении.

Мы назначаем его переменной faces которая является Array . Если объект image еще не существует, нам нужно создать его экземпляр. Если она существует, нам нужно выйти из функции, потому что код уже был выполнен один раз. Мы выполняем тот же код, который мы делали в методе detectFaces чтобы отобразить image используя анимацию с постепенным исчезновением. Затем мы очищаем текстовое поле.

Цикл for each используется для итерации каждого Rectangle в массиве faces . При отслеживании на панели вывода обнаруженное лицо будет выглядеть примерно так:

1
(x=30, y=30, w=123, h=123)

Метод newTag генерирует новый объект PhotoTag для отображения на изображении на основе предоставленного параметра Rectangle . Прежде чем мы сможем добавить тег на дисплей, нам нужно сначала создать наш класс тегов. Мы создадим этот класс на следующем шаге.

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


Создайте новый класс с именем PhotoTag который расширяет Sprite .

add_button_component

Импортируйте следующие классы в класс PhotoTag .

1
2
3
4
5
6
7
8
9
import flash.display.Sprite;
import flash.geom.Rectangle;
import flash.events.Event;
import flash.display.MovieClip;
import flash.filters.DropShadowFilter;
import flash.events.MouseEvent;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import com.greensock.TweenLite;

Создайте следующие переменные и константы.

01
02
03
04
05
06
07
08
09
10
11
12
private var _rectangle:Rectangle;
private var _image:MovieClip;
private var _editMode:Boolean;
private var _removed:Boolean;
private var removeBtn:RemoveButton;
 
public static const LINE_THICKNESS:Number = 6;
public static const LINE_COLOR:uint = 0x00CCFF;
public static const FILL_COLOR:uint = 0xFFFFFF;
public static const ELLIPSE_WIDTH:Number = 10;
public static const ELLIPSE_HEIGHT:Number = 10;
public static const INIT_ALPHA:Number = .6;

Добавьте следующий код в конструктор класса.

01
02
03
04
05
06
07
08
09
10
11
12
13
public function PhotoTag( rectangle:Rectangle, image:MovieClip ) {
 
    _rectangle = rectangle;
    _image = image;
    removeBtn = new RemoveButton();
    graphics.lineStyle( LINE_THICKNESS, LINE_COLOR );
    graphics.beginFill( FILL_COLOR, .5 );
    graphics.drawRoundRect( 0, 0, rectangle.width, rectangle.height, ELLIPSE_WIDTH, ELLIPSE_HEIGHT );
    filters = [ new DropShadowFilter( 5, 45, 0, 1, 5, 5, 1, 3 ) ];
    alpha = INIT_ALPHA;
    _editMode = false;
    addEventListener( Event.ADDED, onAdded );
}

Конструктор класса принимает два параметра:

  • Первый параметр — это объект rectangle который содержит данные об обрезанной грани.
  • Вторым параметром является MovieClip который содержит загруженное растровое изображение.

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

На следующей строке создается новый экземпляр класса RemoveButton . Используйте унаследованный graphics объект, чтобы нарисовать прямоугольник со скругленными углами, Rectangle width и height объекта Rectangle . Ширина и высота эллипса прямоугольника зависят от констант класса, а также от атрибутов линии прямоугольника. Мы применяем фильтр теней для добавления глубины к объекту и устанавливаем alpha на константу INIT_ALPHA (начальная альфа) . _editMode — это свойство, которое сообщает нам, находится ли тег в редактируемом состоянии.

Последняя строка в конструкторе прослушивает событие ADDED затем вызывает метод onAdded после добавления тега в список отображения. Мы делаем это, потому что нам нужно получить доступ к объекту stage который в настоящее время является null . Как только объект был добавлен, мы можем безопасно получить доступ к stage .

1
2
3
4
5
private function onAdded( e:Event ):void {
     
    removeEventListener( Event.ADDED, onAdded );
    init();
}

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


Создайте метод init в классе PhotoTag .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function init():void {
     
    if ( this.parent != image ) _image.addChild( this );
    x = _rectangle.x;
    y = _rectangle.y;
    addChild( removeBtn );
    removeBtn.alpha = 0;
    doubleClickEnabled = true;
    addEventListener( MouseEvent.ROLL_OVER, overState );
    addEventListener( MouseEvent.MOUSE_DOWN, onMouseDown );
    stage.addEventListener( MouseEvent.MOUSE_WHEEL, onMouseWheel );
    stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyDownE );
     
    removeBtn.addEventListener( MouseEvent.CLICK, remove );
}

Первая строка кода в методе init проверяет, действительно ли тег был добавлен к объекту image . Если это не так, тег добавит себя к изображению.

Затем теги x и y устанавливаются в rectangle x и y . Это просто помещает тег на обрезанное лицо. По умолчанию alpha свойство removeBtn установлено в 0 потому что мы не хотим, чтобы оно было видимым. Позже нам нужно будет прослушивать событие DOUBLE_CLICK и для тега doubleClickEnabled должно быть установлено значение true чтобы это событие было отправлено.

Последние пять строк кода добавляют прослушиватели событий в тег, на stage и в removeBtn .

При нажатии на removeBtn мы хотим удалить тег из списка отображения. Создайте метод remove .

1
2
3
4
5
6
7
8
private function remove( e:MouseEvent ):void {
     
    if ( this.parent ) {
         
        _removed = true;
        this.parent.removeChild( this );
    }
}

Метод удаляет тег из родительского объекта, если он есть. Мы устанавливаем для свойства _removed значение true поскольку объект удаляется.

Свойство _removed является частным свойством, но к нему нужно обращаться за пределами области действия этого класса. Мы только хотим предоставить коду вне класса доступ для чтения данных свойств. Мы также хотим предоставить такой же ограниченный доступ к объекту _image объекту _image и свойству _editMode . Создайте следующие методы получения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public function get rectangle():Rectangle {
 
    return _rectangle;
}
 
public function get image():MovieClip {
     
    return _image;
}
 
public function get editMode():Boolean {
     
    return _editMode;
}
 
public function get removed():Boolean {
     
    return _removed;
}

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


Если мы хотим, чтобы у наших объектов PhotoTag было немного жизни, нам нужно добавить немного взаимодействия и анимации. Добавьте следующие методы в класс PhotoTag .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
private function resize( percent:Number ):void {
     
    var newWidth:Number = ( percent * _rectangle.width ) / 100 + _rectangle.width;
    var newHeight:Number = ( percent * _rectangle.height ) / 100 + _rectangle.height;
     
    if ( newWidth < 60 ) {
         
        newWidth = 60;
    }
    if ( newHeight < 60 ) {
                 
        newHeight = 60;
    }
             
    var newX:Number = ( x — image.x ) — ( newWidth — _rectangle.width ) / 2;
    var newY:Number = ( y — image.y ) — ( newHeight — _rectangle.height ) / 2;
     
    _rectangle.x = newX;
    _rectangle.y = newY;
    _rectangle.width = newWidth;
    _rectangle.height = newHeight;
    draw( newWidth, newHeight );
    x = newX + image.x;
    y = newY + image.y;
}
 
private function overState( e:MouseEvent ):void {
     
    if ( _editMode ) return;
    removeEventListener( MouseEvent.ROLL_OVER, outState );
     
    removeBtn.visible = true;
    removeBtn.alpha = 0;
     
    var percent:Number = 40;
    var newWidth:Number = ( percent * _rectangle.width ) / 100 + _rectangle.width;
    var newHeight:Number = ( percent * _rectangle.height ) / 100 + _rectangle.height;
    var newX:Number = _rectangle.x — ( newWidth — _rectangle.width ) / 2;
    var newY:Number = _rectangle.y — ( newHeight — _rectangle.height ) / 2;
    TweenLite.to( this, .1, { alpha:1, x:newX, y:newY, width:newWidth, height:newHeight } );
    TweenLite.to( removeBtn, .1, { alpha:1 } );
    addEventListener( MouseEvent.ROLL_OUT, outState );
}
 
private function outState( e:MouseEvent ):void {
     
    if ( _editMode ) return;
    removeEventListener( MouseEvent.ROLL_OUT, outState );
    TweenLite.to( this, .1, { alpha:INIT_ALPHA, x:_rectangle.x, y:_rectangle.y, width:_rectangle.width, height:_rectangle.height } );
    TweenLite.to( removeBtn, .1, { alpha:1 } );
    addEventListener( MouseEvent.ROLL_OVER, overState );
}
         
private function onMouseWheel( e:MouseEvent ):void {
     
    if ( !_editMode ) return;
    var percent:Number = e.delta * 10;
    resize( percent );
}
 
private function onKeyDownE( e:KeyboardEvent ):void {
     
    if ( !_editMode ) return;
     
    var percent:Number;
     
    if ( e.keyCode == Keyboard.UP ) {
         
        percent = 10;
    }
    else if ( e.keyCode == Keyboard.DOWN ) {
         
        percent = -10;
    }
    else {
         
        return;
    }
     
    resize( percent );
}

Это может выглядеть как много кода, но то, что он на самом деле выполняет, довольно просто; все методы в основном выполняют один и тот же код.

Ранее мы добавили прослушиватель событий для события ROLL_OVER . Мы передали функцию обработчика eventHandler параметру overState называется overState . Этот метод добавляет простой эффект масштабирования, когда мышь наводит курсор на тег. Новая ширина и высота объекта основана на том, насколько мы хотим увеличить размер объекта. В этом случае мы хотим увеличить размер объекта на 40 процентов.

Мы могли бы остановиться здесь и просто использовать TweenLite для анимации изменяемого размера объекта, но это не выглядело бы очень привлекательно, потому что регистрация объекта была в верхнем левом углу. Что мы действительно хотим сделать, так это преобразовать объект из его центра. Если вы являетесь членом клуба Greensock, то вы можете просто использовать плагин transformAroundCenter, чтобы выполнить это с помощью всего одной строки кода и без необходимости производить вычисления самостоятельно, но для остальных из нас нам понадобится быстрый обходной путь.

Мы вычисляем новую позицию x на основе новой ширины и вычисляем новую позицию y на основе новой высоты. Затем мы добавляем свойства x , y , width и height к их новым значениям. Мы также постепенно removeBtn и делаем тег полностью непрозрачным.

Мы делаем полную противоположность в методе outState который вызывается каждый раз, когда мышь перемещается от тега. Тег возвращается в исходное состояние.

Метод resize выполняет то, что overState метод overState не влияя на свойство alpha и без каких-либо анимаций. Метод resize вызывается каждый раз, когда мы хотим динамически изменить размер тега. Метод принимает один параметр, который представляет собой процент, на который увеличивается или уменьшается размер тега. Метод добавляет минимальный предел размера для тега, чтобы он не стал маленьким.

Метод onMouseWheel вызывается всякий раз, когда пользователь взаимодействует с колесом мыши. Метод onKeyDownE вызывается при каждом нажатии клавиши. Каждый метод выполняет код, только если тег находится в режиме редактирования . И каждый метод изменяет размер тега на основе определенных свойств. Метод onMouseWheel изменяет onMouseWheel тега на основе значения свойства delta MouseEvent . onKeyDownE увеличивает размер тега при нажатии клавиши «Вверх» и уменьшает размер тега при нажатии клавиши «Вниз».

Это одна из последних вещей, которую нам нужно исправить, чтобы правильно настроить анимацию нашего тега. TweenLite изменяет значение свойств width и height в каждом кадре, пока анимация не будет завершена. При изменении размеров объектов во Flash они могут искажаться. Мы не хотим, чтобы наши теги выглядели искаженными. Чтобы решить эту проблему, нам нужно переопределить методы set width и set height из родительского класса.

1
2
3
4
5
6
7
8
9
public override function set width( value:Number ):void {
     
    draw( value, height );
}
 
public override function set height( value:Number ):void {
     
    draw( width, value );
}

Всякий раз, когда ширина или высота тега установлена, мы хотим перерисовать графику тега. Создать метод draw .

1
2
3
4
5
6
7
private function draw( newWidth:Number, newHeight:Number ):void {
     
    graphics.clear();
    graphics.lineStyle( LINE_THICKNESS, LINE_COLOR );
    graphics.beginFill( FILL_COLOR, .5 );
    graphics.drawRoundRect( 0, 0, newWidth, newHeight, ELLIPSE_WIDTH, ELLIPSE_HEIGHT );
}

Метод draw должен принимать два параметра: новую ширину тега и новую высоту тега. Графика очищается, а затем перерисовывается снова на основе параметров newWidth и newHeight . Теперь наш тег оживит красиво.


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

Сначала давайте создадим методы enterEditMode и exitEditMode . Оба эти метода будут открытыми, так как нам нужно будет вызывать их из класса документа.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public function enterEditMode():void {
     
    if ( !_editMode ) {
         
        _editMode = true;
        removeEventListener( MouseEvent.ROLL_OUT, outState );
        draw( _rectangle.width, _rectangle.height );
    }
}
 
public function exitEditMode():void {
     
    if ( _editMode && !removed ) {
         
        _editMode = false;
        image.addChild( this );
        x = _rectangle.x;
        y = _rectangle.y;
        stopDrag();
        alpha = INIT_ALPHA;
        TweenLite.to( removeBtn, .1, { alpha:0 } );
        addEventListener( MouseEvent.ROLL_OVER, overState );
    }
}

Метод enterEditMode выполняет блок кода в операторе if, если тег в данный момент не находится в режиме редактирования . Если тег не находится в режиме редактирования, тег перейдет в режим редактирования , установив для _editMode значение true чтобы позволить пользователю изменять свойства тега. ROLL_OUT события ROLL_OUT удален, так как мы не хотим, чтобы метод случайно выполнялся, если мышь зависает над тегом. Наконец, вызывается метод draw для отображения исходного размера тега.

Метод exitEditMode устанавливает тэг обратно в его начальное состояние, в то время как позиционирует тэг в новых позициях x и y _rectangle . Мы вызываем метод stopDrag на тот случай, если тег перетаскивается пользователем при выходе из режима редактирования .


Теперь вернемся к нашему классу Document FaceTagger . Наконец, мы можем создать метод newTag теперь, когда у нас есть класс PhotoTag . Создайте метод newTag .

1
2
3
4
5
6
7
8
private function newTag( rectangle:Rectangle ):void {
     
    var tag:PhotoTag = new PhotoTag( rectangle, image );
    image.addChild( tag );
    tag.addEventListener( MouseEvent.ROLL_OVER, onTagRollOver );
    tag.addEventListener( MouseEvent.DOUBLE_CLICK, enterEditMode );
    tag.addEventListener( Event.REMOVED, onTagRemoved );
}

Метод создает новый объект PhotoTag передавая параметр rectangle и текущее изображение в метод конструктора PhotoTag . Тег добавляется к текущему изображению. Мы прослушиваем несколько событий, которые тег отправит. Первое событие — это событие ROLL_OVER . Метод onTagRollOver вызывается при отправке события. Мы прослушиваем событие DOUBLE_CLICK чтобы мы могли вызвать метод enterEditMode , который будет отличаться от метода enterEditMode из класса PhotoTag , и, наконец, мы прослушиваем событие onTagRemoved которое вызывает метод onTagRemoved всякий раз, когда тег удаляется из его родительского объекта. ,

Создайте метод обработчика событий с именем onTagRollOver .

1
2
3
4
private function onTagRollOver( e:MouseEvent ):void {
     
    if ( !e.target.editMode ) statusTxt.text = «double-click to edit»;
}

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


Теперь нам нужно разрешить пользователю доступ для редактирования каждого тега. Создайте новую переменную под FaceTagger класса класса FaceTagger и назовите ее currentTag . Это должен быть объект PhotoTag . Начало класса документа теперь должно выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class FaceTagger extends Sprite {
 
        private var statusTxt:TextField;
        private var fileRef:FileReference;
        private var fileFilter:FileFilter;
        private var loader:Loader;
        private var bitmap:Bitmap;
        private var image:MovieClip;
        private var darkBox:Sprite;
        private var detector:FaceDetector;
        private var currentTag:PhotoTag;
 
        public static const MIN_WIDTH:Number = 50;
        public static const MIN_HEIGHT:Number = 50;
        public static const MAX_WIDTH:Number = 1000;
        public static const MAX_HEIGHT:Number = 1000;
        public static const FILE_TYPES:String = «*.jpg; *.jpeg; *.bmp; *.png»;
         
        public function FaceTagger() {

Создайте метод FaceTagger классе FaceTagger . Этот метод является методом обработчика событий, который мы вызвали в методе newTag .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private function enterEditMode( e:MouseEvent ):void {
     
    statusTxt.text = «click the background to exit ‘edit’ mode\n»;
    statusTxt.appendText( «use the mouse wheel or the up and down\n» );
    statusTxt.appendText( «arrow keys to resize the tag» );
     
    var tag:PhotoTag = e.target as PhotoTag;
    darkBox.visible = true;
    addChild( darkBox );
    addChild( tag );
    addChild( statusTxt );
    tag.x = image.x + tag.rectangle.x;
    tag.y = image.y + tag.rectangle.y;
    tag.enterEditMode();
    currentTag = tag;
    positionContents();
}

Приведенный выше метод уведомляет пользователя о том, что он в данный момент находится в режиме редактирования . Экран затемнен, а метка удалена с imageобъекта и размещена на stage. Теги xи yсвойства должны быть установлены в значения, которые соответствуют положению изображения на сцене. Если бы мы этого не делали, тег бы просто находился в верхнем левом углу экрана. Когда вызывается этот метод, даже не кажется, что позиция тегов вообще изменилась, но на самом деле она изменилась совсем немного.

Наконец, мы вызываем метод tagобъектов enterEditMode, назначаем ссылку на tagобъект currentTagпеременной и размещаем содержимое экрана (особенно statusTxtтекстовое поле).


После того, как пользователь завершил редактирование currentTag, тег необходимо добавить обратно к imageобъекту и правильно расположить. Создайте exitEditModeметод в FaceTaggerклассе.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private function exitEditMode( e:MouseEvent = null ):void {
     
    darkBox.visible = false;
     
    if ( currentTag ) {
         
        statusTxt.text = " ";
        currentTag.rectangle.x = currentTag.x - image.x;
        currentTag.rectangle.y = currentTag.y - image.y;
        currentTag.exitEditMode();
        currentTag = null;
        positionContents();
    }
}

Обратите внимание, как MouseEventпараметр установлен nullпо умолчанию. Мы собираемся вызвать этот метод из метода onTagRemovedобработчика событий позже, и у нас не будет MouseEventобъекта для передачи в качестве параметра метода, поэтому мы присваиваем параметру nullзначение по умолчанию. Это делает метод более доступным.

Первая задача, которую выполняет метод, — скрыть darkBoxобъект, чтобы дать пользователю ощущение выхода из режима редактирования . Блок кода в ifинструкции выполняется, если currentTagсуществует. Текстовое поле установлено в один пробел ( " "). Помните, что текстовое поле имеет autoSizeсвойство изменять размер из своего центра. Чтобы текстовое поле располагалось правильно, нам нужно убедиться, что присутствует какой-то текст, чтобы текстовое поле не уменьшало себя до нуля. Это может вызвать потенциальную проблему, если пользователь выходит из режима редактирования, а затем наводит курсор на тег. Эффект вызван тем, что текстовое поле может появиться за пределами экрана.

Далее, currentTag«S rectangleобъекта задается новое xи yзначение свойств. Эти значения основаны на текущей позиции тега stageотносительно imageпозиции в stage. Когда currentTag«ы exitEditModeметод называется currentTagдобавляется обратно в imageобъект и позиционируется на rectangle» нового xи yпозиции.

Наконец, мы устанавливаем ссылку currentTagна nullи размещаем содержимое на экране.

Создать onTagRemovedметод.

1
2
3
4
5
6
7
private function onTagRemoved( e:Event ):void {
             
    if ( e.target.removed ) {  
         
        exitEditMode();
    }
}

Этот метод выходит из режима редактирования, если был вызван removeметод целевого тега ( e.target). Таким образом, если пользователь нажал кнопку удаления на целевом теге, мы выйдем из режима редактирования . Без этого метода, если пользователь удалит тег в режиме редактирования , экран останется темным.


Вернитесь в PhotoTagкласс и добавьте в него следующие методы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function onMouseDown( e:MouseEvent ):void {
     
    if ( !_editMode ) return;  
    removeEventListener( MouseEvent.MOUSE_DOWN, onMouseDown );
    var thisRect:Rectangle = image.getRect( this );
    startDrag( false, new Rectangle( image.x, image.y, thisRect.width - _rectangle.width, thisRect.height - _rectangle.height ) );
    addEventListener( MouseEvent.MOUSE_UP, onMouseUp );
}
 
private function onMouseUp( e:MouseEvent ):void {
     
    if ( !_editMode ) return;
    removeEventListener( MouseEvent.MOUSE_UP, onMouseUp );
    stopDrag();
    addEventListener( MouseEvent.MOUSE_DOWN, onMouseDown );
}

Оба метода требуют, чтобы тег находился в режиме редактирования для полного выполнения. onMouseUpМетод начинает перетаскивание тега и ограничивает тег только перетащить в пределах _imageобъекта. onMouseUpМетод останавливает процесс перетаскивания.


К настоящему времени вы, вероятно, задаетесь вопросом, что случилось с addBtn. При нажатии этой кнопки новый PhotoTagобъект добавляется к текущему изображению. Давайте создадим addTagметод в классе документа.

Примечание. Вы должны помнить этот метод из шага 6. Это метод, который был передан в качестве параметра обработчика событий, когда мы добавили прослушиватель событий, который прослушивает CLICKсобытие, которое будет отправлено из addBtnобъекта .

1
2
3
4
5
6
7
8
private function addTag( e:MouseEvent ):void {
             
    if ( image ) {
         
        var rect:Rectangle = new Rectangle( 0, 0, 75, 75 );
        newTag( rect );
    }
}

Если imageсуществует, мы вызываем newTagметод, как и раньше. Мы создаем новый составленный Rectangleобъект, чтобы пользователь мог настроить тег самостоятельно. Тег просто добавляется к изображению в левом верхнем углу.


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

add_button_component

Сохраните класс в корневую папку. Импортируйте следующее внутри RemoveButton:

1
2
3
import flash.display.MovieClip;
import flash.filters.DropShadowFilter;
import flash.filters.BevelFilter;

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

1
filters = [ new DropShadowFilter( 5, 45, 0, 1, 5, 5, 1, 3 ), new BevelFilter( 10, 90, 0xF5E1AF, .6, 0, 1, 15, 15, 1, 3 ) ];

Мы только что добавили a DropShadowFilterи a BevelFilterк RemoveButtonобъекту. Это добавит большую глубину нашей кнопке.


На этом этапе, если мы тестируем наше приложение, все должно работать без сбоев. Но мы столкнемся с проблемой, когда загрузим изображение и начнется процесс распознавания лиц. Если вы не поместите файл face.zip в тот же каталог, что и SWF-файл , процесс обнаружения лица может не произойти. Это очень важно помнить, чтобы сделать это. После того, как вы поместили face.zip в тот же каталог, что и SWF , вы можете запустить приложение.

конечный результат

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