Размытие по Гауссу, средний фильтр, свертка

Я хочу реализовать функцию свертки для использования в среднем фильтре и гауссовском фильтре, и мне также нужно реализовать эти 2 фильтра для применения к файлам pgm. У меня есть

typedef struct _PGM{
int row;
int col;
int max_value;
int **matrix;
}PGM;

структура и

int convolution(int ** kernel,int ksize, PGM * image, PGM * output){

   int i, j, x, y;
   int sum;
   int data;
   int scale =ksize*ksize;
   int coeff;

 for (x=ksize/2; x<image->row-ksize/2;++x) {
  for (y=ksize/2; y<image->col-ksize/2; ++y){
     sum = 0;
    for (i=-ksize/2; i<=ksize/2; ++i){
      for (j=-ksize/2; j<=ksize/2; ++j){
        data = image->matrix[x +i][y +j];
        coeff = kernel[i+ksize/2][j+ksize/2];
        sum += data * coeff;
    }
  }
  output->matrix[x][y] = sum / scale; 
 }
}

return sum/scale;
}

сверточной функции, но я получаю ошибку (на самом деле она завершается) в сверточной функции, поэтому я не могу приступить к фильтрации. Можете ли вы помочь мне с реализацией?

Спасибо.


person Ege    schedule 16.10.2013    source источник
comment
Я получаю сообщение об ошибке, это не полезное описание. Какая точная ошибка? Что вы обнаружили, когда запускали это в отладчике?   -  person Oliver Charlesworth    schedule 17.10.2013
comment
@OliCharlesworth говорит, что в моей программе возникло нарушение прав доступа, но когда я выполнил трассировку, я не могу понять, что я сделал неправильно.   -  person Ege    schedule 17.10.2013
comment
Ну, на какой линии он разбился? Каковы были значения соответствующих переменных в то время? Они кажутся здравомыслящими?   -  person Oliver Charlesworth    schedule 17.10.2013
comment
@OliCharlesworth Вылетает при coeff = kernel[i+ksize/2][j+ksize/2]; строка, но размер кажется 3, я равен -1, а j равен -1, поэтому coeff должен быть равен ядру [0] [0]. Я не вижу смысла, который я упускаю.   -  person Ege    schedule 17.10.2013
comment
Вы уверены, что все задействованные указатели указывают на допустимую память?   -  person Oliver Charlesworth    schedule 17.10.2013
comment
@OliCharlesworth Я не знаю, как быть уверенным на самом деле. Когда я смотрю на адреса, которые они указывают в отладке, они кажутся нормальными, но кроме этого я не знаю, что делать.   -  person Ege    schedule 17.10.2013
comment
Не могли бы вы предоставить для нашего ознакомления код, который считывает коэффициенты ядра и вызывает convolution()?   -  person Iwillnotexist Idonotexist    schedule 18.10.2013


Ответы (1)


В вашей свертке есть две неверные вещи, которые, вероятно, не вызывают сбой. Во-первых, это стиль: вы используете x для перебора строк изображения, что я представляю больше как смещение y, и наоборот. Во-вторых, когда вы вычисляете сумму, вы не сбрасываете переменную sum = 0 до оценки ядра (двух внутренних циклов) для каждого пикселя. Вместо этого вы накапливаете sum по всем пикселям, что, вероятно, в конечном итоге приводит к целочисленному переполнению. Хотя, строго говоря, это UB и может привести к сбою, это не та проблема, с которой вы столкнулись.

Если вы любезно подтвердите, что сбой происходит на первом пикселе (x = ksize/2, y = ksize/2), то, поскольку сбой происходит на первом коэффициенте, считанном из ядра, я подозреваю, что вы могли передать «не ту вещь» как kernel. В представленном виде kernel является int**. Для размера ядра 3x3 это означает, что для корректного вызова этой функции вы должны были выделить в куче или стеке массив int*, где вы хранили 3 указателя на int массивов по 3 коэффициента в каждом. Если вместо этого вы передали массив int[3][3], функция свертки попытается интерпретировать первые один или два int в массиве как указатель на int, когда это не так, и попытается разыменовать его, чтобы получить коэффициент. Это, скорее всего, вызовет segfault.

Я также не знаю, почему вы возвращаете накопленную сумму. Это не «традиционный» вывод свертки, но я предполагаю, что вас интересовала средняя яркость выходного изображения, что является законным; В этом случае вы должны использовать отдельный и более широкий целочисленный аккумулятор (long или long long) и в конце разделить его на количество пикселей в выходе.

Вероятно, вы нашли структуру данных PGM в Интернете, скажем, здесь. Позвольте мне расстаться с этим практическим советом. В моей области (компьютерное зрение) выбранная библиотека компьютерного зрения OpenCV не выражает матрицу в виде массива row указателей на буферы из col элементов. Вместо этого выделяется большой блок памяти, в данном случае размером не менее image->row * image->col * sizeof(int), но часто image->row * image->step * sizeof(int), где image->step равно image->col, округленному до следующего числа, кратного 4 или 16. Затем сохраняется только один указатель, указатель на основание всего изображения, хотя необходимо сохранить дополнительное поле (шаг), если изображения не являются непрерывными.

Поэтому я бы переработал ваш код следующим образом:

/* Includes */
#include <stdlib.h>



/* Defines */
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))



/* Structure */

/**
 * Mat structure.
 * 
 * Stores the number of rows and columns in the matrix, the step size
 * (number of elements to jump from one row to the next; must be larger than or
 *  equal to the number of columns), and a pointer to the first element.
 */

typedef struct Mat{
    int  rows;
    int  cols;
    int  step;
    int* data;
} Mat;



/* Functions */

/**
 * Allocation. Allocates a matrix big enough to hold rows * cols elements.
 * 
 * If a custom step size is wanted, it can be given. Otherwise, an invalid one
 * can be given (such as 0 or -1), and the step size will be chosen
 * automatically.
 * 
 * If a pointer to existing data is provided, don't bother allocating fresh
 * memory. However, in that case, rows, cols and step must all be provided and
 * must be correct.
 * 
 * @param [in] rows         The number of rows of the new Mat.
 * @param [in] cols         The number of columns of the new Mat.
 * @param [in] step         The step size of the new Mat. For newly-allocated
 *                          images (existingData == NULL), can be <= 0, in
 *                          which case a default step size is chosen; For
 *                          pre-existing data (existingData != NULL), must be
 *                          provided.
 * @param [in] existingData A pointer to existing data. If NULL, a fresh buffer
 *                          is allocated; Otherwise the given data is used as
 *                          the base pointer.
 * @return An allocated Mat structure.
 */

Mat allocMat(int rows, int cols, int step, int* existingData){
    Mat M;

    M.rows = max(rows, 0);
    M.cols = max(cols, 0);
    M.step = max(step, M.cols);

    if(rows <= 0 || cols <= 0){
        M.data = 0;
    }else if(existingData == 0){
        M.data = malloc(M.rows * M.step * sizeof(*M.data));
    }else{
        M.data = existingData;
    }

    return M;
}

/**
 * Convolution. Convolves input by the given kernel (centered) and stores
 * to output. Does not handle boundaries (i.e., in locations near the border,
 * leaves output unchanged).
 * 
 * @param [in]  input  The input image.
 * @param [in]  kern   The kernel. Both width and height must be odd.
 * @param [out] output The output image.
 * @return Average brightness of output.
 * 
 * Note: None of the image buffers may overlap with each other.
 */

int convolution(const Mat* input, const Mat* kern, Mat* output){
    int i, j, x, y;
    int coeff, data;
    int sum;
    int avg;
    long long acc = 0;

    /* Short forms of the image dimensions */
    const int iw = input ->cols, ih = input ->rows, is = input ->step;
    const int kw = kern  ->cols, kh = kern  ->rows, ks = kern  ->step;
    const int ow = output->cols, oh = output->rows, os = output->step;

    /* Kernel half-sizes and number of elements */
    const int kw2   = kw/2,        kh2 = kh/2;
    const int kelem = kw*kh;

    /* Left, right, top and bottom limits */
    const int l = kw2,
              r = max(min(iw-kw2, ow-kw2), l),
              t = kh2,
              b = max(min(ih-kh2, oh-kh2), t);

    /* Total number of pixels */
    const int totalPixels = (r-l)*(b-t);

    /* Input, kernel and output base pointers */
    const int*  iPtr = input ->data;
    const int*  kPtr = kern  ->data + kw2 + ks*kh2;
    int*        oPtr = output->data;


    /* Iterate over pixels of image */
    for(y=t; y<b; y++){
        for(x=l; x<r; x++){
            sum = 0;

            /* Iterate over elements of kernel */
            for(i=-kh2; i<=kh2; i++){
                for(j=-kw2; j<=kw2; j++){
                    data   = iPtr[j + is*i + x];
                    coeff  = kPtr[j + ks*i    ];
                    sum   += data * coeff;
                }
            }

            /* Compute average. Add to accumulator and store as output. */
            avg      = sum / kelem;
            acc     += avg;
            oPtr[x]  = avg;
        }

        /* Bump pointers by one row step. */
        iPtr += is;
        oPtr += os;
    }

    /* Compute average brightness over entire output */
    if(totalPixels == 0){
        avg = 0;
    }else{
        avg = acc/totalPixels;
    }

    /* Return average brightness */
    return avg;
}


/**
 * Main
 */

int main(int argc, char* argv[]){
    /**
     * Coefficients of K. Binomial 3x3, separable. Unnormalized (weight = 16).
     * Step = 3.
     */

    int Kcoeff[3][3] = {{1, 2, 1}, {2, 4, 2}, {1, 2, 1}};

    Mat I = allocMat(1920, 1080, 0, 0);/* FullHD 1080p:  1920x1080 */
    Mat O = allocMat(1920, 1080, 0, 0);/* FullHD 1080p:  1920x1080 */
    Mat K = allocMat(   3,    3, 3, &Kcoeff[0][0]);

    /* Fill Mat I with something.... */

    /* Convolve with K... */
    int avg = convolution(&I, &K, &O);

    /* Do something with O... */

    /* Return */
    return 0;
}

Справка: Многолетний опыт работы с компьютерным зрением.

person Iwillnotexist Idonotexist    schedule 17.10.2013
comment
Спасибо, сэр, за вашу помощь. Я новичок в компьютерном зрении, на самом деле это моя первая попытка, поэтому я не имел дело с большими изображениями, это было изображение в градациях серого 500x500, и пользователю нужно ввести ядро ​​​​и его размер, поэтому я предпочел использовать int **, так как размер неизвестно, когда он создан. Он должен был возвращать сумму/шкалу, но я его отредактировал. Я отправляю правильное ядро, на самом деле я проверял его при отладке. Я попытаюсь понять ваш код сейчас, но я хочу понять, что на самом деле не так с моим, так как я студент, и мне нужно учиться на своих ошибках. - person Ege; 17.10.2013
comment
Помимо проблемы с отсутствием sum = 0 и проблемы со стилем, ваш код выглядит довольно хорошо для меня. Вот почему я предполагаю, что это как-то связано с тем, как вы передаете свой аргумент kernel. Если вы передаете int[3][3], он сломается. Если вы передаете что-то вроде int k1[3] = {1, 2, 1}; int k2[3] = {2, 4, 2}; int k3[3] = {1, 2, 1}; int* K[3] = {k1, k2, k3}; convolution(K, 3, image, output);, это должно работать. - person Iwillnotexist Idonotexist; 18.10.2013
comment
Код элегантен и очень легко читается. Ваш опыт, безусловно, показывает. Я украду этот =) - person étale-cohomology; 12.05.2018