Почему filter2D в OpenCV дает результаты, отличные от imfilter в Matlab? - PullRequest
0 голосов
/ 20 сентября 2018

У меня есть оригинальное изображение:

enter image description here

Затем я читаю его, создаю PSF и размываю его в Matlab:

lenawords1=imread('lenawords.bmp');
%create PSF
sigma=6;
PSFgauss=fspecial('gaussian', 8*sigma+1, sigma);

%blur it
lenablur1=imfilter(lenawords1, PSFgauss, 'conv');
lenablurgray1=mat2gray(lenablur1);
PSFgauss1 = PSFgauss/max(PSFgauss(:));

и я сохранил размытое изображение:

imwrite(lenablurgray1, 'lenablur.bmp');
imwrite(PSFgauss1, 'PSFgauss.bmp');

Их значения в Matlab и OpenCV совпадают.

Matlab:

 disp(lenablurgray1(91:93, 71:75)*256)
142.2222  147.9111  153.6000  159.2889  164.9778
153.6000  164.9778  170.6667  176.3556  176.3556
164.9778  176.3556  182.0444  187.7333  187.7333

disp(PSFgauss1(24:26, 24:26)*256)
248.9867  252.4690  248.9867
252.4690  256.0000  252.4690
248.9867  252.4690  248.9867

OpenCV:

Mat img = imread("lenablur.bmp");
cvtColor(img, img, cv::COLOR_BGR2GRAY);
cv::Mat kernel = imread("PSFgauss.bmp");
cvtColor(kernel, kernel, cv::COLOR_BGR2GRAY);

for (int r = 90; r < 93; r++) {
    for (int c = 70; c < 75; c++) {
        cout << (int)img.at<uchar>(r, c) << " ";
    }
    cout << endl;
}

142 147 153 159 164
153 164 ...
164 ...

cout << "PSF" << endl;
for (int r = 23; r < 26; r++) {
    for (int c = 23; c < 26; c++) {
        cout << (int)kernel.at<uchar>(r, c) << " ";
    }
    cout << endl;
}

248 251 248
251 255 251
248 251 248

Однако значения из filter2D в OpenCV и imfilter в Matlab не совпадают:

Matlab:

conv1=imfilter(lenablurgray1, PSFgauss1, 'conv');

disp(conv1(91:93, 71:75))
91.8094   96.1109   99.8904  103.1280  105.8210
97.3049  101.7757  105.6828  109.0073  111.7486
102.0122  106.5953  110.5755  113.9353  116.6769

OpenCV:

Mat conv1; 
filter2D(img, conv1, img.depth(), kernel, Point(-1, -1), 0,
BORDER_REFLECT);

for (int r = 90; r < 93; r++) {
    for (int c = 70; c < 75; c++) {
        cout << (int)conv1.at<uchar>(r, c) << " ";
    }
    cout << endl;
}

255 255 255 255 255
255 255 255 255 255
255 255 255 255 255

Почему значения filter2D неверны?

EDIT2:

cv::Mat kernel = imread("PSFgauss.bmp");
cvtColor(kernel, kernel, cv::COLOR_BGR2GRAY);
kernel.convertTo(kernel, CV_64F);
cv::Scalar kernelsum= cv::sum(kernel);
divide(kernel, kernelsum, kernel);

filter2D(img, conv1, img.depth(), kernel, Point(-1, -1), 0, BORDER_REFLECT);

for (int r = 90; r < 93; r++) {
    for (int c = 70; c < 75; c++) {
        cout << (int)conv1.at<uchar>(r, c) << " ";
}

дает

103 108 112 116 119
109 ..
115 ..

, что соответствует Matlabзначения conv1 при умножении на коэффициент 1,133

disp(conv1(91:93, 71:75) * 1.133)

104.0201  108.8937  113.1758  116.8441  119.8952
110.2464  115.3118  119.7386  123.5053  126.6112
115.5798  120.7725  125.2820  129.0887  132.1950

Однако значения отличаются, когда я делю img на conv1:

Matlab:

conv2 = lenablurgray1./conv1
disp(conv2(91:93, 71:75))

0.0061    0.0060    0.0060    0.0060    0.0061
0.0062    0.0063    0.0063    0.0063    0.0062
0.0063    0.0065    0.0064    0.0064    0.0063

OpenCV:

Mat conv2;
divide(img, conv1, conv2);

for (int r = 90; r < 93; r++) {
    for (int c = 70; c < 75; c++) {
        cout << (int)conv2.at<uchar>(r, c) << " ";
    }
    cout << endl;
}

1 1 1 1 1
1 1 ...
1 ...

почему это так?

1 Ответ

0 голосов
/ 20 сентября 2018

Когда вы делаете

lenablur1 = imfilter(lenawords1, PSFgauss, 'conv');

в MATLAB, PSFgauss нормализуется.Это означает, что его значения составляют до 1:

sum(PSFgauss(:)) == 1.0  % or at least it should be very close

Затем вы масштабируете его так, чтобы его максимальное значение было равно 1, чтобы вы могли сохранить его как файл BMP.Это дополнительно вызывает округление значений до 256 различных целых чисел.

Затем в OpenCV вы читаете в ядре, используя imread("PSFgauss.bmp"), и конвертируете обратно в изображение с серым значением.В результате получается ядро ​​с целочисленными значениями в диапазоне [0,255].В частности, оно не нормализовано.

В свертке происходит то, что вы умножаете каждый элемент ядра на пиксель изображения и суммируете все значения, чтобы получить одно выходное значение.Если ядро ​​нормализовано, это равно взвешенному усреднению.Если ядро ​​не нормализовано, средняя интенсивность изображения не будет сохранена.Так как ядро ​​здесь имеет значения намного больше, чем оно было изначально, выходные значения будут намного больше значений входного изображения.Поскольку входное изображение представляет собой 8-разрядное целое число без знака, а OpenCV использует насыщенное сложение, операция приводит к значению 255 для каждого пикселя.

В математической записи в MATLAB вы делаете

g = f * k

(* - свертка, f - изображение, k это ядро).В OpenCV вы делаете

g ' = f * Ck

(где C константа, приблизительно равная 255/max(PSFgauss(:), которая является коэффициентом, на который умножается ядро ​​при переходе от MATLAB к OpenCV).

Таким образом, деление на C должно вернуть ядро ​​обратнов том состоянии, когда вы использовали его для свертки в MATLAB.Но учтите, что эффект округления вы не сможете удалить.

Самый простой способ получить C в OpenCV - это разделить kernel на его сумму:

kernel.convertTo(kernel, CV_64F);
kernel /= cv::sum(kernel);
...