1 Востаннє редагувалося Teg Miles (05.09.2024 22:36:32)

Тема: Обробка зображення

Є зображення, подане одновимірним масивом unsigned char,
і фільтр, поданий двовимірним масивом float.
Щоб обробити зображення потрібно помножити фільтр на зображення,
так щоб середина фільтру збігалася з елементом зображення,
а решта з навколишніми елементами зображення (треба доповнити найближчими пікселями, де потрібно).
Тобто елементи фільтру множаться на елементи зображення, а їхня сума — це вже елемент обробленого зображення.
Повна умова тут: https://www.codewars.com/kata/5239078120eeabe18f0000da
Ось моє рішення цього завдання.

#include <vector>

typedef unsigned char u8;

std::vector<std::vector<int>> get_coords_mat(const int& row, const int& col)
{
    std::vector<std::vector<int>> coords(row * col, std::vector<int>(2));
    int j { 0 };
    for (int i = 0; i < row * col; ++i) {
        coords.at(i) = { i % row - row / 2, row / 2 - j / row };
        ++j;
    }
    
    return coords;
}



int find_sum(const int& i, const int& j, const int& height, const int& width,
    const std::vector<u8>& img, std::vector<std::vector<float>>& weights)
{
    int cur_sum { 0 };

    std::vector<std::vector<int>> coords = get_coords_mat(weights.size(), weights.at(0).size());
    for (auto& coord : coords) {
        int row_coord { i + coord.at(0) },
            col_coord { j + coord.at(1) };
        int row_weights = weights.size() / 2 + coord.at(0);
        int col_weights = weights.at(0).size() / 2 + coord.at(1);
        
        cur_sum += img.at(row_coord * width + col_coord) * weights.at(row_weights).at(col_weights);
        
    }
    return cur_sum%256;
}

std::vector<u8> padding(const std::vector<u8>& original, int& height,
    int& width, const int& kernel_row, const int& kernel_col)
{
    int new_mat_row = height + 2 * (kernel_row / 2);
    int new_mat_col = width + 2 * (kernel_col / 2);
    std::vector<u8> new_mat(new_mat_row * new_mat_col);
    for (int i = kernel_row / 2; i < new_mat_row - kernel_row / 2; ++i) {
        for (int j = kernel_col / 2; j < new_mat_col - kernel_col / 2; ++j) {
            new_mat.at(i * new_mat_col + j) = original.at((i - kernel_row / 2) * width + (j - kernel_col / 2));
        }
    }

    for (int i = 0; i < new_mat_row; ++i) {
        for (int j = 0; j < new_mat_col; ++j) {
            if (i >= 0 && i <= kernel_row / 2 && j >= 0 && j <= kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.front();
            } else if (i >= new_mat_row - kernel_row / 2 - 1 && j >= new_mat_col - kernel_col / 2 - 1) {
                new_mat.at(i * new_mat_col + j) = original.back();
            } else if (i >= 0 && i <= kernel_row / 2 && j >= new_mat_col - kernel_col / 2 - 1) {
                new_mat.at(i * new_mat_col + j) = original.at(width - 1);
            } else if (i >= new_mat_row - kernel_row / 2 - 1 && j >= 0 && j <= kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.at((height - 1) * width);
            } else if (i > kernel_row / 2 && i < new_mat_row - kernel_row / 2 - 1 && j >= 0 && j < kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.at((i - kernel_row / 2) * width);
            } else if (i >= 0 && i < kernel_row / 2 && j > kernel_col / 2 && j < new_mat_col - kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.at(j - kernel_col / 2);
            } else if (i > kernel_row / 2 && i < new_mat_row - kernel_row / 2 - 1 && j >= new_mat_col - kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.at((i - kernel_row / 2) * width + width - 1);
            } else if (i > new_mat_row - kernel_row / 2 - 1 && j > kernel_col / 2 && j < new_mat_col - kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.at((height - 1) * width + (j - kernel_col / 2));
            }
        }
    }

    
    return new_mat;
}

std::vector<u8> processImage(const std::vector<u8>& imageData, int height,
    int width, std::vector<std::vector<float>> weights)
{
    std::vector<u8> answer(imageData.size(), 0);
    const int& kernel_row = weights.size();
    const int& kernel_col = weights.at(0).size();
    
    std::vector<u8> padded = padding(imageData, height, width, kernel_row, kernel_col);
    int padded_row = height + (kernel_row / 2) * 2;
    int padded_col = width + (kernel_col / 2) * 2;
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            answer.at(i * width + j) = find_sum(i + kernel_row / 2, j + kernel_col / 2, padded_row, padded_col, padded, weights);
            
        }
    }

    

    return answer;
}

Але щось я зробив не так, бо зображення повертається кашею.
Підозрюю, що це якось пов'язано з множенням unsigned char на float, але не бачу, де саме помилився.

2

Re: Обробка зображення

звісно, треба було суму в окрему функцію винести, а ще краще - створити клас і методи, але мені ліньки
#include <vector>
#include <algorithm>
#include <cmath>

typedef unsigned char u8;

std::vector<u8> processImage (const std::vector <u8> &imageData, int height, int width, std::vector <std::vector <float>> weights) {
    std::vector<u8> result;
    int n = weights.size();
    for(int row=0; row<height; ++row) {
        for(int col=0; col<width; ++col) {
            for(int color=0; color<3; ++color) {
                float sum = 0.0;
                for(int dy=-n/2; dy<=n/2; ++dy) {
                    for(int dx=-n/2; dx<=n/2; ++dx) {
                        int x = std::clamp(col+dx, 0, width-1);
                        int y = std::clamp(row+dy, 0, height-1);
                        int coord = 3*(y*width+x)+color;
                        sum += imageData[coord] * weights[n/2+dy][n/2+dx];
                    }
                }
                result.push_back(std::clamp(int(round(sum)),0,255));
            }
        }
    }
    return result;
}

Що ж до вашої проблеми, то ви в курсі, що це за операція взагалі?

return cur_sum%256;
Подякували: Teg Miles1

3

Re: Обробка зображення

koala написав:
звісно, треба було суму в окрему функцію винести, а ще краще - створити клас і методи, але мені ліньки
#include <vector>
#include <algorithm>
#include <cmath>

typedef unsigned char u8;

std::vector<u8> processImage (const std::vector <u8> &imageData, int height, int width, std::vector <std::vector <float>> weights) {
    std::vector<u8> result;
    int n = weights.size();
    for(int row=0; row<height; ++row) {
        for(int col=0; col<width; ++col) {
            for(int color=0; color<3; ++color) {
                float sum = 0.0;
                for(int dy=-n/2; dy<=n/2; ++dy) {
                    for(int dx=-n/2; dx<=n/2; ++dx) {
                        int x = std::clamp(col+dx, 0, width-1);
                        int y = std::clamp(row+dy, 0, height-1);
                        int coord = 3*(y*width+x)+color;
                        sum += imageData[coord] * weights[n/2+dy][n/2+dx];
                    }
                }
                result.push_back(std::clamp(int(round(sum)),0,255));
            }
        }
    }
    return result;
}

Що ж до вашої проблеми, то ви в курсі, що це за операція взагалі?

return cur_sum%256;

Мені потрібно було отримати числа в межах 0-255, тому використав остачу від ділення.

4

Re: Обробка зображення

Вам треба будь-яке число в межах 0-255, чи все ж залежне від вхідних даних певним конкретним чином? Яким саме? Якщо ви отримали після фільтра значення 256, то що має бути на виході - 0 чи 255?

5

Re: Обробка зображення

До речі, а чому ваша функція find_sum повертає int, а не u8?

Подякували: leofun011

6

Re: Обробка зображення

koala написав:

До речі, а чому ваша функція find_sum повертає int, а не u8?

Бо був неуважний. Остача була лише тимчасовим рішенням, думав такий підхід
впливатиме лише на колір, а все решта більш менш працюватиме.
До речі, з кольором незрозуміло. І у вашому коді я не розумію де там доповнення по найближчому пікселю.

7

Re: Обробка зображення

А що робить std::clamp, як гадаєте?

8

Re: Обробка зображення

koala написав:

А що робить std::clamp, як гадаєте?

Зрозумів, замість створення доповнення навколо матриці,
clamp дає змогу просто стиснути координати до найближчого пікселя.

Ще не зовсім зрозуміли ось цей рядок:

int coord = 3*(y*width+x)+color;

У дужках — це координати пікселя, але нащо множити на три й додавати змінну color?
Розумію, що обробка кольору, але що дають такі дії?

9

Re: Обробка зображення

Teg Miles написав:

У дужках — це координати пікселя, але нащо множити на три й додавати змінну color?

Бо піксель складається з 3 байтів, по одному на колір. Ви краще скажіть - ви як у своєму коді кольори розрізняєте? Константи 3 я у вас там не бачу, як і назв кольорів. Де саме відбувається відокремлення кольорів, щоб їх сумувати?

Подякували: leofun011

10

Re: Обробка зображення

koala написав:
Teg Miles написав:

У дужках — це координати пікселя, але нащо множити на три й додавати змінну color?

Бо піксель складається з 3 байтів, по одному на колір. Ви краще скажіть - ви як у своєму коді кольори розрізняєте? Константи 3 я у вас там не бачу, як і назв кольорів. Де саме відбувається відокремлення кольорів, щоб їх сумувати?

Ніде, поки що. Я ще не розібрався, як саме в моєму коді це реалізувати.

11

Re: Обробка зображення

Ось так зробив, але все одно десь помилка є, проходжу лише два тести:

#include <vector>
#include <algorithm>
#include <cmath>
typedef unsigned char u8;


std::vector<std::vector<int>> get_coords_mat(const int& row, const int& col)
{
    std::vector<std::vector<int>> coords(row * col, std::vector<int>(2));
    int j { 0 };
    for (int i = 0; i < row * col; ++i) {
        coords.at(i) = { i % row - row / 2, row / 2 - j / row };
        ++j;
    }
    
    return coords;
}



u8 find_sum(const int& i, const int& j, const int& width,
    const std::vector<u8>& img, std::vector<std::vector<float>>& weights)
{
    float cur_sum { 0.0 };

    std::vector<std::vector<int>> coords = get_coords_mat(weights.size(), weights.at(0).size());
    for (auto& coord : coords) {

        int row_coord { i + coord.at(0) },
            col_coord { j + coord.at(1) };
        int row_weights = weights.size() / 2 + coord.at(0);
        int col_weights = weights.at(0).size() / 2 + coord.at(1);
        
        cur_sum += img.at(row_coord * width + col_coord) * weights.at(row_weights).at(col_weights);
        
    }
    return std::clamp(int(std::round(cur_sum)), 0, 255);
}

std::vector<u8> padding(const std::vector<u8>& original, int& height,
    int& width, const int& kernel_row, const int& kernel_col)
{
    int new_mat_row = height + 2 * (kernel_row / 2);
    int new_mat_col = width + 2 * (kernel_col / 2);
    std::vector<u8> new_mat(new_mat_row * new_mat_col);
    for (int i = kernel_row / 2; i < new_mat_row - kernel_row / 2; ++i) {
        for (int j = kernel_col / 2; j < new_mat_col - kernel_col / 2; ++j) {
            new_mat.at(i * new_mat_col + j) = original.at((i - kernel_row / 2) * width + (j - kernel_col / 2));
        }
    }

    for (int i = 0; i < new_mat_row; ++i) {
        for (int j = 0; j < new_mat_col; ++j) {
            if (i >= 0 && i <= kernel_row / 2 && j >= 0 && j <= kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.front();
            } else if (i >= new_mat_row - kernel_row / 2 - 1 && j >= new_mat_col - kernel_col / 2 - 1) {
                new_mat.at(i * new_mat_col + j) = original.back();
            } else if (i >= 0 && i <= kernel_row / 2 && j >= new_mat_col - kernel_col / 2 - 1) {
                new_mat.at(i * new_mat_col + j) = original.at(width - 1);
            } else if (i >= new_mat_row - kernel_row / 2 - 1 && j >= 0 && j <= kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.at((height - 1) * width);
            } else if (i > kernel_row / 2 && i < new_mat_row - kernel_row / 2 - 1 && j >= 0 && j < kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.at((i - kernel_row / 2) * width);
            } else if (i >= 0 && i < kernel_row / 2 && j > kernel_col / 2 && j < new_mat_col - kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.at(j - kernel_col / 2);
            } else if (i > kernel_row / 2 && i < new_mat_row - kernel_row / 2 - 1 && j >= new_mat_col - kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.at((i - kernel_row / 2) * width + width - 1);
            } else if (i > new_mat_row - kernel_row / 2 - 1 && j > kernel_col / 2 && j < new_mat_col - kernel_col / 2) {
                new_mat.at(i * new_mat_col + j) = original.at((height - 1) * width + (j - kernel_col / 2));
            }
        }
    }

    
    return new_mat;
}

std::vector<u8> processImage(const std::vector<u8>& imageData, int height,
    int width, std::vector<std::vector<float>> weights)
{
    std::vector<u8> answer(3*imageData.size(), 0);
    const int& kernel_row = weights.size();
    const int& kernel_col = weights.at(0).size();
    
    std::vector<u8> padded = padding(imageData, height, width, kernel_row, kernel_col);
    
    int padded_col = width + (kernel_col / 2) * 2;
  
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
          for (int color=0; color<3; ++color){
            answer.at(3*(i * width + j)+color) = find_sum(i + kernel_row / 2, j + kernel_col / 2, padded_col, padded, weights);
          }
            
            
        }
    }

    

    return answer;
}

12

Re: Обробка зображення

Teg Miles написав:

Ось так зробив, але все одно десь помилка є, проходжу лише два тести[

Можливо я чогось не розумію, але:

Teg Miles написав:
    int new_mat_row = height + 2 * (kernel_row / 2);
    int new_mat_col = width + 2 * (kernel_col / 2);

Який сенс множити на 2 та ділити на 2 ???!
А від стилю коду просто  :! хочеться. Читається важко, просто  *WALL*! Автор про коментарі хоч щось чув?!
Ну от, наприклад взяти функцію padding, котра, напевно за задумом автора, має повернути нове зображення, враховуючи розміри зображення на вході та розміри ядра. Тобто, ця функція створює нове зображення з новими розмірами, тоді так і називай функцію - newImageWithPadding, чи якось подібно, щоб було зрозуміло із найменування функції, що вона має виконувати.
Додавай коментарі до коду! Взяти хоча б шматок коду із купою if-else. Бля-я-я! Туди навіть заглядати не хочеться! Можна було б, хоча якось так:

Прихований текст
std::vector<u8> newImageWithPadding(const std::vector<u8> &inputImage, const int &imageHeight, const int &imageWidth, const int &kernelRows, const int &kernelColumns)
{
  // Ррозрахунок нового розміру зображення
  int heigthNewImage = ...;
  int widthNewImage = ... ;

  // Нове зображення
  std::vector<u8> outputImage(heigthNewImage * widthNewImage);

  // Заповнення відступів
  for (int i = 0; i < heigthNewImage; ++i)  {
    for (int j = 0; j < widthNewImage; ++j)    {
     // Лівий верхній кут
      if (i >= 0 && i <= kernelRows / 2 && j >= 0 && j <= kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.front();
      }

      // Правий верхній кут
      else if (i >= 0 && i <= kernelRows / 2 && j >= widthNewImage - kernelColumns / 2 - 1) {
        outputImage.at(i *widthNewImage + j) = inputImage.at(imageWidth - 1);
      }

      // Лівий нижній кут
      else if (i >= heigthNewImage - kernelRows / 2 - 1 && j >= 0 && j <= kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.at((imageHeight - 1) *imageWidth);
      }

      // Правий нижній кут
      else if (i >= heigthNewImage - kernelRows / 2 - 1 && j >= widthNewImage - kernelColumns / 2 - 1) {
        outputImage.at(i *widthNewImage + j) = inputImage.back();
      }

      // Лівий край
      else if (i > kernelRows / 2 && i < heigthNewImage - kernelRows / 2 - 1 && j >= 0 && j < kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.at((i - kernelRows / 2) *imageWidth);
      }

      // Верхній край
      else if (i >= 0 && i < kernelRows / 2 && j > kernelColumns / 2 && j < widthNewImage - kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.at(j - kernelColumns / 2);
      }

      // Правий край
      else if (i > kernelRows / 2 && i < heigthNewImage - kernelRows / 2 - 1 && j >= widthNewImage - kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.at((i - kernelRows / 2) *imageWidth + imageWidth - 1);
      }

      // Нижній край
      else if (i > heigthNewImage - kernelRows / 2 - 1 && j > kernelColumns / 2 && j < widthNewImage - kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.at((imageHeight - 1) *imageWidth + (j - kernelColumns / 2));
      }
    }
  }

  return outputImage;
}

13

Re: Обробка зображення

lucas-kane написав:
Teg Miles написав:

Ось так зробив, але все одно десь помилка є, проходжу лише два тести[

Можливо я чогось не розумію, але:

Teg Miles написав:
    int new_mat_row = height + 2 * (kernel_row / 2);
    int new_mat_col = width + 2 * (kernel_col / 2);

Який сенс множити на 2 та ділити на 2 ???!
А від стилю коду просто  :! хочеться. Читається важко, просто  *WALL*! Автор про коментарі хоч щось чув?!
Ну от, наприклад взяти функцію padding, котра, напевно за задумом автора, має повернути нове зображення, враховуючи розміри зображення на вході та розміри ядра. Тобто, ця функція створює нове зображення з новими розмірами, тоді так і називай функцію - newImageWithPadding, чи якось подібно, щоб було зрозуміло із найменування функції, що вона має виконувати.
Додавай коментарі до коду! Взяти хоча б шматок коду із купою if-else. Бля-я-я! Туди навіть заглядати не хочеться! Можна було б, хоча якось так:

Прихований текст
std::vector<u8> newImageWithPadding(const std::vector<u8> &inputImage, const int &imageHeight, const int &imageWidth, const int &kernelRows, const int &kernelColumns)
{
  // Ррозрахунок нового розміру зображення
  int heigthNewImage = ...;
  int widthNewImage = ... ;

  // Нове зображення
  std::vector<u8> outputImage(heigthNewImage * widthNewImage);

  // Заповнення відступів
  for (int i = 0; i < heigthNewImage; ++i)  {
    for (int j = 0; j < widthNewImage; ++j)    {
     // Лівий верхній кут
      if (i >= 0 && i <= kernelRows / 2 && j >= 0 && j <= kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.front();
      }

      // Правий верхній кут
      else if (i >= 0 && i <= kernelRows / 2 && j >= widthNewImage - kernelColumns / 2 - 1) {
        outputImage.at(i *widthNewImage + j) = inputImage.at(imageWidth - 1);
      }

      // Лівий нижній кут
      else if (i >= heigthNewImage - kernelRows / 2 - 1 && j >= 0 && j <= kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.at((imageHeight - 1) *imageWidth);
      }

      // Правий нижній кут
      else if (i >= heigthNewImage - kernelRows / 2 - 1 && j >= widthNewImage - kernelColumns / 2 - 1) {
        outputImage.at(i *widthNewImage + j) = inputImage.back();
      }

      // Лівий край
      else if (i > kernelRows / 2 && i < heigthNewImage - kernelRows / 2 - 1 && j >= 0 && j < kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.at((i - kernelRows / 2) *imageWidth);
      }

      // Верхній край
      else if (i >= 0 && i < kernelRows / 2 && j > kernelColumns / 2 && j < widthNewImage - kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.at(j - kernelColumns / 2);
      }

      // Правий край
      else if (i > kernelRows / 2 && i < heigthNewImage - kernelRows / 2 - 1 && j >= widthNewImage - kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.at((i - kernelRows / 2) *imageWidth + imageWidth - 1);
      }

      // Нижній край
      else if (i > heigthNewImage - kernelRows / 2 - 1 && j > kernelColumns / 2 && j < widthNewImage - kernelColumns / 2) {
        outputImage.at(i *widthNewImage + j) = inputImage.at((imageHeight - 1) *imageWidth + (j - kernelColumns / 2));
      }
    }
  }

  return outputImage;
}

Там цілі числа, тому 3/2 = 1, 1*2 = 2. А без двійок було б 3.
Стиль коду поганий, бо я лише навчаюся.
Код був невеликий, тому вирішив не коментувати. Хоча, може, й потрібно було.