1# Copyright 2023 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Verifies that autoframing can adjust FoV to include all faces.""" 15 16import logging 17import os.path 18 19from mobly import test_runner 20 21import its_base_test 22import camera_properties_utils 23import capture_request_utils 24import image_processing_utils 25import its_session_utils 26import opencv_processing_utils 27 28_AUTOFRAMING_CONVERGED = 2 29_CV2_FACE_SCALE_FACTOR = 1.05 # 5% step for resizing image to find face 30_CV2_FACE_MIN_NEIGHBORS = 4 # recommended 3-6: higher for less faces 31_NAME = os.path.splitext(os.path.basename(__file__))[0] 32_NUM_TEST_FRAMES = 150 33_NUM_FACES = 3 34_W, _H = 640, 480 35 36 37class AutoframingTest(its_base_test.ItsBaseTest): 38 """Test autoframing for faces with different skin tones. 39 """ 40 41 def save_image(self, cap, props, faces): 42 img, img_name, faces_cropped = self.get_image_data(cap, props, faces) 43 # Save images with green boxes around faces 44 opencv_processing_utils.draw_green_boxes_around_faces( 45 img, faces_cropped, img_name) 46 47 def assert_no_face_distortion(self, cap, props, faces): 48 img, img_name, faces_cropped = self.get_image_data(cap, props, faces) 49 opencv_faces = opencv_processing_utils.find_opencv_faces( 50 img, _CV2_FACE_SCALE_FACTOR, _CV2_FACE_MIN_NEIGHBORS) 51 opencv_processing_utils.match_face_locations( 52 faces_cropped, opencv_faces, img, img_name) 53 54 def get_image_data(self, cap, props, faces): 55 img = image_processing_utils.convert_capture_to_rgb_image( 56 cap, props=props) 57 img_name = os.path.join(self.log_path, _NAME) + '.jpg' 58 59 crop_region = cap['metadata']['android.scaler.cropRegion'] 60 faces_cropped = opencv_processing_utils.correct_faces_for_crop( 61 faces, img, crop_region) 62 63 return img, img_name, faces_cropped 64 65 def test_autoframing(self): 66 """Test if fov gets adjusted to accommodate all the faces in the frame. 67 68 Do a large zoom on scene2_a using do_3a so that none of that faces are 69 visible initially, trigger autoframing, wait for the state to converge and 70 make sure all the faces are found. 71 """ 72 with its_session_utils.ItsSession( 73 device_id=self.dut.serial, 74 camera_id=self.camera_id, 75 hidden_physical_id=self.hidden_physical_id) as cam: 76 props = cam.get_camera_properties() 77 props = cam.override_with_hidden_physical_camera_props(props) 78 79 # Load chart for scene 80 its_session_utils.load_scene( 81 cam, props, self.scene, self.tablet, self.chart_distance, 82 log_path=self.log_path) 83 84 # Check SKIP conditions 85 # Don't run autoframing if face detection or autoframing is not supported 86 camera_properties_utils.skip_unless( 87 camera_properties_utils.face_detect(props) and 88 camera_properties_utils.autoframing(props)) 89 90 # Do max-ish zoom with the help of do_3a, keeping all the 'A's off. This 91 # zooms into the scene so that none of the faces are in the view 92 # initially - which gives room for autoframing to take place. 93 max_zoom_ratio = camera_properties_utils.get_max_digital_zoom(props) 94 cam.do_3a(zoom_ratio=max_zoom_ratio) 95 96 req = capture_request_utils.auto_capture_request( 97 do_autoframing=True, zoom_ratio=max_zoom_ratio) 98 req['android.statistics.faceDetectMode'] = 1 # Simple 99 fmt = {'format': 'yuv', 'width': _W, 'height': _H} 100 caps = cam.do_capture([req]*_NUM_TEST_FRAMES, fmt) 101 for i, cap in enumerate(caps): 102 faces = cap['metadata']['android.statistics.faces'] 103 autoframing_state = cap['metadata']['android.control.autoframingState'] 104 logging.debug('Frame %d faces: %d, autoframingState: %d', i, len(faces), 105 autoframing_state) 106 107 # Face detection and autoframing could take several frames to warm up, 108 # but should detect the correct number of faces before the last frame 109 if autoframing_state == _AUTOFRAMING_CONVERGED: 110 control_zoom_ratio = cap['metadata']['android.control.zoomRatio'] 111 logging.debug('Control zoom ratio: %d', control_zoom_ratio) 112 # Save image when autoframing state converges 113 self.save_image(cap, props, faces) 114 num_faces_found = len(faces) 115 if num_faces_found != _NUM_FACES: 116 raise AssertionError('Wrong num of faces found! Found: ' 117 f'{num_faces_found}, expected: {_NUM_FACES}') 118 119 # Also check the faces with open cv to make sure the scene is not 120 # distorted or anything. 121 self.assert_no_face_distortion(cap, props, faces) 122 break 123 124 # Autoframing didn't converge till the last frame 125 elif i == _NUM_TEST_FRAMES - 1: 126 # Save image (for debugging) when autoframing state hasn't converged 127 # by the last frame 128 self.save_image(cap, props, faces) 129 raise AssertionError('Autoframing failed to converge') 130 131 logging.debug('Faces: %s', str(faces)) 132 133 134if __name__ == '__main__': 135 test_runner.main() 136