奇怪的结果来自Kuwahara滤波器

10

我正在使用C++和OpenCV实现一个Kuwahara滤波器来帮助打开和显示图像。这个想法非常简单,但不知道为什么结果很奇怪。以下是代码:

#include "opencv2/opencv.hpp"
#include <iostream>
#include <iomanip>
#include <cmath>

using namespace std;
using namespace cv;

//This class is essentially a struct of 4 Kuwahara regions surrounding a pixel, along with each one's mean, sum and variance.
class Regions{
    int* Area[4];
    int Size[4];
    unsigned long long Sum[4];
    double Var[4];
    int kernel;
public:
    Regions(int _kernel) : kernel(_kernel) {
        for (int i = 0; i<4; i++) {
            Area[i] = new int[kernel*kernel];
            Size[i] = 0;
            Sum[i] = 0;
            Var[i] = 0.0;
        }
    }

    //Update data, increase the size of the area, update the sum
    void sendData(int area, int data){
        Area[area][Size[area]] = data;
        Sum[area] += data;
        Size[area]++;
    }
    //Calculate the variance of each area
    double var(int area) {
        int __mean = Sum[area]/Size[area];
        double temp = 0;
        for (int i = 0; i<Size[area]; i++) {
            temp+= (Area[area][i] - __mean) * (Area[area][i] - __mean);
        }
        if (Size[area]==1) return 1.7e38; //If there is only one pixel inside the region then return the maximum of double
                                           //So that with this big number, the region will never be considered in the below minVar()
        return sqrt(temp/(Size[area]-1));
    }
    //Call the above function to calc the variances of all 4 areas
    void calcVar() {
        for (int i = 0; i<4; i++) {
            Var[i] = var(i);
        }
    }
    //Find out which regions has the least variance
    int minVar() {
        calcVar();
        int i = 0;
        double __var = Var[0];
        if (__var > Var[1]) {__var = Var[1]; i = 1;}
        if (__var > Var[2]) {__var = Var[2]; i = 2;}
        if (__var > Var[3]) {__var = Var[3]; i = 3;}
        return i;
    }

    //Return the mean of that regions
    uchar result(){
        int i = minVar();
        return saturate_cast<uchar> ((double) (Sum[i] *1.0 / Size[i]));
    }
};

class Kuwahara{
private:
    int wid, hei, pad, kernel;
    Mat image;
public:
    Regions getRegions(int x, int y){
        Regions regions(kernel);

        uchar *data = image.data;

        //Update data for each region, pixels that are outside the image's boundary will be ignored.

        //Area 1 (upper left)
        for (int j = (y-pad >=0)? y-pad : 0; j>= 0 && j<=y && j<hei; j++)
            for (int i = ((x-pad >=0) ? x-pad : 0); i>= 0 && i<=x && i<wid; i++) {
                regions.sendData(1,data[(j*wid)+i]);
            }
        //Area 2 (upper right)
        for (int j = (y-pad >=0)? y-pad : 0; j<=y && j<hei; j++)
            for (int i = x; i<=x+pad && i<wid; i++) {
                regions.sendData(2,data[(j*wid)+i]);
            }
        //Area 3 (bottom left)
        for (int j = y; j<=y+pad && j<hei; j++)
            for (int i = ((x-pad >=0) ? x-pad : 0); i<=x && i<wid; i++) {
                regions.sendData(3,data[(j*wid)+i]);
            }
        //Area 0 (bottom right)
        for (int j = y; j<=y+pad && j<hei; j++)
            for (int i = x; i<=x+pad && i<wid; i++) {
                regions.sendData(0,data[(j*wid)+i]);
            }
        return regions;
    }

    //Constructor
    Kuwahara(const Mat& _image, int _kernel) : kernel(_kernel) {
        image = _image.clone();
        wid = image.cols; hei = image.rows;
        pad = kernel-1;
    }

    //Create new image and replace its pixels by the results of Kuwahara filter on the original pixels
    Mat apply(){
        Mat temp;
        temp.create(image.size(), CV_8U);
        uchar* data = temp.data;

        for (int j= 0; j<hei; j++) {
            for (int i = 0; i<wid; i++)
                data[j*wid+i] = getRegions(i,j).result();
        }
        return temp;
    }
};

int main() {
    Mat img = imread("limes.tif", 1);
    Mat gray, dest;
    int kernel = 15;
    gray.create(img.size(), CV_8U);
    cvtColor(img, gray, CV_BGR2GRAY);

    Kuwahara filter(gray, kernel);

    dest = filter.apply();

    imshow("Result", dest);
    imwrite("result.jpg", dest);
    waitKey();
}

以下是结果: enter image description here

可以看到与正确结果不同,这些青柠的边框似乎被复制并向上移动了。如果我应用15x15滤波器,会得到完全混乱的结果,如下图所示:

enter image description here

我已经花了一整天时间进行调试,但到目前为止还没有发现任何问题。我甚至手动计算小图片的结果,并将其与实际结果进行比较,也没有发现任何差异。 有谁能帮我找出我做错了什么吗? 非常感谢。


1
那里有很多代码要检查,但我立即注意到的是,在你的var()函数中,你在某些地方使用了整数算术,这可能会导致一些值的截断。我不确定在预期范围内它是否重要,但值得尝试以双精度执行所有操作,看看是否有任何区别。 - Roger Rowland
@roger_rowland 感谢您抽出时间阅读我的代码并指出错误。然而,当我使用double代替int并强制所有算术计算都处理double时,结果并没有改变。 - Max
3
这不会解决你的问题,但最好练习使用 std::numeric_limits<double>::max() 来代替手动编写 double 的最大值。此外,你应该使用常量来命名你的区域,比如使用 UPPER_LEFT 而非 1。正如我说的那样,这些细节不会解决你的问题,但如果你的代码易于阅读和自我记录,人们更可能愿意帮助你 :) - Morwenn
@Morwenn 很酷,谢谢。在我不得不硬编码最大值之前,我尝试搜索了numeric_limits函数。 - Max
如果您需要一些基础代码来构建,我发现Jan Eric Kyprianidis的Kuwahara滤波器的初始实现是一个很好的起点:http://code.google.com/p/gpuakf/source/browse/glsl/kuwahara.glsl(来自此项目:http://code.google.com/p/gpuakf/和此出版物:http://www.kyprianidis.com/p/pg2009/)。这是GL着色器代码,但类似于C语言,您应该能够在不费太多力气的情况下将其适应到您的情况中。我在这里的着色器中使用了基于此的东西:https://dev59.com/3m025IYBdhLWcg3wvIgo#9402041 - Brad Larson
显示剩余2条评论
1个回答

7
事实证明,我的代码并没有问题,但我定义卷积核的方式是有问题的。我的卷积核实际上是四个小的Kuwahara部分之一,而正确的卷积核定义是每个像素计算数据的整个区域,因此包含所有四个部分的区域实际上是卷积核。因此,当谈论一个7x7的“卷积核”时,我实际上应用了一个15x15的卷积核,并且可怕的结果不是来自我认为的15x15卷积核,而是来自31x31卷积核。在那个尺寸下,Kuwahara滤波器根本没有意义,奇怪的结果是不可避免的。

旧帖子,但我无法直接联系到您。我正在研究一些边缘保留滤波器,并想尝试Kuwahara算法。您有任何进展吗?您是否有机会将任何变体添加到OpenCV贡献库中? - StringTheory

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接