/* * Catroid: An on-device visual programming system for Android devices * Copyright (C) 2010-2016 The Catrobat Team * (<http://developer.catrobat.org/credits>) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * An additional term exception under section 7 of the GNU Affero * General Public License, version 3, is available at * http://developer.catrobat.org/license_additional_term * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.catrobat.catroid.test.facedetection; import android.graphics.Rect; import android.hardware.Camera; import android.hardware.Camera.Face; import android.test.InstrumentationTestCase; import android.util.Log; import org.catrobat.catroid.common.ScreenValues; import org.catrobat.catroid.facedetection.IcsFaceDetector; import org.catrobat.catroid.formulaeditor.SensorCustomEvent; import org.catrobat.catroid.formulaeditor.SensorCustomEventListener; import org.catrobat.catroid.formulaeditor.Sensors; import org.catrobat.catroid.uitest.annotation.Device; import java.util.Random; public class IcsFaceDetectorTest extends InstrumentationTestCase { private static final String TAG = IcsFaceDetectorTest.class.getSimpleName(); private static final int FACE_RECT_SIZE = 2000; // see reference of Camera.Face.rect (1000 - -1000 = 2000) private static final int COUNTER_INDEX = 0; private static final int SIZE_INDEX = 1; private static final int X_POSITION_INDEX = 2; private static final int Y_POSITION_INDEX = 3; private static final int FACE_LEFT = -20; private static final int FACE_RIGHT = 200; private static final int FACE_TOP = -100; private static final int FACE_BOTTOM = 300; private static final int LOW_SCORE_FACE_WIDTH = 10; @Override public void setUp() throws Exception { super.setUp(); ScreenValues.SCREEN_WIDTH = 720; ScreenValues.SCREEN_HEIGHT = 1080; } // does not run on emulator, and nexus 7 @Device public void testDeviceFaceDetectionSupport() { int possibleFaces = 0; Camera camera = null; try { camera = Camera.open(); possibleFaces = camera.getParameters().getMaxNumDetectedFaces(); camera.release(); } catch (Exception exc) { Log.e(TAG, Log.getStackTraceString(exc)); } finally { if (camera != null) { camera.release(); } } if (possibleFaces == 0) { Log.w(TAG, "The hardware does not support facedetection"); } } @Device public void testNotAvailable() { Camera camera = null; try { camera = Camera.open(); IcsFaceDetector detector = new IcsFaceDetector(); boolean success = false; if ((camera.getParameters()).getMaxNumDetectedFaces() > 0) { success = detector.startFaceDetection(); assertFalse("IcsFaceDetector should not start if camera not available.", success); } } catch (Exception exc) { fail("Camera not available (" + exc.getMessage() + ")"); } finally { if (camera != null) { camera.release(); } } } @Device public void testDoubleStart() { Camera camera = null; try { camera = Camera.open(); } catch (Exception exc) { fail("Camera not available (" + exc.getMessage() + ")"); } IcsFaceDetector detector = new IcsFaceDetector(); if ((camera.getParameters()).getMaxNumDetectedFaces() > 0) { detector.startFaceDetection(); try { detector.startFaceDetection(); } catch (Exception e) { fail("Second start of face detector should be ignored and not cause errors: " + e.getMessage()); } finally { detector.stopFaceDetection(); camera.release(); camera = null; } } if (camera != null) { camera.release(); } } @Device public void testOnFaceDetectionStatusListener() { IcsFaceDetector detector = new IcsFaceDetector(); final float[] detected = new float[1]; SensorCustomEventListener listener = new SensorCustomEventListener() { public void onCustomSensorChanged(SensorCustomEvent event) { detected[0] = event.values[0]; } }; detector.addOnFaceDetectionStatusListener(listener); assertEquals("unexpected detection of a face", 0f, detected[0]); detector.onFaceDetection(new Face[0], null); assertEquals("ICS Face Detector posted wrong status", 0f, detected[0]); Face[] faces = new Face[1]; faces[0] = new Face(); faces[0].rect = new Rect(); detector.onFaceDetection(faces, null); assertEquals("ICS Face Detector posted wrong status", 1f, detected[0]); detector.onFaceDetection(new Face[0], null); assertEquals("ICS Face Detector did not post status change", 0f, detected[0]); } @Device public void testOnFaceDetectedListener() { IcsFaceDetector detector = new IcsFaceDetector(); final int[] detectedFaces = new int[4]; SensorCustomEventListener detectionListener = new SensorCustomEventListener() { public void onCustomSensorChanged(SensorCustomEvent event) { detectedFaces[COUNTER_INDEX]++; int icsValue = (int) event.values[0]; float intFloatDifference = event.values[0] - icsValue; assertEquals("Face detection values should be integer", intFloatDifference, 0f); switch (event.sensor) { case FACE_X_POSITION: detectedFaces[X_POSITION_INDEX] = icsValue; break; case FACE_Y_POSITION: detectedFaces[Y_POSITION_INDEX] = icsValue; break; case FACE_SIZE: detectedFaces[SIZE_INDEX] = icsValue; break; default: fail("Unexpected Sensor on Ics Face Detection event. Expected face size or position." + event.sensor); } } }; detector.addOnFaceDetectedListener(detectionListener); assertEquals("Face Detection Listener receives unexpected calls", 0, detectedFaces[COUNTER_INDEX]); Rect faceBounds = new Rect(FACE_LEFT, FACE_TOP, FACE_RIGHT, FACE_BOTTOM); Face[] faces = new Face[2]; faces[0] = new Face(); faces[0].rect = new Rect(0, 0, LOW_SCORE_FACE_WIDTH, 0); faces[0].score = 60; faces[1] = new Face(); faces[1].rect = faceBounds; faces[1].score = 80; detector.onFaceDetection(faces, null); assertEquals("Face Detection Listener does not receive calls", 3, detectedFaces[COUNTER_INDEX]); int lowScoreFaceSize = LOW_SCORE_FACE_WIDTH * 100 * 2 / FACE_RECT_SIZE; if (detectedFaces[SIZE_INDEX] == lowScoreFaceSize) { fail("Wrong face used for face detection"); } int expectedSize = (FACE_RIGHT - FACE_LEFT) * 100 * 2 / FACE_RECT_SIZE; assertEquals("Unexpected size of face", expectedSize, detectedFaces[SIZE_INDEX]); int expectedXPosition = Math.abs((FACE_TOP + (FACE_BOTTOM - FACE_TOP) / 2) * ScreenValues.SCREEN_WIDTH / FACE_RECT_SIZE); assertEquals("Unexpected x position of face", expectedXPosition, Math.abs(detectedFaces[X_POSITION_INDEX])); int expectedYPosition = Math.abs((FACE_LEFT + (FACE_RIGHT - FACE_LEFT) / 2) * ScreenValues.SCREEN_HEIGHT / FACE_RECT_SIZE); assertEquals("Unexpected y position of face", expectedYPosition, Math.abs(detectedFaces[Y_POSITION_INDEX])); detector.onFaceDetection(faces, null); assertTrue("Face Detection Listener reveices too many calls", detectedFaces[COUNTER_INDEX] <= 6); assertEquals("Face Detection Listener does not receive calls", 6, detectedFaces[COUNTER_INDEX]); detector.removeOnFaceDetectedListener(detectionListener); } @Device public void testFaceSizeBounds() { IcsFaceDetector detector = new IcsFaceDetector(); final float[] faceSize = new float[1]; SensorCustomEventListener detectionListener = new SensorCustomEventListener() { public void onCustomSensorChanged(SensorCustomEvent event) { if (event.sensor == Sensors.FACE_SIZE) { faceSize[0] = event.values[0]; } } }; detector.addOnFaceDetectedListener(detectionListener); Rect faceBounds = new Rect(FACE_LEFT, FACE_TOP, FACE_RIGHT, FACE_BOTTOM); Face[] faces = new Face[1]; faces[0] = new Face(); faces[0].rect = faceBounds; detector.onFaceDetection(faces, null); assertTrue("Face size must not be negative", faceSize[0] >= 0); assertTrue("Illegal face size, range is [0,100]", faceSize[0] <= 100); Random random = new Random(); int left = random.nextInt(FACE_RECT_SIZE - 1); int top = random.nextInt(FACE_RECT_SIZE - 1); int right = left + random.nextInt(FACE_RECT_SIZE - left); int bottom = top + random.nextInt(FACE_RECT_SIZE - top); faceBounds = new Rect(left - FACE_RECT_SIZE / 2, top - FACE_RECT_SIZE / 2, right - FACE_RECT_SIZE / 2, bottom - FACE_RECT_SIZE / 2); faces[0].rect = faceBounds; detector.onFaceDetection(faces, null); assertTrue("Face size must not be negative", faceSize[0] >= 0); assertTrue("Illegal face size, range is [0,100]", faceSize[0] <= 100); detector.removeOnFaceDetectedListener(detectionListener); } }