xref: /aosp_15_r20/cts/apps/CameraITS/tests/scene2_d/test_autoframing.py (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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