14.07.2015

Reconhecimento Facial no iPhone - Parte 2

Reconhecimento Facial no iPhone Imagem via akiyama.com.br

Intel Open Source Computer Vision Library, ou simplesmente, OpenCV, foi criado pelo grupo de desenvolvimento da Intel em 2000 e possui uma coleção de ferramentas poderosas para o desenvolvimento de aplicativos na área de Visão Computacional. O OpenCV possui módulos de Processamento de Imagens e Video I/O, Estrutura de dados, Álgebra Linear, GUI Básica com sistema de janelas independentes, controle de mouse e teclado, além de mais de 350 algorítmos de Visão Computacional como: filtros de imagem, calibração de câmera, reconhecimento de objetos, análise estrutural, entre outros.

OpenCV é uma das mais poderosas bibliotecas do mundo de Visão Computacional, tendo sido utilizado em muitos, muitos aplicativos, como software de vigilância, veículos off-road, robôs que dirigem sozinhos e armas sentinelas são apenas alguns dos seus usos atuais. Ela foi criada para desenvolver especialmente aplicativos em tempo real onde normalmente se está muito preocupado com o desempenho dos algorítmos. Vamos incorporar em nossa biblioteca esses algoritmos que foram escritos em C/C++.

A primeira tarefa a ser feita é o download do OpenCV e, depois, configurar e compilar as bibliotecas de modo que elas possam ser usadas com o processador ARM encontrados no iPhone e iPod Touch. Não vamos entrar em detalhes sobre como efetuar a compilação, e vamos partir do princípio que o as bibliotecas já estejam portadas.

Agora abra o Xcode e crie um novo projeto “iPhone” usando a aplicação "view-based" como modelo. Adicione a biblioteca "opencvlib” que você acabou de baixar na raiz do projeto. Ao adicionar a biblioteca, não esqueça de marcar para fazer uma cópia, caso contrário o projeto irá somente referenciá-lo e não criar uma cópia da mesma dentro do projeto.

Na barra de menu superior, vá para o Project > Edit > Configuration. Selecione a guia "Build". Agora, procure o "Flags Linker Others" e adicione uma nova entrada para ela com a string "-lstdc + +". Isso permite que CCG faça o link padrão com bibliotecas C++ estaticamente, impedindo um fluxo de erros desagradável.

O OpenCV usa IplImage, que é a sua própria estrutura de dados para armazenar as imagens e sem uma forma de convertê-los para CGImage, nada poderemos fazer. Devemos então implementar essas funções. Abra a interface ViewController e importe a biblioteca "cv.h". Isto proporciona o acesso as funções do OpenCV.

Você vai usar um ImagePicker para selecionar as imagens, por isso faça com que o controlado implemente as duas interfaces: UIImagePickerControllerDelegate e UINavigationControllerDelegate. Depois adicione os métodos para suas declarações da interface.

typedef enum {
    ActionSheetToSelectTypeOfSource = 1,
    ActionSheetToSelectTypeOfMarks
} OpenCVTestViewControllerActionSheetAction;

@interface OpenCVTestViewController : UIViewController <UIActionSheetDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate> {
    IBOutlet UIImageView *imageView;
    OpenCVTestViewControllerActionSheetAction actionSheetAction;
}

- (IBAction)loadImage:(id)sender;
- (IBAction)faceDetect:(id)sender;

@property (nonatomic, retain) UIImageView *imageView;
@end

Com isso você vai criar as estruturas de retorno e de dados para armazenar os metadados para sua imagem. O "cvCreateImage()" inicializa um cabeçalho de imagem e aloca uma matriz contígua de pixels. O tamanho e a estrutura dessa matriz depende de como os canais de cores e quantos bits por canal são usados. Aqui você irá usar 4 canais (RGBA) imagem com 8 bits por canal, que é menos eficiente do espaço, mas te dá uma forma mais flexível para armazenar uma imagem.

Com o espaço alocado da imagem, você precisa definir seus pixels. Uma maneira de fazer isso é criar um contexto de bitmap a ser elaborado pela passagem CGBitmapContextCreate(), o ponteiro, a matriz de pixels da IplImage's (imageData). O contexto tem a mesma dimensão, propriedades e codificação de cores como o IplImage. Para transferir os dados da imagem, você pode simplesmente chamar a UIImage para o contexto, cuja matriz de pixels é de fato o imageData IplImage's. Finalmente, você precisa limpar o contexto bitmap e o objeto, retornando a imagem convertida.

- (IplImage *)CreateIplImageFromUIImage:(UIImage *)image {
    CGImageRef imageRef = image.CGImage;

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    IplImage *iplimage = cvCreateImage(cvSize(image.size.width, image.size.height), IPL_DEPTH_8U, 4);
    CGContextRef contextRef = CGBitmapContextCreate(iplimage->imageData, iplimage->width, iplimage->height,
                                                    iplimage->depth, iplimage->widthStep,
                                                    colorSpace, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrderDefault);
    CGContextDrawImage(contextRef, CGRectMake(0, 0, image.size.width, image.size.height), imageRef);
    CGContextRelease(contextRef);
    CGColorSpaceRelease(colorSpace);

    IplImage *ret = cvCreateImage(cvGetSize(iplimage), IPL_DEPTH_8U, 3);
    cvCvtColor(iplimage, ret, CV_RGBA2BGR);
    cvReleaseImage(&iplimage);

    return ret;
}

Você também vai precisar de uma função para converter de volta para IplImages UIImages. Para começar, você cria uma estrutura NSData e copiar a matriz IplImage de pixel inteira nele. A fim de construir o UIImage, você vai precisar de uma referência de cores e um provedor de dados (DataProvider) para o NSData recém-criado. Você cria um CGImage do tamanho e tipo adequados, utilizando os dados, em seguida, encapsula-a com um UIImage.

- (UIImage *)UIImageFromIplImage:(IplImage *)image {
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    NSData *data = [NSData dataWithBytes:image->imageData length:image->imageSize];
    CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)data);
    CGImageRef imageRef = CGImageCreate(image->width, image->height,
                                        image->depth, image->depth * image->nChannels, image->widthStep,
                                        colorSpace, kCGImageAlphaNone|kCGBitmapByteOrderDefault,
                                        provider, NULL, false, kCGRenderingIntentDefault);
    UIImage *ret = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);
    return ret;
}

Como a maioria dos aplicativos para o iPhone, o nosso usuário irá interagir através de uma GUI (interface gráfica). Vai ser simples e construído no Interface Builder, em vez de programação. Crie somente um UIImageView que vai apresentar a imagem e capturada e um botão para inicialiar o processo de detecção de faces.

Vamos utilizar agora a biblioteca de fotos. Você deve implementar a tela que acionará o selecionador de imagem e depois deve implementar o método que manipula a imagem escolhida. Este é o mesmo selecionador que surge ao escolher uma imagem para e-mail, por exemplo. Vamos facilitar o nosso caminho.

- (IBAction)loadImage:(id)sender {
    if(!actionSheetAction) {
        UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@""
                                                                 delegate:self cancelButtonTitle:@"Cancelar" destructiveButtonTitle:nil
                                                        otherButtonTitles:@"Usar foto da biblioteca", @"Usar Câmera", nil];
        actionSheet.actionSheetStyle = UIActionSheetStyleDefault;
        actionSheetAction = ActionSheetToSelectTypeOfSource;
        [actionSheet showInView:self.view];
        [actionSheet release];
    }
}

- (void)imagePickerController:(UIImagePickerController *)picker
        didFinishPickingImage:(UIImage *)image
                  editingInfo:(NSDictionary *)editingInfo
{
    imageView.image = image;
    [[picker parentViewController] dismissModalViewControllerAnimated:YES];
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    [[picker parentViewController] dismissModalViewControllerAnimated:YES];
}

Uma maneira de realizar a detecção de rosto com OpenCV é usando uma representação de objetos. Ele funciona através da soma e subtração dos valores de pixel de formas retangulares. Essas formas são verificadas através da imagem, e as intensidades de pixels nas regiões negras são subtraídas das intensidades nas regiões em branco. O resultado é uma soma que codifica as propriedades de borda (borda intensidade, ângulo e polaridade) de todos os pixels em cada local em um único número, simplificando enormemente o processamento.

Detecção de objetos é essencialmente um modelo correspondência onde tentaremos combinar as características faciais de cada posição na imagem em uma variedade de escalas. Felizmente OpenCV já vem com alguns exemplos. Para este projeto, você irá usar um detector de face frontal, de modo que não irá detectar inclinado ou de costas! A representação é o "haarcascade-frontalface-alt-tree.xml", que funciona bem. Esse arquivo está localizado no OpenCV/data/haarcascades. Copie-o para o seu projeto.

Agora temos os meios para obter uma imagem da biblioteca de fotos e temos uma referência carregada na inicialização. É hora de implementar o método para detectar rostos em uma imagem. Vamos desenhar um retângulo ao redor das faces para demarcar a área e observar o resultado.

- (IBAction)faceDetect:(id)sender {
    cvSetErrMode(CV_ErrModeParent);
    if(imageView.image && !actionSheetAction) {
        UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@""
                                                                 delegate:self cancelButtonTitle:@"Cancelar" destructiveButtonTitle:nil
                                                        otherButtonTitles:@"Retângulo", @"Dimenor", nil];
        actionSheet.actionSheetStyle = UIActionSheetStyleDefault;
        actionSheetAction = ActionSheetToSelectTypeOfMarks;
        [actionSheet showInView:self.view];
        [actionSheet release];
    }
}

- (void) opencvFaceDetect:(UIImage *)overlayImage  {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    if(imageView.image) {
        cvSetErrMode(CV_ErrModeParent);

        IplImage *image = [self CreateIplImageFromUIImage:imageView.image];
        
        // Escalando imagem
        IplImage *small_image = cvCreateImage(cvSize(image->width/2,image->height/2), IPL_DEPTH_8U, 3);
        cvPyrDown(image, small_image, CV_GAUSSIAN_5x5);
        int scale = 2;
        
        // Carregando XML
        NSString *path = [[NSBundle mainBundle] pathForResource:@"haarcascade_frontalface_default" ofType:@"xml"];
        CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad([path cStringUsingEncoding:NSASCIIStringEncoding], NULL, NULL, NULL);
        CvMemStorage* storage = cvCreateMemStorage(0);
        
        // Detectando face e desenhando o retângulo sobre ela
        CvSeq* faces = cvHaarDetectObjects(small_image, cascade, storage, 1.2f, 2, CV_HAAR_DO_CANNY_PRUNING, cvSize(0,0), cvSize(20, 20));
        cvReleaseImage(&small_image);
        
        // Criando canvas para exibir no resultado
        CGImageRef imageRef = imageView.image.CGImage;
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef contextRef = CGBitmapContextCreate(NULL, imageView.image.size.width, imageView.image.size.height,
                                                        8, imageView.image.size.width * 4,
                                                        colorSpace, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrderDefault);
        CGContextDrawImage(contextRef, CGRectMake(0, 0, imageView.image.size.width, imageView.image.size.height), imageRef);
        
        CGContextSetLineWidth(contextRef, 4);
        CGContextSetRGBStrokeColor(contextRef, 0.0, 0.0, 1.0, 0.5);
        
        // Desenhando resultado na imagem
        for(int i = 0; i < faces->total; i++) {
            NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
            
            // Calculando a área das faces
            CvRect cvrect = *(CvRect*)cvGetSeqElem(faces, i);
            CGRect face_rect = CGContextConvertRectToDeviceSpace(contextRef, CGRectMake(cvrect.x * scale, cvrect.y * scale, cvrect.width * scale, cvrect.height * scale));
            
            if(overlayImage) {
                CGContextDrawImage(contextRef, face_rect, overlayImage.CGImage);
            } else {
                CGContextStrokeRect(contextRef, face_rect);
            }
            
            [pool release];
        }
        
        imageView.image = [UIImage imageWithCGImage:CGBitmapContextCreateImage(contextRef)];
        CGContextRelease(contextRef);
        CGColorSpaceRelease(colorSpace);
        
        cvReleaseMemStorage(&storage);
        cvReleaseHaarClassifierCascade(&cascade);
    }

    [pool release];
}

Carregar essa referência é um processo custoso que usa muita memória. Um XML é uma forma muito ineficiente para armazenar números e em cima disso, a análise do OpenCV XML é muito demorada. Portanto, você só vai querer fazer isso uma única vez se possível, então coloque no "viewDidLoad".

Tendo em vista a grande onda de assaltos feitos por menores de idade hoje em dia, vamos fazer com que o nosso aplicativo coloque uma "tarja" sobre a face detectada, ou seja, desenhe um quadrado preto ao redor da face, escondendo o rosto.

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
    switch(actionSheetAction) {
        case ActionSheetToSelectTypeOfSource: {
            UIImagePickerControllerSourceType sourceType;
            
            // Recupera imagem da biblioteca de fotos ou da câmera
            if (buttonIndex == 0) {
                sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
            } else if(buttonIndex == 1) {
                sourceType = UIImagePickerControllerSourceTypeCamera;
            } else {
                // Cancelar
                break;
            }
            if([UIImagePickerController isSourceTypeAvailable:sourceType]) {
                UIImagePickerController *picker = [[UIImagePickerController alloc] init];
                picker.sourceType = sourceType;
                picker.delegate = self;
                [self presentModalViewController:picker animated:YES];
                [picker release];
            }
            break;
        }
        case ActionSheetToSelectTypeOfMarks: {
            // Exibe retângulo ou tarja preta
           UIImage *image = nil;
            if(buttonIndex == 1) {
                // Caminho da imagem tarja preta
                NSString *path = [[NSBundle mainBundle] pathForResource:@"tarja_preta" ofType:@"png"];
                image = [UIImage imageWithContentsOfFile:path];
            }
            
            [self performSelectorInBackground:@selector(opencvFaceDetect:) withObject:image];
            break;
        }
    }
    actionSheetAction = 0;
}

Este foi apenas um exemplo muito simples para ilustrar o que algumas linhas de código são capazes de com OpenCV. Podemos modificar o programa para adicionar bigodes e chapéus nas pessoas das fotos, aleatoriamente deformar suas cabeças ou substituí-los com os rostos de celebridades!

Podemos também usar um algoritmo de pintura para reparar imagens danificadas ou remover espinhas de uma pessoa, capturar cédulas e identificar os valores delas (muito útil para deficientes visuais) ou até um solucionador de partidas de damas.

Bruno Fernandes

Analista de sistemas, carioca, apaixonado por tecnologia e entusiasta da cultura web. Trabalha com desenvolvimento de aplicativos e soluções de TI para sistemas de internet, dispositivos móveis e embarcados. Preferência pela utilização de ferramentas livres e ambientes Linux/MacOS.

< voltar a página de artigos