227 votes

Existe-t-il un moyen de détecter si une image est floue ?

Je me demandais s'il existe un moyen de déterminer si une image est floue ou non en analysant les données de l'image.

7 votes

Question connexe qui a une bonne réponse, mais aussi une formulation de question plus impliquée. stackoverflow.com/questions/5180327/

172voto

nikie Points 7479

Une autre façon très simple d'estimer la netteté d'une image est d'utiliser un filtre de Laplace (ou LoG) et de choisir simplement la valeur maximale. L'utilisation d'une mesure robuste comme un quantile à 99,9 % est probablement préférable si vous vous attendez à du bruit (c'est-à-dire si vous choisissez le Nième contraste le plus élevé au lieu du contraste le plus élevé). Si vous vous attendez à des variations de la luminosité de l'image, vous devriez également inclure une étape de prétraitement pour normaliser la luminosité/contraste de l'image (par exemple, une égalisation d'histogramme).

J'ai implémenté la suggestion de Simon et celle-ci dans Mathematica, et je l'ai essayé sur quelques images de test :

test images

Le premier test consiste à flouter les images de test à l'aide d'un filtre gaussien avec un noyau de taille variable, puis à calculer la FFT de l'image floue et à prendre la moyenne des 90 % de fréquences les plus élevées :

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}]

Résultat dans un graphique logarithmique :

fft result

Les 5 lignes représentent les 5 images de test, l'axe X représente le rayon du filtre gaussien. Les graphiques sont décroissants, donc la FFT est une bonne mesure de la netteté.

Ceci est le code pour l'estimateur de flou "LoG le plus élevé" : Il applique simplement un filtre LoG et renvoie le pixel le plus brillant dans le résultat du filtre :

testLaplacian[img_] := Table[
  (
   blurred = GaussianFilter[img, r];
   Max[Flatten[ImageData[LaplacianGaussianFilter[blurred, 1]]]];
   ), {r, 0, 10, 0.5}]

Résultat : un tracé logarithmique :

laplace result

La dispersion pour les images non floues est un peu meilleure ici (2,5 contre 3,3), principalement parce que cette méthode n'utilise que le contraste le plus fort de l'image, alors que la FFT est essentiellement une moyenne sur l'ensemble de l'image. Les fonctions diminuent également plus rapidement, il pourrait donc être plus facile de définir un seuil "flou".

1 votes

Et si je cherche la mesure du flou local. En d'autres termes, une photo présente des zones où elle est floue et d'autres où elle est nette. Je veux avoir une carte qui estime le niveau de flou par pixel.

4 votes

@Drazick : Je ne suis pas sûr que ce soit possible. Par exemple, regardez l'image de Lena : Il y a de grandes zones où il n'y a pas de contraste (par exemple la peau de Lena) bien que la zone soit nette. Je ne vois pas comment dire si une telle zone lisse est "floue", ou comment la distinguer d'une zone non mise au point. Vous devriez poser cette question séparément (peut-être sur DSP.SE). Peut-être que quelqu'un d'autre a de meilleures idées.

2 votes

Est-il adapté au flou de mouvement ? ou seulement au flou gaussien ?

148voto

Simon Points 3820

Oui, c'est bien cela. Calculez la transformée de Fourier rapide et analysez le résultat. La transformée de Fourier vous indique quelles fréquences sont présentes dans l'image. S'il y a une faible quantité de hautes fréquences, alors l'image est floue.

C'est à vous de définir les termes "faible" et "élevé".

Modifier :

Comme indiqué dans les commentaires, si vous voulez un seul flotteur représentant le flou d'une image donnée, vous devez trouver une métrique appropriée.

la réponse de nikie fournir une telle métrique. Convoluer l'image avec un noyau laplacien :

   1
1 -4  1
   1

Et utilisez une métrique de maximum robuste sur la sortie pour obtenir un nombre que vous pouvez utiliser pour le seuillage. Essayez d'éviter de trop lisser les images avant de calculer le laplacien, car vous vous rendrez compte qu'une image lissée est effectivement floue :-).

16 votes

Le seul problème est que les valeurs "basse" et "haute" dépendent également de la scène. +1

4 votes

À moins que votre image ne soit cyclique, vous aurez généralement des arêtes vives aux bords de l'image qui conduisent à des fréquences très élevées.

2 votes

En général, vous étendez virtuellement votre image pour éviter cet effet. Vous pouvez également utiliser de petites fenêtres pour calculer le fft local.

91voto

mevatron Points 8271

Lors d'un travail sur un objectif autofocus, j'ai découvert cet ensemble d'algorithmes très utiles pour la mise au point de l'image. détection de la mise au point de l'image . Il est implémenté dans MATLAB, mais la plupart des fonctions sont assez faciles à porter vers OpenCV avec filtre2D .

Il s'agit essentiellement d'une mise en œuvre d'enquête de nombreux algorithmes de mesure de la focalisation. Si vous voulez lire les articles originaux, les références aux auteurs des algorithmes sont fournies dans le code. L'article de 2012 de Pertuz, et al. Analyse des opérateurs de mesure du focus pour la forme à partir du focus (SFF) donne un excellent aperçu de toutes ces mesures ainsi que de leurs performances (tant en termes de vitesse que de précision appliquées au SFF).

EDIT : Ajout du code MATLAB au cas où le lien ne fonctionne pas.

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
%******************************************************************

Quelques exemples de versions d'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;
}

Nous ne pouvons pas garantir que ces mesures constituent le meilleur choix pour votre problème, mais si vous recherchez les documents associés à ces mesures, ils peuvent vous éclairer davantage. J'espère que vous trouverez ce code utile ! En tout cas, moi, je l'ai trouvé utile.

0 votes

Dans l'algorithme tenengrad, quelle serait une valeur nominale pour kSize ?

0 votes

@mans J'utilise normalement 3, 5 ou 7 selon la résolution de l'image. Si vous avez besoin d'aller plus haut que cela, vous pouvez envisager de sous-échantillonner l'image.

34voto

Yaur Points 4362

Pour faire suite à la réponse de Nike. Il est facile d'implémenter la méthode basée sur les laplaciens avec 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;
}

Renverra un court-circuit indiquant la netteté maximale détectée, qui, d'après mes tests sur des échantillons du monde réel, est un assez bon indicateur pour savoir si une caméra est au point ou non. Il n'est pas surprenant que les valeurs normales dépendent de la scène mais beaucoup moins que la méthode FFT, dont le taux de faux positifs est trop élevé pour être utile dans mon application.

0 votes

Quelle serait la valeur seuil pour dire qu'une image est floue ? Je l'ai testé. Mais il montre des résultats variables. Pouvez-vous m'aider à définir le seuil ?

0 votes

J'ai également essayé votre suggestion, mais les chiffres que j'obtiens sont un peu aléatoires. Si je pose une nouvelle question concernant cette mise en œuvre particulière, pourriez-vous y jeter un coup d'œil ?

0 votes

@stpn Le bon seuil dépend de la scène. Dans mon application (CCTV), j'utilise un seuil par défaut de 300. Pour les caméras où ce seuil est trop bas, quelqu'un de l'assistance modifiera la valeur configurée pour cette caméra particulière.

30voto

Goldorak84 Points 594

J'ai trouvé une solution totalement différente. J'avais besoin d'analyser les images fixes de la vidéo pour trouver la plus nette dans chaque (X) images. De cette façon, je pouvais détecter les images floues et/ou mal cadrées.

J'ai fini par utiliser la détection Canny Edge et j'ai obtenu de TRES TRES bons résultats avec presque tous les types de vidéo (avec la méthode de Nikie, j'avais des problèmes avec les vidéos VHS numérisées et les vidéos fortement entrelacées).

J'ai optimisé les performances en définissant une région d'intérêt (ROI) sur l'image originale.

Utilisation d'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));
}

0 votes

Hey @Goladorak84, merci pour cela. Puis-je te demander si tu as choisi de multiplier par 1000.0 dans dSharpness ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X