1# Copyright 2020 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 15import multiprocessing 16import os.path 17import subprocess 18import sys 19import time 20import its.caps 21import its.device 22import its.image 23import its.objects 24 25BRIGHT_CHANGE_TOL = 0.2 26CONTINUOUS_PICTURE_MODE = 4 27CONVERGED_3A = [2, 2, 2] # [AE, AF, AWB] 28# AE_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED, 29# 4: FLASH_REQ, 5: PRECAPTURE} 30# AF_STATES: {0: INACTIVE, 1: PASSIVE_SCAN, 2: PASSIVE_FOCUSED, 31# 3: ACTIVE_SCAN, 4: FOCUS_LOCKED, 5: NOT_FOCUSED_LOCKED, 32# 6: PASSIVE_UNFOCUSED} 33# AWB_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED} 34DELAY_CAPTURE = 1.5 # delay in first capture to sync events (sec) 35DELAY_DISPLAY = 3.0 # time when display turns OFF (sec) 36FPS = 30 37FRAME_SHIFT = 5.0 # number of frames to shift to try and find scene change 38NAME = os.path.basename(__file__).split('.')[0] 39NUM_BURSTS = 6 40NUM_FRAMES = 50 41W, H = 640, 480 42 43 44def get_cmd_line_args(): 45 chart_host_id = None 46 for s in list(sys.argv[1:]): 47 if s[:6] == 'chart=' and len(s) > 6: 48 chart_host_id = s[6:] 49 return chart_host_id 50 51 52def mask_3a_settling_frames(cap_data): 53 converged_frame = -1 54 for i, cap in enumerate(cap_data): 55 if cap['3a_state'] == CONVERGED_3A: 56 converged_frame = i 57 break 58 print 'Frames index where 3A converges: %d' % converged_frame 59 return converged_frame 60 61 62def determine_if_scene_changed(cap_data, converged_frame): 63 scene_changed = False 64 bright_changed = False 65 start_frame_brightness = cap_data[0]['avg'] 66 for i in range(converged_frame, len(cap_data)): 67 if cap_data[i]['avg'] <= ( 68 start_frame_brightness * (1.0 - BRIGHT_CHANGE_TOL)): 69 bright_changed = True 70 if cap_data[i]['flag'] == 1: 71 scene_changed = True 72 return scene_changed, bright_changed 73 74 75def toggle_screen(chart_host_id, state, delay): 76 t0 = time.time() 77 screen_id_arg = ('screen=%s' % chart_host_id) 78 state_id_arg = 'state=%s' % state 79 delay_arg = 'delay=%.3f' % delay 80 cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools', 81 'toggle_screen.py'), screen_id_arg, 82 state_id_arg, delay_arg] 83 screen_cmd_code = subprocess.call(cmd) 84 assert screen_cmd_code == 0 85 t = time.time() - t0 86 print 'tablet event %s: %.3f' % (state, t) 87 88 89def capture_frames(cam, delay, burst): 90 """Capture frames.""" 91 cap_data_list = [] 92 req = its.objects.auto_capture_request() 93 req['android.control.afMode'] = CONTINUOUS_PICTURE_MODE 94 fmt = {'format': 'yuv', 'width': W, 'height': H} 95 t0 = time.time() 96 time.sleep(delay) 97 print 'cap event start:', time.time() - t0 98 caps = cam.do_capture([req]*NUM_FRAMES, fmt) 99 print 'cap event stop:', time.time() - t0 100 # extract frame metadata and frame 101 for i, cap in enumerate(caps): 102 cap_data = {} 103 md = cap['metadata'] 104 exp = md['android.sensor.exposureTime'] 105 iso = md['android.sensor.sensitivity'] 106 fd = md['android.lens.focalLength'] 107 ae_state = md['android.control.aeState'] 108 af_state = md['android.control.afState'] 109 awb_state = md['android.control.awbState'] 110 fd_str = 'infinity' 111 if fd != 0.0: 112 fd_str = str(round(1.0E2/fd, 2)) + 'cm' 113 scene_change_flag = md['android.control.afSceneChange'] 114 assert scene_change_flag in [0, 1], 'afSceneChange not in [0,1]' 115 img = its.image.convert_capture_to_rgb_image(cap) 116 its.image.write_image(img, '%s_%d_%d.jpg' % (NAME, burst, i)) 117 tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1) 118 g = its.image.compute_image_means(tile)[1] 119 print '%d, iso: %d, exp: %.2fms, fd: %s, avg: %.3f' % ( 120 i, iso, exp*1E-6, fd_str, g), 121 print '[ae,af,awb]: [%d,%d,%d], change: %d' % ( 122 ae_state, af_state, awb_state, scene_change_flag) 123 cap_data['exp'] = exp 124 cap_data['iso'] = iso 125 cap_data['fd'] = fd 126 cap_data['3a_state'] = [ae_state, af_state, awb_state] 127 cap_data['avg'] = g 128 cap_data['flag'] = scene_change_flag 129 cap_data_list.append(cap_data) 130 return cap_data_list 131 132 133def main(): 134 """Test scene change. 135 136 Do auto capture with face scene. Power down tablet and recapture. 137 Confirm android.control.afSceneChangeDetected is True. 138 """ 139 # check for skip conditions and do 3a up front 140 with its.device.ItsSession() as cam: 141 props = cam.get_camera_properties() 142 props = cam.override_with_hidden_physical_camera_props(props) 143 its.caps.skip_unless(its.caps.continuous_picture(props) and 144 its.caps.af_scene_change(props) and 145 its.caps.read_3a(props)) 146 cam.do_3a() 147 148 # do captures with scene change 149 chart_host_id = get_cmd_line_args() 150 scene_delay = DELAY_DISPLAY 151 for burst in range(NUM_BURSTS): 152 print 'burst number: %d' % burst 153 # create scene change by turning off chart display & capture frames 154 if chart_host_id: 155 print '\nToggling tablet. Scene change at %.3fs.' % scene_delay 156 multiprocessing.Process(name='p1', target=toggle_screen, 157 args=(chart_host_id, 'OFF', 158 scene_delay,)).start() 159 else: 160 print '\nWave hand in front of camera to create scene change.' 161 cap_data = capture_frames(cam, DELAY_CAPTURE, burst) 162 163 # find frame where 3A converges 164 converged_frame = mask_3a_settling_frames(cap_data) 165 166 # turn tablet back on to return to baseline scene state 167 if chart_host_id: 168 toggle_screen(chart_host_id, 'ON', 0) 169 170 # determine if brightness changed and/or scene change flag asserted 171 scene_changed, bright_changed = determine_if_scene_changed( 172 cap_data, converged_frame) 173 174 # handle different capture cases 175 if converged_frame > -1: # 3A converges 176 if scene_changed: 177 if bright_changed: 178 print ' scene & brightness change on burst %d.' % burst 179 sys.exit(0) 180 else: 181 msg = ' scene change, but no brightness change.' 182 assert False, msg 183 else: # shift scene change timing if no scene change 184 scene_shift = FRAME_SHIFT / FPS 185 if bright_changed: 186 print ' No scene change, but brightness change.' 187 print 'Shift %.3fs earlier' % scene_shift 188 scene_delay -= scene_shift # tablet-off earlier 189 else: 190 scene_shift = FRAME_SHIFT / FPS * NUM_BURSTS 191 print ' No scene change, no brightness change.' 192 if cap_data[NUM_FRAMES-1]['avg'] < 0.2: 193 print ' Scene dark entire capture.', 194 print 'Shift %.3fs later.' % scene_shift 195 scene_delay += scene_shift # tablet-off later 196 else: 197 print ' Scene light entire capture.', 198 print 'Shift %.3fs earlier.' % scene_shift 199 scene_delay -= scene_shift # tablet-off earlier 200 201 else: # 3A does not converge 202 if bright_changed: 203 scene_shift = FRAME_SHIFT / FPS 204 print ' 3A does not converge, but brightness change.', 205 print 'Shift %.3fs later' % scene_shift 206 scene_delay += scene_shift # tablet-off earlier 207 else: 208 msg = ' 3A does not converge with no brightness change.' 209 assert False, msg 210 211 # fail out if too many tries 212 msg = 'No scene change in %dx tries' % NUM_BURSTS 213 assert False, msg 214 215 216if __name__ == '__main__': 217 main() 218