1*b7c941bbSAndroid Build Coastguard Worker# Copyright 2024 The Android Open Source Project 2*b7c941bbSAndroid Build Coastguard Worker# 3*b7c941bbSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*b7c941bbSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*b7c941bbSAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*b7c941bbSAndroid Build Coastguard Worker# 7*b7c941bbSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*b7c941bbSAndroid Build Coastguard Worker# 9*b7c941bbSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*b7c941bbSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*b7c941bbSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*b7c941bbSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*b7c941bbSAndroid Build Coastguard Worker# limitations under the License. 14*b7c941bbSAndroid Build Coastguard Worker"""Utility functions for low light camera tests.""" 15*b7c941bbSAndroid Build Coastguard Worker 16*b7c941bbSAndroid Build Coastguard Workerimport logging 17*b7c941bbSAndroid Build Coastguard Workerimport os.path 18*b7c941bbSAndroid Build Coastguard Worker 19*b7c941bbSAndroid Build Coastguard Workerimport camera_properties_utils 20*b7c941bbSAndroid Build Coastguard Workerimport capture_request_utils 21*b7c941bbSAndroid Build Coastguard Workerimport cv2 22*b7c941bbSAndroid Build Coastguard Workerimport image_processing_utils 23*b7c941bbSAndroid Build Coastguard Workerimport matplotlib.pyplot as plt 24*b7c941bbSAndroid Build Coastguard Workerimport numpy as np 25*b7c941bbSAndroid Build Coastguard Workerimport opencv_processing_utils 26*b7c941bbSAndroid Build Coastguard Worker 27*b7c941bbSAndroid Build Coastguard Worker_LOW_LIGHT_BOOST_AVG_DELTA_LUMINANCE_THRESH = 18 28*b7c941bbSAndroid Build Coastguard Worker_LOW_LIGHT_BOOST_AVG_LUMINANCE_THRESH = 90 29*b7c941bbSAndroid Build Coastguard Worker_BOUNDING_BOX_COLOR = (0, 255, 0) 30*b7c941bbSAndroid Build Coastguard Worker_BOX_MIN_SIZE_RATIO = 0.08 # 8% of the cropped image width 31*b7c941bbSAndroid Build Coastguard Worker_BOX_MAX_SIZE_RATIO = 0.5 # 50% of the cropped image width 32*b7c941bbSAndroid Build Coastguard Worker_BOX_PADDING_RATIO = 0.2 33*b7c941bbSAndroid Build Coastguard Worker_CROP_PADDING = 10 34*b7c941bbSAndroid Build Coastguard Worker_EXPECTED_NUM_OF_BOXES = 20 # The captured image must result in 20 detected 35*b7c941bbSAndroid Build Coastguard Worker # boxes since the test scene has 20 boxes 36*b7c941bbSAndroid Build Coastguard Worker_KEY_BOTTOM_LEFT = 'bottom_left' 37*b7c941bbSAndroid Build Coastguard Worker_KEY_BOTTOM_RIGHT = 'bottom_right' 38*b7c941bbSAndroid Build Coastguard Worker_KEY_TOP_LEFT = 'top_left' 39*b7c941bbSAndroid Build Coastguard Worker_KEY_TOP_RIGHT = 'top_right' 40*b7c941bbSAndroid Build Coastguard Worker_MAX_ASPECT_RATIO = 1.2 41*b7c941bbSAndroid Build Coastguard Worker_MIN_ASPECT_RATIO = 0.8 42*b7c941bbSAndroid Build Coastguard Worker_RED_BGR_COLOR = (0, 0, 255) 43*b7c941bbSAndroid Build Coastguard Worker_NUM_CLUSTERS = 8 44*b7c941bbSAndroid Build Coastguard Worker_K_MEANS_ITERATIONS = 10 45*b7c941bbSAndroid Build Coastguard Worker_K_MEANS_EPSILON = 0.5 46*b7c941bbSAndroid Build Coastguard Worker_TEXT_COLOR = (255, 255, 255) 47*b7c941bbSAndroid Build Coastguard Worker_FIG_SIZE = (10, 6) 48*b7c941bbSAndroid Build Coastguard Worker 49*b7c941bbSAndroid Build Coastguard Worker# Allowed tablets for low light scenes 50*b7c941bbSAndroid Build Coastguard Worker# List entries must be entered in lowercase 51*b7c941bbSAndroid Build Coastguard WorkerTABLET_LOW_LIGHT_SCENES_ALLOWLIST = ( 52*b7c941bbSAndroid Build Coastguard Worker 'hwcmr09', # Huawei MediaPad M5 53*b7c941bbSAndroid Build Coastguard Worker 'gta8wifi', # Samsung Galaxy Tab A8 54*b7c941bbSAndroid Build Coastguard Worker 'gta8', # Samsung Galaxy Tab A8 LTE 55*b7c941bbSAndroid Build Coastguard Worker 'gta9pwifi', # Samsung Galaxy Tab A9+ 56*b7c941bbSAndroid Build Coastguard Worker 'gta9p', # Samsung Galaxy Tab A9+ 5G 57*b7c941bbSAndroid Build Coastguard Worker 'nabu', # Xiaomi Pad 5 58*b7c941bbSAndroid Build Coastguard Worker 'nabu_tw', # Xiaomi Pad 5 59*b7c941bbSAndroid Build Coastguard Worker 'xun', # Xiaomi Redmi Pad SE 60*b7c941bbSAndroid Build Coastguard Worker) 61*b7c941bbSAndroid Build Coastguard Worker 62*b7c941bbSAndroid Build Coastguard Worker# Tablet brightness mapping strings for (rear, front) facing camera tests 63*b7c941bbSAndroid Build Coastguard Worker# List entries must be entered in lowercase 64*b7c941bbSAndroid Build Coastguard WorkerTABLET_BRIGHTNESS = { 65*b7c941bbSAndroid Build Coastguard Worker 'hwcmr09': ('4', '8'), # Huawei MediaPad M5 66*b7c941bbSAndroid Build Coastguard Worker 'gta8wifi': ('6', '12'), # Samsung Galaxy Tab A8 67*b7c941bbSAndroid Build Coastguard Worker 'gta8': ('6', '12'), # Samsung Galaxy Tab A8 LTE 68*b7c941bbSAndroid Build Coastguard Worker 'gta9pwifi': ('6', '12'), # Samsung Galaxy Tab A9+ 69*b7c941bbSAndroid Build Coastguard Worker 'gta9p': ('6', '12'), # Samsung Galaxy Tab A9+ 5G 70*b7c941bbSAndroid Build Coastguard Worker 'nabu': ('8', '14'), # Xiaomi Pad 5 71*b7c941bbSAndroid Build Coastguard Worker 'nabu_tw': ('8', '14'), # Xiaomi Pad 5 72*b7c941bbSAndroid Build Coastguard Worker 'xun': ('6', '12'), # Xiaomi Redmi Pad SE 73*b7c941bbSAndroid Build Coastguard Worker} 74*b7c941bbSAndroid Build Coastguard Worker 75*b7c941bbSAndroid Build Coastguard Worker 76*b7c941bbSAndroid Build Coastguard Workerdef get_metering_region(cam, file_stem): 77*b7c941bbSAndroid Build Coastguard Worker """Get the metering region for the given image. 78*b7c941bbSAndroid Build Coastguard Worker 79*b7c941bbSAndroid Build Coastguard Worker Detects the chart in the preview image and returns the coordinates of the 80*b7c941bbSAndroid Build Coastguard Worker chart in the active array. 81*b7c941bbSAndroid Build Coastguard Worker 82*b7c941bbSAndroid Build Coastguard Worker Args: 83*b7c941bbSAndroid Build Coastguard Worker cam: ItsSession object to send commands. 84*b7c941bbSAndroid Build Coastguard Worker file_stem: File prefix for captured images. 85*b7c941bbSAndroid Build Coastguard Worker Returns: 86*b7c941bbSAndroid Build Coastguard Worker The metering region sensor coordinates in the active array or None if the 87*b7c941bbSAndroid Build Coastguard Worker test chart was not detected. 88*b7c941bbSAndroid Build Coastguard Worker """ 89*b7c941bbSAndroid Build Coastguard Worker req = capture_request_utils.auto_capture_request() 90*b7c941bbSAndroid Build Coastguard Worker cap = cam.do_capture(req, cam.CAP_YUV) 91*b7c941bbSAndroid Build Coastguard Worker img = image_processing_utils.convert_capture_to_rgb_image(cap) 92*b7c941bbSAndroid Build Coastguard Worker region_detection_file = f'{file_stem}_region_detection.jpg' 93*b7c941bbSAndroid Build Coastguard Worker image_processing_utils.write_image(img, region_detection_file) 94*b7c941bbSAndroid Build Coastguard Worker img = cv2.imread(region_detection_file) 95*b7c941bbSAndroid Build Coastguard Worker 96*b7c941bbSAndroid Build Coastguard Worker coords = _find_chart_bounding_region(img) 97*b7c941bbSAndroid Build Coastguard Worker if coords is None: 98*b7c941bbSAndroid Build Coastguard Worker return None 99*b7c941bbSAndroid Build Coastguard Worker 100*b7c941bbSAndroid Build Coastguard Worker # Convert image coordinates to sensor coordinates for metering rectangle 101*b7c941bbSAndroid Build Coastguard Worker img_w = img.shape[1] 102*b7c941bbSAndroid Build Coastguard Worker img_h = img.shape[0] 103*b7c941bbSAndroid Build Coastguard Worker props = cam.get_camera_properties() 104*b7c941bbSAndroid Build Coastguard Worker aa = props['android.sensor.info.activeArraySize'] 105*b7c941bbSAndroid Build Coastguard Worker aa_width, aa_height = aa['right'] - aa['left'], aa['bottom'] - aa['top'] 106*b7c941bbSAndroid Build Coastguard Worker logging.debug('Active array size: %s', aa) 107*b7c941bbSAndroid Build Coastguard Worker coords_tl = (coords[0], coords[1]) 108*b7c941bbSAndroid Build Coastguard Worker coords_br = (coords[0] + coords[2], coords[1] + coords[3]) 109*b7c941bbSAndroid Build Coastguard Worker s_coords_tl = image_processing_utils.convert_image_coords_to_sensor_coords( 110*b7c941bbSAndroid Build Coastguard Worker aa_width, aa_height, coords_tl, img_w, img_h) 111*b7c941bbSAndroid Build Coastguard Worker s_coords_br = image_processing_utils.convert_image_coords_to_sensor_coords( 112*b7c941bbSAndroid Build Coastguard Worker aa_width, aa_height, coords_br, img_w, img_h) 113*b7c941bbSAndroid Build Coastguard Worker sensor_coords = (s_coords_tl[0], s_coords_tl[1], s_coords_br[0], 114*b7c941bbSAndroid Build Coastguard Worker s_coords_br[1]) 115*b7c941bbSAndroid Build Coastguard Worker 116*b7c941bbSAndroid Build Coastguard Worker # If testing front camera, mirror coordinates either left/right or up/down 117*b7c941bbSAndroid Build Coastguard Worker # Preview are flipped on device's natural orientation 118*b7c941bbSAndroid Build Coastguard Worker # For sensor orientation 90 or 270, it is up or down 119*b7c941bbSAndroid Build Coastguard Worker # For sensor orientation 0 or 180, it is left or right 120*b7c941bbSAndroid Build Coastguard Worker if (props['android.lens.facing'] == 121*b7c941bbSAndroid Build Coastguard Worker camera_properties_utils.LENS_FACING['FRONT']): 122*b7c941bbSAndroid Build Coastguard Worker if props['android.sensor.orientation'] in (90, 270): 123*b7c941bbSAndroid Build Coastguard Worker tl_coordinates = (sensor_coords[0], aa_height - sensor_coords[3]) 124*b7c941bbSAndroid Build Coastguard Worker br_coordinates = (sensor_coords[2], aa_height - sensor_coords[1]) 125*b7c941bbSAndroid Build Coastguard Worker logging.debug('Found sensor orientation %d, flipping up down', 126*b7c941bbSAndroid Build Coastguard Worker props['android.sensor.orientation']) 127*b7c941bbSAndroid Build Coastguard Worker else: 128*b7c941bbSAndroid Build Coastguard Worker tl_coordinates = (aa_width - sensor_coords[2], sensor_coords[1]) 129*b7c941bbSAndroid Build Coastguard Worker br_coordinates = (aa_width - sensor_coords[0], sensor_coords[3]) 130*b7c941bbSAndroid Build Coastguard Worker logging.debug('Found sensor orientation %d, flipping left right', 131*b7c941bbSAndroid Build Coastguard Worker props['android.sensor.orientation']) 132*b7c941bbSAndroid Build Coastguard Worker logging.debug('Mirrored top-left coordinates: %s', tl_coordinates) 133*b7c941bbSAndroid Build Coastguard Worker logging.debug('Mirrored bottom-right coordinates: %s', br_coordinates) 134*b7c941bbSAndroid Build Coastguard Worker else: 135*b7c941bbSAndroid Build Coastguard Worker tl_coordinates = (sensor_coords[0], sensor_coords[1]) 136*b7c941bbSAndroid Build Coastguard Worker br_coordinates = (sensor_coords[2], sensor_coords[3]) 137*b7c941bbSAndroid Build Coastguard Worker 138*b7c941bbSAndroid Build Coastguard Worker tl_x = int(tl_coordinates[0]) 139*b7c941bbSAndroid Build Coastguard Worker tl_y = int(tl_coordinates[1]) 140*b7c941bbSAndroid Build Coastguard Worker br_x = int(br_coordinates[0]) 141*b7c941bbSAndroid Build Coastguard Worker br_y = int(br_coordinates[1]) 142*b7c941bbSAndroid Build Coastguard Worker rect_w = br_x - tl_x 143*b7c941bbSAndroid Build Coastguard Worker rect_h = br_y - tl_y 144*b7c941bbSAndroid Build Coastguard Worker return {'x': tl_x, 145*b7c941bbSAndroid Build Coastguard Worker 'y': tl_y, 146*b7c941bbSAndroid Build Coastguard Worker 'width': rect_w, 147*b7c941bbSAndroid Build Coastguard Worker 'height': rect_h, 148*b7c941bbSAndroid Build Coastguard Worker 'weight': opencv_processing_utils.AE_AWB_METER_WEIGHT} 149*b7c941bbSAndroid Build Coastguard Worker 150*b7c941bbSAndroid Build Coastguard Worker 151*b7c941bbSAndroid Build Coastguard Workerdef _find_chart_bounding_region(img): 152*b7c941bbSAndroid Build Coastguard Worker """Finds the bounding region of the chart. 153*b7c941bbSAndroid Build Coastguard Worker 154*b7c941bbSAndroid Build Coastguard Worker Args: 155*b7c941bbSAndroid Build Coastguard Worker img: numpy array; captured image from scene_low_light. 156*b7c941bbSAndroid Build Coastguard Worker Returns: 157*b7c941bbSAndroid Build Coastguard Worker The coordinates of the bounding region relative to the input image. This 158*b7c941bbSAndroid Build Coastguard Worker is returned as (left, top, width, height) or None if the test chart was 159*b7c941bbSAndroid Build Coastguard Worker not detected. 160*b7c941bbSAndroid Build Coastguard Worker """ 161*b7c941bbSAndroid Build Coastguard Worker # To apply k-means clustering, we need to convert the image in to an array 162*b7c941bbSAndroid Build Coastguard Worker # where each row represents a pixel in the image, and each column is a feature 163*b7c941bbSAndroid Build Coastguard Worker # In this case, the feature represents the RGB channels of the pixel 164*b7c941bbSAndroid Build Coastguard Worker data = img.reshape((-1, 3)) 165*b7c941bbSAndroid Build Coastguard Worker data = np.float32(data) 166*b7c941bbSAndroid Build Coastguard Worker 167*b7c941bbSAndroid Build Coastguard Worker k_means_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 168*b7c941bbSAndroid Build Coastguard Worker _K_MEANS_ITERATIONS, _K_MEANS_EPSILON) 169*b7c941bbSAndroid Build Coastguard Worker _, labels, centers = cv2.kmeans(data, _NUM_CLUSTERS, None, k_means_criteria, 170*b7c941bbSAndroid Build Coastguard Worker _K_MEANS_ITERATIONS, 171*b7c941bbSAndroid Build Coastguard Worker cv2.KMEANS_RANDOM_CENTERS) 172*b7c941bbSAndroid Build Coastguard Worker # Find the cluster closest to red 173*b7c941bbSAndroid Build Coastguard Worker min_dist = float('inf') 174*b7c941bbSAndroid Build Coastguard Worker closest_cluster_index = -1 175*b7c941bbSAndroid Build Coastguard Worker for index, center in enumerate(centers): 176*b7c941bbSAndroid Build Coastguard Worker dist = np.linalg.norm(center - np.array(_RED_BGR_COLOR)) 177*b7c941bbSAndroid Build Coastguard Worker if dist < min_dist: 178*b7c941bbSAndroid Build Coastguard Worker min_dist = dist 179*b7c941bbSAndroid Build Coastguard Worker closest_cluster_index = index 180*b7c941bbSAndroid Build Coastguard Worker 181*b7c941bbSAndroid Build Coastguard Worker target_label = closest_cluster_index 182*b7c941bbSAndroid Build Coastguard Worker 183*b7c941bbSAndroid Build Coastguard Worker # create a mask using the data associated with the cluster closest to red 184*b7c941bbSAndroid Build Coastguard Worker mask = labels.flatten() == target_label 185*b7c941bbSAndroid Build Coastguard Worker mask = mask.reshape((img.shape[0], img.shape[1])) 186*b7c941bbSAndroid Build Coastguard Worker mask = mask.astype(np.uint8) 187*b7c941bbSAndroid Build Coastguard Worker 188*b7c941bbSAndroid Build Coastguard Worker contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, 189*b7c941bbSAndroid Build Coastguard Worker cv2.CHAIN_APPROX_SIMPLE) 190*b7c941bbSAndroid Build Coastguard Worker 191*b7c941bbSAndroid Build Coastguard Worker max_area = 20 192*b7c941bbSAndroid Build Coastguard Worker max_box = None 193*b7c941bbSAndroid Build Coastguard Worker 194*b7c941bbSAndroid Build Coastguard Worker # Find the largest box that is closest to square 195*b7c941bbSAndroid Build Coastguard Worker for c in contours: 196*b7c941bbSAndroid Build Coastguard Worker x, y, w, h = cv2.boundingRect(c) 197*b7c941bbSAndroid Build Coastguard Worker aspect_ratio = w / h 198*b7c941bbSAndroid Build Coastguard Worker if _MIN_ASPECT_RATIO < aspect_ratio < _MAX_ASPECT_RATIO: 199*b7c941bbSAndroid Build Coastguard Worker area = w * h 200*b7c941bbSAndroid Build Coastguard Worker if area > max_area: 201*b7c941bbSAndroid Build Coastguard Worker max_area = area 202*b7c941bbSAndroid Build Coastguard Worker max_box = (x, y, w, h) 203*b7c941bbSAndroid Build Coastguard Worker 204*b7c941bbSAndroid Build Coastguard Worker return max_box 205*b7c941bbSAndroid Build Coastguard Worker 206*b7c941bbSAndroid Build Coastguard Worker 207*b7c941bbSAndroid Build Coastguard Workerdef _crop(img): 208*b7c941bbSAndroid Build Coastguard Worker """Crops the captured image according to the red square outline. 209*b7c941bbSAndroid Build Coastguard Worker 210*b7c941bbSAndroid Build Coastguard Worker Args: 211*b7c941bbSAndroid Build Coastguard Worker img: numpy array; captured image from scene_low_light. 212*b7c941bbSAndroid Build Coastguard Worker Returns: 213*b7c941bbSAndroid Build Coastguard Worker numpy array of the cropped image or the original image if the crop region 214*b7c941bbSAndroid Build Coastguard Worker isn't found. 215*b7c941bbSAndroid Build Coastguard Worker """ 216*b7c941bbSAndroid Build Coastguard Worker max_box = _find_chart_bounding_region(img) 217*b7c941bbSAndroid Build Coastguard Worker 218*b7c941bbSAndroid Build Coastguard Worker # If the box is found then return the cropped image 219*b7c941bbSAndroid Build Coastguard Worker # otherwise the original image is returned 220*b7c941bbSAndroid Build Coastguard Worker if max_box: 221*b7c941bbSAndroid Build Coastguard Worker x, y, w, h = max_box 222*b7c941bbSAndroid Build Coastguard Worker cropped_img = img[ 223*b7c941bbSAndroid Build Coastguard Worker y+_CROP_PADDING:y+h-_CROP_PADDING, 224*b7c941bbSAndroid Build Coastguard Worker x+_CROP_PADDING:x+w-_CROP_PADDING 225*b7c941bbSAndroid Build Coastguard Worker ] 226*b7c941bbSAndroid Build Coastguard Worker return cropped_img 227*b7c941bbSAndroid Build Coastguard Worker 228*b7c941bbSAndroid Build Coastguard Worker return img 229*b7c941bbSAndroid Build Coastguard Worker 230*b7c941bbSAndroid Build Coastguard Worker 231*b7c941bbSAndroid Build Coastguard Workerdef _find_boxes(image): 232*b7c941bbSAndroid Build Coastguard Worker """Finds boxes in the captured image for computing luminance. 233*b7c941bbSAndroid Build Coastguard Worker 234*b7c941bbSAndroid Build Coastguard Worker The captured image should be of scene_low_light.png. The boxes are detected 235*b7c941bbSAndroid Build Coastguard Worker by finding the contours by applying a threshold followed erosion. 236*b7c941bbSAndroid Build Coastguard Worker 237*b7c941bbSAndroid Build Coastguard Worker Args: 238*b7c941bbSAndroid Build Coastguard Worker image: numpy array; the captured image. 239*b7c941bbSAndroid Build Coastguard Worker Returns: 240*b7c941bbSAndroid Build Coastguard Worker array; an array of boxes, where each box is (x, y, w, h). 241*b7c941bbSAndroid Build Coastguard Worker """ 242*b7c941bbSAndroid Build Coastguard Worker gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 243*b7c941bbSAndroid Build Coastguard Worker blur = cv2.GaussianBlur(gray, (3, 3), 0) 244*b7c941bbSAndroid Build Coastguard Worker 245*b7c941bbSAndroid Build Coastguard Worker thresh = cv2.adaptiveThreshold( 246*b7c941bbSAndroid Build Coastguard Worker blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 31, -5) 247*b7c941bbSAndroid Build Coastguard Worker 248*b7c941bbSAndroid Build Coastguard Worker kernel = np.ones((3, 3), np.uint8) 249*b7c941bbSAndroid Build Coastguard Worker eroded = cv2.erode(thresh, kernel, iterations=1) 250*b7c941bbSAndroid Build Coastguard Worker 251*b7c941bbSAndroid Build Coastguard Worker contours, _ = cv2.findContours(eroded, cv2.RETR_EXTERNAL, 252*b7c941bbSAndroid Build Coastguard Worker cv2.CHAIN_APPROX_SIMPLE) 253*b7c941bbSAndroid Build Coastguard Worker boxes = [] 254*b7c941bbSAndroid Build Coastguard Worker 255*b7c941bbSAndroid Build Coastguard Worker # Filter out boxes that are too small or too large 256*b7c941bbSAndroid Build Coastguard Worker # and boxes that are not square 257*b7c941bbSAndroid Build Coastguard Worker img_hw_size_max = max(image.shape[0], image.shape[1]) 258*b7c941bbSAndroid Build Coastguard Worker box_min_size = int(round(img_hw_size_max * _BOX_MIN_SIZE_RATIO, 0)) 259*b7c941bbSAndroid Build Coastguard Worker if box_min_size == 0: 260*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('Minimum box size calculated was 0. Check cropped ' 261*b7c941bbSAndroid Build Coastguard Worker 'image size.') 262*b7c941bbSAndroid Build Coastguard Worker box_max_size = int(img_hw_size_max * _BOX_MAX_SIZE_RATIO) 263*b7c941bbSAndroid Build Coastguard Worker for c in contours: 264*b7c941bbSAndroid Build Coastguard Worker x, y, w, h = cv2.boundingRect(c) 265*b7c941bbSAndroid Build Coastguard Worker aspect_ratio = w / h 266*b7c941bbSAndroid Build Coastguard Worker if (w > box_min_size and h > box_min_size and 267*b7c941bbSAndroid Build Coastguard Worker w < box_max_size and h < box_max_size and 268*b7c941bbSAndroid Build Coastguard Worker _MIN_ASPECT_RATIO < aspect_ratio < _MAX_ASPECT_RATIO): 269*b7c941bbSAndroid Build Coastguard Worker boxes.append((x, y, w, h)) 270*b7c941bbSAndroid Build Coastguard Worker return boxes 271*b7c941bbSAndroid Build Coastguard Worker 272*b7c941bbSAndroid Build Coastguard Worker 273*b7c941bbSAndroid Build Coastguard Workerdef _correct_image_rotation(img, regions): 274*b7c941bbSAndroid Build Coastguard Worker """Corrects the captured image orientation. 275*b7c941bbSAndroid Build Coastguard Worker 276*b7c941bbSAndroid Build Coastguard Worker The captured image should be of scene_low_light.png. The darkest square 277*b7c941bbSAndroid Build Coastguard Worker must appear in the bottom right and the brightest square must appear in 278*b7c941bbSAndroid Build Coastguard Worker the bottom left. This is necessary in order to traverse the hilbert 279*b7c941bbSAndroid Build Coastguard Worker ordered squares to return a darkest to brightest ordering. 280*b7c941bbSAndroid Build Coastguard Worker 281*b7c941bbSAndroid Build Coastguard Worker Args: 282*b7c941bbSAndroid Build Coastguard Worker img: numpy array; the original image captured. 283*b7c941bbSAndroid Build Coastguard Worker regions: the tuple of (box, luminance) computed for each square 284*b7c941bbSAndroid Build Coastguard Worker in the image. 285*b7c941bbSAndroid Build Coastguard Worker Returns: 286*b7c941bbSAndroid Build Coastguard Worker numpy array; image in the corrected orientation. 287*b7c941bbSAndroid Build Coastguard Worker """ 288*b7c941bbSAndroid Build Coastguard Worker corner_brightness = { 289*b7c941bbSAndroid Build Coastguard Worker _KEY_TOP_LEFT: regions[2][1], 290*b7c941bbSAndroid Build Coastguard Worker _KEY_BOTTOM_LEFT: regions[5][1], 291*b7c941bbSAndroid Build Coastguard Worker _KEY_TOP_RIGHT: regions[14][1], 292*b7c941bbSAndroid Build Coastguard Worker _KEY_BOTTOM_RIGHT: regions[17][1], 293*b7c941bbSAndroid Build Coastguard Worker } 294*b7c941bbSAndroid Build Coastguard Worker 295*b7c941bbSAndroid Build Coastguard Worker darkest_corner = ('', float('inf')) 296*b7c941bbSAndroid Build Coastguard Worker brightest_corner = ('', float('-inf')) 297*b7c941bbSAndroid Build Coastguard Worker 298*b7c941bbSAndroid Build Coastguard Worker for corner, luminance in corner_brightness.items(): 299*b7c941bbSAndroid Build Coastguard Worker if luminance < darkest_corner[1]: 300*b7c941bbSAndroid Build Coastguard Worker darkest_corner = (corner, luminance) 301*b7c941bbSAndroid Build Coastguard Worker if luminance > brightest_corner[1]: 302*b7c941bbSAndroid Build Coastguard Worker brightest_corner = (corner, luminance) 303*b7c941bbSAndroid Build Coastguard Worker 304*b7c941bbSAndroid Build Coastguard Worker if darkest_corner == brightest_corner: 305*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('The captured image failed to detect the location ' 306*b7c941bbSAndroid Build Coastguard Worker 'of the darkest and brightest squares.') 307*b7c941bbSAndroid Build Coastguard Worker 308*b7c941bbSAndroid Build Coastguard Worker if darkest_corner[0] == _KEY_TOP_LEFT: 309*b7c941bbSAndroid Build Coastguard Worker if brightest_corner[0] == _KEY_BOTTOM_LEFT: 310*b7c941bbSAndroid Build Coastguard Worker # rotate 90 CW and then flip vertically 311*b7c941bbSAndroid Build Coastguard Worker img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) 312*b7c941bbSAndroid Build Coastguard Worker img = cv2.flip(img, 0) 313*b7c941bbSAndroid Build Coastguard Worker elif brightest_corner[0] == _KEY_TOP_RIGHT: 314*b7c941bbSAndroid Build Coastguard Worker # flip both vertically and horizontally 315*b7c941bbSAndroid Build Coastguard Worker img = cv2.flip(img, -1) 316*b7c941bbSAndroid Build Coastguard Worker else: 317*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('The captured image failed to detect the location ' 318*b7c941bbSAndroid Build Coastguard Worker 'of the brightest square.') 319*b7c941bbSAndroid Build Coastguard Worker elif darkest_corner[0] == _KEY_BOTTOM_LEFT: 320*b7c941bbSAndroid Build Coastguard Worker if brightest_corner[0] == _KEY_TOP_LEFT: 321*b7c941bbSAndroid Build Coastguard Worker # rotate 90 CCW 322*b7c941bbSAndroid Build Coastguard Worker img = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE) 323*b7c941bbSAndroid Build Coastguard Worker elif brightest_corner[0] == _KEY_BOTTOM_RIGHT: 324*b7c941bbSAndroid Build Coastguard Worker # flip horizontally 325*b7c941bbSAndroid Build Coastguard Worker img = cv2.flip(img, 1) 326*b7c941bbSAndroid Build Coastguard Worker else: 327*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('The captured image failed to detect the location ' 328*b7c941bbSAndroid Build Coastguard Worker 'of the brightest square.') 329*b7c941bbSAndroid Build Coastguard Worker elif darkest_corner[0] == _KEY_TOP_RIGHT: 330*b7c941bbSAndroid Build Coastguard Worker if brightest_corner[0] == _KEY_TOP_LEFT: 331*b7c941bbSAndroid Build Coastguard Worker # flip vertically 332*b7c941bbSAndroid Build Coastguard Worker img = cv2.flip(img, 0) 333*b7c941bbSAndroid Build Coastguard Worker elif brightest_corner[0] == _KEY_BOTTOM_RIGHT: 334*b7c941bbSAndroid Build Coastguard Worker # rotate 90 CW 335*b7c941bbSAndroid Build Coastguard Worker img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) 336*b7c941bbSAndroid Build Coastguard Worker else: 337*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('The captured image failed to detect the location ' 338*b7c941bbSAndroid Build Coastguard Worker 'of the brightest square.') 339*b7c941bbSAndroid Build Coastguard Worker elif darkest_corner[0] == _KEY_BOTTOM_RIGHT: 340*b7c941bbSAndroid Build Coastguard Worker if brightest_corner[0] == _KEY_BOTTOM_LEFT: 341*b7c941bbSAndroid Build Coastguard Worker # correct orientation 342*b7c941bbSAndroid Build Coastguard Worker pass 343*b7c941bbSAndroid Build Coastguard Worker elif brightest_corner[0] == _KEY_TOP_RIGHT: 344*b7c941bbSAndroid Build Coastguard Worker # rotate 90 and flip horizontally 345*b7c941bbSAndroid Build Coastguard Worker img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) 346*b7c941bbSAndroid Build Coastguard Worker img = cv2.flip(img, 1) 347*b7c941bbSAndroid Build Coastguard Worker else: 348*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('The captured image failed to detect the location ' 349*b7c941bbSAndroid Build Coastguard Worker 'of the brightest square.') 350*b7c941bbSAndroid Build Coastguard Worker return img 351*b7c941bbSAndroid Build Coastguard Worker 352*b7c941bbSAndroid Build Coastguard Worker 353*b7c941bbSAndroid Build Coastguard Workerdef _compute_luminance_regions(image, boxes): 354*b7c941bbSAndroid Build Coastguard Worker """Compute the luminance for each box in scene_low_light. 355*b7c941bbSAndroid Build Coastguard Worker 356*b7c941bbSAndroid Build Coastguard Worker Args: 357*b7c941bbSAndroid Build Coastguard Worker image: numpy array; captured image. 358*b7c941bbSAndroid Build Coastguard Worker boxes: array; array of boxes where each box is (x, y, w, h). 359*b7c941bbSAndroid Build Coastguard Worker Returns: 360*b7c941bbSAndroid Build Coastguard Worker Array of tuples where each tuple is (box, luminance). 361*b7c941bbSAndroid Build Coastguard Worker """ 362*b7c941bbSAndroid Build Coastguard Worker intensities = [] 363*b7c941bbSAndroid Build Coastguard Worker for b in boxes: 364*b7c941bbSAndroid Build Coastguard Worker x, y, w, h = b 365*b7c941bbSAndroid Build Coastguard Worker padding = min(w, h) * _BOX_PADDING_RATIO 366*b7c941bbSAndroid Build Coastguard Worker left = int(x + padding) 367*b7c941bbSAndroid Build Coastguard Worker top = int(y + padding) 368*b7c941bbSAndroid Build Coastguard Worker right = int(x + w - padding) 369*b7c941bbSAndroid Build Coastguard Worker bottom = int(y + h - padding) 370*b7c941bbSAndroid Build Coastguard Worker box = image[top:bottom, left:right] 371*b7c941bbSAndroid Build Coastguard Worker box_xyz = cv2.cvtColor(box, cv2.COLOR_BGR2XYZ) 372*b7c941bbSAndroid Build Coastguard Worker intensity = int(np.mean(box_xyz[1])) 373*b7c941bbSAndroid Build Coastguard Worker intensities.append((b, intensity)) 374*b7c941bbSAndroid Build Coastguard Worker return intensities 375*b7c941bbSAndroid Build Coastguard Worker 376*b7c941bbSAndroid Build Coastguard Worker 377*b7c941bbSAndroid Build Coastguard Workerdef _draw_luminance(image, intensities): 378*b7c941bbSAndroid Build Coastguard Worker """Draws the luma and noise for each box in scene_low_light for debugging. 379*b7c941bbSAndroid Build Coastguard Worker 380*b7c941bbSAndroid Build Coastguard Worker Args: 381*b7c941bbSAndroid Build Coastguard Worker image: numpy array; captured image. 382*b7c941bbSAndroid Build Coastguard Worker intensities: array; array of tuples (box, luminance intensity). 383*b7c941bbSAndroid Build Coastguard Worker """ 384*b7c941bbSAndroid Build Coastguard Worker for b, intensity in intensities: 385*b7c941bbSAndroid Build Coastguard Worker x, y, w, h = b 386*b7c941bbSAndroid Build Coastguard Worker padding = min(w, h) * _BOX_PADDING_RATIO 387*b7c941bbSAndroid Build Coastguard Worker left = int(x + padding) 388*b7c941bbSAndroid Build Coastguard Worker top = int(y + padding) 389*b7c941bbSAndroid Build Coastguard Worker right = int(x + w - padding) 390*b7c941bbSAndroid Build Coastguard Worker bottom = int(y + h - padding) 391*b7c941bbSAndroid Build Coastguard Worker noise_stats = image_processing_utils.compute_patch_noise( 392*b7c941bbSAndroid Build Coastguard Worker image, (left, top, (right - left), (bottom - top))) 393*b7c941bbSAndroid Build Coastguard Worker cv2.rectangle(image, (left, top), (right, bottom), _BOUNDING_BOX_COLOR, 2) 394*b7c941bbSAndroid Build Coastguard Worker # place the luma value above the box offset by 10 pixels 395*b7c941bbSAndroid Build Coastguard Worker cv2.putText(img=image, text=f'{intensity}', org=(x, y - 10), 396*b7c941bbSAndroid Build Coastguard Worker fontFace=cv2.FONT_HERSHEY_PLAIN, fontScale=1, 397*b7c941bbSAndroid Build Coastguard Worker color=_TEXT_COLOR) 398*b7c941bbSAndroid Build Coastguard Worker luma = str(round(noise_stats['luma'], 1)) 399*b7c941bbSAndroid Build Coastguard Worker cu = str(round(noise_stats['chroma_u'], 1)) 400*b7c941bbSAndroid Build Coastguard Worker cv = str(round(noise_stats['chroma_v'], 1)) 401*b7c941bbSAndroid Build Coastguard Worker # place the noise (luma, chroma u, chroma v) values above the luma value 402*b7c941bbSAndroid Build Coastguard Worker # offset by 30 pixels 403*b7c941bbSAndroid Build Coastguard Worker cv2.putText(img=image, text=f'{luma}, {cu}, {cv}', org=(x, y - 30), 404*b7c941bbSAndroid Build Coastguard Worker fontFace=cv2.FONT_HERSHEY_PLAIN, fontScale=1, 405*b7c941bbSAndroid Build Coastguard Worker color=_TEXT_COLOR) 406*b7c941bbSAndroid Build Coastguard Worker 407*b7c941bbSAndroid Build Coastguard Worker 408*b7c941bbSAndroid Build Coastguard Workerdef _compute_avg(results): 409*b7c941bbSAndroid Build Coastguard Worker """Computes the average luminance of the first 6 boxes. 410*b7c941bbSAndroid Build Coastguard Worker 411*b7c941bbSAndroid Build Coastguard Worker The boxes are part of scene_low_light. 412*b7c941bbSAndroid Build Coastguard Worker 413*b7c941bbSAndroid Build Coastguard Worker Args: 414*b7c941bbSAndroid Build Coastguard Worker results: A list of tuples where each tuple is (box, luminance). 415*b7c941bbSAndroid Build Coastguard Worker Returns: 416*b7c941bbSAndroid Build Coastguard Worker float; The average luminance of the first 6 boxes. 417*b7c941bbSAndroid Build Coastguard Worker """ 418*b7c941bbSAndroid Build Coastguard Worker luminance_values = [luminance for _, luminance in results[:6]] 419*b7c941bbSAndroid Build Coastguard Worker avg = sum(luminance_values) / len(luminance_values) 420*b7c941bbSAndroid Build Coastguard Worker return avg 421*b7c941bbSAndroid Build Coastguard Worker 422*b7c941bbSAndroid Build Coastguard Worker 423*b7c941bbSAndroid Build Coastguard Workerdef _compute_avg_delta_of_successive_boxes(results): 424*b7c941bbSAndroid Build Coastguard Worker """Computes the delta of successive boxes & takes the average of the first 5. 425*b7c941bbSAndroid Build Coastguard Worker 426*b7c941bbSAndroid Build Coastguard Worker The boxes are part of scene_low_light. 427*b7c941bbSAndroid Build Coastguard Worker 428*b7c941bbSAndroid Build Coastguard Worker Args: 429*b7c941bbSAndroid Build Coastguard Worker results: A list of tuples where each tuple is (box, luminance). 430*b7c941bbSAndroid Build Coastguard Worker Returns: 431*b7c941bbSAndroid Build Coastguard Worker float; The average of the first 5 deltas of successive boxes. 432*b7c941bbSAndroid Build Coastguard Worker """ 433*b7c941bbSAndroid Build Coastguard Worker luminance_values = [luminance for _, luminance in results[:6]] 434*b7c941bbSAndroid Build Coastguard Worker delta = [luminance_values[i] - luminance_values[i - 1] 435*b7c941bbSAndroid Build Coastguard Worker for i in range(1, len(luminance_values))] 436*b7c941bbSAndroid Build Coastguard Worker avg = sum(delta) / len(delta) 437*b7c941bbSAndroid Build Coastguard Worker return avg 438*b7c941bbSAndroid Build Coastguard Worker 439*b7c941bbSAndroid Build Coastguard Worker 440*b7c941bbSAndroid Build Coastguard Workerdef _plot_results(results, file_stem): 441*b7c941bbSAndroid Build Coastguard Worker """Plots the computed luminance for each box in scene_low_light. 442*b7c941bbSAndroid Build Coastguard Worker 443*b7c941bbSAndroid Build Coastguard Worker Args: 444*b7c941bbSAndroid Build Coastguard Worker results: A list of tuples where each tuple is (box, luminance). 445*b7c941bbSAndroid Build Coastguard Worker file_stem: The output file where the plot is saved. 446*b7c941bbSAndroid Build Coastguard Worker """ 447*b7c941bbSAndroid Build Coastguard Worker luminance_values = [luminance for _, luminance in results] 448*b7c941bbSAndroid Build Coastguard Worker box_labels = [f'Box {i + 1}' for i in range(len(results))] 449*b7c941bbSAndroid Build Coastguard Worker 450*b7c941bbSAndroid Build Coastguard Worker plt.figure(figsize=_FIG_SIZE) 451*b7c941bbSAndroid Build Coastguard Worker plt.plot(box_labels, luminance_values, marker='o', linestyle='-', color='b') 452*b7c941bbSAndroid Build Coastguard Worker plt.scatter(box_labels, luminance_values, color='r') 453*b7c941bbSAndroid Build Coastguard Worker 454*b7c941bbSAndroid Build Coastguard Worker plt.title('Luminance for each Box') 455*b7c941bbSAndroid Build Coastguard Worker plt.xlabel('Boxes') 456*b7c941bbSAndroid Build Coastguard Worker plt.ylabel('Luminance (pixel intensity)') 457*b7c941bbSAndroid Build Coastguard Worker plt.grid('True') 458*b7c941bbSAndroid Build Coastguard Worker plt.xticks(rotation=45) 459*b7c941bbSAndroid Build Coastguard Worker plt.savefig(f'{file_stem}_luminance_plot.png', dpi=300) 460*b7c941bbSAndroid Build Coastguard Worker plt.close() 461*b7c941bbSAndroid Build Coastguard Worker 462*b7c941bbSAndroid Build Coastguard Worker 463*b7c941bbSAndroid Build Coastguard Workerdef _plot_successive_difference(results, file_stem): 464*b7c941bbSAndroid Build Coastguard Worker """Plots the successive difference in luminance between each box. 465*b7c941bbSAndroid Build Coastguard Worker 466*b7c941bbSAndroid Build Coastguard Worker The boxes are part of scene_low_light. 467*b7c941bbSAndroid Build Coastguard Worker 468*b7c941bbSAndroid Build Coastguard Worker Args: 469*b7c941bbSAndroid Build Coastguard Worker results: A list of tuples where each tuple is (box, luminance). 470*b7c941bbSAndroid Build Coastguard Worker file_stem: The output file where the plot is saved. 471*b7c941bbSAndroid Build Coastguard Worker """ 472*b7c941bbSAndroid Build Coastguard Worker luminance_values = [luminance for _, luminance in results] 473*b7c941bbSAndroid Build Coastguard Worker delta = [luminance_values[i] - luminance_values[i - 1] 474*b7c941bbSAndroid Build Coastguard Worker for i in range(1, len(luminance_values))] 475*b7c941bbSAndroid Build Coastguard Worker box_labels = [f'Box {i} to Box {i + 1}' for i in range(1, len(results))] 476*b7c941bbSAndroid Build Coastguard Worker 477*b7c941bbSAndroid Build Coastguard Worker plt.figure(figsize=_FIG_SIZE) 478*b7c941bbSAndroid Build Coastguard Worker plt.plot(box_labels, delta, marker='o', linestyle='-', color='b') 479*b7c941bbSAndroid Build Coastguard Worker plt.scatter(box_labels, delta, color='r') 480*b7c941bbSAndroid Build Coastguard Worker 481*b7c941bbSAndroid Build Coastguard Worker plt.title('Difference in Luminance Between Successive Boxes') 482*b7c941bbSAndroid Build Coastguard Worker plt.xlabel('Box Transition') 483*b7c941bbSAndroid Build Coastguard Worker plt.ylabel('Luminance Difference') 484*b7c941bbSAndroid Build Coastguard Worker plt.grid('True') 485*b7c941bbSAndroid Build Coastguard Worker plt.xticks(rotation=45) 486*b7c941bbSAndroid Build Coastguard Worker plt.savefig( 487*b7c941bbSAndroid Build Coastguard Worker f'{file_stem}_luminance_difference_between_successive_boxes_plot.png', 488*b7c941bbSAndroid Build Coastguard Worker dpi=300) 489*b7c941bbSAndroid Build Coastguard Worker plt.close() 490*b7c941bbSAndroid Build Coastguard Worker 491*b7c941bbSAndroid Build Coastguard Worker 492*b7c941bbSAndroid Build Coastguard Workerdef _plot_noise(results, file_stem, img, test_name): 493*b7c941bbSAndroid Build Coastguard Worker """Plots the noise in the image. 494*b7c941bbSAndroid Build Coastguard Worker 495*b7c941bbSAndroid Build Coastguard Worker The boxes are part of scene_low_light. 496*b7c941bbSAndroid Build Coastguard Worker 497*b7c941bbSAndroid Build Coastguard Worker Args: 498*b7c941bbSAndroid Build Coastguard Worker results: A list of tuples where each tuple is (box, luminance). 499*b7c941bbSAndroid Build Coastguard Worker file_stem: The output file where the plot is saved. 500*b7c941bbSAndroid Build Coastguard Worker img: The captured image used to measure patch noise. 501*b7c941bbSAndroid Build Coastguard Worker test_name: Name of the test being plotted. 502*b7c941bbSAndroid Build Coastguard Worker """ 503*b7c941bbSAndroid Build Coastguard Worker luma_noise_values = [] 504*b7c941bbSAndroid Build Coastguard Worker chroma_u_noise_values = [] 505*b7c941bbSAndroid Build Coastguard Worker chroma_v_noise_values = [] 506*b7c941bbSAndroid Build Coastguard Worker for region, _ in results: 507*b7c941bbSAndroid Build Coastguard Worker x, y, w, h = region 508*b7c941bbSAndroid Build Coastguard Worker padding = min(w, h) * _BOX_PADDING_RATIO 509*b7c941bbSAndroid Build Coastguard Worker left = int(x + padding) 510*b7c941bbSAndroid Build Coastguard Worker top = int(y + padding) 511*b7c941bbSAndroid Build Coastguard Worker right = int(x + w - padding) 512*b7c941bbSAndroid Build Coastguard Worker bottom = int(y + h - padding) 513*b7c941bbSAndroid Build Coastguard Worker noise_stats = image_processing_utils.compute_patch_noise( 514*b7c941bbSAndroid Build Coastguard Worker img, (left, top, (right - left), (bottom - top))) 515*b7c941bbSAndroid Build Coastguard Worker luma_noise_values.append(noise_stats['luma']) 516*b7c941bbSAndroid Build Coastguard Worker chroma_u_noise_values.append(noise_stats['chroma_u']) 517*b7c941bbSAndroid Build Coastguard Worker chroma_v_noise_values.append(noise_stats['chroma_v']) 518*b7c941bbSAndroid Build Coastguard Worker 519*b7c941bbSAndroid Build Coastguard Worker box_labels = [f'Box {i + 1}' for i in range(len(results))] 520*b7c941bbSAndroid Build Coastguard Worker 521*b7c941bbSAndroid Build Coastguard Worker plt.figure(figsize=_FIG_SIZE) 522*b7c941bbSAndroid Build Coastguard Worker plt.plot(box_labels, luma_noise_values, marker='o', linestyle='-', 523*b7c941bbSAndroid Build Coastguard Worker color='b', label='luma') 524*b7c941bbSAndroid Build Coastguard Worker plt.plot(box_labels, chroma_u_noise_values, marker='o', linestyle='-', 525*b7c941bbSAndroid Build Coastguard Worker color='r', label='chroma u') 526*b7c941bbSAndroid Build Coastguard Worker plt.plot(box_labels, chroma_v_noise_values, marker='o', linestyle='-', 527*b7c941bbSAndroid Build Coastguard Worker color='g', label='chroma v') 528*b7c941bbSAndroid Build Coastguard Worker plt.legend() 529*b7c941bbSAndroid Build Coastguard Worker 530*b7c941bbSAndroid Build Coastguard Worker plt.title('Luma, Chroma U, and Chroma V Noise per Box') 531*b7c941bbSAndroid Build Coastguard Worker plt.xlabel('Box') 532*b7c941bbSAndroid Build Coastguard Worker plt.ylabel('Noise (std dev)') 533*b7c941bbSAndroid Build Coastguard Worker plt.grid('True') 534*b7c941bbSAndroid Build Coastguard Worker plt.xticks(rotation=45) 535*b7c941bbSAndroid Build Coastguard Worker plt.savefig(f'{file_stem}_noise_per_box_plot.png', dpi=300) 536*b7c941bbSAndroid Build Coastguard Worker plt.close() 537*b7c941bbSAndroid Build Coastguard Worker # print the chart luma values for telemetry purposes 538*b7c941bbSAndroid Build Coastguard Worker # do not convert to logging.debug 539*b7c941bbSAndroid Build Coastguard Worker print(f'{test_name}_noise_luma: {luma_noise_values}') 540*b7c941bbSAndroid Build Coastguard Worker print(f'{test_name}_noise_chroma_u: {chroma_u_noise_values}') 541*b7c941bbSAndroid Build Coastguard Worker print(f'{test_name}_noise_chroma_v: {chroma_v_noise_values}') 542*b7c941bbSAndroid Build Coastguard Worker 543*b7c941bbSAndroid Build Coastguard Worker 544*b7c941bbSAndroid Build Coastguard Workerdef _sort_by_columns(regions): 545*b7c941bbSAndroid Build Coastguard Worker """Sort the regions by columns and then by row within each column. 546*b7c941bbSAndroid Build Coastguard Worker 547*b7c941bbSAndroid Build Coastguard Worker These regions are part of scene_low_light. 548*b7c941bbSAndroid Build Coastguard Worker 549*b7c941bbSAndroid Build Coastguard Worker Args: 550*b7c941bbSAndroid Build Coastguard Worker regions: The tuple of (box, luminance) of each square. 551*b7c941bbSAndroid Build Coastguard Worker Returns: 552*b7c941bbSAndroid Build Coastguard Worker array; an array of tuples of (box, luminance) sorted by columns then by row 553*b7c941bbSAndroid Build Coastguard Worker within each column. 554*b7c941bbSAndroid Build Coastguard Worker """ 555*b7c941bbSAndroid Build Coastguard Worker # The input is 20 elements. The first two and last two elements represent the 556*b7c941bbSAndroid Build Coastguard Worker # 4 boxes on the outside used for diagnostics. Boxes in indices 2 through 17 557*b7c941bbSAndroid Build Coastguard Worker # represent the elements in the 4x4 grid. 558*b7c941bbSAndroid Build Coastguard Worker 559*b7c941bbSAndroid Build Coastguard Worker # Sort all elements by column 560*b7c941bbSAndroid Build Coastguard Worker col_sorted = sorted(regions, key=lambda r: r[0][0]) 561*b7c941bbSAndroid Build Coastguard Worker 562*b7c941bbSAndroid Build Coastguard Worker # Sort elements within each column by row 563*b7c941bbSAndroid Build Coastguard Worker result = [] 564*b7c941bbSAndroid Build Coastguard Worker result.extend(sorted(col_sorted[:2], key=lambda r: r[0][1])) 565*b7c941bbSAndroid Build Coastguard Worker 566*b7c941bbSAndroid Build Coastguard Worker for i in range(4): 567*b7c941bbSAndroid Build Coastguard Worker # take 4 rows per column and then sort the rows 568*b7c941bbSAndroid Build Coastguard Worker # skip the first two elements 569*b7c941bbSAndroid Build Coastguard Worker offset = i*4+2 570*b7c941bbSAndroid Build Coastguard Worker col = col_sorted[offset:(offset+4)] 571*b7c941bbSAndroid Build Coastguard Worker result.extend(sorted(col, key=lambda r: r[0][1])) 572*b7c941bbSAndroid Build Coastguard Worker 573*b7c941bbSAndroid Build Coastguard Worker result.extend(sorted(col_sorted[-2:], key=lambda r: r[0][1])) 574*b7c941bbSAndroid Build Coastguard Worker return result 575*b7c941bbSAndroid Build Coastguard Worker 576*b7c941bbSAndroid Build Coastguard Worker 577*b7c941bbSAndroid Build Coastguard Workerdef analyze_low_light_scene_capture( 578*b7c941bbSAndroid Build Coastguard Worker file_stem, 579*b7c941bbSAndroid Build Coastguard Worker img, 580*b7c941bbSAndroid Build Coastguard Worker avg_luminance_threshold=_LOW_LIGHT_BOOST_AVG_LUMINANCE_THRESH, 581*b7c941bbSAndroid Build Coastguard Worker avg_delta_luminance_threshold=_LOW_LIGHT_BOOST_AVG_DELTA_LUMINANCE_THRESH): 582*b7c941bbSAndroid Build Coastguard Worker """Analyze a captured frame to check if it meets low light scene criteria. 583*b7c941bbSAndroid Build Coastguard Worker 584*b7c941bbSAndroid Build Coastguard Worker The capture is cropped first, then detects for boxes, and then computes the 585*b7c941bbSAndroid Build Coastguard Worker luminance of each box. 586*b7c941bbSAndroid Build Coastguard Worker 587*b7c941bbSAndroid Build Coastguard Worker Args: 588*b7c941bbSAndroid Build Coastguard Worker file_stem: The file prefix for results saved. 589*b7c941bbSAndroid Build Coastguard Worker img: numpy array; The captured image loaded by cv2 as and available for 590*b7c941bbSAndroid Build Coastguard Worker analysis. 591*b7c941bbSAndroid Build Coastguard Worker avg_luminance_threshold: minimum average luminance of the first 6 boxes. 592*b7c941bbSAndroid Build Coastguard Worker avg_delta_luminance_threshold: minimum average difference in luminance 593*b7c941bbSAndroid Build Coastguard Worker of the first 5 successive boxes of luminance. 594*b7c941bbSAndroid Build Coastguard Worker """ 595*b7c941bbSAndroid Build Coastguard Worker cv2.imwrite(f'{file_stem}_original.jpg', img) 596*b7c941bbSAndroid Build Coastguard Worker img = _crop(img) 597*b7c941bbSAndroid Build Coastguard Worker cv2.imwrite(f'{file_stem}_cropped.jpg', img) 598*b7c941bbSAndroid Build Coastguard Worker boxes = _find_boxes(img) 599*b7c941bbSAndroid Build Coastguard Worker if len(boxes) != _EXPECTED_NUM_OF_BOXES: 600*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('The captured image failed to detect the expected ' 601*b7c941bbSAndroid Build Coastguard Worker 'number of boxes. ' 602*b7c941bbSAndroid Build Coastguard Worker 'Check the captured image to see if the image was ' 603*b7c941bbSAndroid Build Coastguard Worker 'correctly captured and try again. ' 604*b7c941bbSAndroid Build Coastguard Worker f'Actual: {len(boxes)}, ' 605*b7c941bbSAndroid Build Coastguard Worker f'Expected: {_EXPECTED_NUM_OF_BOXES}') 606*b7c941bbSAndroid Build Coastguard Worker 607*b7c941bbSAndroid Build Coastguard Worker regions = _compute_luminance_regions(img, boxes) 608*b7c941bbSAndroid Build Coastguard Worker 609*b7c941bbSAndroid Build Coastguard Worker # Sorted so each column is read left to right 610*b7c941bbSAndroid Build Coastguard Worker sorted_regions = _sort_by_columns(regions) 611*b7c941bbSAndroid Build Coastguard Worker img = _correct_image_rotation(img, sorted_regions) 612*b7c941bbSAndroid Build Coastguard Worker cv2.imwrite(f'{file_stem}_rotated.jpg', img) 613*b7c941bbSAndroid Build Coastguard Worker 614*b7c941bbSAndroid Build Coastguard Worker # The orientation of the image may have changed which will affect the 615*b7c941bbSAndroid Build Coastguard Worker # coordinates of the squares. Therefore, locate the squares, recompute the 616*b7c941bbSAndroid Build Coastguard Worker # regions, and sort again 617*b7c941bbSAndroid Build Coastguard Worker boxes = _find_boxes(img) 618*b7c941bbSAndroid Build Coastguard Worker regions = _compute_luminance_regions(img, boxes) 619*b7c941bbSAndroid Build Coastguard Worker sorted_regions = _sort_by_columns(regions) 620*b7c941bbSAndroid Build Coastguard Worker 621*b7c941bbSAndroid Build Coastguard Worker # Reorder this so the regions are increasing in luminance according to the 622*b7c941bbSAndroid Build Coastguard Worker # Hilbert curve arrangement pattern of the grid 623*b7c941bbSAndroid Build Coastguard Worker # See scene_low_light_reference.png which indicates the order of each 624*b7c941bbSAndroid Build Coastguard Worker # box 625*b7c941bbSAndroid Build Coastguard Worker hilbert_ordered = [ 626*b7c941bbSAndroid Build Coastguard Worker sorted_regions[17], 627*b7c941bbSAndroid Build Coastguard Worker sorted_regions[13], 628*b7c941bbSAndroid Build Coastguard Worker sorted_regions[12], 629*b7c941bbSAndroid Build Coastguard Worker sorted_regions[16], 630*b7c941bbSAndroid Build Coastguard Worker sorted_regions[15], 631*b7c941bbSAndroid Build Coastguard Worker sorted_regions[14], 632*b7c941bbSAndroid Build Coastguard Worker sorted_regions[10], 633*b7c941bbSAndroid Build Coastguard Worker sorted_regions[11], 634*b7c941bbSAndroid Build Coastguard Worker sorted_regions[7], 635*b7c941bbSAndroid Build Coastguard Worker sorted_regions[6], 636*b7c941bbSAndroid Build Coastguard Worker sorted_regions[2], 637*b7c941bbSAndroid Build Coastguard Worker sorted_regions[3], 638*b7c941bbSAndroid Build Coastguard Worker sorted_regions[4], 639*b7c941bbSAndroid Build Coastguard Worker sorted_regions[8], 640*b7c941bbSAndroid Build Coastguard Worker sorted_regions[9], 641*b7c941bbSAndroid Build Coastguard Worker sorted_regions[5], 642*b7c941bbSAndroid Build Coastguard Worker ] 643*b7c941bbSAndroid Build Coastguard Worker 644*b7c941bbSAndroid Build Coastguard Worker test_name = os.path.basename(file_stem) 645*b7c941bbSAndroid Build Coastguard Worker 646*b7c941bbSAndroid Build Coastguard Worker _plot_results(hilbert_ordered, file_stem) 647*b7c941bbSAndroid Build Coastguard Worker _plot_successive_difference(hilbert_ordered, file_stem) 648*b7c941bbSAndroid Build Coastguard Worker _plot_noise(hilbert_ordered, file_stem, img, test_name) 649*b7c941bbSAndroid Build Coastguard Worker 650*b7c941bbSAndroid Build Coastguard Worker _draw_luminance(img, regions) 651*b7c941bbSAndroid Build Coastguard Worker cv2.imwrite(f'{file_stem}_result.jpg', img) 652*b7c941bbSAndroid Build Coastguard Worker 653*b7c941bbSAndroid Build Coastguard Worker avg = _compute_avg(hilbert_ordered) 654*b7c941bbSAndroid Build Coastguard Worker delta_avg = _compute_avg_delta_of_successive_boxes(hilbert_ordered) 655*b7c941bbSAndroid Build Coastguard Worker 656*b7c941bbSAndroid Build Coastguard Worker # the following print statements are necessary for telemetry 657*b7c941bbSAndroid Build Coastguard Worker # do not convert to logging.debug 658*b7c941bbSAndroid Build Coastguard Worker print(f'{test_name}_avg_luma: {avg:.2f}') 659*b7c941bbSAndroid Build Coastguard Worker print(f'{test_name}_delta_avg_luma: {delta_avg:.2f}') 660*b7c941bbSAndroid Build Coastguard Worker chart_luma_values = [v[1] for v in hilbert_ordered] 661*b7c941bbSAndroid Build Coastguard Worker print(f'{test_name}_chart_luma: {chart_luma_values}') 662*b7c941bbSAndroid Build Coastguard Worker 663*b7c941bbSAndroid Build Coastguard Worker logging.debug('average luminance of the 6 boxes: %.2f', avg) 664*b7c941bbSAndroid Build Coastguard Worker logging.debug('average difference in luminance of 5 successive boxes: %.2f', 665*b7c941bbSAndroid Build Coastguard Worker delta_avg) 666*b7c941bbSAndroid Build Coastguard Worker if avg < float(avg_luminance_threshold): 667*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('Average luminance of the first 6 boxes did not ' 668*b7c941bbSAndroid Build Coastguard Worker 'meet minimum requirements for low light scene ' 669*b7c941bbSAndroid Build Coastguard Worker 'criteria. ' 670*b7c941bbSAndroid Build Coastguard Worker f'Actual: {avg:.2f}, ' 671*b7c941bbSAndroid Build Coastguard Worker f'Expected: {avg_luminance_threshold}') 672*b7c941bbSAndroid Build Coastguard Worker if delta_avg < float(avg_delta_luminance_threshold): 673*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('The average difference in luminance of the first 5 ' 674*b7c941bbSAndroid Build Coastguard Worker 'successive boxes did not meet minimum requirements ' 675*b7c941bbSAndroid Build Coastguard Worker 'for low light scene criteria. ' 676*b7c941bbSAndroid Build Coastguard Worker f'Actual: {delta_avg:.2f}, ' 677*b7c941bbSAndroid Build Coastguard Worker f'Expected: {avg_delta_luminance_threshold}') 678