Me preguntaba si hay una manera de determinar si una imagen está borrosa o no analizando los datos de la imagen.
Me preguntaba si hay una manera de determinar si una imagen está borrosa o no analizando los datos de la imagen.
Respuestas:
Sí lo es. Calcule la Transformada rápida de Fourier y analice el resultado. La transformación de Fourier le dice qué frecuencias están presentes en la imagen. Si hay una baja cantidad de frecuencias altas, la imagen está borrosa.
Definir los términos 'bajo' y 'alto' depende de usted.
Editar :
Como se indicó en los comentarios, si desea un flotador único que represente el desenfoque de una imagen determinada, debe calcular una métrica adecuada.
La respuesta de Nikie proporciona esa métrica. Convolucione la imagen con un núcleo laplaciano:
1
1 -4 1
1
Y use una métrica máxima robusta en la salida para obtener un número que pueda usar para el umbral. Intente evitar suavizar demasiado las imágenes antes de calcular el Laplaciano, porque solo descubrirá que una imagen suavizada está borrosa :-).
Otra forma muy simple de estimar la nitidez de una imagen es usar un filtro de Laplace (o LoG) y simplemente elegir el valor máximo. Usar una medida robusta como un cuantil del 99.9% probablemente sea mejor si espera ruido (es decir, elegir el enésimo contraste más alto en lugar del contraste más alto). Si espera un brillo de imagen variable, también debe incluir un paso de preprocesamiento para normalizar el brillo de la imagen / contraste (por ejemplo, ecualización de histograma).
Implementé la sugerencia de Simon y esta en Mathematica, y la probé en algunas imágenes de prueba:
La primera prueba difumina las imágenes de prueba usando un filtro gaussiano con un tamaño de núcleo variable, luego calcula la FFT de la imagen borrosa y toma el promedio de las frecuencias más altas del 90%:
testFft[img_] := Table[
(
blurred = GaussianFilter[img, r];
fft = Fourier[ImageData[blurred]];
{w, h} = Dimensions[fft];
windowSize = Round[w/2.1];
Mean[Flatten[(Abs[
fft[[w/2 - windowSize ;; w/2 + windowSize,
h/2 - windowSize ;; h/2 + windowSize]]])]]
), {r, 0, 10, 0.5}]
Resultado en una trama logarítmica:
Las 5 líneas representan las 5 imágenes de prueba, el eje X representa el radio del filtro gaussiano. Los gráficos están disminuyendo, por lo que la FFT es una buena medida de nitidez.
Este es el código para el estimador de borrosidad "LoG más alto": simplemente aplica un filtro LoG y devuelve el píxel más brillante en el resultado del filtro:
testLaplacian[img_] := Table[
(
blurred = GaussianFilter[img, r];
Max[Flatten[ImageData[LaplacianGaussianFilter[blurred, 1]]]];
), {r, 0, 10, 0.5}]
Resultado en una trama logarítmica:
La extensión de las imágenes no borrosas es un poco mejor aquí (2.5 frente a 3.3), principalmente porque este método solo usa el contraste más fuerte en la imagen, mientras que el FFT es esencialmente un promedio sobre toda la imagen. Las funciones también disminuyen más rápido, por lo que podría ser más fácil establecer un umbral "borroso".
Durante algunos trabajos con una lente de enfoque automático, me encontré con este conjunto muy útil de algoritmos para detectar el enfoque de la imagen . Está implementado en MATLAB, pero la mayoría de las funciones son bastante fáciles de portar a OpenCV con filter2D .
Básicamente es una implementación de encuestas de muchos algoritmos de medición de enfoque. Si desea leer los documentos originales, en el código se proporcionan referencias a los autores de los algoritmos. El artículo de 2012 de Pertuz, et al. El análisis de los operadores de medidas de enfoque para la forma desde el foco (SFF) ofrece una excelente revisión de todas estas medidas, así como de su rendimiento (tanto en términos de velocidad como de precisión aplicadas a SFF).
EDITAR: se agregó código MATLAB en caso de que el enlace muera.
function FM = fmeasure(Image, Measure, ROI)
%This function measures the relative degree of focus of
%an image. It may be invoked as:
%
% FM = fmeasure(Image, Method, ROI)
%
%Where
% Image, is a grayscale image and FM is the computed
% focus value.
% Method, is the focus measure algorithm as a string.
% see 'operators.txt' for a list of focus
% measure methods.
% ROI, Image ROI as a rectangle [xo yo width heigth].
% if an empty argument is passed, the whole
% image is processed.
%
% Said Pertuz
% Abr/2010
if ~isempty(ROI)
Image = imcrop(Image, ROI);
end
WSize = 15; % Size of local window (only some operators)
switch upper(Measure)
case 'ACMO' % Absolute Central Moment (Shirvaikar2004)
if ~isinteger(Image), Image = im2uint8(Image);
end
FM = AcMomentum(Image);
case 'BREN' % Brenner's (Santos97)
[M N] = size(Image);
DH = Image;
DV = Image;
DH(1:M-2,:) = diff(Image,2,1);
DV(:,1:N-2) = diff(Image,2,2);
FM = max(DH, DV);
FM = FM.^2;
FM = mean2(FM);
case 'CONT' % Image contrast (Nanda2001)
ImContrast = inline('sum(abs(x(:)-x(5)))');
FM = nlfilter(Image, [3 3], ImContrast);
FM = mean2(FM);
case 'CURV' % Image Curvature (Helmli2001)
if ~isinteger(Image), Image = im2uint8(Image);
end
M1 = [-1 0 1;-1 0 1;-1 0 1];
M2 = [1 0 1;1 0 1;1 0 1];
P0 = imfilter(Image, M1, 'replicate', 'conv')/6;
P1 = imfilter(Image, M1', 'replicate', 'conv')/6;
P2 = 3*imfilter(Image, M2, 'replicate', 'conv')/10 ...
-imfilter(Image, M2', 'replicate', 'conv')/5;
P3 = -imfilter(Image, M2, 'replicate', 'conv')/5 ...
+3*imfilter(Image, M2, 'replicate', 'conv')/10;
FM = abs(P0) + abs(P1) + abs(P2) + abs(P3);
FM = mean2(FM);
case 'DCTE' % DCT energy ratio (Shen2006)
FM = nlfilter(Image, [8 8], @DctRatio);
FM = mean2(FM);
case 'DCTR' % DCT reduced energy ratio (Lee2009)
FM = nlfilter(Image, [8 8], @ReRatio);
FM = mean2(FM);
case 'GDER' % Gaussian derivative (Geusebroek2000)
N = floor(WSize/2);
sig = N/2.5;
[x,y] = meshgrid(-N:N, -N:N);
G = exp(-(x.^2+y.^2)/(2*sig^2))/(2*pi*sig);
Gx = -x.*G/(sig^2);Gx = Gx/sum(Gx(:));
Gy = -y.*G/(sig^2);Gy = Gy/sum(Gy(:));
Rx = imfilter(double(Image), Gx, 'conv', 'replicate');
Ry = imfilter(double(Image), Gy, 'conv', 'replicate');
FM = Rx.^2+Ry.^2;
FM = mean2(FM);
case 'GLVA' % Graylevel variance (Krotkov86)
FM = std2(Image);
case 'GLLV' %Graylevel local variance (Pech2000)
LVar = stdfilt(Image, ones(WSize,WSize)).^2;
FM = std2(LVar)^2;
case 'GLVN' % Normalized GLV (Santos97)
FM = std2(Image)^2/mean2(Image);
case 'GRAE' % Energy of gradient (Subbarao92a)
Ix = Image;
Iy = Image;
Iy(1:end-1,:) = diff(Image, 1, 1);
Ix(:,1:end-1) = diff(Image, 1, 2);
FM = Ix.^2 + Iy.^2;
FM = mean2(FM);
case 'GRAT' % Thresholded gradient (Snatos97)
Th = 0; %Threshold
Ix = Image;
Iy = Image;
Iy(1:end-1,:) = diff(Image, 1, 1);
Ix(:,1:end-1) = diff(Image, 1, 2);
FM = max(abs(Ix), abs(Iy));
FM(FM<Th)=0;
FM = sum(FM(:))/sum(sum(FM~=0));
case 'GRAS' % Squared gradient (Eskicioglu95)
Ix = diff(Image, 1, 2);
FM = Ix.^2;
FM = mean2(FM);
case 'HELM' %Helmli's mean method (Helmli2001)
MEANF = fspecial('average',[WSize WSize]);
U = imfilter(Image, MEANF, 'replicate');
R1 = U./Image;
R1(Image==0)=1;
index = (U>Image);
FM = 1./R1;
FM(index) = R1(index);
FM = mean2(FM);
case 'HISE' % Histogram entropy (Krotkov86)
FM = entropy(Image);
case 'HISR' % Histogram range (Firestone91)
FM = max(Image(:))-min(Image(:));
case 'LAPE' % Energy of laplacian (Subbarao92a)
LAP = fspecial('laplacian');
FM = imfilter(Image, LAP, 'replicate', 'conv');
FM = mean2(FM.^2);
case 'LAPM' % Modified Laplacian (Nayar89)
M = [-1 2 -1];
Lx = imfilter(Image, M, 'replicate', 'conv');
Ly = imfilter(Image, M', 'replicate', 'conv');
FM = abs(Lx) + abs(Ly);
FM = mean2(FM);
case 'LAPV' % Variance of laplacian (Pech2000)
LAP = fspecial('laplacian');
ILAP = imfilter(Image, LAP, 'replicate', 'conv');
FM = std2(ILAP)^2;
case 'LAPD' % Diagonal laplacian (Thelen2009)
M1 = [-1 2 -1];
M2 = [0 0 -1;0 2 0;-1 0 0]/sqrt(2);
M3 = [-1 0 0;0 2 0;0 0 -1]/sqrt(2);
F1 = imfilter(Image, M1, 'replicate', 'conv');
F2 = imfilter(Image, M2, 'replicate', 'conv');
F3 = imfilter(Image, M3, 'replicate', 'conv');
F4 = imfilter(Image, M1', 'replicate', 'conv');
FM = abs(F1) + abs(F2) + abs(F3) + abs(F4);
FM = mean2(FM);
case 'SFIL' %Steerable filters (Minhas2009)
% Angles = [0 45 90 135 180 225 270 315];
N = floor(WSize/2);
sig = N/2.5;
[x,y] = meshgrid(-N:N, -N:N);
G = exp(-(x.^2+y.^2)/(2*sig^2))/(2*pi*sig);
Gx = -x.*G/(sig^2);Gx = Gx/sum(Gx(:));
Gy = -y.*G/(sig^2);Gy = Gy/sum(Gy(:));
R(:,:,1) = imfilter(double(Image), Gx, 'conv', 'replicate');
R(:,:,2) = imfilter(double(Image), Gy, 'conv', 'replicate');
R(:,:,3) = cosd(45)*R(:,:,1)+sind(45)*R(:,:,2);
R(:,:,4) = cosd(135)*R(:,:,1)+sind(135)*R(:,:,2);
R(:,:,5) = cosd(180)*R(:,:,1)+sind(180)*R(:,:,2);
R(:,:,6) = cosd(225)*R(:,:,1)+sind(225)*R(:,:,2);
R(:,:,7) = cosd(270)*R(:,:,1)+sind(270)*R(:,:,2);
R(:,:,7) = cosd(315)*R(:,:,1)+sind(315)*R(:,:,2);
FM = max(R,[],3);
FM = mean2(FM);
case 'SFRQ' % Spatial frequency (Eskicioglu95)
Ix = Image;
Iy = Image;
Ix(:,1:end-1) = diff(Image, 1, 2);
Iy(1:end-1,:) = diff(Image, 1, 1);
FM = mean2(sqrt(double(Iy.^2+Ix.^2)));
case 'TENG'% Tenengrad (Krotkov86)
Sx = fspecial('sobel');
Gx = imfilter(double(Image), Sx, 'replicate', 'conv');
Gy = imfilter(double(Image), Sx', 'replicate', 'conv');
FM = Gx.^2 + Gy.^2;
FM = mean2(FM);
case 'TENV' % Tenengrad variance (Pech2000)
Sx = fspecial('sobel');
Gx = imfilter(double(Image), Sx, 'replicate', 'conv');
Gy = imfilter(double(Image), Sx', 'replicate', 'conv');
G = Gx.^2 + Gy.^2;
FM = std2(G)^2;
case 'VOLA' % Vollath's correlation (Santos97)
Image = double(Image);
I1 = Image; I1(1:end-1,:) = Image(2:end,:);
I2 = Image; I2(1:end-2,:) = Image(3:end,:);
Image = Image.*(I1-I2);
FM = mean2(Image);
case 'WAVS' %Sum of Wavelet coeffs (Yang2003)
[C,S] = wavedec2(Image, 1, 'db6');
H = wrcoef2('h', C, S, 'db6', 1);
V = wrcoef2('v', C, S, 'db6', 1);
D = wrcoef2('d', C, S, 'db6', 1);
FM = abs(H) + abs(V) + abs(D);
FM = mean2(FM);
case 'WAVV' %Variance of Wav...(Yang2003)
[C,S] = wavedec2(Image, 1, 'db6');
H = abs(wrcoef2('h', C, S, 'db6', 1));
V = abs(wrcoef2('v', C, S, 'db6', 1));
D = abs(wrcoef2('d', C, S, 'db6', 1));
FM = std2(H)^2+std2(V)+std2(D);
case 'WAVR'
[C,S] = wavedec2(Image, 3, 'db6');
H = abs(wrcoef2('h', C, S, 'db6', 1));
V = abs(wrcoef2('v', C, S, 'db6', 1));
D = abs(wrcoef2('d', C, S, 'db6', 1));
A1 = abs(wrcoef2('a', C, S, 'db6', 1));
A2 = abs(wrcoef2('a', C, S, 'db6', 2));
A3 = abs(wrcoef2('a', C, S, 'db6', 3));
A = A1 + A2 + A3;
WH = H.^2 + V.^2 + D.^2;
WH = mean2(WH);
WL = mean2(A);
FM = WH/WL;
otherwise
error('Unknown measure %s',upper(Measure))
end
end
%************************************************************************
function fm = AcMomentum(Image)
[M N] = size(Image);
Hist = imhist(Image)/(M*N);
Hist = abs((0:255)-255*mean2(Image))'.*Hist;
fm = sum(Hist);
end
%******************************************************************
function fm = DctRatio(M)
MT = dct2(M).^2;
fm = (sum(MT(:))-MT(1,1))/MT(1,1);
end
%************************************************************************
function fm = ReRatio(M)
M = dct2(M);
fm = (M(1,2)^2+M(1,3)^2+M(2,1)^2+M(2,2)^2+M(3,1)^2)/(M(1,1)^2);
end
%******************************************************************
Algunos ejemplos de versiones de OpenCV:
// OpenCV port of 'LAPM' algorithm (Nayar89)
double modifiedLaplacian(const cv::Mat& src)
{
cv::Mat M = (Mat_<double>(3, 1) << -1, 2, -1);
cv::Mat G = cv::getGaussianKernel(3, -1, CV_64F);
cv::Mat Lx;
cv::sepFilter2D(src, Lx, CV_64F, M, G);
cv::Mat Ly;
cv::sepFilter2D(src, Ly, CV_64F, G, M);
cv::Mat FM = cv::abs(Lx) + cv::abs(Ly);
double focusMeasure = cv::mean(FM).val[0];
return focusMeasure;
}
// OpenCV port of 'LAPV' algorithm (Pech2000)
double varianceOfLaplacian(const cv::Mat& src)
{
cv::Mat lap;
cv::Laplacian(src, lap, CV_64F);
cv::Scalar mu, sigma;
cv::meanStdDev(lap, mu, sigma);
double focusMeasure = sigma.val[0]*sigma.val[0];
return focusMeasure;
}
// OpenCV port of 'TENG' algorithm (Krotkov86)
double tenengrad(const cv::Mat& src, int ksize)
{
cv::Mat Gx, Gy;
cv::Sobel(src, Gx, CV_64F, 1, 0, ksize);
cv::Sobel(src, Gy, CV_64F, 0, 1, ksize);
cv::Mat FM = Gx.mul(Gx) + Gy.mul(Gy);
double focusMeasure = cv::mean(FM).val[0];
return focusMeasure;
}
// OpenCV port of 'GLVN' algorithm (Santos97)
double normalizedGraylevelVariance(const cv::Mat& src)
{
cv::Scalar mu, sigma;
cv::meanStdDev(src, mu, sigma);
double focusMeasure = (sigma.val[0]*sigma.val[0]) / mu.val[0];
return focusMeasure;
}
No hay garantías de si estas medidas son o no la mejor opción para su problema, pero si rastrea los documentos asociados con estas medidas, pueden darle más información. ¡Espero que encuentres útil el código! Sé que lo hice.
Partiendo de la respuesta de Nike. Es sencillo implementar el método basado en laplacian con opencv:
short GetSharpness(char* data, unsigned int width, unsigned int height)
{
// assumes that your image is already in planner yuv or 8 bit greyscale
IplImage* in = cvCreateImage(cvSize(width,height),IPL_DEPTH_8U,1);
IplImage* out = cvCreateImage(cvSize(width,height),IPL_DEPTH_16S,1);
memcpy(in->imageData,data,width*height);
// aperture size of 1 corresponds to the correct matrix
cvLaplace(in, out, 1);
short maxLap = -32767;
short* imgData = (short*)out->imageData;
for(int i =0;i<(out->imageSize/2);i++)
{
if(imgData[i] > maxLap) maxLap = imgData[i];
}
cvReleaseImage(&in);
cvReleaseImage(&out);
return maxLap;
}
Devolverá un breve indicando la máxima nitidez detectada, que según mis pruebas en muestras del mundo real, es un indicador bastante bueno de si una cámara está enfocada o no. No es sorprendente que los valores normales dependan de la escena, pero mucho menos que el método FFT, que tiene una tasa de falsos positivos alta para ser útil en mi aplicación.
Se me ocurrió una solución totalmente diferente. Necesitaba analizar cuadros fijos de video para encontrar el más nítido en cada (X) cuadros. De esta manera, detectaría imágenes borrosas y / o desenfocadas.
Terminé usando la detección de Canny Edge y obtuve MUY MUY buenos resultados con casi todos los tipos de video (con el método de Nikie, tuve problemas con los videos VHS digitalizados y los videos entrelazados pesados).
Optimicé el rendimiento estableciendo una región de interés (ROI) en la imagen original.
Usando EmguCV:
//Convert image using Canny
using (Image<Gray, byte> imgCanny = imgOrig.Canny(225, 175))
{
//Count the number of pixel representing an edge
int nCountCanny = imgCanny.CountNonzero()[0];
//Compute a sharpness grade:
//< 1.5 = blurred, in movement
//de 1.5 à 6 = acceptable
//> 6 =stable, sharp
double dSharpness = (nCountCanny * 1000.0 / (imgCanny.Cols * imgCanny.Rows));
}
Gracias nikie por esa gran sugerencia de Laplace. Los documentos de OpenCV me apuntaron en la misma dirección: usando python, cv2 (opencv 2.4.10) y numpy ...
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
numpy.max(cv2.convertScaleAbs(cv2.Laplacian(gray_image,3)))
El resultado es entre 0-255. Encontré que cualquier cosa superior a 200ish está muy enfocada, y para 100, es notablemente borrosa. el máximo nunca tiene mucho menos de 20 incluso si está completamente borroso
Una forma que estoy usando actualmente mide la extensión de los bordes en la imagen. Busque este documento:
@ARTICLE{Marziliano04perceptualblur,
author = {Pina Marziliano and Frederic Dufaux and Stefan Winkler and Touradj Ebrahimi},
title = {Perceptual blur and ringing metrics: Application to JPEG2000,” Signal Process},
journal = {Image Commun},
year = {2004},
pages = {163--172} }
Por lo general, está detrás de un muro de pago, pero he visto algunas copias gratuitas. Básicamente, localizan bordes verticales en una imagen y luego miden qué tan anchos son esos bordes. Al promediar el ancho se obtiene el resultado final de estimación de desenfoque para la imagen. Los bordes más anchos corresponden a imágenes borrosas, y viceversa.
Este problema pertenece al campo de la estimación de calidad de imagen sin referencia . Si lo busca en Google Scholar, obtendrá muchas referencias útiles.
EDITAR
Aquí hay una trama de las estimaciones de desenfoque obtenidas para las 5 imágenes en la publicación de Nikie. Los valores más altos corresponden a un mayor desenfoque. Utilicé un filtro gaussiano de tamaño fijo 11x11 y varié la desviación estándar (usando el convert
comando de imagemagick para obtener las imágenes borrosas).
Si compara imágenes de diferentes tamaños, no olvide normalizar por el ancho de la imagen, ya que las imágenes más grandes tendrán bordes más anchos.
Finalmente, un problema importante es distinguir entre el desenfoque artístico y el desenfoque no deseado (causado por falta de enfoque, compresión, movimiento relativo del sujeto hacia la cámara), pero eso está más allá de enfoques simples como este. Para ver un ejemplo de desenfoque artístico, eche un vistazo a la imagen de Lenna: el reflejo de Lenna en el espejo es borroso, pero su rostro está perfectamente enfocado. Esto contribuye a una estimación de desenfoque más alta para la imagen de Lenna.
Probé una solución basada en el filtro laplaciano de esta publicación. No me ha ayudado. Entonces, probé la solución de esta publicación y fue buena para mi caso (pero es lenta):
import cv2
image = cv2.imread("test.jpeg")
height, width = image.shape[:2]
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
def px(x, y):
return int(gray[y, x])
sum = 0
for x in range(width-1):
for y in range(height):
sum += abs(px(x, y) - px(x+1, y))
¡La imagen menos borrosa tiene el sum
valor máximo !
También puede ajustar la velocidad y la precisión cambiando el paso, p. Ej.
esta parte
for x in range(width - 1):
puedes reemplazar con este
for x in range(0, width - 1, 10):
Las respuestas anteriores aclararon muchas cosas, pero creo que es útil hacer una distinción conceptual.
¿Qué sucede si toma una imagen perfectamente enfocada de una imagen borrosa?
El problema de detección de desenfoque solo está bien planteado cuando tiene una referencia . Si necesita diseñar, por ejemplo, un sistema de enfoque automático, compara una secuencia de imágenes tomadas con diferentes grados de desenfoque o suavizado e intenta encontrar el punto de desenfoque mínimo dentro de este conjunto. En otras palabras, debe hacer una referencia cruzada de las diversas imágenes utilizando una de las técnicas ilustradas anteriormente (básicamente, con varios niveles posibles de refinamiento en el enfoque, buscando la única imagen con el mayor contenido de alta frecuencia).
El código Matlab de dos métodos que se han publicado en revistas de gran prestigio (Transacciones IEEE sobre procesamiento de imágenes) están disponibles aquí: https://ivulab.asu.edu/software
verifique los algoritmos CPBDM y JNBM. Si revisa el código, no es muy difícil portarlo y, por cierto, se basa en el método de Marzialiano como característica básica.
Lo implementé, use fft en matlab y verifique el histograma de la media de cálculo de fft y std, pero también se puede hacer la función de ajuste
fa = abs(fftshift(fft(sharp_img)));
fb = abs(fftshift(fft(blured_img)));
f1=20*log10(0.001+fa);
f2=20*log10(0.001+fb);
figure,imagesc(f1);title('org')
figure,imagesc(f2);title('blur')
figure,hist(f1(:),100);title('org')
figure,hist(f2(:),100);title('blur')
mf1=mean(f1(:));
mf2=mean(f2(:));
mfd1=median(f1(:));
mfd2=median(f2(:));
sf1=std(f1(:));
sf2=std(f2(:));
Eso es lo que hago en Opencv para detectar la calidad del enfoque en una región:
Mat grad;
int scale = 1;
int delta = 0;
int ddepth = CV_8U;
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
/// Gradient X
Sobel(matFromSensor, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
/// Gradient Y
Sobel(matFromSensor, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
convertScaleAbs(grad_y, abs_grad_y);
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
cv::Scalar mu, sigma;
cv::meanStdDev(grad, /* mean */ mu, /*stdev*/ sigma);
focusMeasure = mu.val[0] * mu.val[0];