CameraITS: add aspect ratio detection and crop test
Test aspect ratio and check if images are cropped correctly under
each output size.
The test image is a black circle inside a black square. When raw
capture is available, set the height vs. width ratio of the circle
in the full-frame raw as ground truth. Then compare with images of
request combinations of different formats ("jpeg" and "yuv") and
sizes. If raw capture is unavailable, take a picture of the test
image right in front to eliminate shooting angle effect.
The height vs. width ratio for the circle should be close to 1.
Considering shooting position error, aspect ratio greater than 1.05
or smaller than 0.95 will fail the test.
Change-Id: I051890e5aa43e2ffa66e563b989223b03de1571f
diff --git a/apps/CameraITS/CameraITS.pdf b/apps/CameraITS/CameraITS.pdf
index 8953af9..01ecf54 100644
--- a/apps/CameraITS/CameraITS.pdf
+++ b/apps/CameraITS/CameraITS.pdf
Binary files differ
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index 71dd366..dd35ca2 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -36,6 +36,17 @@
print "Test skipped"
sys.exit(SKIP_RET_CODE)
+def level3(props):
+ """Returns whether a device is a LEVEL3 capability camera2 device.
+
+ Args:
+ props: Camera properties object.
+
+ Returns:
+ Boolean.
+ """
+ return props.has_key("android.info.supportedHardwareLevel") and \
+ props["android.info.supportedHardwareLevel"] == 3
def full(props):
"""Returns whether a device is a FULL capability camera2 device.
@@ -73,6 +84,19 @@
return props.has_key("android.info.supportedHardwareLevel") and \
props["android.info.supportedHardwareLevel"] == 2
+def radial_distortion_correction(props):
+ """Returns whether a device supports RADIAL_DISTORTION_CORRECTION
+ capabilities.
+
+ Args:
+ props: Camera properties object.
+
+ Returns:
+ Boolean.
+ """
+ return props.has_key("android.lens.radialDistortion") and \
+ props["android.lens.radialDistortion"] is not None
+
def manual_sensor(props):
"""Returns whether a device supports MANUAL_SENSOR capabilities.
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 835a4a4..008f095 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -595,8 +595,20 @@
formats = ['yuv']
ncap = len(cmd["captureRequests"])
nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
+ # Only allow yuv output to multiple targets
+ yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"]
+ n_yuv = len(yuv_surfaces)
+ # Compute the buffer size of YUV targets
+ yuv_sizes = [c["width"]*c["height"]*3/2 for c in yuv_surfaces]
+ # Currently we don't pass enough metadta from ItsService to distinguish
+ # different yuv stream of same buffer size
+ if len(yuv_sizes) != len(set(yuv_sizes)):
+ raise its.error.Error(
+ 'ITS does not support yuv outputs of same buffer size')
if len(formats) > len(set(formats)):
- raise its.error.Error('Duplicate format requested')
+ if n_yuv != len(formats) - len(set(formats)) + 1:
+ raise its.error.Error('Duplicate format requested')
+
raw_formats = 0;
raw_formats += 1 if "dng" in formats else 0
raw_formats += 1 if "raw" in formats else 0
@@ -627,19 +639,24 @@
# the burst, however individual images of different formats can come
# out in any order for that capture.
nbufs = 0
- bufs = {"yuv":[], "raw":[], "raw10":[], "raw12":[],
+ bufs = {"raw":[], "raw10":[], "raw12":[],
"rawStats":[], "dng":[], "jpeg":[]}
+ yuv_bufs = {size:[] for size in yuv_sizes}
mds = []
widths = None
heights = None
while nbufs < ncap*nsurf or len(mds) < ncap:
jsonObj,buf = self.__read_response_from_socket()
- if jsonObj['tag'] in ['jpegImage', 'yuvImage', 'rawImage', \
+ if jsonObj['tag'] in ['jpegImage', 'rawImage', \
'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \
and buf is not None:
fmt = jsonObj['tag'][:-5]
bufs[fmt].append(buf)
nbufs += 1
+ elif jsonObj['tag'] == 'yuvImage':
+ buf_size = numpy.product(buf.shape)
+ yuv_bufs[buf_size].append(buf)
+ nbufs += 1
elif jsonObj['tag'] == 'captureResults':
mds.append(jsonObj['objValue']['captureResult'])
outputs = jsonObj['objValue']['outputs']
@@ -653,11 +670,15 @@
objs = []
for i in range(ncap):
obj = {}
- obj["data"] = bufs[fmt][i]
obj["width"] = widths[j]
obj["height"] = heights[j]
obj["format"] = fmt
obj["metadata"] = mds[i]
+ if fmt == 'yuv':
+ buf_size = widths[j] * heights[j] * 3 / 2
+ obj["data"] = yuv_bufs[buf_size][i]
+ else:
+ obj["data"] = bufs[fmt][i]
objs.append(obj)
rets.append(objs if ncap>1 else objs[0])
self.sock.settimeout(self.SOCK_TIMEOUT)
diff --git a/apps/CameraITS/tests/scene4/SampleTarget.pdf b/apps/CameraITS/tests/scene4/SampleTarget.pdf
new file mode 100644
index 0000000..e693b34
--- /dev/null
+++ b/apps/CameraITS/tests/scene4/SampleTarget.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
new file mode 100644
index 0000000..7733c5d
--- /dev/null
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -0,0 +1,408 @@
+# Copyright 2015 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 its.image
+import its.caps
+import its.device
+import its.objects
+import os.path
+import cv2
+import numpy as np
+
+
+def main():
+ """ Test aspect ratio and check if images are cropped correctly under each
+ output size
+ Aspect ratio test runs on level3, full and limited devices. Crop test only
+ runs on full and level3 devices.
+ The test image is a black circle inside a black square. When raw capture is
+ available, set the height vs. width ratio of the circle in the full-frame
+ raw as ground truth. Then compare with images of request combinations of
+ different formats ("jpeg" and "yuv") and sizes.
+ If raw capture is unavailable, take a picture of the test image right in
+ front to eliminate shooting angle effect. the height vs. width ratio for
+ the circle should be close to 1. Considering shooting position error, aspect
+ ratio greater than 1.05 or smaller than 0.95 will fail the test.
+ """
+ NAME = os.path.basename(__file__).split(".")[0]
+ LARGE_SIZE = 2000 # Define the size of a large image
+ # pass/fail threshold of large size images for aspect ratio test
+ THRES_L_AR_TEST = 0.02
+ # pass/fail threshold of mini size images for aspect ratio test
+ THRES_XS_AR_TEST = 0.05
+ # pass/fail threshold of large size images for crop test
+ THRES_L_CP_TEST = 0.02
+ # pass/fail threshold of mini size images for crop test
+ THRES_XS_CP_TEST = 0.05
+ PREVIEW_SIZE = (1920, 1080) # preview size
+ aspect_ratio_gt = 1 # ground truth
+ failed_ar = [] # streams failed the aspect ration test
+ failed_crop = [] # streams failed the crop test
+ format_list = [] # format list for multiple capture objects.
+ # Do multi-capture of "iter" and "cmpr". Iterate through all the
+ # available sizes of "iter", and only use the size specified for "cmpr"
+ # Do single-capture to cover untouched sizes in multi-capture when needed.
+ format_list.append({"iter": "yuv", "iter_max": None,
+ "cmpr": "yuv", "cmpr_size": PREVIEW_SIZE})
+ format_list.append({"iter": "yuv", "iter_max": PREVIEW_SIZE,
+ "cmpr": "jpeg", "cmpr_size": None})
+ format_list.append({"iter": "yuv", "iter_max": PREVIEW_SIZE,
+ "cmpr": "raw", "cmpr_size": None})
+ format_list.append({"iter": "jpeg", "iter_max": None,
+ "cmpr": "raw", "cmpr_size": None})
+ format_list.append({"iter": "jpeg", "iter_max": None,
+ "cmpr": "yuv", "cmpr_size": PREVIEW_SIZE})
+ with its.device.ItsSession() as cam:
+ props = cam.get_camera_properties()
+ # Todo: test for radial distortion enabled devices has not yet been
+ # implemented
+ its.caps.skip_unless(not its.caps.radial_distortion_correction(props))
+ full_device = its.caps.full(props)
+ limited_device = its.caps.limited(props)
+ its.caps.skip_unless(full_device or limited_device)
+ level3_device = its.caps.level3(props)
+ raw_avlb = its.caps.raw16(props)
+ run_crop_test = (level3_device or full_device) and raw_avlb
+ if not run_crop_test:
+ print "Crop test skipped"
+ # Converge 3A and get the estimates.
+ sens, exp, gains, xform, focus = cam.do_3a(get_results=True,
+ lock_ae=True, lock_awb=True)
+ print "AE sensitivity %d, exposure %dms" % (sens, exp / 1000000.0)
+ print "AWB gains", gains
+ print "AWB transform", xform
+ print "AF distance", focus
+ req = its.objects.manual_capture_request(
+ sens, exp, True, props)
+ xform_rat = its.objects.float_to_rational(xform)
+ req["android.colorCorrection.gains"] = gains
+ req["android.colorCorrection.transform"] = xform_rat
+
+ # If raw capture is available, use it as ground truth.
+ if raw_avlb:
+ # Capture full-frame raw. Use its aspect ratio and circle center
+ # location as ground truth for the other jepg or yuv images.
+ out_surface = {"format": "raw"}
+ cap_raw = cam.do_capture(req, out_surface)
+ print "Captured %s %dx%d" % ("raw", cap_raw["width"],
+ cap_raw["height"])
+ img_raw = its.image.convert_capture_to_rgb_image(cap_raw,
+ props=props)
+ size_raw = img_raw.shape
+ img_name = "%s_%s_w%d_h%d.png" \
+ % (NAME, "raw", size_raw[1], size_raw[0])
+ aspect_ratio_gt, cc_ct_gt, circle_size_raw = measure_aspect_ratio(
+ img_raw, 1, img_name)
+ # Normalize the circle size to 1/4 of the image size, so that
+ # circle size won"t affect the crop test result
+ factor_cp_thres = (min(size_raw[0:1])/4.0) / max(circle_size_raw)
+ thres_l_cp_test = THRES_L_CP_TEST * factor_cp_thres
+ thres_xs_cp_test = THRES_XS_CP_TEST * factor_cp_thres
+
+ # Take pictures of each settings with all the image sizes available.
+ for fmt in format_list:
+ fmt_iter = fmt["iter"]
+ fmt_cmpr = fmt["cmpr"]
+ dual_target = fmt_cmpr is not "none"
+ # Get the size of "cmpr"
+ if dual_target:
+ size_cmpr = its.objects.get_available_output_sizes(
+ fmt_cmpr, props, fmt["cmpr_size"])[0]
+ for size_iter in its.objects.get_available_output_sizes(
+ fmt_iter, props, fmt["iter_max"]):
+ w_iter = size_iter[0]
+ h_iter = size_iter[1]
+ # Skip testing same format/size combination
+ # ITS does not handle that properly now
+ if dual_target and \
+ w_iter == size_cmpr[0] and \
+ h_iter == size_cmpr[1] and \
+ fmt_iter == fmt_cmpr:
+ continue
+ out_surface = [{"width": w_iter,
+ "height": h_iter,
+ "format": fmt_iter}]
+ if dual_target:
+ out_surface.append({"width": size_cmpr[0],
+ "height": size_cmpr[1],
+ "format": fmt_cmpr})
+ cap = cam.do_capture(req, out_surface)
+ if dual_target:
+ frm_iter = cap[0]
+ else:
+ frm_iter = cap
+ assert (frm_iter["format"] == fmt_iter)
+ assert (frm_iter["width"] == w_iter)
+ assert (frm_iter["height"] == h_iter)
+ print "Captured %s with %s %dx%d" \
+ % (fmt_iter, fmt_cmpr, w_iter, h_iter)
+ img = its.image.convert_capture_to_rgb_image(frm_iter)
+ img_name = "%s_%s_with_%s_w%d_h%d.png" \
+ % (NAME, fmt_iter, fmt_cmpr, w_iter, h_iter)
+ aspect_ratio, cc_ct, _ = measure_aspect_ratio(img, raw_avlb,
+ img_name)
+ # check pass/fail for aspect ratio
+ # image size >= LARGE_SIZE: use THRES_L_AR_TEST
+ # image size == 0 (extreme case): THRES_XS_AR_TEST
+ # 0 < image size < LARGE_SIZE: scale between THRES_XS_AR_TEST
+ # and THRES_L_AR_TEST
+ thres_ar_test = max(THRES_L_AR_TEST,
+ THRES_XS_AR_TEST + max(w_iter, h_iter) *
+ (THRES_L_AR_TEST-THRES_XS_AR_TEST)/LARGE_SIZE)
+ thres_range_ar = (aspect_ratio_gt-thres_ar_test,
+ aspect_ratio_gt+thres_ar_test)
+ if aspect_ratio < thres_range_ar[0] \
+ or aspect_ratio > thres_range_ar[1]:
+ failed_ar.append({"fmt_iter": fmt_iter,
+ "fmt_cmpr": fmt_cmpr,
+ "w": w_iter, "h": h_iter,
+ "ar": aspect_ratio,
+ "valid_range": thres_range_ar})
+
+ # check pass/fail for crop
+ if run_crop_test:
+ # image size >= LARGE_SIZE: use thres_l_cp_test
+ # image size == 0 (extreme case): thres_xs_cp_test
+ # 0 < image size < LARGE_SIZE: scale between
+ # thres_xs_cp_test and thres_l_cp_test
+ thres_hori_cp_test = max(thres_l_cp_test,
+ thres_xs_cp_test + w_iter *
+ (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
+ thres_range_h_cp = (cc_ct_gt["hori"]-thres_hori_cp_test,
+ cc_ct_gt["hori"]+thres_hori_cp_test)
+ thres_vert_cp_test = max(thres_l_cp_test,
+ thres_xs_cp_test + h_iter *
+ (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
+ thres_range_v_cp = (cc_ct_gt["vert"]-thres_vert_cp_test,
+ cc_ct_gt["vert"]+thres_vert_cp_test)
+ if cc_ct["hori"] < thres_range_h_cp[0] \
+ or cc_ct["hori"] > thres_range_h_cp[1] \
+ or cc_ct["vert"] < thres_range_v_cp[0] \
+ or cc_ct["vert"] > thres_range_v_cp[1]:
+ failed_crop.append({"fmt_iter": fmt_iter,
+ "fmt_cmpr": fmt_cmpr,
+ "w": w_iter, "h": h_iter,
+ "ct_hori": cc_ct["hori"],
+ "ct_vert": cc_ct["vert"],
+ "valid_range_h": thres_range_h_cp,
+ "valid_range_v": thres_range_v_cp})
+
+ # Print aspect ratio test results
+ failed_image_number_for_aspect_ratio_test = len(failed_ar)
+ if failed_image_number_for_aspect_ratio_test > 0:
+ print "\nAspect ratio test summary"
+ print "Images failed in the aspect ratio test:"
+ print "Aspect ratio value: width / height"
+ for fa in failed_ar:
+ print "%s with %s %dx%d: %.3f; valid range: %.3f ~ %.3f" % \
+ (fa["fmt_iter"], fa["fmt_cmpr"], fa["w"], fa["h"], fa["ar"],
+ fa["valid_range"][0], fa["valid_range"][1])
+
+ # Print crop test results
+ failed_image_number_for_crop_test = len(failed_crop)
+ if failed_image_number_for_crop_test > 0:
+ print "\nCrop test summary"
+ print "Images failed in the crop test:"
+ print "Circle center position, (horizontal x vertical), listed " \
+ "below is relative to the image center."
+ for fc in failed_crop:
+ print "%s with %s %dx%d: %.3f x %.3f; " \
+ "valid horizontal range: %.3f ~ %.3f; " \
+ "valid vertical range: %.3f ~ %.3f" \
+ % (fc["fmt_iter"], fc["fmt_cmpr"], fc["w"], fc["h"],
+ fc["ct_hori"], fc["ct_vert"], fc["valid_range_h"][0],
+ fc["valid_range_h"][1], fc["valid_range_v"][0],
+ fc["valid_range_v"][1])
+
+ assert (failed_image_number_for_aspect_ratio_test == 0)
+ if level3_device:
+ assert (failed_image_number_for_crop_test == 0)
+
+
+def measure_aspect_ratio(img, raw_avlb, img_name):
+ """ Measure the aspect ratio of the black circle in the test image.
+
+ Args:
+ img: Numpy float image array in RGB, with pixel values in [0,1].
+ raw_avlb: True: raw capture is available; False: raw capture is not
+ available.
+ img_name: string with image info of format and size.
+ Returns:
+ aspect_ratio: aspect ratio number in float.
+ cc_ct: circle center position relative to the center of image.
+ (circle_w, circle_h): tuple of the circle size
+ """
+ size = img.shape
+ img = img * 255
+ # Gray image
+ img_gray = 0.299 * img[:,:,2] + 0.587 * img[:,:,1] + 0.114 * img[:,:,0]
+
+ # otsu threshold to binarize the image
+ ret3, img_bw = cv2.threshold(np.uint8(img_gray), 0, 255,
+ cv2.THRESH_BINARY + cv2.THRESH_OTSU)
+
+ # connected component
+ contours, hierarchy = cv2.findContours(255-img_bw, cv2.RETR_TREE,
+ cv2.CHAIN_APPROX_SIMPLE)
+
+ # Check each component and find the black circle
+ min_cmpt = size[0] * size[1] * 0.005
+ max_cmpt = size[0] * size[1] * 0.35
+ num_circle = 0
+ aspect_ratio = 0
+ for ct, hrch in zip(contours, hierarchy[0]):
+ # The radius of the circle is 1/3 of the length of the square, meaning
+ # around 1/3 of the area of the square
+ # Parental component should exist and the area is acceptable.
+ # The coutour of a circle should have at least 5 points
+ child_area = cv2.contourArea(ct)
+ if hrch[3] == -1 or child_area < min_cmpt or child_area > max_cmpt or \
+ len(ct) < 15:
+ continue
+ # Check the shapes of current component and its parent
+ child_shape = component_shape(ct)
+ parent = hrch[3]
+ prt_shape = component_shape(contours[parent])
+ prt_area = cv2.contourArea(contours[parent])
+ dist_x = abs(child_shape["ctx"]-prt_shape["ctx"])
+ dist_y = abs(child_shape["cty"]-prt_shape["cty"])
+ # 1. 0.56*Parent"s width < Child"s width < 0.76*Parent"s width.
+ # 2. 0.56*Parent"s height < Child"s height < 0.76*Parent"s height.
+ # 3. Child"s width > 0.1*Image width
+ # 4. Child"s height > 0.1*Image height
+ # 5. 0.25*Parent"s area < Child"s area < 0.45*Parent"s area
+ # 6. Child is a black, and Parent is white
+ # 7. Center of Child and center of parent should overlap
+ if prt_shape["width"] * 0.56 < child_shape["width"] \
+ < prt_shape["width"] * 0.76 \
+ and prt_shape["height"] * 0.56 < child_shape["height"] \
+ < prt_shape["height"] * 0.76 \
+ and child_shape["width"] > 0.1 * size[1] \
+ and child_shape["height"] > 0.1 * size[0] \
+ and 0.30 * prt_area < child_area < 0.50 * prt_area \
+ and img_bw[child_shape["cty"]][child_shape["ctx"]] == 0 \
+ and img_bw[child_shape["top"]][child_shape["left"]] == 255 \
+ and dist_x < 0.1 * child_shape["width"] \
+ and dist_y < 0.1 * child_shape["height"]:
+ # If raw capture is not available, check the camera is placed right
+ # in front of the test page:
+ # 1. Distances between parent and child horizontally on both side,0
+ # dist_left and dist_right, should be close.
+ # 2. Distances between parent and child vertically on both side,
+ # dist_top and dist_bottom, should be close.
+ if not raw_avlb:
+ dist_left = child_shape["left"] - prt_shape["left"]
+ dist_right = prt_shape["right"] - child_shape["right"]
+ dist_top = child_shape["top"] - prt_shape["top"]
+ dist_bottom = prt_shape["bottom"] - child_shape["bottom"]
+ if abs(dist_left-dist_right) > 0.05 * child_shape["width"] or \
+ abs(dist_top-dist_bottom) > \
+ 0.05 * child_shape["height"]:
+ continue
+ # Calculate aspect ratio
+ aspect_ratio = float(child_shape["width"]) / \
+ float(child_shape["height"])
+ circle_ctx = child_shape["ctx"]
+ circle_cty = child_shape["cty"]
+ circle_w = float(child_shape["width"])
+ circle_h = float(child_shape["height"])
+ cc_ct = {"hori": float(child_shape["ctx"]-size[1]/2) / circle_w,
+ "vert": float(child_shape["cty"]-size[0]/2) / circle_h}
+ num_circle += 1
+ # If more than one circle found, break
+ if num_circle == 2:
+ break
+
+ if num_circle == 0:
+ its.image.write_image(img/255, img_name, True)
+ print "No black circle was detected. Please take pictures according " \
+ "to instruction carefully!\n"
+ assert (num_circle == 1)
+
+ if num_circle > 1:
+ its.image.write_image(img/255, img_name, True)
+ print "More than one black circle was detected. Background of scene " \
+ "may be too complex.\n"
+ assert (num_circle == 1)
+
+ # draw circle center and image center, and save the image
+ line_width = max(1, max(size)/500)
+ move_text_dist = line_width * 3
+ cv2.line(img, (circle_ctx, circle_cty), (size[1]/2, size[0]/2),
+ (255, 0, 0), line_width)
+ if circle_cty > size[0]/2:
+ move_text_down_circle = 4
+ move_text_down_image = -1
+ else:
+ move_text_down_circle = -1
+ move_text_down_image = 4
+ if circle_ctx > size[1]/2:
+ move_text_right_circle = 2
+ move_text_right_image = -1
+ else:
+ move_text_right_circle = -1
+ move_text_right_image = 2
+ # circle center
+ text_circle_x = move_text_dist * move_text_right_circle + circle_ctx
+ text_circle_y = move_text_dist * move_text_down_circle + circle_cty
+ cv2.circle(img, (circle_ctx, circle_cty), line_width*2, (255, 0, 0), -1)
+ cv2.putText(img, "circle center", (text_circle_x, text_circle_y),
+ cv2.FONT_HERSHEY_SIMPLEX, line_width/2.0, (255, 0, 0),
+ line_width)
+ # image center
+ text_imgct_x = move_text_dist * move_text_right_image + size[1]/2
+ text_imgct_y = move_text_dist * move_text_down_image + size[0]/2
+ cv2.circle(img, (size[1]/2, size[0]/2), line_width*2, (255, 0, 0), -1)
+ cv2.putText(img, "image center", (text_imgct_x, text_imgct_y),
+ cv2.FONT_HERSHEY_SIMPLEX, line_width/2.0, (255, 0, 0),
+ line_width)
+ its.image.write_image(img/255, img_name, True)
+
+ print "Aspect ratio: %.3f" % aspect_ratio
+ print "Circle center position regarding to image center: %.3fx%.3f" % \
+ (cc_ct["vert"], cc_ct["hori"])
+ return aspect_ratio, cc_ct, (circle_w, circle_h)
+
+
+def component_shape(contour):
+ """ Measure the shape for a connected component in the aspect ratio test
+
+ Args:
+ contour: return from cv2.findContours. A list of pixel coordinates of
+ the contour.
+
+ Returns:
+ The most left, right, top, bottom pixel location, height, width, and
+ the center pixel location of the contour.
+ """
+ shape = {"left": np.inf, "right": 0, "top": np.inf, "bottom": 0,
+ "width": 0, "height": 0, "ctx": 0, "cty": 0}
+ for pt in contour:
+ if pt[0][0] < shape["left"]:
+ shape["left"] = pt[0][0]
+ if pt[0][0] > shape["right"]:
+ shape["right"] = pt[0][0]
+ if pt[0][1] < shape["top"]:
+ shape["top"] = pt[0][1]
+ if pt[0][1] > shape["bottom"]:
+ shape["bottom"] = pt[0][1]
+ shape["width"] = shape["right"] - shape["left"] + 1
+ shape["height"] = shape["bottom"] - shape["top"] + 1
+ shape["ctx"] = (shape["left"]+shape["right"])/2
+ shape["cty"] = (shape["top"]+shape["bottom"])/2
+ return shape
+
+
+if __name__ == "__main__":
+ main()
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index dd12512..ec0aab7 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -43,17 +43,21 @@
"test_yuv_plus_jpeg"
],
"scene2":[],
- "scene3":[]
+ "scene3":[],
+ "scene4":[]
}
# Get all the scene0 and scene1 tests, which can be run using the same
# physical setup.
- scenes = ["scene0", "scene1", "scene2", "scene3"]
+ scenes = ["scene0", "scene1", "scene2", "scene3", "scene4"]
scene_req = {
"scene0" : None,
"scene1" : "A grey card covering at least the middle 30% of the scene",
"scene2" : "A picture containing human faces",
- "scene3" : "A chart containing sharp edges like ISO 12233"
+ "scene3" : "A chart containing sharp edges like ISO 12233",
+ "scene4" : "A specific test page of a circle covering at least the "
+ "middle 50% of the scene. See CameraITS.pdf section 2.3.4 "
+ "for more detail"
}
tests = []
for d in scenes: