1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18 package android.hardware.camera2.params; 19 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.graphics.Point; 24 import android.graphics.Rect; 25 import android.hardware.camera2.CameraCharacteristics; 26 import android.hardware.camera2.CameraMetadata; 27 import android.hardware.camera2.CaptureResult; 28 29 /** 30 * Describes a face detected in an image. 31 */ 32 public final class Face { 33 34 /** 35 * The ID is {@code -1} when the optional set of fields is unsupported. 36 * 37 * @see #getId() 38 */ 39 public static final int ID_UNSUPPORTED = -1; 40 41 /** 42 * The minimum possible value for the confidence level. 43 * 44 * @see #getScore() 45 */ 46 public static final int SCORE_MIN = 1; 47 48 /** 49 * The maximum possible value for the confidence level. 50 * 51 * @see #getScore() 52 */ 53 public static final int SCORE_MAX = 100; 54 55 private Rect mBounds; 56 private int mScore; 57 private int mId; 58 private Point mLeftEye; 59 private Point mRightEye; 60 private Point mMouth; 61 62 /** 63 * Create a new face with all fields set. 64 * 65 * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional. 66 * They are only required when the {@link CaptureResult} reports that the value of key 67 * {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} is 68 * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_FULL}. 69 * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and 70 * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, 71 * rightEyePosition, and mouthPosition may be independently null or not-null.</p> 72 * 73 * @param bounds Bounds of the face. 74 * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}. 75 * @param id A unique ID per face visible to the tracker. 76 * @param leftEyePosition The position of the left eye. 77 * @param rightEyePosition The position of the right eye. 78 * @param mouthPosition The position of the mouth. 79 * 80 * @throws IllegalArgumentException 81 * if bounds is {@code null}, 82 * or if the confidence is not in the range of 83 * {@value #SCORE_MIN}-{@value #SCORE_MAX}, 84 * or if id is {@value #ID_UNSUPPORTED} and 85 * leftEyePosition/rightEyePosition/mouthPosition aren't all null, 86 * or else if id is negative. 87 * 88 * @hide 89 */ Face(@onNull Rect bounds, int score, int id, @NonNull Point leftEyePosition, @NonNull Point rightEyePosition, @NonNull Point mouthPosition)90 public Face(@NonNull Rect bounds, int score, int id, 91 @NonNull Point leftEyePosition, @NonNull Point rightEyePosition, 92 @NonNull Point mouthPosition) { 93 init(bounds, score, id, leftEyePosition, rightEyePosition, mouthPosition); 94 } 95 96 /** 97 * Create a new face without the optional fields. 98 * 99 * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional. 100 * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and 101 * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, 102 * rightEyePosition, and mouthPosition may be independently null or not-null. When devices 103 * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as 104 * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult}, 105 * the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition, 106 * rightEyePosition, and mouthPositions are expected to be {@code null} for each face.</p> 107 * 108 * @param bounds Bounds of the face. 109 * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}. 110 * 111 * @throws IllegalArgumentException 112 * if bounds is {@code null}, 113 * or if the confidence is not in the range of 114 * {@value #SCORE_MIN}-{@value #SCORE_MAX}. 115 * 116 * @hide 117 */ Face(@onNull Rect bounds, int score)118 public Face(@NonNull Rect bounds, int score) { 119 init(bounds, score, ID_UNSUPPORTED, 120 /*leftEyePosition*/null, /*rightEyePosition*/null, /*mouthPosition*/null); 121 } 122 123 /** 124 * Initialize the object (shared by constructors). 125 */ init(@onNull Rect bounds, int score, int id, @Nullable Point leftEyePosition, @Nullable Point rightEyePosition, @Nullable Point mouthPosition)126 private void init(@NonNull Rect bounds, int score, int id, 127 @Nullable Point leftEyePosition, @Nullable Point rightEyePosition, 128 @Nullable Point mouthPosition) { 129 checkNotNull("bounds", bounds); 130 checkScore(score); 131 checkId(id); 132 if (id == ID_UNSUPPORTED) { 133 checkNull("leftEyePosition", leftEyePosition); 134 checkNull("rightEyePosition", rightEyePosition); 135 checkNull("mouthPosition", mouthPosition); 136 } 137 checkFace(leftEyePosition, rightEyePosition, mouthPosition); 138 139 mBounds = bounds; 140 mScore = score; 141 mId = id; 142 mLeftEye = leftEyePosition; 143 mRightEye = rightEyePosition; 144 mMouth = mouthPosition; 145 } 146 147 /** 148 * Bounds of the face. 149 * 150 * <p>A rectangle relative to the sensor's 151 * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0) 152 * representing the top-left corner of the active array rectangle.</p> 153 * 154 * <p>There is no constraints on the Rectangle value other than it 155 * is not-{@code null}.</p> 156 */ getBounds()157 public Rect getBounds() { 158 return mBounds; 159 } 160 161 /** 162 * The confidence level for the detection of the face. 163 * 164 * <p>The range is {@value #SCORE_MIN} to {@value #SCORE_MAX}. 165 * {@value #SCORE_MAX} is the highest confidence.</p> 166 * 167 * <p>Depending on the device, even very low-confidence faces may be 168 * listed, so applications should filter out faces with low confidence, 169 * depending on the use case. For a typical point-and-shoot camera 170 * application that wishes to display rectangles around detected faces, 171 * filtering out faces with confidence less than half of {@value #SCORE_MAX} 172 * is recommended.</p> 173 * 174 * @see #SCORE_MAX 175 * @see #SCORE_MIN 176 */ 177 @IntRange(from = SCORE_MIN, to = SCORE_MAX) getScore()178 public int getScore() { 179 return mScore; 180 } 181 182 /** 183 * An unique id per face while the face is visible to the tracker. 184 * 185 * <p> 186 * If the face leaves the field-of-view and comes back, it will get a new 187 * id.</p> 188 * 189 * <p>This is an optional field and may not be supported on all devices. 190 * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and 191 * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, 192 * rightEyePosition, and mouthPosition may be independently null or not-null. When devices 193 * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as 194 * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult}, 195 * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p> 196 * 197 * <p>This value will either be {@value #ID_UNSUPPORTED} or 198 * otherwise greater than {@code 0}.</p> 199 * 200 * @see #ID_UNSUPPORTED 201 */ getId()202 public int getId() { 203 return mId; 204 } 205 206 /** 207 * The coordinates of the center of the left eye. 208 * 209 * <p>The coordinates are in 210 * the same space as the ones for {@link #getBounds}. This is an 211 * optional field and may not be supported on all devices. If not 212 * supported, the value will always be set to null. 213 * This value will always be null only if {@link #getId()} returns 214 * {@value #ID_UNSUPPORTED}.</p> 215 * 216 * @return The left eye position, or {@code null} if unknown. 217 */ getLeftEyePosition()218 public Point getLeftEyePosition() { 219 return mLeftEye; 220 } 221 222 /** 223 * The coordinates of the center of the right eye. 224 * 225 * <p>The coordinates are 226 * in the same space as the ones for {@link #getBounds}.This is an 227 * optional field and may not be supported on all devices. If not 228 * supported, the value will always be set to null. 229 * This value will always be null only if {@link #getId()} returns 230 * {@value #ID_UNSUPPORTED}.</p> 231 * 232 * @return The right eye position, or {@code null} if unknown. 233 */ getRightEyePosition()234 public Point getRightEyePosition() { 235 return mRightEye; 236 } 237 238 /** 239 * The coordinates of the center of the mouth. 240 * 241 * <p>The coordinates are in 242 * the same space as the ones for {@link #getBounds}. This is an optional 243 * field and may not be supported on all devices. If not 244 * supported, the value will always be set to null. 245 * This value will always be null only if {@link #getId()} returns 246 * {@value #ID_UNSUPPORTED}.</p> 247 * </p> 248 * 249 * @return The mouth position, or {@code null} if unknown. 250 */ getMouthPosition()251 public Point getMouthPosition() { 252 return mMouth; 253 } 254 255 /** 256 * Represent the Face as a string for debugging purposes. 257 */ 258 @Override toString()259 public String toString() { 260 return String.format("{ bounds: %s, score: %s, id: %d, " + 261 "leftEyePosition: %s, rightEyePosition: %s, mouthPosition: %s }", 262 mBounds, mScore, mId, mLeftEye, mRightEye, mMouth); 263 } 264 checkNotNull(String name, Object obj)265 private static void checkNotNull(String name, Object obj) { 266 if (obj == null) { 267 throw new IllegalArgumentException(name + " was required, but it was null"); 268 } 269 } 270 checkNull(String name, Object obj)271 private static void checkNull(String name, Object obj) { 272 if (obj != null) { 273 throw new IllegalArgumentException(name + " was required to be null, but it wasn't"); 274 } 275 } 276 checkScore(int score)277 private static void checkScore(int score) { 278 if (score < SCORE_MIN || score > SCORE_MAX) { 279 throw new IllegalArgumentException("Confidence out of range"); 280 } 281 } 282 checkId(int id)283 private static void checkId(int id) { 284 if (id < 0 && id != ID_UNSUPPORTED) { 285 throw new IllegalArgumentException("Id out of range"); 286 } 287 } 288 checkFace(@ullable Point leftEyePosition, @Nullable Point rightEyePosition, @Nullable Point mouthPosition)289 private static void checkFace(@Nullable Point leftEyePosition, 290 @Nullable Point rightEyePosition, @Nullable Point mouthPosition) { 291 if (leftEyePosition != null || rightEyePosition != null || mouthPosition != null) { 292 if (leftEyePosition == null || rightEyePosition == null || mouthPosition == null) { 293 throw new IllegalArgumentException("If any of leftEyePosition, rightEyePosition, " 294 + "or mouthPosition are non-null, all three must be non-null."); 295 } 296 } 297 } 298 299 /** 300 * Builds a Face object. 301 * 302 * <p>This builder is public to allow for easier application testing by 303 * creating custom object instances. It's not necessary to construct these 304 * objects during normal use of the camera API.</p> 305 */ 306 public static final class Builder { 307 private long mBuilderFieldsSet = 0L; 308 309 private static final long FIELD_BOUNDS = 1 << 1; 310 private static final long FIELD_SCORE = 1 << 2; 311 private static final long FIELD_ID = 1 << 3; 312 private static final long FIELD_LEFT_EYE = 1 << 4; 313 private static final long FIELD_RIGHT_EYE = 1 << 5; 314 private static final long FIELD_MOUTH = 1 << 6; 315 private static final long FIELD_BUILT = 1 << 0; 316 317 private static final String FIELD_NAME_BOUNDS = "bounds"; 318 private static final String FIELD_NAME_SCORE = "score"; 319 private static final String FIELD_NAME_LEFT_EYE = "left eye"; 320 private static final String FIELD_NAME_RIGHT_EYE = "right eye"; 321 private static final String FIELD_NAME_MOUTH = "mouth"; 322 323 private Rect mBounds = null; 324 private int mScore = 0; 325 private int mId = ID_UNSUPPORTED; 326 private Point mLeftEye = null; 327 private Point mRightEye = null; 328 private Point mMouth = null; 329 Builder()330 public Builder() { 331 // Empty 332 } 333 Builder(@onNull Face current)334 public Builder(@NonNull Face current) { 335 mBounds = current.mBounds; 336 mScore = current.mScore; 337 mId = current.mId; 338 mLeftEye = current.mLeftEye; 339 mRightEye = current.mRightEye; 340 mMouth = current.mMouth; 341 } 342 343 /** 344 * Bounds of the face. 345 * 346 * <p>A rectangle relative to the sensor's 347 * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0) 348 * representing the top-left corner of the active array rectangle.</p> 349 * 350 * <p>There is no constraints on the Rectangle value other than it 351 * is not-{@code null}.</p> 352 * 353 * @param bounds Bounds of the face. 354 * @return This builder. 355 */ setBounds(@onNull Rect bounds)356 public @NonNull Builder setBounds(@NonNull Rect bounds) { 357 checkNotUsed(); 358 mBuilderFieldsSet |= FIELD_BOUNDS; 359 mBounds = bounds; 360 return this; 361 } 362 363 /** 364 * The confidence level for the detection of the face. 365 * 366 * <p>The range is {@value #SCORE_MIN} to {@value #SCORE_MAX}. 367 * {@value #SCORE_MAX} is the highest confidence.</p> 368 * 369 * <p>Depending on the device, even very low-confidence faces may be 370 * listed, so applications should filter out faces with low confidence, 371 * depending on the use case. For a typical point-and-shoot camera 372 * application that wishes to display rectangles around detected faces, 373 * filtering out faces with confidence less than half of {@value #SCORE_MAX} 374 * is recommended.</p> 375 * 376 * @see #SCORE_MAX 377 * @see #SCORE_MIN 378 * 379 * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}. 380 * @return This builder. 381 */ setScore(@ntRangefrom = SCORE_MIN, to = SCORE_MAX) int score)382 public @NonNull Builder setScore(@IntRange(from = SCORE_MIN, to = SCORE_MAX) int score) { 383 checkNotUsed(); 384 checkScore(score); 385 mBuilderFieldsSet |= FIELD_SCORE; 386 mScore = score; 387 return this; 388 } 389 390 /** 391 * An unique id per face while the face is visible to the tracker. 392 * 393 * <p> 394 * If the face leaves the field-of-view and comes back, it will get a new 395 * id.</p> 396 * 397 * <p>This is an optional field and may not be supported on all devices. 398 * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and 399 * mouthPositions should be {@code null}. Otherwise, each of leftEyePosition, 400 * rightEyePosition, and mouthPosition may be independently null or not-null. When devices 401 * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as 402 * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult}, 403 * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p> 404 * 405 * <p>This value should either be {@value #ID_UNSUPPORTED} or 406 * otherwise greater than {@code 0}.</p> 407 * 408 * @see #ID_UNSUPPORTED 409 * 410 * @param id A unique ID per face visible to the tracker. 411 * @return This builder. 412 */ setId(int id)413 public @NonNull Builder setId(int id) { 414 checkNotUsed(); 415 checkId(id); 416 mBuilderFieldsSet |= FIELD_ID; 417 mId = id; 418 return this; 419 } 420 421 /** 422 * The coordinates of the center of the left eye. 423 * 424 * <p>The coordinates should be 425 * in the same space as the ones for {@link #setBounds}. This is an 426 * optional field and may not be supported on all devices. If not 427 * supported, the value should always be unset or set to null. 428 * This value should always be null if {@link #setId} is called with 429 * {@value #ID_UNSUPPORTED}.</p> 430 * 431 * @param leftEyePosition The position of the left eye. 432 * @return This builder. 433 */ setLeftEyePosition(@onNull Point leftEyePosition)434 public @NonNull Builder setLeftEyePosition(@NonNull Point leftEyePosition) { 435 checkNotUsed(); 436 mBuilderFieldsSet |= FIELD_LEFT_EYE; 437 mLeftEye = leftEyePosition; 438 return this; 439 } 440 441 /** 442 * The coordinates of the center of the right eye. 443 * 444 * <p>The coordinates should be 445 * in the same space as the ones for {@link #setBounds}.This is an 446 * optional field and may not be supported on all devices. If not 447 * supported, the value should always be set to null. 448 * This value should always be null if {@link #setId} is called with 449 * {@value #ID_UNSUPPORTED}.</p> 450 * 451 * @param rightEyePosition The position of the right eye. 452 * @return This builder. 453 */ setRightEyePosition(@onNull Point rightEyePosition)454 public @NonNull Builder setRightEyePosition(@NonNull Point rightEyePosition) { 455 checkNotUsed(); 456 mBuilderFieldsSet |= FIELD_RIGHT_EYE; 457 mRightEye = rightEyePosition; 458 return this; 459 } 460 461 /** 462 * The coordinates of the center of the mouth. 463 * 464 * <p>The coordinates should be in 465 * the same space as the ones for {@link #setBounds}. This is an optional 466 * field and may not be supported on all devices. If not 467 * supported, the value should always be set to null. 468 * This value should always be null if {@link #setId} is called with 469 * {@value #ID_UNSUPPORTED}.</p> 470 * </p> 471 * 472 * @param mouthPosition The position of the mouth. 473 * @return This builder. 474 */ setMouthPosition(@onNull Point mouthPosition)475 public @NonNull Builder setMouthPosition(@NonNull Point mouthPosition) { 476 checkNotUsed(); 477 mBuilderFieldsSet |= FIELD_MOUTH; 478 mMouth = mouthPosition; 479 return this; 480 } 481 482 /** 483 * Returns an instance of <code>Face</code> created from the fields set 484 * on this builder. 485 * 486 * @return A Face. 487 */ build()488 public @NonNull Face build() { 489 checkNotUsed(); 490 checkFieldSet(FIELD_BOUNDS, FIELD_NAME_BOUNDS); 491 checkFieldSet(FIELD_SCORE, FIELD_NAME_SCORE); 492 if (mId == ID_UNSUPPORTED) { 493 checkIdUnsupportedThenNull(mLeftEye, FIELD_NAME_LEFT_EYE); 494 checkIdUnsupportedThenNull(mRightEye, FIELD_NAME_RIGHT_EYE); 495 checkIdUnsupportedThenNull(mMouth, FIELD_NAME_MOUTH); 496 } 497 checkFace(mLeftEye, mRightEye, mMouth); 498 499 mBuilderFieldsSet |= FIELD_BUILT; 500 501 return new Face(mBounds, mScore, mId, mLeftEye, mRightEye, mMouth); 502 } 503 checkNotUsed()504 private void checkNotUsed() { 505 if ((mBuilderFieldsSet & FIELD_BUILT) != 0) { 506 throw new IllegalStateException( 507 "This Builder should not be reused. Use a new Builder instance instead"); 508 } 509 } 510 checkFieldSet(long field, String fieldName)511 private void checkFieldSet(long field, String fieldName) { 512 if ((mBuilderFieldsSet & field) == 0) { 513 throw new IllegalStateException( 514 "Field \"" + fieldName + "\" must be set before building."); 515 } 516 } 517 checkIdUnsupportedThenNull(Object obj, String fieldName)518 private void checkIdUnsupportedThenNull(Object obj, String fieldName) { 519 if (obj != null) { 520 throw new IllegalArgumentException("Field \"" + fieldName 521 + "\" must be unset or null if id is ID_UNSUPPORTED."); 522 } 523 } 524 } 525 } 526