Home | History | Annotate | Download | only in its
      1 # Copyright 2016 The Android Open Source Project
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #      http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 # See the License for the specific language governing permissions and
     13 # limitations under the License.
     14 
     15 import matplotlib
     16 matplotlib.use('Agg')
     17 
     18 import its.error
     19 from matplotlib import pylab
     20 import sys
     21 from PIL import Image
     22 import numpy
     23 import math
     24 import unittest
     25 import cStringIO
     26 import scipy.stats
     27 import copy
     28 import cv2
     29 import os
     30 
     31 def scale_img(img, scale=1.0):
     32     """Scale and image based on a real number scale factor."""
     33     dim = (int(img.shape[1]*scale), int(img.shape[0]*scale))
     34     return cv2.resize(img.copy(), dim, interpolation=cv2.INTER_AREA)
     35 
     36 class Chart(object):
     37     """Definition for chart object.
     38 
     39     Defines PNG reference file, chart size and distance, and scaling range.
     40     """
     41 
     42     def __init__(self, chart_file, height, distance, scale_start, scale_stop,
     43                  scale_step):
     44         """Initial constructor for class.
     45 
     46         Args:
     47             chart_file:     str; absolute path to png file of chart
     48             height:         float; height in cm of displayed chart
     49             distance:       float; distance in cm from camera of displayed chart
     50             scale_start:    float; start value for scaling for chart search
     51             scale_stop:     float; stop value for scaling for chart search
     52             scale_step:     float; step value for scaling for chart search
     53         """
     54         self._file = chart_file
     55         self._height = height
     56         self._distance = distance
     57         self._scale_start = scale_start
     58         self._scale_stop = scale_stop
     59         self._scale_step = scale_step
     60 
     61     def _calc_scale_factors(self, cam, props, fmt, s, e, fd):
     62         """Take an image with s, e, & fd to find the chart location.
     63 
     64         Args:
     65             cam:            An open device session.
     66             props:          Properties of cam
     67             fmt:            Image format for the capture
     68             s:              Sensitivity for the AF request as defined in
     69                             android.sensor.sensitivity
     70             e:              Exposure time for the AF request as defined in
     71                             android.sensor.exposureTime
     72             fd:             float; autofocus lens position
     73         Returns:
     74             template:       numpy array; chart template for locator
     75             img_3a:         numpy array; RGB image for chart location
     76             scale_factor:   float; scaling factor for chart search
     77         """
     78         req = its.objects.manual_capture_request(s, e)
     79         req['android.lens.focusDistance'] = fd
     80         cap_chart = its.image.stationary_lens_cap(cam, req, fmt)
     81         img_3a = its.image.convert_capture_to_rgb_image(cap_chart, props)
     82         img_3a = its.image.flip_mirror_img_per_argv(img_3a)
     83         its.image.write_image(img_3a, 'af_scene.jpg')
     84         template = cv2.imread(self._file, cv2.IMREAD_ANYDEPTH)
     85         focal_l = cap_chart['metadata']['android.lens.focalLength']
     86         pixel_pitch = (props['android.sensor.info.physicalSize']['height'] /
     87                        img_3a.shape[0])
     88         print ' Chart distance: %.2fcm' % self._distance
     89         print ' Chart height: %.2fcm' % self._height
     90         print ' Focal length: %.2fmm' % focal_l
     91         print ' Pixel pitch: %.2fum' % (pixel_pitch*1E3)
     92         print ' Template height: %dpixels' % template.shape[0]
     93         chart_pixel_h = self._height * focal_l / (self._distance * pixel_pitch)
     94         scale_factor = template.shape[0] / chart_pixel_h
     95         print 'Chart/image scale factor = %.2f' % scale_factor
     96         return template, img_3a, scale_factor
     97 
     98     def locate(self, cam, props, fmt, s, e, fd):
     99         """Find the chart in the image.
    100 
    101         Args:
    102             cam:            An open device session
    103             props:          Properties of cam
    104             fmt:            Image format for the capture
    105             s:              Sensitivity for the AF request as defined in
    106                             android.sensor.sensitivity
    107             e:              Exposure time for the AF request as defined in
    108                             android.sensor.exposureTime
    109             fd:             float; autofocus lens position
    110 
    111         Returns:
    112             xnorm:          float; [0, 1] left loc of chart in scene
    113             ynorm:          float; [0, 1] top loc of chart in scene
    114             wnorm:          float; [0, 1] width of chart in scene
    115             hnorm:          float; [0, 1] height of chart in scene
    116         """
    117         chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
    118                                                           s, e, fd)
    119         scale_start = self._scale_start * s_factor
    120         scale_stop = self._scale_stop * s_factor
    121         scale_step = self._scale_step * s_factor
    122         max_match = []
    123         # check for normalized image
    124         if numpy.amax(scene) <= 1.0:
    125             scene = (scene * 255.0).astype(numpy.uint8)
    126         if len(scene.shape) == 2:
    127             scene_gray = scene.copy()
    128         elif len(scene.shape) == 3:
    129             if scene.shape[2] == 1:
    130                 scene_gray = scene[:, :, 0]
    131             else:
    132                 scene_gray = cv2.cvtColor(scene.copy(), cv2.COLOR_RGB2GRAY)
    133         print 'Finding chart in scene...'
    134         for scale in numpy.arange(scale_start, scale_stop, scale_step):
    135             scene_scaled = scale_img(scene_gray, scale)
    136             result = cv2.matchTemplate(scene_scaled, chart, cv2.TM_CCOEFF)
    137             _, opt_val, _, top_left_scaled = cv2.minMaxLoc(result)
    138             # print out scale and match
    139             print ' scale factor: %.3f, opt val: %.f' % (scale, opt_val)
    140             max_match.append((opt_val, top_left_scaled))
    141 
    142         # determine if optimization results are valid
    143         opt_values = [x[0] for x in max_match]
    144         if 2.0*min(opt_values) > max(opt_values):
    145             estring = ('Unable to find chart in scene!\n'
    146                        'Check camera distance and self-reported '
    147                        'pixel pitch, focal length and hyperfocal distance.')
    148             raise its.error.Error(estring)
    149         # find max and draw bbox
    150         match_index = max_match.index(max(max_match, key=lambda x: x[0]))
    151         scale = scale_start + scale_step * match_index
    152         print 'Optimum scale factor: %.3f' %  scale
    153         top_left_scaled = max_match[match_index][1]
    154         h, w = chart.shape
    155         bottom_right_scaled = (top_left_scaled[0] + w, top_left_scaled[1] + h)
    156         top_left = (int(top_left_scaled[0]/scale),
    157                     int(top_left_scaled[1]/scale))
    158         bottom_right = (int(bottom_right_scaled[0]/scale),
    159                         int(bottom_right_scaled[1]/scale))
    160         wnorm = float((bottom_right[0]) - top_left[0]) / scene.shape[1]
    161         hnorm = float((bottom_right[1]) - top_left[1]) / scene.shape[0]
    162         xnorm = float(top_left[0]) / scene.shape[1]
    163         ynorm = float(top_left[1]) / scene.shape[0]
    164         return xnorm, ynorm, wnorm, hnorm
    165 
    166 
    167 class __UnitTest(unittest.TestCase):
    168     """Run a suite of unit tests on this module.
    169     """
    170 
    171     def test_compute_image_sharpness(self):
    172         """Unit test for compute_img_sharpness.
    173 
    174         Test by using PNG of ISO12233 chart and blurring intentionally.
    175         'sharpness' should drop off by sqrt(2) for 2x blur of image.
    176 
    177         We do one level of blur as PNG image is not perfect.
    178         """
    179         yuv_full_scale = 1023.0
    180         chart_file = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules',
    181                                   'its', 'test_images', 'ISO12233.png')
    182         chart = cv2.imread(chart_file, cv2.IMREAD_ANYDEPTH)
    183         white_level = numpy.amax(chart).astype(float)
    184         sharpness = {}
    185         for j in [2, 4, 8]:
    186             blur = cv2.blur(chart, (j, j))
    187             blur = blur[:, :, numpy.newaxis]
    188             sharpness[j] = (yuv_full_scale *
    189                     its.image.compute_image_sharpness(blur / white_level))
    190         self.assertTrue(numpy.isclose(sharpness[2]/sharpness[4],
    191                                       numpy.sqrt(2), atol=0.1))
    192         self.assertTrue(numpy.isclose(sharpness[4]/sharpness[8],
    193                                       numpy.sqrt(2), atol=0.1))
    194 
    195 
    196 if __name__ == '__main__':
    197     unittest.main()
    198