Path: blob/master/core/imagelib/estimate_sharpness.py
628 views
"""1Copyright (c) 2009-2010 Arizona Board of Regents. All Rights Reserved.2Contact: Lina Karam ([email protected]) and Niranjan Narvekar ([email protected])3Image, Video, and Usabilty (IVU) Lab, http://ivulab.asu.edu , Arizona State University4This copyright statement may not be removed from any file containing it or from modifications to these files.5This copyright notice must also be included in any file or product that is derived from the source files.67Redistribution and use of this code in source and binary forms, with or without modification, are permitted provided that the8following conditions are met:9- Redistribution's of source code must retain the above copyright notice, this list of conditions and the following disclaimer.10- Redistribution's in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer11in the documentation and/or other materials provided with the distribution.12- The Image, Video, and Usability Laboratory (IVU Lab, http://ivulab.asu.edu) is acknowledged in any publication that13reports research results using this code, copies of this code, or modifications of this code.14The code and our papers are to be cited in the bibliography as:1516N. D. Narvekar and L. J. Karam, "CPBD Sharpness Metric Software", http://ivulab.asu.edu/Quality/CPBD1718N. D. Narvekar and L. J. Karam, "A No-Reference Image Blur Metric Based on the Cumulative19Probability of Blur Detection (CPBD)," accepted and to appear in the IEEE Transactions on Image Processing, 2011.2021N. D. Narvekar and L. J. Karam, "An Improved No-Reference Sharpness Metric Based on the Probability of Blur Detection," International Workshop on Video Processing and Quality Metrics for Consumer Electronics (VPQM), January 2010, http://www.vpqm.org (pdf)2223N. D. Narvekar and L. J. Karam, "A No Reference Perceptual Quality Metric based on Cumulative Probability of Blur Detection," First International Workshop on the Quality of Multimedia Experience (QoMEX), pp. 87-91, July 2009.2425DISCLAIMER:26This software is provided by the copyright holders and contributors "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the Arizona Board of Regents, Arizona State University, IVU Lab members, authors or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute27goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.28"""2930import numpy as np31import cv232from math import atan2, pi333435def sobel(image):36# type: (numpy.ndarray) -> numpy.ndarray37"""38Find edges using the Sobel approximation to the derivatives.3940Inspired by the [Octave implementation](https://sourceforge.net/p/octave/image/ci/default/tree/inst/edge.m#l196).41"""42from skimage.filters.edges import HSOBEL_WEIGHTS43h1 = np.array(HSOBEL_WEIGHTS)44h1 /= np.sum(abs(h1)) # normalize h14546from scipy.ndimage import convolve47strength2 = np.square(convolve(image, h1.T))4849# Note: https://sourceforge.net/p/octave/image/ci/default/tree/inst/edge.m#l5950thresh2 = 2 * np.sqrt(np.mean(strength2))5152strength2[strength2 <= thresh2] = 053return _simple_thinning(strength2)545556def _simple_thinning(strength):57# type: (numpy.ndarray) -> numpy.ndarray58"""59Perform a very simple thinning.6061Inspired by the [Octave implementation](https://sourceforge.net/p/octave/image/ci/default/tree/inst/edge.m#l512).62"""63num_rows, num_cols = strength.shape6465zero_column = np.zeros((num_rows, 1))66zero_row = np.zeros((1, num_cols))6768x = (69(strength > np.c_[zero_column, strength[:, :-1]]) &70(strength > np.c_[strength[:, 1:], zero_column])71)7273y = (74(strength > np.r_[zero_row, strength[:-1, :]]) &75(strength > np.r_[strength[1:, :], zero_row])76)7778return x | y798081828384# threshold to characterize blocks as edge/non-edge blocks85THRESHOLD = 0.00286# fitting parameter87BETA = 3.688# block size89BLOCK_HEIGHT, BLOCK_WIDTH = (64, 64)90# just noticeable widths based on the perceptual experiments91WIDTH_JNB = np.concatenate([5*np.ones(51), 3*np.ones(205)])929394def compute(image):95# type: (numpy.ndarray) -> float96"""Compute the sharpness metric for the given data."""9798# convert the image to double for further processing99image = image.astype(np.float64)100101# edge detection using canny and sobel canny edge detection is done to102# classify the blocks as edge or non-edge blocks and sobel edge103# detection is done for the purpose of edge width measurement.104from skimage.feature import canny105canny_edges = canny(image)106sobel_edges = sobel(image)107108# edge width calculation109marziliano_widths = marziliano_method(sobel_edges, image)110111# sharpness metric calculation112return _calculate_sharpness_metric(image, canny_edges, marziliano_widths)113114115def marziliano_method(edges, image):116# type: (numpy.ndarray, numpy.ndarray) -> numpy.ndarray117"""118Calculate the widths of the given edges.119120:return: A matrix with the same dimensions as the given image with 0's at121non-edge locations and edge-widths at the edge locations.122"""123124# `edge_widths` consists of zero and non-zero values. A zero value125# indicates that there is no edge at that position and a non-zero value126# indicates that there is an edge at that position and the value itself127# gives the edge width.128edge_widths = np.zeros(image.shape)129130# find the gradient for the image131gradient_y, gradient_x = np.gradient(image)132133# dimensions of the image134img_height, img_width = image.shape135136# holds the angle information of the edges137edge_angles = np.zeros(image.shape)138139# calculate the angle of the edges140for row in range(img_height):141for col in range(img_width):142if gradient_x[row, col] != 0:143edge_angles[row, col] = atan2(gradient_y[row, col], gradient_x[row, col]) * (180 / pi)144elif gradient_x[row, col] == 0 and gradient_y[row, col] == 0:145edge_angles[row,col] = 0146elif gradient_x[row, col] == 0 and gradient_y[row, col] == pi/2:147edge_angles[row, col] = 90148149150if np.any(edge_angles):151152# quantize the angle153quantized_angles = 45 * np.round(edge_angles / 45)154155for row in range(1, img_height - 1):156for col in range(1, img_width - 1):157if edges[row, col] == 1:158159# gradient angle = 180 or -180160if quantized_angles[row, col] == 180 or quantized_angles[row, col] == -180:161for margin in range(100 + 1):162inner_border = (col - 1) - margin163outer_border = (col - 2) - margin164165# outside image or intensity increasing from left to right166if outer_border < 0 or (image[row, outer_border] - image[row, inner_border]) <= 0:167break168169width_left = margin + 1170171for margin in range(100 + 1):172inner_border = (col + 1) + margin173outer_border = (col + 2) + margin174175# outside image or intensity increasing from left to right176if outer_border >= img_width or (image[row, outer_border] - image[row, inner_border]) >= 0:177break178179width_right = margin + 1180181edge_widths[row, col] = width_left + width_right182183184# gradient angle = 0185if quantized_angles[row, col] == 0:186for margin in range(100 + 1):187inner_border = (col - 1) - margin188outer_border = (col - 2) - margin189190# outside image or intensity decreasing from left to right191if outer_border < 0 or (image[row, outer_border] - image[row, inner_border]) >= 0:192break193194width_left = margin + 1195196for margin in range(100 + 1):197inner_border = (col + 1) + margin198outer_border = (col + 2) + margin199200# outside image or intensity decreasing from left to right201if outer_border >= img_width or (image[row, outer_border] - image[row, inner_border]) <= 0:202break203204width_right = margin + 1205206edge_widths[row, col] = width_right + width_left207208return edge_widths209210211def _calculate_sharpness_metric(image, edges, edge_widths):212# type: (numpy.array, numpy.array, numpy.array) -> numpy.float64213214# get the size of image215img_height, img_width = image.shape216217total_num_edges = 0218hist_pblur = np.zeros(101)219220# maximum block indices221num_blocks_vertically = int(img_height / BLOCK_HEIGHT)222num_blocks_horizontally = int(img_width / BLOCK_WIDTH)223224# loop over the blocks225for i in range(num_blocks_vertically):226for j in range(num_blocks_horizontally):227228# get the row and col indices for the block pixel positions229rows = slice(BLOCK_HEIGHT * i, BLOCK_HEIGHT * (i + 1))230cols = slice(BLOCK_WIDTH * j, BLOCK_WIDTH * (j + 1))231232if is_edge_block(edges[rows, cols], THRESHOLD):233block_widths = edge_widths[rows, cols]234# rotate block to simulate column-major boolean indexing235block_widths = np.rot90(np.flipud(block_widths), 3)236block_widths = block_widths[block_widths != 0]237238block_contrast = get_block_contrast(image[rows, cols])239block_jnb = WIDTH_JNB[block_contrast]240241# calculate the probability of blur detection at the edges242# detected in the block243prob_blur_detection = 1 - np.exp(-abs(block_widths/block_jnb) ** BETA)244245# update the statistics using the block information246for probability in prob_blur_detection:247bucket = int(round(probability * 100))248hist_pblur[bucket] += 1249total_num_edges += 1250251# normalize the pdf252if total_num_edges > 0:253hist_pblur = hist_pblur / total_num_edges254255# calculate the sharpness metric256return np.sum(hist_pblur[:64])257258259def is_edge_block(block, threshold):260# type: (numpy.ndarray, float) -> bool261"""Decide whether the given block is an edge block."""262return np.count_nonzero(block) > (block.size * threshold)263264265def get_block_contrast(block):266# type: (numpy.ndarray) -> int267return int(np.max(block) - np.min(block))268269270def estimate_sharpness(image):271if image.ndim == 3:272if image.shape[2] > 1:273image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)274else:275image = image[...,0]276277return compute(image)278279280