| # Copyright 2020 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import multiprocessing |
| import os.path |
| import subprocess |
| import sys |
| import time |
| import its.caps |
| import its.device |
| import its.image |
| import its.objects |
| |
| BRIGHT_CHANGE_TOL = 0.2 |
| CONTINUOUS_PICTURE_MODE = 4 |
| CONVERGED_3A = [2, 2, 2] # [AE, AF, AWB] |
| # AE_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED, |
| # 4: FLASH_REQ, 5: PRECAPTURE} |
| # AF_STATES: {0: INACTIVE, 1: PASSIVE_SCAN, 2: PASSIVE_FOCUSED, |
| # 3: ACTIVE_SCAN, 4: FOCUS_LOCKED, 5: NOT_FOCUSED_LOCKED, |
| # 6: PASSIVE_UNFOCUSED} |
| # AWB_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED} |
| DELAY_CAPTURE = 1.5 # delay in first capture to sync events (sec) |
| DELAY_DISPLAY = 3.0 # time when display turns OFF (sec) |
| FPS = 30 |
| FRAME_SHIFT = 5.0 # number of frames to shift to try and find scene change |
| NAME = os.path.basename(__file__).split('.')[0] |
| NUM_BURSTS = 6 |
| NUM_FRAMES = 50 |
| W, H = 640, 480 |
| |
| |
| def get_cmd_line_args(): |
| chart_host_id = None |
| for s in list(sys.argv[1:]): |
| if s[:6] == 'chart=' and len(s) > 6: |
| chart_host_id = s[6:] |
| return chart_host_id |
| |
| |
| def mask_3a_settling_frames(cap_data): |
| converged_frame = -1 |
| for i, cap in enumerate(cap_data): |
| if cap['3a_state'] == CONVERGED_3A: |
| converged_frame = i |
| break |
| print 'Frames index where 3A converges: %d' % converged_frame |
| return converged_frame |
| |
| |
| def determine_if_scene_changed(cap_data, converged_frame): |
| scene_changed = False |
| bright_changed = False |
| start_frame_brightness = cap_data[0]['avg'] |
| for i in range(converged_frame, len(cap_data)): |
| if cap_data[i]['avg'] <= ( |
| start_frame_brightness * (1.0 - BRIGHT_CHANGE_TOL)): |
| bright_changed = True |
| if cap_data[i]['flag'] == 1: |
| scene_changed = True |
| return scene_changed, bright_changed |
| |
| |
| def toggle_screen(chart_host_id, state, delay): |
| t0 = time.time() |
| screen_id_arg = ('screen=%s' % chart_host_id) |
| state_id_arg = 'state=%s' % state |
| delay_arg = 'delay=%.3f' % delay |
| cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools', |
| 'toggle_screen.py'), screen_id_arg, |
| state_id_arg, delay_arg] |
| screen_cmd_code = subprocess.call(cmd) |
| assert screen_cmd_code == 0 |
| t = time.time() - t0 |
| print 'tablet event %s: %.3f' % (state, t) |
| |
| |
| def capture_frames(cam, delay, burst): |
| """Capture frames.""" |
| cap_data_list = [] |
| req = its.objects.auto_capture_request() |
| req['android.control.afMode'] = CONTINUOUS_PICTURE_MODE |
| fmt = {'format': 'yuv', 'width': W, 'height': H} |
| t0 = time.time() |
| time.sleep(delay) |
| print 'cap event start:', time.time() - t0 |
| caps = cam.do_capture([req]*NUM_FRAMES, fmt) |
| print 'cap event stop:', time.time() - t0 |
| # extract frame metadata and frame |
| for i, cap in enumerate(caps): |
| cap_data = {} |
| md = cap['metadata'] |
| exp = md['android.sensor.exposureTime'] |
| iso = md['android.sensor.sensitivity'] |
| fd = md['android.lens.focalLength'] |
| ae_state = md['android.control.aeState'] |
| af_state = md['android.control.afState'] |
| awb_state = md['android.control.awbState'] |
| fd_str = 'infinity' |
| if fd != 0.0: |
| fd_str = str(round(1.0E2/fd, 2)) + 'cm' |
| scene_change_flag = md['android.control.afSceneChange'] |
| assert scene_change_flag in [0, 1], 'afSceneChange not in [0,1]' |
| img = its.image.convert_capture_to_rgb_image(cap) |
| its.image.write_image(img, '%s_%d_%d.jpg' % (NAME, burst, i)) |
| tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1) |
| g = its.image.compute_image_means(tile)[1] |
| print '%d, iso: %d, exp: %.2fms, fd: %s, avg: %.3f' % ( |
| i, iso, exp*1E-6, fd_str, g), |
| print '[ae,af,awb]: [%d,%d,%d], change: %d' % ( |
| ae_state, af_state, awb_state, scene_change_flag) |
| cap_data['exp'] = exp |
| cap_data['iso'] = iso |
| cap_data['fd'] = fd |
| cap_data['3a_state'] = [ae_state, af_state, awb_state] |
| cap_data['avg'] = g |
| cap_data['flag'] = scene_change_flag |
| cap_data_list.append(cap_data) |
| return cap_data_list |
| |
| |
| def main(): |
| """Test scene change. |
| |
| Do auto capture with face scene. Power down tablet and recapture. |
| Confirm android.control.afSceneChangeDetected is True. |
| """ |
| # check for skip conditions and do 3a up front |
| with its.device.ItsSession() as cam: |
| props = cam.get_camera_properties() |
| props = cam.override_with_hidden_physical_camera_props(props) |
| its.caps.skip_unless(its.caps.continuous_picture(props) and |
| its.caps.af_scene_change(props) and |
| its.caps.read_3a(props)) |
| cam.do_3a() |
| |
| # do captures with scene change |
| chart_host_id = get_cmd_line_args() |
| scene_delay = DELAY_DISPLAY |
| for burst in range(NUM_BURSTS): |
| print 'burst number: %d' % burst |
| # create scene change by turning off chart display & capture frames |
| if chart_host_id: |
| print '\nToggling tablet. Scene change at %.3fs.' % scene_delay |
| multiprocessing.Process(name='p1', target=toggle_screen, |
| args=(chart_host_id, 'OFF', |
| scene_delay,)).start() |
| else: |
| print '\nWave hand in front of camera to create scene change.' |
| cap_data = capture_frames(cam, DELAY_CAPTURE, burst) |
| |
| # find frame where 3A converges |
| converged_frame = mask_3a_settling_frames(cap_data) |
| |
| # turn tablet back on to return to baseline scene state |
| if chart_host_id: |
| toggle_screen(chart_host_id, 'ON', 0) |
| |
| # determine if brightness changed and/or scene change flag asserted |
| scene_changed, bright_changed = determine_if_scene_changed( |
| cap_data, converged_frame) |
| |
| # handle different capture cases |
| if converged_frame > -1: # 3A converges |
| if scene_changed: |
| if bright_changed: |
| print ' scene & brightness change on burst %d.' % burst |
| sys.exit(0) |
| else: |
| msg = ' scene change, but no brightness change.' |
| assert False, msg |
| else: # shift scene change timing if no scene change |
| scene_shift = FRAME_SHIFT / FPS |
| if bright_changed: |
| print ' No scene change, but brightness change.' |
| print 'Shift %.3fs earlier' % scene_shift |
| scene_delay -= scene_shift # tablet-off earlier |
| else: |
| scene_shift = FRAME_SHIFT / FPS * NUM_BURSTS |
| print ' No scene change, no brightness change.' |
| if cap_data[NUM_FRAMES-1]['avg'] < 0.2: |
| print ' Scene dark entire capture.', |
| print 'Shift %.3fs later.' % scene_shift |
| scene_delay += scene_shift # tablet-off later |
| else: |
| print ' Scene light entire capture.', |
| print 'Shift %.3fs earlier.' % scene_shift |
| scene_delay -= scene_shift # tablet-off earlier |
| |
| else: # 3A does not converge |
| if bright_changed: |
| scene_shift = FRAME_SHIFT / FPS |
| print ' 3A does not converge, but brightness change.', |
| print 'Shift %.3fs later' % scene_shift |
| scene_delay += scene_shift # tablet-off earlier |
| else: |
| msg = ' 3A does not converge with no brightness change.' |
| assert False, msg |
| |
| # fail out if too many tries |
| msg = 'No scene change in %dx tries' % NUM_BURSTS |
| assert False, msg |
| |
| |
| if __name__ == '__main__': |
| main() |