CameraITS: remove cv2 dependcy from image.py
So that only tests require opencv needs to import opencv
Test: python tools/run_all_tests.py scenes=0,3 ...
Bug: 31995717
Change-Id: I08ff1ddd8b0508220180e8c1188c48adba66f26d
diff --git a/apps/CameraITS/pymodules/its/cv2image.py b/apps/CameraITS/pymodules/its/cv2image.py
new file mode 100644
index 0000000..83e654e
--- /dev/null
+++ b/apps/CameraITS/pymodules/its/cv2image.py
@@ -0,0 +1,197 @@
+# Copyright 2016 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 matplotlib
+matplotlib.use('Agg')
+
+import its.error
+from matplotlib import pylab
+import sys
+from PIL import Image
+import numpy
+import math
+import unittest
+import cStringIO
+import scipy.stats
+import copy
+import cv2
+import os
+
+def scale_img(img, scale=1.0):
+ """Scale and image based on a real number scale factor."""
+ dim = (int(img.shape[1]*scale), int(img.shape[0]*scale))
+ return cv2.resize(img.copy(), dim, interpolation=cv2.INTER_AREA)
+
+class Chart(object):
+ """Definition for chart object.
+
+ Defines PNG reference file, chart size and distance, and scaling range.
+ """
+
+ def __init__(self, chart_file, height, distance, scale_start, scale_stop,
+ scale_step):
+ """Initial constructor for class.
+
+ Args:
+ chart_file: str; absolute path to png file of chart
+ height: float; height in cm of displayed chart
+ distance: float; distance in cm from camera of displayed chart
+ scale_start: float; start value for scaling for chart search
+ scale_stop: float; stop value for scaling for chart search
+ scale_step: float; step value for scaling for chart search
+ """
+ self._file = chart_file
+ self._height = height
+ self._distance = distance
+ self._scale_start = scale_start
+ self._scale_stop = scale_stop
+ self._scale_step = scale_step
+
+ def _calc_scale_factors(self, cam, props, fmt, s, e, fd):
+ """Take an image with s, e, & fd to find the chart location.
+
+ Args:
+ cam: An open device session.
+ props: Properties of cam
+ fmt: Image format for the capture
+ s: Sensitivity for the AF request as defined in
+ android.sensor.sensitivity
+ e: Exposure time for the AF request as defined in
+ android.sensor.exposureTime
+ fd: float; autofocus lens position
+ Returns:
+ template: numpy array; chart template for locator
+ img_3a: numpy array; RGB image for chart location
+ scale_factor: float; scaling factor for chart search
+ """
+ req = its.objects.manual_capture_request(s, e)
+ req['android.lens.focusDistance'] = fd
+ cap_chart = its.image.stationary_lens_cap(cam, req, fmt)
+ img_3a = its.image.convert_capture_to_rgb_image(cap_chart, props)
+ img_3a = its.image.flip_mirror_img_per_argv(img_3a)
+ its.image.write_image(img_3a, 'af_scene.jpg')
+ template = cv2.imread(self._file, cv2.IMREAD_ANYDEPTH)
+ focal_l = cap_chart['metadata']['android.lens.focalLength']
+ pixel_pitch = (props['android.sensor.info.physicalSize']['height'] /
+ img_3a.shape[0])
+ print ' Chart distance: %.2fcm' % self._distance
+ print ' Chart height: %.2fcm' % self._height
+ print ' Focal length: %.2fmm' % focal_l
+ print ' Pixel pitch: %.2fum' % (pixel_pitch*1E3)
+ print ' Template height: %dpixels' % template.shape[0]
+ chart_pixel_h = self._height * focal_l / (self._distance * pixel_pitch)
+ scale_factor = template.shape[0] / chart_pixel_h
+ print 'Chart/image scale factor = %.2f' % scale_factor
+ return template, img_3a, scale_factor
+
+ def locate(self, cam, props, fmt, s, e, fd):
+ """Find the chart in the image.
+
+ Args:
+ cam: An open device session
+ props: Properties of cam
+ fmt: Image format for the capture
+ s: Sensitivity for the AF request as defined in
+ android.sensor.sensitivity
+ e: Exposure time for the AF request as defined in
+ android.sensor.exposureTime
+ fd: float; autofocus lens position
+
+ Returns:
+ xnorm: float; [0, 1] left loc of chart in scene
+ ynorm: float; [0, 1] top loc of chart in scene
+ wnorm: float; [0, 1] width of chart in scene
+ hnorm: float; [0, 1] height of chart in scene
+ """
+ chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
+ s, e, fd)
+ scale_start = self._scale_start * s_factor
+ scale_stop = self._scale_stop * s_factor
+ scale_step = self._scale_step * s_factor
+ max_match = []
+ # check for normalized image
+ if numpy.amax(scene) <= 1.0:
+ scene = (scene * 255.0).astype(numpy.uint8)
+ if len(scene.shape) == 2:
+ scene_gray = scene.copy()
+ elif len(scene.shape) == 3:
+ if scene.shape[2] == 1:
+ scene_gray = scene[:, :, 0]
+ else:
+ scene_gray = cv2.cvtColor(scene.copy(), cv2.COLOR_RGB2GRAY)
+ print 'Finding chart in scene...'
+ for scale in numpy.arange(scale_start, scale_stop, scale_step):
+ scene_scaled = scale_img(scene_gray, scale)
+ result = cv2.matchTemplate(scene_scaled, chart, cv2.TM_CCOEFF)
+ _, opt_val, _, top_left_scaled = cv2.minMaxLoc(result)
+ # print out scale and match
+ print ' scale factor: %.3f, opt val: %.f' % (scale, opt_val)
+ max_match.append((opt_val, top_left_scaled))
+
+ # determine if optimization results are valid
+ opt_values = [x[0] for x in max_match]
+ if 2.0*min(opt_values) > max(opt_values):
+ estring = ('Unable to find chart in scene!\n'
+ 'Check camera distance and self-reported '
+ 'pixel pitch, focal length and hyperfocal distance.')
+ raise its.error.Error(estring)
+ # find max and draw bbox
+ match_index = max_match.index(max(max_match, key=lambda x: x[0]))
+ scale = scale_start + scale_step * match_index
+ print 'Optimum scale factor: %.3f' % scale
+ top_left_scaled = max_match[match_index][1]
+ h, w = chart.shape
+ bottom_right_scaled = (top_left_scaled[0] + w, top_left_scaled[1] + h)
+ top_left = (int(top_left_scaled[0]/scale),
+ int(top_left_scaled[1]/scale))
+ bottom_right = (int(bottom_right_scaled[0]/scale),
+ int(bottom_right_scaled[1]/scale))
+ wnorm = float((bottom_right[0]) - top_left[0]) / scene.shape[1]
+ hnorm = float((bottom_right[1]) - top_left[1]) / scene.shape[0]
+ xnorm = float(top_left[0]) / scene.shape[1]
+ ynorm = float(top_left[1]) / scene.shape[0]
+ return xnorm, ynorm, wnorm, hnorm
+
+
+class __UnitTest(unittest.TestCase):
+ """Run a suite of unit tests on this module.
+ """
+
+ def test_compute_image_sharpness(self):
+ """Unit test for compute_img_sharpness.
+
+ Test by using PNG of ISO12233 chart and blurring intentionally.
+ 'sharpness' should drop off by sqrt(2) for 2x blur of image.
+
+ We do one level of blur as PNG image is not perfect.
+ """
+ yuv_full_scale = 1023.0
+ chart_file = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules',
+ 'its', 'test_images', 'ISO12233.png')
+ chart = cv2.imread(chart_file, cv2.IMREAD_ANYDEPTH)
+ white_level = numpy.amax(chart).astype(float)
+ sharpness = {}
+ for j in [2, 4, 8]:
+ blur = cv2.blur(chart, (j, j))
+ blur = blur[:, :, numpy.newaxis]
+ sharpness[j] = (yuv_full_scale *
+ its.image.compute_image_sharpness(blur / white_level))
+ self.assertTrue(numpy.isclose(sharpness[2]/sharpness[4],
+ numpy.sqrt(2), atol=0.1))
+ self.assertTrue(numpy.isclose(sharpness[4]/sharpness[8],
+ numpy.sqrt(2), atol=0.1))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index d62e610..4d98f9c 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -25,7 +25,6 @@
import cStringIO
import scipy.stats
import copy
-import cv2
import os
DEFAULT_YUV_TO_RGB_CCM = numpy.matrix([
@@ -747,13 +746,6 @@
[gy, gx] = numpy.gradient(luma)
return numpy.average(numpy.sqrt(gy*gy + gx*gx))
-
-def scale_img(img, scale=1.0):
- """Scale and image based on a real number scale factor."""
- dim = (int(img.shape[1]*scale), int(img.shape[0]*scale))
- return cv2.resize(img.copy(), dim, interpolation=cv2.INTER_AREA)
-
-
def normalize_img(img):
"""Normalize the image values to between 0 and 1.
@@ -764,7 +756,6 @@
"""
return (img - numpy.amin(img))/(numpy.amax(img) - numpy.amin(img))
-
def flip_mirror_img_per_argv(img):
"""Flip/mirror an image if "flip" or "mirror" is in argv
@@ -804,138 +795,6 @@
raise its.error.Error('Cannot settle lens after %d trys!' % trys)
return cap[NUM_FRAMES-1]
-
-class Chart(object):
- """Definition for chart object.
-
- Defines PNG reference file, chart size and distance, and scaling range.
- """
-
- def __init__(self, chart_file, height, distance, scale_start, scale_stop,
- scale_step):
- """Initial constructor for class.
-
- Args:
- chart_file: str; absolute path to png file of chart
- height: float; height in cm of displayed chart
- distance: float; distance in cm from camera of displayed chart
- scale_start: float; start value for scaling for chart search
- scale_stop: float; stop value for scaling for chart search
- scale_step: float; step value for scaling for chart search
- """
- self._file = chart_file
- self._height = height
- self._distance = distance
- self._scale_start = scale_start
- self._scale_stop = scale_stop
- self._scale_step = scale_step
-
- def _calc_scale_factors(self, cam, props, fmt, s, e, fd):
- """Take an image with s, e, & fd to find the chart location.
-
- Args:
- cam: An open device session.
- props: Properties of cam
- fmt: Image format for the capture
- s: Sensitivity for the AF request as defined in
- android.sensor.sensitivity
- e: Exposure time for the AF request as defined in
- android.sensor.exposureTime
- fd: float; autofocus lens position
- Returns:
- template: numpy array; chart template for locator
- img_3a: numpy array; RGB image for chart location
- scale_factor: float; scaling factor for chart search
- """
- req = its.objects.manual_capture_request(s, e)
- req['android.lens.focusDistance'] = fd
- cap_chart = stationary_lens_cap(cam, req, fmt)
- img_3a = convert_capture_to_rgb_image(cap_chart, props)
- img_3a = flip_mirror_img_per_argv(img_3a)
- write_image(img_3a, 'af_scene.jpg')
- template = cv2.imread(self._file, cv2.IMREAD_ANYDEPTH)
- focal_l = cap_chart['metadata']['android.lens.focalLength']
- pixel_pitch = (props['android.sensor.info.physicalSize']['height'] /
- img_3a.shape[0])
- print ' Chart distance: %.2fcm' % self._distance
- print ' Chart height: %.2fcm' % self._height
- print ' Focal length: %.2fmm' % focal_l
- print ' Pixel pitch: %.2fum' % (pixel_pitch*1E3)
- print ' Template height: %dpixels' % template.shape[0]
- chart_pixel_h = self._height * focal_l / (self._distance * pixel_pitch)
- scale_factor = template.shape[0] / chart_pixel_h
- print 'Chart/image scale factor = %.2f' % scale_factor
- return template, img_3a, scale_factor
-
- def locate(self, cam, props, fmt, s, e, fd):
- """Find the chart in the image.
-
- Args:
- cam: An open device session
- props: Properties of cam
- fmt: Image format for the capture
- s: Sensitivity for the AF request as defined in
- android.sensor.sensitivity
- e: Exposure time for the AF request as defined in
- android.sensor.exposureTime
- fd: float; autofocus lens position
-
- Returns:
- xnorm: float; [0, 1] left loc of chart in scene
- ynorm: float; [0, 1] top loc of chart in scene
- wnorm: float; [0, 1] width of chart in scene
- hnorm: float; [0, 1] height of chart in scene
- """
- chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
- s, e, fd)
- scale_start = self._scale_start * s_factor
- scale_stop = self._scale_stop * s_factor
- scale_step = self._scale_step * s_factor
- max_match = []
- # check for normalized image
- if numpy.amax(scene) <= 1.0:
- scene = (scene * 255.0).astype(numpy.uint8)
- if len(scene.shape) == 2:
- scene_gray = scene.copy()
- elif len(scene.shape) == 3:
- if scene.shape[2] == 1:
- scene_gray = scene[:, :, 0]
- else:
- scene_gray = cv2.cvtColor(scene.copy(), cv2.COLOR_RGB2GRAY)
- print 'Finding chart in scene...'
- for scale in numpy.arange(scale_start, scale_stop, scale_step):
- scene_scaled = scale_img(scene_gray, scale)
- result = cv2.matchTemplate(scene_scaled, chart, cv2.TM_CCOEFF)
- _, opt_val, _, top_left_scaled = cv2.minMaxLoc(result)
- # print out scale and match
- print ' scale factor: %.3f, opt val: %.f' % (scale, opt_val)
- max_match.append((opt_val, top_left_scaled))
-
- # determine if optimization results are valid
- opt_values = [x[0] for x in max_match]
- if 2.0*min(opt_values) > max(opt_values):
- estring = ('Unable to find chart in scene!\n'
- 'Check camera distance and self-reported '
- 'pixel pitch, focal length and hyperfocal distance.')
- raise its.error.Error(estring)
- # find max and draw bbox
- match_index = max_match.index(max(max_match, key=lambda x: x[0]))
- scale = scale_start + scale_step * match_index
- print 'Optimum scale factor: %.3f' % scale
- top_left_scaled = max_match[match_index][1]
- h, w = chart.shape
- bottom_right_scaled = (top_left_scaled[0] + w, top_left_scaled[1] + h)
- top_left = (int(top_left_scaled[0]/scale),
- int(top_left_scaled[1]/scale))
- bottom_right = (int(bottom_right_scaled[0]/scale),
- int(bottom_right_scaled[1]/scale))
- wnorm = float((bottom_right[0]) - top_left[0]) / scene.shape[1]
- hnorm = float((bottom_right[1]) - top_left[1]) / scene.shape[0]
- xnorm = float(top_left[0]) / scene.shape[1]
- ynorm = float(top_left[1]) / scene.shape[0]
- return xnorm, ynorm, wnorm, hnorm
-
-
class __UnitTest(unittest.TestCase):
"""Run a suite of unit tests on this module.
"""
@@ -974,30 +833,5 @@
passed = all([math.fabs(y[i] - y_ref[i]) < 0.001 for i in xrange(3)])
self.assertTrue(passed)
- def test_compute_image_sharpness(self):
- """Unit test for compute_img_sharpness.
-
- Test by using PNG of ISO12233 chart and blurring intentionally.
- 'sharpness' should drop off by sqrt(2) for 2x blur of image.
-
- We do one level of blur as PNG image is not perfect.
- """
- yuv_full_scale = 1023.0
- chart_file = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules',
- 'its', 'test_images', 'ISO12233.png')
- chart = cv2.imread(chart_file, cv2.IMREAD_ANYDEPTH)
- white_level = numpy.amax(chart).astype(float)
- sharpness = {}
- for j in [2, 4, 8]:
- blur = cv2.blur(chart, (j, j))
- blur = blur[:, :, numpy.newaxis]
- sharpness[j] = yuv_full_scale * compute_image_sharpness(blur /
- white_level)
- self.assertTrue(numpy.isclose(sharpness[2]/sharpness[4],
- numpy.sqrt(2), atol=0.1))
- self.assertTrue(numpy.isclose(sharpness[4]/sharpness[8],
- numpy.sqrt(2), atol=0.1))
-
-
if __name__ == '__main__':
unittest.main()
diff --git a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
index a54eae8..cd563be 100644
--- a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
@@ -15,6 +15,7 @@
import os
import its.caps
+import its.cv2image
import its.device
import its.image
import its.objects
@@ -57,9 +58,9 @@
"""
# initialize chart class
- chart = its.image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
- CHART_SCALE_START, CHART_SCALE_STOP,
- CHART_SCALE_STEP)
+ chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
+ CHART_SCALE_START, CHART_SCALE_STOP,
+ CHART_SCALE_STEP)
# find chart location
xnorm, ynorm, wnorm, hnorm = chart.locate(cam, props, fmt, sensitivity,
diff --git a/apps/CameraITS/tests/scene3/test_lens_position.py b/apps/CameraITS/tests/scene3/test_lens_position.py
index e2bd040..f850e3d 100644
--- a/apps/CameraITS/tests/scene3/test_lens_position.py
+++ b/apps/CameraITS/tests/scene3/test_lens_position.py
@@ -15,6 +15,7 @@
import os
import its.caps
+import its.cv2image
import its.device
import its.image
import its.objects
@@ -58,9 +59,9 @@
"""
# initialize chart class
- chart = its.image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
- CHART_SCALE_START, CHART_SCALE_STOP,
- CHART_SCALE_STEP)
+ chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
+ CHART_SCALE_START, CHART_SCALE_STOP,
+ CHART_SCALE_STEP)
# find chart location
xnorm, ynorm, wnorm, hnorm = chart.locate(cam, props, fmt, sensitivity,