Java e OpenCv - Se divertindo com a detecção facial (Tutorial + Código)

Com a biblioteca OpenCv possível fazer bastante coisa, o foco desse post é a detecção facial. Para quer tiver interessado em conhecer um pouco mais: http://opencv.org/

Vamos utilizar Java que é umas das linguagens suportadas. Como vamos brincar com essa biblioteca iremos construir um aplicativo para desfocar apenas os rotos das pessoas em fotos:


"Chaves de menor":

Atenção, Creuzebek!

Faça do download da biblioteca no site do OpenCv: http://opencv.org/. Crie um projeto Java no Eclipse.

Clique com o potão direito sobre o seu projeto vá em: Build Path -> Configure Build Path,
espanda o item referente a JRE clique em edit e selecione o diretório
onde estão as bibliotecas nativas do OpenCv que por padrão estão em C:/opencv/opencv/build/java/x86,
mas pode mudar dependendo da sua instalação.



Nesse projeto eu utilizei os conceitos do SOLID, que bem resumidamente diz que cada classe deve servir a um único propósito. Dessa forma essa ficou sendo a minha estrutura de pacotes:



Para começar coloque a linha a seguir no main do seu código antes de qualquer outro trecho:

  
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
Essa linha é responsável por "invocar" as bibliotecas nativas que inserimos anteriormente, que na realidade é quem faz todo o trabalho. Logo após você precisará carregar um arquivo xml que contém as parametrizações necessárias para fazer o reconhecimento dos rostos:

CascadeClassifier cascadeClassifier = new CascadeClassifier(System.getProperty("user.dir") + "/haarcascade_frontalface_alt_tree.xml");

Bom aí você me pergunta onde encontro esse arquivo? dependendo da sua instalação ele fica arqui: C:\opencv\opencv\sources\data\haarcascades . Sugiro que você copie esse arquivo para raiz do seu projeto e carregue ele como está acima.
Após isso precisamos carregar a imagem, você deve fazer dessa forma:

Mat mat = Highgui.imread(System.getProperty("user.dir") +"/chaves.jpg");

No método imread você passa o caminho até o arquivo.

Para facilitar o entendimento eu crie um diagrama de atividades que ilustra os passos necessários para a brincadeira acontecer:



Passo 1 - Detectar faces

Para essa atividade ou passo está na classe ServiceDeteccaoFacesImagem e temo o método que faz a detecção das faces:

public MatOfRect detectarFaces(CascadeClassifier cascadeClassifier, Mat mat){
MatOfRect matOfRect = new MatOfRect();
cascadeClassifier.detectMultiScale(mat, matOfRect);
return matOfRect;
}

Esse método recebe os dois objeto criados anteriormente, que são respectivamente: a classe que "carrega" o xml e a classe que "carrega" a imagem. o objeto CascadeClassifier contém o método detectMultScale que localiza o que está sendo procurado na imagem que em nosso caso são rostos.

Passo 2 - Obter dados das faces

Os dados que serão obtidos das faces serão sua posição (x,y) na imagem e a largura e altura da face. O método a seguir contém a lógica de extração desses dados novamente a classe utilizada é ServiceDeteccaoFacesImagem:

public List obterDadosFaces(MatOfRect matOfRect){
  
  List dados = new ArrayList();
  
  for (Rect rect : matOfRect.toArray()) {
   
   PropriedadesFace prop = new PropriedadesFace();
   prop.setX(rect.x);
   prop.setY(rect.y);
   prop.setHeight(rect.height);
   prop.setWidth(rect.width);
   
   dados.add(prop);

  }
  
  return dados;
 }


Notem que o retorno é uma lista do objeto PropriedadesFace abaixo o modelo da classe desse objeto, ocultado os geters e seteres:

public class PropriedadesFace {
 private int x;
 private int y;
 private int width;
 private int height;
 private BufferedImage imageCortada;
Passo 3 - Desfocar toda a imagem

Vamos desfocar a imagem antes de "recortar" as faces, essa atividade está na classe ServiceDesfoqueImagem:

public BufferedImage DesfocarImagem(Mat mat){
  
  mat = Desfocar(mat);
  
  return Util.converterParaImage(mat);
 }
 
 private Mat Desfocar(Mat image){
         
  Mat destination = new Mat(image.rows(),image.cols(),image.type());
         
  Imgproc.GaussianBlur(image, destination,new Size(45,45), 0);
  
  return destination;
 }
 

Temos aqui esses 2 métodos um recebe o objeto Mat e o outro faz o desfoque da imagem. O desfoque é feito utilizado o método GaussianBlur da classe Imgproc, que pode ser invocado diretamente porque é um método estático. Por fim convertemos o objeto Mat para o BufferedImage, esse método se encontra na classe Util:

public static BufferedImage converterParaImage(Mat image){

  MatOfByte bytemat = new MatOfByte();

  Highgui.imencode(".jpg", image, bytemat);

  byte[] bytes = bytemat.toArray();

  InputStream in = new ByteArrayInputStream(bytes);

  BufferedImage img=null;

  try {
   img = ImageIO.read(in);
  } catch (IOException e) {
   e.printStackTrace();
  }

  return img;
 }

Passo 4 - Recortar as faces da imagem

Para cortar as faces da imagem utilizamos o método CortarImagem da classe ServiceCorteImagem:

public List CortarImagem(List dados, BufferedImage imagem){
  
  for(PropriedadesFace dado : dados){
   dado.setImageCortada(imagem.getSubimage(dado.getX(), dado.getY(), dado.getWidth(), dado.getHeight()));
  }
  
  return dados;
 }

Utilizamos o método getSubImage para "cortar" uma determinada parte da imagem e a imagem "cortada" é incluída no objeto PropriedadesFace, o mesmo objeto que contém seus dados.

Passo 5 - Obter imagem sem o desfoque

Para obter essa imagem utilizamos o método converterParaImagem na classe Util:

public static BufferedImage converterParaImage(Mat image){

  MatOfByte bytemat = new MatOfByte();

  Highgui.imencode(".jpg", image, bytemat);

  byte[] bytes = bytemat.toArray();

  InputStream in = new ByteArrayInputStream(bytes);

  BufferedImage img=null;

  try {
   img = ImageIO.read(in);
  } catch (IOException e) {
   e.printStackTrace();
  }

  return img;
 }

Essa imagem vai servir como fundo para "colarmos" as imagens dos rostos desfocados sobre ela.

Passo 6 -  Juntar imagem com as faces recortadas

Utilizamos esse dois métodos para sobrepor as imagens:

public BufferedImage juntarImagens(List dados, BufferedImage imagemPrincipal){
  
  for(PropriedadesFace dado: dados){
   imagemPrincipal = juntarUmaImage(imagemPrincipal, dado.getImageCortada(),dado.getX(),dado.getY());
  }
  
  return imagemPrincipal;
  
 }
 
 public static BufferedImage juntarUmaImage(BufferedImage imagemPrincipal,
            BufferedImage imagemCortada, int x, int y) {
 
        Graphics2D g = imagemPrincipal.createGraphics();
        
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        
        g.drawImage(imagemPrincipal, 0,0, null);
 
        g.drawImage(imagemCortada, x, y, null);
 
        g.dispose();
        return imagemPrincipal;
    }

O primeiro percorre todas as imagens que estão juntas com os dados das suas posições, já o segundo faz a sobreposição em si. Os dois métodos estão na classe ServiceSobreposicaoImagem.

Para terminar

Esse é o código do método main responsável por sequenciar as chamadas dos métodos:

System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

  CascadeClassifier cascadeClassifier = new CascadeClassifier(System.getProperty("user.dir") + "/haarcascade_frontalface_alt_tree.xml");

  Mat mat = Highgui.imread(System.getProperty("user.dir") +"/chaves.jpg");
  
  ServiceDeteccaoFacesImagem serviceExtractFaces = new ServiceDeteccaoFacesImagem();
  MatOfRect matOfRect = serviceExtractFaces.detectarFaces(cascadeClassifier, mat);
  
  List propsFaces = serviceExtractFaces.obterDadosFaces(matOfRect);
  
  ServiceDesfoqueImagem serviceBlur = new ServiceDesfoqueImagem();
  BufferedImage imagemCorteDesfoque = serviceBlur.DesfocarImagem(mat);
  
  ServiceCorteImagem serviceCrop = new ServiceCorteImagem();
  propsFaces = serviceCrop.CortarImagem(propsFaces, imagemCorteDesfoque);
  
  ServiceSobreposicaoImagem serviceOverlay = new ServiceSobreposicaoImagem();
  
  BufferedImage imagemSemEfeitos = Util.converterParaImage(mat);
  
  imagemCorteDesfoque = serviceOverlay.juntarImagens(propsFaces, imagemSemEfeitos);
  
  File outputfile = new File("chaves menor.jpg");
  
     try {
   ImageIO.write(imagemCorteDesfoque, "jpg", outputfile);
  } catch (IOException e) {
   e.printStackTrace();
  }

E é isso galera quem quiser o código ele pode ser acessado aqui: https://github.com/rafaelguinho/HidingHerFace

15 comentários:

  1. Ótimo post! Parabéns! Aqui deu certo! Porém quando testei com fotos maiores, o desfoque foi quase imperceptível. Onde devo mexer para resolver esse detalhe? Além disso, desejo saber se usando essa biblioteca OpenCV posso fazer comparações entre fotos para saber se elas são da mesma pessoa ou comparar a imagem obtida em uma webcam com uma foto armazenada.

    ResponderExcluir
  2. Cara. Aqui esta dando erro nesse import ``import org.opencv.highgui.Highgui; ``

    ResponderExcluir
    Respostas
    1. Você deve importar o .jar da blblioteca OpenCV, creio que seja esse o problema.

      Excluir
    2. A partir da versão 3, este comando foi alterado.
      Utilize no lugar:

      import org.opencv.imgcodecs.Imgcodecs;

      Exemplo:
      Imgcodecs.imencode(fileExten, matrix, mob);

      Excluir
    3. Não se usa mais o Highgui, os metodos dele foram para imgcodecs e imgproc

      Excluir
  3. Amigo, esse código serve para imagens limerializadas ?

    ResponderExcluir
  4. Como será o código para reconhecimento dos olhos abertos e fechados, mas não sendo em imagem e sim em vídeo?

    ResponderExcluir
  5. Classe main um erro System.loadLibrary(Core.NATIVE_LIBRARY_NAM);
    run:
    Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - Erroneous tree type:
    at main.Principal.main(Principal.java:30)
    C:\Users\Maquinador Chefe\AppData\Local\NetBeans\Cache\8.1\executor-snippets\run.xml:53: Java returned: 1
    FALHA NA CONSTRUÇÃO (tempo total: 1 segundo)


    desculpa se isso for um erro simples, mas preciso de ajuda nisso. Eu estou me orientando pelo seu código para desenvolver o meu. muito bem comentado o seu código, á eu estou usando o Netbeans pra programar.

    ResponderExcluir
  6. Já existem componentes prontos e de facil utilização, olhe em
    www.dvoz.org

    ResponderExcluir
  7. Caros amigos, gostaria de saber se essa Biblioteca funciona no NetBeans IDE 8.2

    ResponderExcluir
  8. Bom dia! Você já fez funcionar para uma aplicação java web como por exemplo no VRaptor ou tem alguma outra sugestão para utilizar essa biblioteca nativa em sistema web Java? eu achei algo como uma interface da Vaadin: https://dzone.com/articles/combining-html-web, se conhecer algum vídeo ou material/tutorial.

    ResponderExcluir
  9. Olá, vocês tem o link atualizado com o código completo?

    ResponderExcluir