Questa è una sorta di un follow-up alla tua previousquestions la differenza tra imresize
in MATLAB e cv::resize
in OpenCV dato un interpolazione bicubica.
Ero interessato a scoprire perché c'è una differenza.Queste sono le mie conclusioni (poiché ho compreso gli algoritmi, correggimi se faccio degli errori).
Pensate ridimensionamento di un'immagine come una trasformazione planare da un'immagine input di dimensione M-by-N
ad un'immagine di uscita di dimensioni scaledM-by-scaledN
.
Il problema è che i punti non si adattano necessariamente alla griglia discreta, pertanto per ottenere intensità di pixel nell'immagine di output, è necessario interpolare i valori di alcuni dei campioni adiacenti (solitamente eseguiti nell'ordine inverso, cioè per ogni pixel di uscita, troviamo il corrispondente punto non intero nello spazio di input e interpolato attorno ad esso).
Questo è dove gli algoritmi di interpolazione differiscono, scegliendo la dimensione del vicinato e i coefficienti di peso che danno a ciascun punto in quel quartiere. La relazione può essere di ordine primo o superiore (dove la variabile coinvolta è la distanza dal campione non intero inverso mappato ai punti discreti sulla griglia dell'immagine originale). Solitamente si assegnano pesi maggiori a punti più vicini.
Guardando imresize
in MATLAB, ecco i pesi funzioni lineari e noccioli cubi:
function f = triangle(x)
% or simply: 1-abs(x) for x in [-1,1]
f = (1+x) .* ((-1 <= x) & (x < 0)) + ...
(1-x) .* ((0 <= x) & (x <= 1));
end
function f = cubic(x)
absx = abs(x);
absx2 = absx.^2;
absx3 = absx.^3;
f = (1.5*absx3 - 2.5*absx2 + 1) .* (absx <= 1) + ...
(-0.5*absx3 + 2.5*absx2 - 4*absx + 2) .* ((1 < absx) & (absx <= 2));
end
(tratta in sostanza restituire il peso interpolazione di un campione in base a quanto dista da un punto interpolato.)
Questo è come queste funzioni assomigliano:
>> subplot(121), ezplot(@triangle,[-2 2]) % triangle
>> subplot(122), ezplot(@cubic,[-3 3]) % Mexican hat

Si noti che il kernel lineare (le funzioni lineari a tratti su intervalli [-1,0] e [0,1] e gli zero in altri casi) funzionano sui punti a 2 punti vicini, mentre il kernel cubico (pezzo per pezzo le funzioni cubiche sugli intervalli [-2, -1], [-1,1] e [1,2] e zeri altrove) funzionano su 4 punti adiacenti.
Qui è un'illustrazione per il caso 1-dimensionale, che mostra come interpolare il valore x
dai punti discreti f(x_k)
usando un kernel cilindrata:

Il kernel funzione h(x)
è centrata a x
, la posizione del punto da interpolare. Il valore interpolato f(x)
è la somma ponderata dei punti adiacenti discreti (2 a sinistra e 2 a destra) ridimensionati dal valore della funzione di interpolazione in quei punti discreti.
Say se la distanza tra x
e il punto più vicino è d
(0 <= d < 1
), il valore interpolato in posizione x
sarà:
f(x) = f(x1)*h(-d-1) + f(x2)*h(-d) + f(x3)*h(-d+1) + f(x4)*h(-d+2)
dove l'ordine dei punti è descritta sotto (si noti che x(k+1)-x(k) = 1
):
x1 x2 x x3 x4
o--------o---+----o--------o
\___/
distance d
Ora, poiché i punti sono discreti e campionato a intervalli uniformi, e la larghezza del kernel è solitamente di piccole dimensioni, l'interpolazione può essere formulato sinteticamente come convoluzione funzionamento:

Il concetto si estende a 2 dimensioni semplicemente prima interpolazione lungo una dimensione, e quindi interpolando attraverso l'altra dimensione utilizzando i risultati della fase precedente.
Ecco un esempio di interpolazione bilineare, che in 2D considera 4 punti vicini:

L'interpolazione bicubico in 2D utilizza 16 punti vicini:

Innanzitutto interpolare lungo le righe (i punti rossi) usando i 16 campioni della griglia (rosa). Quindi interpoliamo lungo l'altra dimensione (linea rossa) usando i punti interpolati del passo precedente. In ogni fase viene eseguita un'interpolazione 1D regolare. In questo le equazioni sono troppo lunghe e complicate per me da elaborare a mano!
Ora, se torniamo alla funzione cubic
in MATLAB, in realtà corrisponde alla definizione del nucleo di convoluzione mostrato nella reference paper come l'equazione (4). Qui è la stessa cosa tratta da Wikipedia:

Si può vedere che nella definizione di cui sopra, MATLAB ha scelto un valore di a=-0.5
.
Ora la differenza tra l'attuazione in MATLAB e OpenCV è che OpenCV scelto un valore di a=-0.75
.
static inline void interpolateCubic(float x, float* coeffs)
{
const float A = -0.75f;
coeffs[0] = ((A*(x + 1) - 5*A)*(x + 1) + 8*A)*(x + 1) - 4*A;
coeffs[1] = ((A + 2)*x - (A + 3))*x*x + 1;
coeffs[2] = ((A + 2)*(1 - x) - (A + 3))*(1 - x)*(1 - x) + 1;
coeffs[3] = 1.f - coeffs[0] - coeffs[1] - coeffs[2];
}
Questo potrebbe non essere evidente subito, ma il codice non calcolare i termini della funzione di convoluzione cubica (elencate subito dopo l'equazione (25) nella carta):

Noi in grado di verificare che con l'aiuto della Symbolic Math Toolbox:
A = -0.5;
syms x
c0 = ((A*(x + 1) - 5*A)*(x + 1) + 8*A)*(x + 1) - 4*A;
c1 = ((A + 2)*x - (A + 3))*x*x + 1;
c2 = ((A + 2)*(1 - x) - (A + 3))*(1 - x)*(1 - x) + 1;
c3 = 1 - c0 - c1 - c2;
Quelle espressioni possono essere riscritti come:
012.351.641,061 mila
>> expand([c0;c1;c2;c3])
ans =
- x^3/2 + x^2 - x/2
(3*x^3)/2 - (5*x^2)/2 + 1
- (3*x^3)/2 + 2*x^2 + x/2
x^3/2 - x^2/2
che corrispondono alle condizioni dall'equazione sopra.
Ovviamente la differenza tra MATLAB e OpenCV si riduce all'utilizzo di un valore diverso per il termine libero a
. Secondo gli autori del documento, un valore di 0.5
è la scelta preferita perché implica proprietà migliori per l'errore di approssimazione rispetto a qualsiasi altra scelta per a
.
+1 - Cool! ... ecco da dove vengono queste chiavi! – rayryeng
@Gilad Ricordo che stavi esaminando l'interpolazione cubica di MATLAB rispetto a OpenCV e sembra che la differenza sia a = -0.5 per MATLAB e [a = -0.75 per OpenCV] (https://github.com/Itseez/opencv/ blob/master/modules/ImgProc/src/imgwarp.cpp # L155). – chappjc
@chappjc: +1 buona ricerca. In realtà stavo scrivendo una risposta a riguardo :) – Amro