
Um dos interessantes tópicos abordados no evento iOS 5 Tech Talk Tour, que aconteceu em São Paulo no último dia 9 de janeiro, se refere ao Core Image, um framework que já era disponível no MacOS e agora pode ser usado pelos desenvolvedores de aplicativos para o iOS.
É importante ressaltar que este framework está disponível apenas a partir do iOS 5.0, o que torna o seu uso ainda limitado aos requisitos do aplicativo. Porém, de acordo com artigo do site CNET em novembro de 2011 a porcentagem de devices utilizando o iOS 5 já era de 40%, o que mostra que em pouco tempo os aplicativos lançados para essa versão já poderão ser baixados pela maior parte dos usuários.
Com certeza uma das características mais interessantes do Core Image para o iPhone é sua capacidade de reconhecimento de face, que vamos abordar neste artigo. Esta nova técnica faz com que possamos pensar em diversas aplicações utilizando este conceito, com baixo custo de implementação.
Vamos mostrar como fazer o reconhecimento de faces lendo diretamente do stream da câmera do iPhone. O código-fonte mostrado baseia-se no projeto SquareCam disponibilizado pela Apple como exemplo de uso de Reconhecimento de Faces, presente aqui.
Primeiramente, iremos configurar a câmera utilizando o framework AVFoundation, disponível a partir do iOS 4, de modo que possamos ler diretamente o stream do dispositivo.
A configuração é feita para que tenhamos as seguintes instâncias:
- AVCaptureSession – Este objeto representa uma sessão que coordena o fluxo de dados dos dispositivos de entrada AV para as saídas. Para isso, adicionamos os dispositivos de captura e saídas para esta sessão e iniciamos o fluxo de dados enviando uma mensagem startRunning e paramos com uma mensagem stopRunning.
- AVCaptureDevice – É uma abstração de um dispositivo físico de captura que fornece uma entrada para um objeto AVCaptureSession. Há um objeto disponível para cada tipo de dispositivo de entrada. Por exemplo: no iPhone 3GS há uma entrada de vídeo, enquanto que no iPhone 4 há duas entradas.
- AVCaptureDeviceInput – É uma subclasse de AVCaptureInput usada para adicionar um dispositivo de entrada em uma sessão (AVCaptureSession).
- AVCaptureOutput – É uma classe abstrata usada para buscar uma saída da sessão (AVCaptureSession).

- (void)setupAVCapture { AVCaptureSession *session = [AVCaptureSession new]; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) [session setSessionPreset:AVCaptureSessionPreset640x480]; else [session setSessionPreset:AVCaptureSessionPresetPhoto]; // Select a video device, make an input AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil]; if ( [session canAddInput:deviceInput] ) [session addInput:deviceInput]; // Make a video data output videoDataOutput = [[AVCaptureVideoDataOutput alloc] init]; // we want BGRA, both CoreGraphics and OpenGL work well with 'BGRA' NSDictionary *rgbOutputSettings = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt:kCMPixelFormat_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]; [videoDataOutput setVideoSettings:rgbOutputSettings]; [videoDataOutput setAlwaysDiscardsLateVideoFrames:YES]; // discard if the data output queue is blocked (as we process the still image) videoDataOutputQueue = dispatch_queue_create("VideoDataOutputQueue", NULL); [videoDataOutput setSampleBufferDelegate:self queue:videoDataOutputQueue]; if ( [session canAddOutput:videoDataOutput] ) [session addOutput:videoDataOutput]; previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session]; [previewLayer setBackgroundColor:[[UIColor blackColor] CGColor]]; [previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect]; CALayer *rootLayer = [previewView layer]; [rootLayer setMasksToBounds:YES]; [previewLayer setFrame:[rootLayer bounds]]; [rootLayer addSublayer:previewLayer]; [session startRunning]; }
Identificando um Rosto com um CIDetector
De acordo com o CIDetector Class Reference da Apple, um objeto CIDetector, disponível a partir do iOS 5 no framework CoreImage.framework, usa processamento de imagem para encontrar “features” em uma imagem.
Assim, o próximo passo para identificar o rosto no nosso stream de vídeo é configurar um CIDetector. Após adicionamos este framework no projeto, criamos uma instância dele para utilizarmos da seguinte forma:
NSDictionary *detectorOptions = [[NSDictionary alloc] initWithObjectsAndKeys:CIDetectorAccuracyLow, CIDetectorAccuracy, nil]; faceDetector = [[CIDetector detectorOfType:CIDetectorTypeFace context:nil options:detectorOptions] retain];
Em nossa inicialização da câmera feita anteriormente configuramos nosso controller para atuar como delegate do stream de vídeo (videoDataOutput) com a seguinte linha:
[videoDataOutput setSampleBufferDelegate:self queue:videoDataOutputQueue];
Desta forma, podemos implementar o seguinte método para ler o stream do vídeo:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { … }
Por fim, com uma instância de um CIDetector e um método que lê o stream da câmera, podemos identificar um rosto utilizando o seguinte:
// got an image CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, sampleBuffer, kCMAttachmentMode_ShouldPropagate); CIImage *ciImage = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer options:(NSDictionary *)attachments]; if (attachments) CFRelease(attachments); NSDictionary *imageOptions = nil; // '6' identifies device on vertical position imageOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:6] forKey:CIDetectorImageOrientation]; NSArray *features = [faceDetector featuresInImage:ciImage options:imageOptions]; [ciImage release];
No código acima, temos o array features onde cada elemento é uma instância de um CIFaceFeature, que identifica uma face encontrada no vídeo e permite encontrar diversas informações sobre ela.
CIFaceFeature de uma Imagem
Um objeto CIFaceFeature descreve uma face encontrada em uma imagem. Suas propriedades informam as posições dos olhos e da boca da face. Estas propriedades são as seguintes:
hasLeftEyePosition – Informa se a posição do olho esquerdo esta disponível
hasRightEyePosition – Informa se a posição do olho direito esta disponível
hasMouthPosition – Informa se a posição da boca esta disponível
leftEyePosition – Informa a posição do olho esquerdo
rightEyePosition – Informa a posição do olho direito
mouthPosition – Informa a posição da boca
Além disso, por herdar de um CIFeature, também possui as seguintes propriedades:
bounds – Um retângulo que contém a feature encontrada
type – O tipo de feature encontrado
A partir destas informações diversas ações podem ser tomadas, como inserir elementos visuais sobre a face encontrada.
Modo CIDetectorAccuracyLow Vs CIDetectorAccuracyHigh
Quando criamos nosso CIDetector, um parâmetro de configuração utilizado foi CIDetectorAccuracyLow, como pode ser visto a seguir:
NSDictionary *detectorOptions = [[NSDictionary alloc] initWithObjectsAndKeys:CIDetectorAccuracyLow, CIDetectorAccuracy, nil];
O motivo desse uso foi pelo fato de estarmos fazendo a detecção diretamente do stream de vídeo e, por isso, utilizar esta opção faz com a análise de cada frame seja mais rápida, porém tendo uma chance maior de não detectar uma face.
De modo geral, a opção CIDetectorAccuracyHigh é utilizada somente quando vamos analisar uma única imagem, o que permite que possamos ter um processamento um pouco mais lento, encontrando com mais facilidade as faces.
Como podemos ver, a partir do iOS 5 ficou bem mais fácil utilizar detecção de faces em aplicativos, o que nos permite pensar em diversas soluções que anteriormente poderiam ser consideradas inviáveis para um projeto. Porém, como dito, devemos ainda nos alertar quanto aos requisitos do projeto, uma vez que nem todos os usuários já possuem esta versão de iOS.
Este post também está disponível em inglês aqui.