프로젝트/OpenCV

영상처리 알고리즘 병렬화 - [OpenCV][C++]영상에 Median Filter(중간값 필터) 적용

윤깡패 2023. 6. 13. 13:12

Previous Post

영상처리 알고리즘 병렬화 - [OpenCV][C++]영상에 Salt and Pepper Noise 추가

 

영상처리 알고리즘 병렬화 - [OpenCV][C++]영상에 Salt and Pepper Noise 추가

Previous Post 영상처리 알고리즘 병렬화 - [OpenCV][C++]영상 읽기/출력/저장 영상처리 알고리즘 병렬화 - [OpenCV][C++]영상 읽기/출력/저장 OpenCV(Open Source Computer Vision Library) : 실시간 컴퓨터 비전을 목적

soobin0821.tistory.com

 


 

Goal

  • 노이즈 추가된 영상에 Median Filter 적용
  • 필터 적용된 영상을 출력 및 저장

 

Median Filter 알고리즘

이미지 내의 노이즈를 제거하는 방법 중 하나로, 주어진 마스크 영역의 값들을 크기 순서대로 정렬한 후 그 중 중간 크기의 값을 선택하는 필터

 

Median Filter 원리

[ 예시 ]

 

1. 전체 이미지를 일정한 크기의 작은 영역으로 나눈다.

[ 예시 ]

마스크 크기 : 3 * 3

 

2. 나누어진 영역의 모든 픽셀값를 크기 순으로 정렬하고, 나누어진 영역의 가운데 픽셀을 중간값으로 대체한다.

[ 예시 ]

픽셀값 정렬 전 : 0, 5, 3, 6, 6, 5, 5, 2, 4

픽셀값 정렬 후 : 0, 2, 3, 4, 5, 5, 5, 6, 6

중간값 : 4

 

3. 이미지 픽셀을 픽셀 단위로 한 칸씩 이동하면서 계속 작동한다.

[ 예시 ]

 

[ 예시 ]

픽셀값 정렬 전 : 5, 3, 2, 6, 5, 10, 2, 4, 8

픽셀값 정렬 후 : 2, 2, 3, 4, 5, 5, 6, 8, 10

중간값 : 5

 

 

Median Filter 특징

+ Salt and Pepper 노이즈라고 불리는 Impulse 노이즈 제거에 효과적이다.

  ⇒ 검은색(필셀값 0) 또는 흰색(픽셀값 255) 점과 같이 데이터들의 중심에서 멀리 벗어나는 이상치(outlier)들을 제외하기 때문

  * Impulse Noise : 순간적으로 값이 튀는 잡음

 

마스크의 크기가 작은 경우, 잡음이 몰려 있는 영역은 잡음이 남아있을 수 있다.

 

- 마스크의 크기가 클 경우, 이미지의 윤곽이 무너질 수 있다.

  

- 연산량이 많이 필요하다.

  ⇒ 픽셀값들을 정렬해야 하기 때문

 

◦ void cv::medianBlur(InputArray src, OutputArray dst, int ksize)
이미지에 Median Filter를 적용한다.
- src : 입력 이미지
- dst : 반환 이미지
- ksize : 커널의 크기
    - 1보다 큰 홀수 (중간값을 찾기 위함)

 

 

Content

  • 이후 Median Filter 알고리즘의 주요 연산과정을 병렬화하기 위해  
    • OpenCV에서 제공하는 medianBlur() 함수를 사용하지 않고 Median Filter 알고리즘을 직접 구현하였다. 
    • STL에서 제공하는 sort() 함수를 사용하지 않고 정렬 알고리즘을 직접 구현하였다. 
void applyFilter(Mat &image, int kernal_size)
{
    int row_size = image.rows;
    int col_size = image.cols;
    int ch_num = image.channels();
    
    int center_pixel = kernal_size / 2;
    
    if(ch_num == 1)
    {
        vector<int> pixels(kernal_size * kernal_size);
        for(int row = 0; row < row_size - kernal_size; row++)
        {
            for(int col = 0; col < col_size - kernal_size; col++)
            {
                pixels.clear();
                for(int r = 0; r < kernal_size; r++)
                {
                    for(int c = 0; c < kernal_size; c++)
                    {
                        pixels.push_back(image.at<uchar>(row + r, col + c));
                    }
                }
                
                Bubble_Sort(pixels);
                
                image.at<uchar>(row + center_pixel, col + center_pixel) = pixels.at(kernal_size + center_pixel);
            }
        }
    }
    else if(ch_num == 3)
    {
        vector<int> pixels_B(kernal_size * kernal_size);
        vector<int> pixels_G(kernal_size * kernal_size);
        vector<int> pixels_R(kernal_size * kernal_size);
        for(int row = 0; row < row_size - kernal_size; row++)
        {
            for(int col = 0; col < col_size - kernal_size; col++)
            {
                pixels_B.clear();
                pixels_R.clear();
                pixels_G.clear();
                for(int r = 0; r < kernal_size; r++)
                {
                    for(int c = 0; c < kernal_size; c++)
                    {
                        pixels_B.push_back(image.at<Vec3b>(row + r, col + c)[0]);
                        pixels_G.push_back(image.at<Vec3b>(row + r, col + c)[1]);
                        pixels_R.push_back(image.at<Vec3b>(row + r, col + c)[2]);
                    }
                }
                
                Bubble_Sort(pixels_B);
                Bubble_Sort(pixels_G);
                Bubble_Sort(pixels_R);
                
                image.at<Vec3b>(row + center_pixel, col + center_pixel)[0] = pixels_B.at(kernal_size + center_pixel);
                image.at<Vec3b>(row + center_pixel, col + center_pixel)[1] = pixels_G.at(kernal_size + center_pixel);
                image.at<Vec3b>(row + center_pixel, col + center_pixel)[2] = pixels_R.at(kernal_size + center_pixel);
            }
        }
    }
}

 

  • 이후  병렬 프로그래밍의 성능 향상을 극대화하기 위해 
    • 시간 복잡도가 높은 Bubble Sort 알고리즘을 직접 구현하였다.
void Bubble_Sort(vector<int> &pixels)
{
    for(int i = 0; i < pixels.size() - 1; i++)
    {
        for(int j = 0; j < pixels.size() - 1; j++)
        {
            if(pixels[j] > pixels[j + 1])
            {
                swap(pixels[j], pixels[j + 1]);
            }
        }
    }
}

 

 

정렬(Sort) 알고리즘

 

정렬(Sort) 알고리즘

정렬(Sort) 알고리즘 데이터를 정해진 기준에 따라 순서대로 나열해 의미 있는 구조로 재설정하는 것 버블(Bubble) 정렬 알고리즘 데이터의 인접 요소끼리 비교하고, swap 연산을 수행하며 정렬하는

soobin0821.tistory.com

 

 

Code

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

void addNoise(Mat &image, int noise_num);   // 노이즈 추가
void applyFilter(Mat &image, int kernal_size);  // 필터 적용
void Bubble_Sort(vector<int> &pixels);   // 버블 정렬

int main(void)
{
    VideoCapture open_origin_video("vtest.avi");
    if(!open_origin_video.isOpened())
    {
        cerr << "ERROR! Unable to open the Origin Video\n";
        open_origin_video.release();
        return -1;
    }
    
    double fps = open_origin_video.get(CAP_PROP_FPS);
    int width = open_origin_video.get(CAP_PROP_FRAME_WIDTH);
    int height = open_origin_video.get(CAP_PROP_FRAME_HEIGHT);
    int fourcc = VideoWriter::fourcc('m', 'p', '4', 'v');
    
    VideoWriter save_origin_video("save_origin_video.mov", fourcc, fps, Size(width, height));
    VideoWriter save_noise_video("save_noise_video.mov", fourcc, fps, Size(width, height));
    VideoWriter save_filter_video("save_filter_video.mov", fourcc, fps, Size(width, height));
    
    while(open_origin_video.isOpened())
    {
        Mat origin_frame;
        open_origin_video >> origin_frame;
        
        if(origin_frame.empty())
        {
            cerr << "ERROR! blank Origin Frame grabbed\n";
            return 0;
        }
        
        // 원본 영상 출력, 저장
        imshow("Origin Video", origin_frame);
        save_origin_video << origin_frame;


        Mat noise_frame;
        noise_frame = origin_frame.clone();
        
        addNoise(noise_frame, 5000);    // 노이즈 추가

        // 노이즈가 적용된 영상 출력, 저장
        imshow("Noise Video", noise_frame);
        save_noise_video << noise_frame;
        
        
        Mat filter_frame;
        filter_frame = noise_frame.clone();
        
        applyFilter(filter_frame, 3);  // 필터 적용
        
        // 필터가 적용된 영상 출력, 저장
        imshow("Filter Video", filter_frame);
        save_filter_video << filter_frame;
        

        if(waitKey(1000) == 'q')
        {
            break;
        }
    }
    
    open_origin_video.release();
    save_origin_video.release();
    save_noise_video.release();
    save_filter_video.release();
    
    
    destroyAllWindows();
    
    return 0;
}


void addNoise(Mat &image, int noise_num)
{
    srand((unsigned int)time(NULL));
    
    for(int k = 0; k < noise_num; k++)
    {
        int col_pixel = rand() % image.cols;    // noise를 추가할 열을 임의로 선택
        int row_pixel = rand() % image.rows;    // noise를 추가할 행을 임의로 선택
        int ch_num = image.channels();
        int noise_color = (rand() % 2) * 255;   // 0 or 255
        
        if(ch_num == 1) // 흑백 영상
        {
            image.at<uchar>(row_pixel, col_pixel) = noise_color;    // 임의 픽셀의 픽셀값을 검정색(0) 혹은 흰색(255)으로 교체
        }
        else if(ch_num == 3)    // 컬러 영상
        {
            image.at<Vec3b>(row_pixel, col_pixel)[0] = noise_color;  // B 채널
            image.at<Vec3b>(row_pixel, col_pixel)[1] = noise_color;  // G 채널
            image.at<Vec3b>(row_pixel, col_pixel)[2] = noise_color;  // R 채널
        }
    }
}

void applyFilter(Mat &image, int kernal_size)
{
    int row_size = image.rows;
    int col_size = image.cols;
    int ch_num = image.channels();
    
    int center_pixel = kernal_size / 2;
    
    if(ch_num == 1) // 흑백 영상
    {
        vector<int> pixels(kernal_size * kernal_size);
        for(int row = 0; row < row_size - kernal_size; row++)
        {
            for(int col = 0; col < col_size - kernal_size; col++)
            {
                pixels.clear();
                for(int r = 0; r < kernal_size; r++)
                {
                    for(int c = 0; c < kernal_size; c++)
                    {
                        pixels.push_back(image.at<uchar>(row + r, col + c));
                    }
                }
                
                Bubble_Sort(pixels);
                
                image.at<uchar>(row + center_pixel, col + center_pixel) = pixels.at(kernal_size + center_pixel);
            }
        }
    }
    else if(ch_num == 3)   // 컬러 영상
    {
        vector<int> pixels_B(kernal_size * kernal_size);
        vector<int> pixels_G(kernal_size * kernal_size);
        vector<int> pixels_R(kernal_size * kernal_size);
        for(int row = 0; row < row_size - kernal_size; row++)
        {
            for(int col = 0; col < col_size - kernal_size; col++)
            {
                pixels_B.clear();
                pixels_R.clear();
                pixels_G.clear();
                for(int r = 0; r < kernal_size; r++)
                {
                    for(int c = 0; c < kernal_size; c++)
                    {
                        pixels_B.push_back(image.at<Vec3b>(row + r, col + c)[0]);
                        pixels_G.push_back(image.at<Vec3b>(row + r, col + c)[1]);
                        pixels_R.push_back(image.at<Vec3b>(row + r, col + c)[2]);
                    }
                }
                
                Bubble_Sort(pixels_B);
                Bubble_Sort(pixels_G);
                Bubble_Sort(pixels_R);
                
                image.at<Vec3b>(row + center_pixel, col + center_pixel)[0] = pixels_B.at(kernal_size + center_pixel);
                image.at<Vec3b>(row + center_pixel, col + center_pixel)[1] = pixels_G.at(kernal_size + center_pixel);
                image.at<Vec3b>(row + center_pixel, col + center_pixel)[2] = pixels_R.at(kernal_size + center_pixel);
            }
        }
    }
}

void Bubble_Sort(vector<int> &pixels)
{
    for(int i = 0; i < pixels.size() - 1; i++)
    {
        for(int j = 0; j < pixels.size() - 1; j++)
        {
            if(pixels[j] > pixels[j + 1])
            {
                swap(pixels[j], pixels[j + 1]);
            }
        }
    }
}

 

 

Result

  • Salt and Pepper Noise가 추가된 영상에 Median Filter가 적용된 새로운 영상이 화면에 출력되는 동안 새로운 영상 파일이 생성된다.

화면에 출력되는 필터가 적용된 영상 캡처

 

파일에 저장된 새로운 영상 캡처

 

- 영상이 종료될 경우, 생성했던 윈도우가 전부 제거된다.

 

  • 원본 영상 - Salt and Pepper Noise 추가된 영상 - Median Filter 적용된 영상 비교

영상 캡처

 

- 추가된 Salt and Pepper 노이즈는 대부분 제거되었다.

- 원본 영상에 비해 블러링(bluring)되었다.

 

 


GitHub

https://github.com/YunSoobin/ParallelProgramming

 

GitHub - YunSoobin/ParallelProgramming: 영상처리 알고리즘 병렬화

영상처리 알고리즘 병렬화. Contribute to YunSoobin/ParallelProgramming development by creating an account on GitHub.

github.com

 

 

728x90