Face Detection with OpenCV in Java

(861 Views)


In this post I will show you how to detect faces (or eyes, or full body) using OpenCV in Java Application.

opencv logo with java

Prerequisites:

1. Write the code

First create a JavaFX project.

File -> New -> Project

Select JavaFX project:

javafx with opencv face detection

For project name, use whatever you want. I will use "opencv-detect". For JRE use Java 8. Click "Next >".

opencv javafx face detect

Sometimes Eclipse doesn't want to add JavaFX SDK for the build path so navigate to...

Libraries -> Add Library -> JavaFX SDK -> Next > -> Finish

We need to add the OpenCV library too.

Libraries -> Add Library -> User Library -> opencv (or what you named it in my tutorial) -> Finish

After you done the steps you should see this:

opencv library javafx detect face

Click "Next >"

For package name, add whatever you want, we will change it in the future. Set language to "FXML". Change File name to "FaceDetection", and change the Controller name to "FaceDetectionController"

It should look like something this:

opencv face detection library in javafx

First we create our files, then I will explain the code.

FaceDetectionController.java:

package it.polito.teaching.cv; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.opencv.core.Mat; import org.opencv.core.MatOfRect; import org.opencv.core.Rect; import org.opencv.core.Scalar; import org.opencv.core.Size; import org.opencv.imgproc.Imgproc; import org.opencv.objdetect.CascadeClassifier; import org.opencv.objdetect.Objdetect; import org.opencv.videoio.VideoCapture; import it.polito.elite.teaching.cv.utils.Utils; import javafx.event.Event; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.image.Image; import javafx.scene.image.ImageView; public class FaceDetectionController { @FXML private Button cameraButton; @FXML private ImageView originalFrame; @FXML private CheckBox haarClassifier; @FXML private CheckBox lbpClassifier; @FXML private CheckBox grayscale_cb; @FXML private CheckBox eyedetect_cb; @FXML private CheckBox fullbdetect_cb; private ScheduledExecutorService timer; private VideoCapture capture; private boolean cameraActive; private CascadeClassifier faceCascade; private CascadeClassifier eyeCascade; private CascadeClassifier FbodyCascade; private int absoluteFaceSize; private int absoluteEyeSize; private int absoluteFbodySize; /** * Init the controller, at start time */ protected void init() { this.capture = new VideoCapture(); this.faceCascade = new CascadeClassifier(); this.eyeCascade = new CascadeClassifier(); this.FbodyCascade = new CascadeClassifier(); this.checkboxSelectionEye("resources/haarcascades/haarcascade_eye_tree_eyeglasses.xml"); this.checkboxSelectionFbody("resources/haarcascades/haarcascade_fullbody.xml"); this.absoluteFaceSize = 0; this.absoluteEyeSize = 0; this.absoluteFbodySize = 0; originalFrame.setFitWidth(600); originalFrame.setPreserveRatio(true); } /** * The action triggered by pushing the button on the GUI */ @FXML protected void startCamera() { if (!this.cameraActive) { this.capture.open("HERE YOU CAMERA'S INDEX/IP"); if (this.capture.isOpened()) { this.cameraActive = true; Runnable frameGrabber = new Runnable() { @Override public void run() { if (capture.isOpened()) { Mat frame = grabFrame(); Mat grayFrame = frame; if (grayscale_cb.isSelected()) Imgproc.cvtColor(frame, grayFrame, Imgproc.COLOR_BGR2GRAY); Image imageToShow = Utils.mat2Image(grayFrame); updateImageView(originalFrame, imageToShow); } } }; this.timer = Executors.newSingleThreadScheduledExecutor(); this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS); this.cameraButton.setText("Stop Camera"); } else { System.err.println("Failed to open the camera connection..."); } } else { this.cameraActive = false; this.cameraButton.setText("Start Camera"); this.haarClassifier.setDisable(false); this.lbpClassifier.setDisable(false); this.stopAcquisition(); } } private Mat grabFrame() { Mat frame = new Mat(); if (this.capture.isOpened()) { try { this.capture.read(frame); if (!frame.empty()) { this.detectAndDisplay(frame); } } catch (Exception e) { System.err.println("Exception during the image elaboration: " + e); } } return frame; } private void detectAndDisplay(Mat frame) { if (!(!haarClassifier.isSelected() && !lbpClassifier.isSelected() && !eyedetect_cb.isSelected() && !fullbdetect_cb.isSelected())) { MatOfRect faces = new MatOfRect(); MatOfRect eyes = new MatOfRect(); MatOfRect fbodys = new MatOfRect(); Mat grayFrame = new Mat(); Imgproc.cvtColor(frame, grayFrame, Imgproc.COLOR_BGR2GRAY); Imgproc.equalizeHist(grayFrame, grayFrame); if (lbpClassifier.isSelected() || haarClassifier.isSelected()) { if (this.absoluteFaceSize == 0) { int height = grayFrame.rows(); if (Math.round(height * 0.2f) > 0) { this.absoluteFaceSize = Math.round(height * 0.2f); } } this.faceCascade.detectMultiScale(grayFrame, faces, 1.1, 2, 0 | Objdetect.CASCADE_SCALE_IMAGE, new Size(this.absoluteFaceSize, this.absoluteFaceSize), new Size()); Rect[] facesArray = faces.toArray(); for (int i = 0; i < facesArray.length; i++) { Imgproc.rectangle(frame, facesArray[i].tl(), facesArray[i].br(), new Scalar(0, 255, 0), 3); } } if (fullbdetect_cb.isSelected()) { if (this.absoluteFbodySize == 0) { int height = grayFrame.rows(); if (Math.round(height * 0.05f) > 0) { this.absoluteFbodySize = Math.round(height * 0.05f); } } this.FbodyCascade.detectMultiScale(grayFrame, fbodys, 1.1, 2, 0 | Objdetect.CASCADE_SCALE_IMAGE, new Size(this.absoluteFbodySize, this.absoluteFbodySize), new Size()); Rect[] fbodysArray = fbodys.toArray(); for (int i = 0; i < fbodysArray.length; i++) { Imgproc.rectangle(frame, fbodysArray[i].tl(), fbodysArray[i].br(), new Scalar(255, 0, 0), 3); } } if (eyedetect_cb.isSelected()) { if (this.absoluteEyeSize == 0) { int height = grayFrame.rows(); if (Math.round(height * 0.1f) > 0) { this.absoluteEyeSize = Math.round(height * 0.1f); } } this.eyeCascade.detectMultiScale(grayFrame, eyes, 1.1, 2, 0 | Objdetect.CASCADE_SCALE_IMAGE, new Size(this.absoluteEyeSize, this.absoluteEyeSize), new Size()); Rect[] eyesArray = eyes.toArray(); for (int i = 0; i < eyesArray.length; i++) { Imgproc.rectangle(frame, eyesArray[i].tl(), eyesArray[i].br(), new Scalar(0, 0, 255), 3); } } } } @FXML protected void haarSelected(Event event) { if (haarClassifier.isSelected()) { lbpClassifier.setSelected(false); lbpClassifier.setDisable(true); } else { lbpClassifier.setSelected(false); lbpClassifier.setDisable(false); } this.checkboxSelection("resources/haarcascades/haarcascade_frontalface_alt.xml"); } /** * The action triggered by selecting the LBP Classifier checkbox. It loads * the trained set to be used for frontal face detection. */ @FXML protected void lbpSelected(Event event) { if (lbpClassifier.isSelected()) { haarClassifier.setSelected(false); haarClassifier.setDisable(true); } else { haarClassifier.setSelected(false); haarClassifier.setDisable(false); } this.checkboxSelection("resources/lbpcascades/lbpcascade_frontalface.xml"); } /** * Method for loading a classifier trained set from disk * * @param classifierPath * the path on disk where a classifier trained set is located */ private void checkboxSelection(String classifierPath) { // load the classifier(s) this.faceCascade.load(classifierPath); // now the video capture can start this.cameraButton.setDisable(false); } private void checkboxSelectionEye(String classifierPath) { // load the classifier(s) this.eyeCascade.load(classifierPath); // now the video capture can start this.cameraButton.setDisable(false); } private void checkboxSelectionFbody(String classifierPath) { // load the classifier(s) this.FbodyCascade.load(classifierPath); // now the video capture can start this.cameraButton.setDisable(false); } /** * Stop the acquisition from the camera and release all the resources */ private void stopAcquisition() { if (this.timer!=null && !this.timer.isShutdown()) { try { // stop the timer this.timer.shutdown(); this.timer.awaitTermination(33, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // log any exception System.err.println("Exception in stopping the frame capture, trying to release the camera now... " + e); } } if (this.capture.isOpened()) { // release the camera this.capture.release(); } } /** * Update the {@link ImageView} in the JavaFX main thread * * @param view * the {@link ImageView} to update * @param image * the {@link Image} to show */ private void updateImageView(ImageView view, Image image) { Utils.onFXThread(view.imageProperty(), image); } /** * On application close, stop the acquisition from the camera */ protected void setClosed() { this.stopAcquisition(); } }

This code looks like a mess, and it is (Oh you don't see the HTML). It's just a sample code, without optimisations. I will explain everything, and you can write your own code from this.

Eclipse will warn you that the code is in a wrong package. You can fix it easily, the editor will give you the options to move it. Search for the function call

this.camera.open(*your camera's index/IP*);

I use an IP webcam, but if you have an USB webcam, most of the time you have to use 0.

Main.java: rename it to FaceDetection.java

package it.polito.teaching.cv; import org.opencv.core.Core; import javafx.application.Application; import javafx.event.EventHandler; import javafx.stage.Stage; import javafx.stage.WindowEvent; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; import javafx.fxml.FXMLLoader; /** * The main class for a JavaFX application. It creates and handle the main * window with its resources (style, graphics, etc.). * * This application handles a video stream and try to find any possible human * face in a frame. It can use the Haar or the LBP classifier. * * @author <a href="mailto:luigi.derussis@polito.it">Luigi De Russis</a> * @version 2.0 (2017-03-10) * @since 1.0 (2014-01-10) * */ public class FaceDetection extends Application { @Override public void start(Stage primaryStage) { try { // load the FXML resource FXMLLoader loader = new FXMLLoader(getClass().getResource("FaceDetection.fxml")); BorderPane root = (BorderPane) loader.load(); // set a whitesmoke background root.setStyle("-fx-background-color: whitesmoke;"); // create and style a scene Scene scene = new Scene(root, 800, 600); scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); // create the stage with the given title and the previously created // scene primaryStage.setTitle("Face Detection and Tracking"); primaryStage.setScene(scene); // show the GUI primaryStage.show(); // init the controller FaceDetectionController controller = loader.getController(); controller.init(); // set the proper behavior on closing the application primaryStage.setOnCloseRequest((new EventHandler<WindowEvent>() { public void handle(WindowEvent we) { controller.setClosed(); } })); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { // load the native OpenCV library System.loadLibrary(Core.NATIVE_LIBRARY_NAME); launch(args); } }

Fix the package issue again.

FaceDetection.fxml:

<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import javafx.geometry.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import javafx.scene.image.*?> <?import javafx.scene.text.*?> <?import javafx.scene.control.CheckBox?> <BorderPane prefHeight="459.0" prefWidth="537.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="it.polito.teaching.cv.FaceDetectionController"> <center> <VBox alignment="CENTER"> <padding> <Insets left="10" right="10" /> </padding> <children> <ImageView fx:id="originalFrame" /> </children> </VBox> </center> <bottom> <HBox alignment="CENTER"> <padding> <Insets bottom="25" left="25" right="25" top="25" /> </padding> <children> <Button fx:id="cameraButton" alignment="center" onAction="#startCamera" text="Start camera" /> </children> </HBox> </bottom> <right> <VBox prefHeight="343.0" prefWidth="199.0" BorderPane.alignment="CENTER"> <children> <CheckBox fx:id="lbpClassifier" onAction="#lbpSelected" text="Face Detection (LBP)" /> <CheckBox fx:id="haarClassifier" onAction="#haarSelected" text="Face Detection (Haar)" /> <CheckBox fx:id="eyedetect_cb" mnemonicParsing="false" text="Eye Detection (Haar)" /> <CheckBox fx:id="fullbdetect_cb" mnemonicParsing="false" text="Full Body Detection (Haar)" /> <CheckBox fx:id="grayscale_cb" mnemonicParsing="false" text="Show in grayscale" /> </children> </VBox> </right> </BorderPane>

This is the UI's code. If you want to change it, right click on it and open it with Scene Builder.

You still have errors right? We need to create an Utils.java file. Do it. It's code is here:

Utils.java

package it.polito.elite.teaching.cv.utils; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import org.opencv.core.Mat; import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.embed.swing.SwingFXUtils; import javafx.scene.image.Image; /** * Provide general purpose methods for handling OpenCV-JavaFX data conversion. * Moreover, expose some "low level" methods for matching few JavaFX behavior. * * @author <a href="mailto:luigi.derussis@polito.it">Luigi De Russis</a> * @author <a href="http://max-z.de">Maximilian Zuleger</a> * @version 1.0 (2016-09-17) * @since 1.0 * */ public final class Utils { /** * Convert a Mat object (OpenCV) in the corresponding Image for JavaFX * * @param frame * the {@link Mat} representing the current frame * @return the {@link Image} to show */ public static Image mat2Image(Mat frame) { try { return SwingFXUtils.toFXImage(matToBufferedImage(frame), null); } catch (Exception e) { System.err.println("Cannot convert the Mat object: " + e); return null; } } /** * Generic method for putting element running on a non-JavaFX thread on the * JavaFX thread, to properly update the UI * * @param property * a {@link ObjectProperty} * @param value * the value to set for the given {@link ObjectProperty} */ public static <T> void onFXThread(final ObjectProperty<T> property, final T value) { Platform.runLater(() -> { property.set(value); }); } /** * Support for the {@link mat2image()} method * * @param original * the {@link Mat} object in BGR or grayscale * @return the corresponding {@link BufferedImage} */ private static BufferedImage matToBufferedImage(Mat original) { // init BufferedImage image = null; int width = original.width(), height = original.height(), channels = original.channels(); byte[] sourcePixels = new byte[width * height * channels]; original.get(0, 0, sourcePixels); if (original.channels() > 1) { image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); } else { image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); } final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); System.arraycopy(sourcePixels, 0, targetPixels, 0, sourcePixels.length); return image; } }

Fix the package issue again. Now you can't have any errors. Now move every file from the "application" package to the "it.polito.teaching.cv" package.

Download this archive (resources.zip)

Then extract it to our project directory.

In theory it our package explorer should look like something this:

Detect Face using OpenCV in java

2. Explaining the code

Utils.java is implementing really useful methods for us.

FaceDetectionController.java connects to our camera, and gets some bools from our checkboxes. Then it communicates with the OpenCV library to detect our face.

FaceDetection.java manages the JavaFX.

3. Trying it out

Face Detection in OpenCV sample

Face Detection in OpenCV example

Solution Worked 1 UpvotesUpvote

        

Solution Didn't Worked 0 DownvotesDownvote



Comments



Search