728x90

허프 변환(Hough Transform)은 컴퓨터 비전, 이미지 처리 및 패턴 인식 분야에서 사용되는 특징 추출 기술들 중 하나 입니다. 1972년에 발표된 오래된 이론으로 처음에는 이미지 내에 직선 성분을 찾기 위한 방법이었지만 추후 원과 타원 추출 방법으로 확장 되었습니다. 현 글에서는 허프 변환을 이용한 직선 추출 방법에 대해 설명하겠습니다.

 

일반적인 에지 탐색 등을 활용한 직선 성분 추출 시 잡음 등에 의해 중간 중간이 끊겨서 완전한 직선을 탐색하기 어려운 경우가 많습니다. 이 때, 허프 변환을 활용하면 일부 좌표 손실 상황에서도 직선 형태를 강건하게 검출할 수 있다는 장점이 있습니다. 일반적으로 카테시안(데카르트) 좌표계에서 직선식 표현하면 1)와 같으며 기울기 m의 경우 무한대가 나올 수 있어 활용하는데 문제가 될 수 있습니다. 그래서 허프 변환에서는 2)와 같이 극좌표계로 변환하여 직선 성분을 탐색합니다.  

 

1) y = mx + b // m: 기울기, b: y절편

2) r = xcosθ + ysinθ // r: 원점에서 직선까지 수직 거리, θ: 수직선과 x축 사이의 각도

 

 

 

허프 변환은 일부 가려짐이나 잡음에 강한 특성을 가지는 반면 계산량이 많아 질 수 있다는 점과 파라미터 설정이 중요할 수 있습니다. 그림에서처럼 이미지 평면에 점 A, B, C가 존재할 때, 각 점에 대해 각도 별(0~180도)로 거리(r)와 각도(theta)를 구하게 됩니다. 각 점을 지나는 극좌표점을 비교해 보면 60도 일때 가장 비슷한 수치를 얻게되며 파랑색 직선을 얻을 수 있습니다.

 

OpenCV에서는 직선 탐색을 위한 허프변환에 HoughLines()와 HoughLinesP()의 두 가지 함수를 제공합니다. 전자는 기본 허프 변환이며, 후자는 확률적 허프 변환(Probabilistic Hough Transform)입니다. 파이썬 예제 코드를 통해 파라미터 설정을 참고해보면 이해하기 쉬울 수 있습니다.

 

lines = cv2.HoughLines(image, rho, theta, threshold)

- image: 8-bit, Canny 등 에지 탐색 된 이진화 이미지

- rho(r): 거리 해상도(1이면 1-pixel 간격)

- theta: 각도 해상도 (0~180), 라디안 값으로 적용 

- threshold: 겹치는 점들의 축적값에 대한 임계값

 

lines = cv2.HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap)

- minLineLength: 직선의 최소 길이

- maxLineGap: 탐색된 직선들 사이에 최대 허용 간격 

 

# Basic Hough Transform -------------------
import cv2
import numpy as np

img = cv2.imread('image.tif')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)

lines = cv2.HoughLines(edges, 1, np.pi / 180, 150) # 허프 변환

for line in lines:
    rho, theta = line[0]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a * rho
    y0 = b * rho
    # 두 점(x1,y1), (x2,y2)로 직선을 그림
    x1 = int(x0 + 1000*(-b))
    y1 = int(y0 + 1000*(a))
    x2 = int(x0 - 1000*(-b))
    y2 = int(y0 - 1000*(a))
    cv2.line(img, (x1,y1), (x2,y2), (0,0,255), 2)

cv2.imshow("Hough Lines", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

# Probabilistic Hough Transform --------------------------
import cv2
import numpy as np

img = cv2.imread('image.tif')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)

# 확률 허프 변환
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 80, minLineLength=50, maxLineGap=10)

for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)

cv2.imshow("Probabilistic Hough", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
728x90
728x90

Pseudo Color 처리는 시각적으로 미묘하게 구분하기 어려운 부분을 해석하기 쉽게 만들어 주는 방식입니다. 그레이 이미지나 온도 및 깊이 등 단일 채널 이미지를 색을 이용하여 명확하게 표현하고자 할때 사용하는 데이터 시각화 기법입니다.

 

인간의 시각은 색상 대비(contrast)에는 민감하지만 상대적으로 밝기 대비에는 둔감합니다. 기술적으로는 단순할 수 있지만 그림에서처럼 의료 흑백 CT와 MRI, 열화상 이미지나 위성 영상등에 많이 활용됩니다. Pseudo Color 기법에도 여러 방식이 있으며 개별 개발환경에 맞춰 정의하고 활용할 수도 있습니다.

 

1) Color Look up Table(CLUT)

그레이 값을 특정 RGB 색상표로 변환될 수 있도록 정의

 

2) HSV (색상 Hue, 채도 Saturation, 명도 Value)

채도와 명도는 고정하고 밝기값을 색상에 매핑하는 방식이며 정밀한 색 조절 가능 

 

3) Intensity Slicing

특정 밝기 구간에 대해 고정된 RGB 색상으로 매핑 방식이며 의료 영상에서 활용

 

4) Rainbow Mapping

전체 색상 스펙트럼을 이용해 연속적인 색상 표현, 위성 영상 처리에 활용

 

5) Heatmap Color Mapping

열화상 등에서 활용하는 방식으로 낮은 값일 수록 파랑, 높은 값일 수록 빨강으로 표현

 

 

예를 들어 Heatmap의 경우 그레이 스케일 0~50(파랑), 51~100(초록), 101~150(노랑), 151~255(빨강)으로 범위별 지정을 통해 칼라맵으로 표현이 가능합니다. OpenCV의 applyColorMap() 함수를 활용하여 Pseudo Color 처리를 테스트 해 볼수 있으며, 입력은 8-bit 단일 채널 이미지 입니다. 당연히 결과는 3채널 칼라(BGR) 이미지이며, Colormap은 OpenCV에 정의된 코드(Jet, Hot, HSV, ... Rainbow 등)로 활용할 수 있습니다. 아래는 applyColorMap을 활용한 파이썬 코드이니 참고해 보시면 활용에 도움이 될 수 있습니다.

 

import cv2

# gray image read
gray_img = cv2.imread('gray.tif', cv2.IMREAD_GRAYSCALE)

# color map
color_jet = cv2.applyColorMap(gray_img, cv2.COLORMAP_JET)
color_hot = cv2.applyColorMap(gray_img, cv2.COLORMAP_HOT)

# results
cv2.imshow('Original', gray)
cv2.imshow('Jet', color_jet)
cv2.imshow('Hot', color_hot)

cv2.waitKey(0)
cv2.destroyAllWindows()
728x90
728x90

이미지 처리에서 알고리즘 구현 시 픽셀 단위 처리나 필터 처리 등이 빈번하게 사용됩니다. 이는 이미지의 데이터 형식이 2차원 행렬 구조이기 때문에 그렇습니다. 영상처리 라이브러리인 OpenCV에서는 이미지 처리를 위해 데이터 타입 Mat을 제공합니다. CV의 여러 함수들 내부에서도 Mat 형식을 통한 처리가 기본이며, CV를 세부처리에 활용하기 위해서는 Mat 활용 방법을 정확히 알고 있어야 합니다.

 

행렬 Matrix와 배열 Array의 차이는 행렬은 X좌표와 Y좌표로 이루어진 2차원 배열을 의미하며, 배열은 1차원, 2차원, 3차원 모두를 포함합니다. 행렬 연산 관련 항목들을 찾다 보면 조밀 행렬 Dense Matrix와 희소 행렬 Sparse Matrix 형식을 볼 수 있습니다. 조밀 행렬은 배열의 모든 값이 의미 있는 값으로 채워져 있는 배열이며, 희소 행렬에서는 “0”이 아닌 값들을 의미 있는 값으로 보게 됩니다. 다시 말해 메모리 공간 확보 시 조밀 행렬은 전체 배열을 할당 해야하지만 희소 행렬은 “0”이 아닌 값만 메모리 할당을 하게 되어 메모리 절약을 할 수 있습니다.

 

Mat 형식은 Create 메서드로 객체 값을 할당 할 수도 있지만 생성자 여러 개로 오버로드 되어 있습니다. 메서드 및 예제는 C/C# 구현 예입니다. 

[Create 메서드]

Create( MatType type, params int[] sizes )

Create( Size size, MatType type )

Create( int rows, int cols, MatType type )

[ 생성자 ]

Mat()

Mat( int rows, int cols, MatType type )

Mat( int rows, int cols, MatType type, Scalar s )

Mat( Size size, MatType type )

Mat( Size size, MatType type, Scalar s )

 

기본적인 사용법은 아래와 같습니다. “CV_8UC3”은 8 bits Unsigned Char의 3채널을 의미합니다.

 

Mat M = new Mat();

예제 1) M.Create( MatType.CV_8UC3, new int[] { 240, 320 } );

예제 2) M.Create( new Size(320, 240), MatType.CV_8UC3 );

예제 3) M.Create( 240, 320, MatType.CV_8UC3 );

 

예제 4) Mat M( 240, 320, MatType.CV_8UC3 );

예제 5) Mat M( new Size(320, 240), MatType.CV_8UC3 );

 

Mat 클래스에서는 아래와 같은 기본 행렬 표현식을 사용할 수 있게 정적 메서드를 제공합니다. 기본 행렬 표현식은 단일 채널에 값을 할당하므로 주의해서 사용하면 됩니다.

[ 정적 메서드 ]

Mat.Eye( int rows, int cols, MatType type ) : 단위 행렬

Mat.Ones( int rows, int cols, MatType type ) : “1”로 채워진 행렬

Mat.Zeros( int rows, int cols, MatType type ) : “0”으로 채워진 행렬

Mat.Diag( Mat d ) : 대각행렬

 

예제) Mat M = Mat.Eye( new Size(3,3), MatType.CV_64FC1 ); // 64 bits double 1채널

 

Mat 데이터를 생성하고 처리했으면 행렬 요소에 접근 방법을 알아야합니다. 요소에 접근해서 값을 가져올때는 At() 또는 Get() 메서드를 사용하고, 값을 설정 할때는 Set() 메서드를 사용할 수 있습니다. 아래는 2차원 행렬 접근시 사용 예입니다. 행렬의 블럭 단위로 요소에 접근할 수 있는 메서드들 중에는 M.Row.Get, M.Col.Get, M.Row.Set, M.Col.Set 도 있습니다. 

[ 픽셀 접근 ]

M.At( i, j ) // 행렬 M의 i, j 위치 요소, (row, col>

M.Get( i, j ) // 행렬 M의 i, j 위치 요소

M.Set( i, j, value ) // 행렬 M의 i, j 위치 요소를 value로 설정 

 

예제) M.At(1, 1) // MatTyped이 CV_64FC1이면 type은 double

728x90
728x90

이미지 처리에서 영역을 분할하는 방법들은 다양합니다. OpenCV에서 제공하는 기능 중 floodFill도 영역을 분할 하는 방법들 중에 하나 입니다. 응용에서는 물체 추적에 전처리나 배경 제거, 물체의 영역 채우기 등에 활용됩니다.

 

아래는 파이썬에서 활용하는 함수 형태와 C/C++에서 사용하는 함수 예 입니다. C/C++에서 활용하는 함수는 두 가지 형태인데 mask 유무가 차이 입니다. 

 

(Python) retval, image, mask, rect = cv2.floodFill(image, mask, seedPoint, newVal,

                                                                                 loDiff=None, upDiff=None, flags=None)

 

(C/C++) int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal,

                                  Rect* rect = 0, Scalar loDiff = Scalar(), Scalar upDiff = Scalar(), int flags = 4);

 

(C/C++) int floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint, 

                    Scalar newVal, Rect* rect = 0, Scalar loDiff = Scalar(), Scalar upDiff = Scalar(), int flags = 4);

 

파라미터 설명은 아래와 같으며 실무 적용에서는 mask 활용할 때와 아닐 때만 유의하면 됩니다. 

 

1) image: 입력 이미지이자 처리 대상 이미지이며 그레이 이미지와 칼라 이미지 적용 가능합니다.

2) mask: 어떤 부분을 채울 때 채우기 작업을 제한하는 역할을 합니다. 예를 들어 영역의 에지를 추출한 Binary 이미지라면 “0”인 부분만 특정 값으로 채울 수 있습니다. 8-bit 그레이 이미지로 정의해야 하며, 원본 이미지보다 가로와 세로가 2 픽셀씩 커야 합니다. 추가로 마스크의 테두리는 “1”로 채우며, 동일한 마스크를 여러번 사용해도 채워진 영역이 겹치지 않도록 할 수 있습니다.

3) seedPoint: 값을 변경하는 시작 좌표(x, y) 입니다.

4) newVal: 변경할 픽셀값 또는 색상 입니다. 칼라라면 (255, 10, 10) 형태가 됩니다.

5) loDiff: 변경할 픽셀값의 하한값 입니다.

6) upDiff: 변경할 픽셀값의 상한값 입니다.

7) flag: 변경 처리 조건을 지정합니다.

 

flag에는 아래 두 가지가 있으며, 실제 적용 시에는 픽셀 연결 방식인 4방향 또는 8방향과 조합해서 사용할 수 있습니다.

 

(flag)

1) FLOODFILL_FIXED_RANGE: 정의된 상한 및 하한 픽셀값 비교

2) FLOODFILL_MASK_ONLY: 입력 이미지는 변화 없으며 mask를 업데이트

(connectivity) 상하좌우 4방향과 대각 포함한 8방향으로 비교

 

아래 예는 mask를 사용하지 않는 파이썬 및 C/C++ 코드 입니다. “flag” 부분을 살펴보면 “flags=4 | cv2.FLOODFILL_FIXED_RANGE”과 “4 | FLOODFILL_FIXED_RANGE”로 정의 되었으며, 4방향 + 정의된 상한 및 하한 픽셀값 비교 로 동일한 의미로 적용되었음을 알 수 있습니다. 

 

import cv2
import numpy as np

# 이미지 로드 및 복사
img = cv2.imread('image.jpg')
im_floodfill = img.copy()

# 마스크 생성
h, w = img.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)

# 위치 및 변환값
seed_point = (125, 125)
new_val = (255, 0, 0)

cv2.floodFill(im_floodfill, mask, seed_point, new_val,
              loDiff=(5, 5, 5), upDiff=(5, 5, 5),
              flags=4 | cv2.FLOODFILL_FIXED_RANGE)

cv2.imshow("Filled Image", im_floodfill)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

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

int main() {
    
    // 이미지 로드
    Mat img = imread("image.jpg");
   
    // 마스크 생성
    Mat mask = Mat::zeros(img.rows + 2, img.cols + 2, CV_8UC1);

    // 위치 및 변환값
    Point seedPoint(125, 125);
    Scalar fillColor(255, 0, 0);  

   floodFill(img, mask, seedPoint, fillColor,
              0,              // rect 포인터 (필요 없으면 0)
              Scalar(5,5,5),  // loDiff
              Scalar(5,5,5),  // upDiff
              4 | FLOODFILL_FIXED_RANGE);  // flags

    // 결과 출력
    imshow("Flood Filled", img);
    waitKey(0);
    return 0;
}
728x90
728x90

이미지에서 우리가 원하는 영역을 찾거나 추출할 때 필요한 방법이 이진화 Binarization 입니다. 물체 탐색, 물체 추적, 이미지 분할 등 알고리즘을 구현할 때 필수적으로 사용해야하는 기법이 이진화 기술들입니다. 이진화 방법에는 다양한 기술들이 있으며, OpenCV 에서 제공하는 기술들 중 하나인 적응적 이진화 기술 Adaptive Thresholding Method에 대해 알아보겠습니다.

 

이진화 방법은 크게 전역적 처리 Global Processing과 지역적 처리 방법 Local Processing 방법으로 구분할 수 있습니다. OpenCV에서 제공하는 “adaptiveThreshold” 방법은 지역적 처리 방법과 적응적 방법이 합쳐진 이진화 방법입니다. Python에서는 아래와 같이 “cv2.adaptiveThreshold” 명으로 적용해 볼 수 있으며, 입력 변수는 아래와 같습니다.

 

[Python] dst = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)

(입력 변수 설명)

- src: 입력 이미지 (Gray Scale Image)

- maxValue: 임계값 Threshold을 초과한 픽셀에 적용할 값 (일반적으로 255)

- adaptiveMethod: 1) cv2.ADAPTIVE_THRESH_MEAN_C, 2) cv2.ADAPTIVE_THRESH_GAUSSIAN_C

- thresholdType: 1) cv2.THRESH_BINARY, 2) cv2.THRESH_BINARY_INV

- blockSize: 픽셀 임계값을 계산할 영역 크기 (홀수: 3, 5, 7 ...)

- C: 계산된 임계값에서 빼는 상수 (일반적으로 0 또는 2~10)

 

여기서 “cv2.ADAPTIVE_THRESH_MEAN_C”는 “blockSize”가 3이라면 3x3 크기 영역 안에 평균 값에서 “C”를 뺀 값을 임계값으로 사용하게 됩니다. “cv2.ADAPTIVE_THRESH_GAUSSIAN_C”는 3x3 크기 영역 안에 가우시안 가중 평균값에서 “C”를 뺀 값을 임계값으로 사용합니다. 앞에서 언급했듯이 “blockSize”는 3은 3x3, 5는 5x5의 블럭을 의미하며 블럭 크기가 작아질 수록 잡음 Noise에 민감해지고 너무 크면 지역적 처리의 이점이 줄어들 수 있으니 설정 시 고민이 필요할 수 있습니다.

 

C/C++ 코드를 사용한다면 아래와 같이 사용할 수 있습니다. 파이썬 함수와 다르게 결과 이미지는 입력 변수와 함께 설정해야 합니다. (dst = 결과 이미지)

[C/C++] adaptiveThreshold(src, dst, maxValue, adaptiveMethod, thresholdType, blockSize, C)

 

지역적이면서 적응적 이진화 방법은 이미지에 조명이 균일하지 않거나 배경이 복잡하여 명암이 불균일한 경우 유용할 수 있으며, 문서 이미지에서 문자 추출 시에도 잘 활용될 수 있습니다. 아래는 파이썬과 C/C++에서 “adaptiveThreshold”를 활용한 예이니 참고해서 보면 좋을 듯 합니다.

 

import cv2

# 그레이 이미지 읽기
img = cv2.imread('grayimage.jpg', cv2.IMREAD_GRAYSCALE)

# 가우시안 적응형 이진화
thresh = cv2.adaptiveThreshold(
    img,
    255,
    cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
    cv2.THRESH_BINARY,
    11,
    2
)

cv2.imshow("Adaptive Threshold", thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

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

int main() {
    // 이미지 불러오기 (그레이스케일)
    Mat src = imread("grayimage.jpg", IMREAD_GRAYSCALE);
    Mat dst;
    // 적응형 이진화 적용
    adaptiveThreshold(
        src,              // 입력 이미지
        dst,              // 출력 이미지
        255,              // 최대값
        ADAPTIVE_THRESH_GAUSSIAN_C,  // 적응형 방법
        THRESH_BINARY,    // 임계값 타입
        7,               // 블록 크기 (홀수)
        2                 // C 상수
    );

    // 결과 출력
    imshow("Original", src);
    imshow("Adaptive Threshold", dst);
    waitKey(0);
    return 0;
}
728x90
728x90

영상처리 기술에서 보간법은 이미지 확대나 회전, 좌표 변환 기술 Affine Transform 등 알고리즘 구현 시 빈번히 사용됩니다. 머신 러닝 Machine Learning 및 딥러닝 Deep Learning 등 특히 CNN 계열에 Coarse Map을 Dense Map으로 Up-Sampling시 활용되는 기술이기도 합니다. 이미지를 두 배로 확대할 때 보간법은 새로운 픽셀 Pixel 값을 그럴듯하게 예측하는 기술로 볼 수 있습니다. 이론적인 부분은 영상처리 기술에서 다루도록 하겠습니다.

 

기본적인 보간법 세가지는 아래와 같습니다.

 

1. 최근접 이웃 보간법 Nearest Neighbor Interpolation

- 소수점 좌표에서 가장 가까운 정수 좌표 픽셀 사용, 수행속도가 가장 빠르지만 계단 현상 및 깨짐 현상 발생

 

2. 양선형 보간법 Bilinear Interpolation

- 주변 4개 픽셀을 사용해 선형 평균을 이용, 최근접 보다 자연스러운 결과를 보이고 일반적으로 많이 활용되는 기술

 

3. 양큐빅 보간법 Bicubic Interpolation

- 주변 16개 픽셀을 활용하며 앞에 두 기술보다 자연스러우며, Super Resolution 기술들에서 비교 결과로 많이 사용, 고화질 이미지 편집에 사용되는 기술이며 처리속도는 느림

 

이미지 회전 시 보간법이 사용되는 이유는 회전은 수학적으로 기존 픽셀 좌표를 회전 행렬로 이동시키는 과정입니다. 회전 시 생성되는 좌표는 보통 소수점 좌표가 되며, 새로운 평면에 매칭 시 좌표는 소수점이 아닌 정수이기때문에 빈공간이 생기게 됩니다. 그 빈공간을 채워주기 위해 보간법이 활용됩니다. 예를 들어 좌표 (5, 5)를 30도 회전하면 (5.3, 3.9) 되므로 실제 좌표 매칭을 위해서는 주변 픽셀 (5, 3), (6, 3), (5, 4), (6, 4)를 이용하여 추정해야 합니다.

 

OpenCV에서 이미지 회전 및 확대 시 보간법을 지정할 수 있습니다. 아래는 파이썬 활용시 CV 상수 입니다. 첫번째 예제는 회전시 파이썬 코드이며, 이전 블로그에서 설명한 “cv2.warpAffine” 함수를 사용합니다. 두번째 예제는 “cv2.resize” 함수를 활용한 보간법 사용 예입니다. 

 

1. 최근접 이웃 보간법 - cv2.INTER_NEAREST

2. 양선형 보간법 - cv2.INTER_LINEAR

3. 양큐빅 보간법 - cv2.INTER_CUBIC

4. Lanczos 보간법 - cv2.INTER_LANCZOS4

 

# 이미지 회전 예제
import cv2
import numpy as np

# 이미지 로드
img = cv2.imread('lena.jpg')

# 회전 중심, 각도, 스케일
center = (img.shape[1] // 2, img.shape[0] // 2)
angle = 50
scale = 1.0

# 회전 행렬 생성
M = cv2.getRotationMatrix2D(center, angle, scale)

# 양큐빅 보간법 사용예
rotated = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]), flags=cv2.INTER_CUBIC)

# 결과 보기
cv2.imshow('Rotated Image', rotat_result)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

# 이미지 확대 예제
img = cv2.imread('lena.jpg')

# 2배 확대, 선형 보간법
resized = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR)

# 결과 보기
cv2.imshow('Resized Image', resiz_result)
cv2.waitKey(0)
cv2.destroyAllWindows()
728x90
728x90

(docs.opencv.org 참조)

 

형태학적 변환 Morphological Transformation은 이미지 형태를 기반으로 한 몇 가지 단순한 연산이며, 보통 이진 Binary 이미지에서 수행됩니다. 한 단계 더 나아가 그레이 Gray 이미지에서도 가능합니다. 동작을 위해서는 원본 이미지와 변환의 특성을 결정하는 마스크 또는 커널을 활용합니다. 기본적인 형태학 연산자는 침식 Erosion과 팽창 Dilation입니다. 그리고 열림 Opening, 닫힘Closing, 기울기 Gradient 등과 같은 변형 연산도 수행할 수 있습니다. 현 블로그에서는 이진 이미지 기준으로 기본 변환을 설명합니다.

 

영상처리에서 형태학적 변환 연산은 관심영역 또는 물체의 크기를 재정의 하거나 잡음 Noise 제거에 빈번히 활용됩니다.

 

1.  침식 Erosion 연산

침식 Erosion은 흰색 영역을 전경이라고 했을 때 흰색 영역의 가장자리를 기준으로 감소시키는 역할을 합니다. 기본 동작은 원본 이미지 기준으로 커널 간 컨볼루션 Convolution 수행이며, 원본 이미지의 픽셀(1 또는 0)은 커널 아래의 모든 픽셀이 1일 때만 1로 간주되고, 그렇지 않으면 침식됩니다(0으로 만듭니다). 따라서 커널의 크기에 따라 경계 근처의 픽셀들이 모두 침식될 수 있으며, 전경 객체의 두께나 크기가 감소하거나 이미지에서 단순히 흰색 영역이 감소합니다. 작은 흰색 잡음을 제거하거나, 연결된 두 개의 객체를 분리하는 등에 유용합니다. Python 예에서 커널 kenel은 모든 element가 1인 5X5 매트릭스이며, Input_Img와 결과 이미지는 이진 Binary 이미지 입니다.

 

Python Example)

kernel = np.ones((5,5),np.uint8)

Erosion_Img = cv.erode(Input_Img, kernel, iterations = 1)

 

 

2. 팽창 Dilation 연산

팽창 연산은 침식과 반대의 효과를 내며, 커널 아래의 적어도 하나의 픽셀이 '1'이면 픽셀 요소는 '1'입니다. 따라서 이미지에서 흰색 영역이 커지거나 전경 물체의 크기가 커집니다. 침식은 배경에 잡음을 제거하는 효과가 있다면 팽창은 전경 내에 잡음 제거에 효과가 있습니다.

 

Dilation_Img = cv.dilate(Input_Img, kernel, iterations = 1)

 

 

3. 열림 Opening 연산

앞에 설명한 침식과 팽창이 연속 연산을 통해 또 다른 효과를 낼 수 있습니다. 열림 Opening 연산은 침식 연산 후에 팽창 연산을 의미합니다. 그림에서와 같이 침식 효과를 통해 배경 잡음을 제거하고 팽창을 통해 전경의 크기를 보전합니다.

 

Opening_Img = cv.morphologyEx(Input_mg, cv.MORPH_OPEN, kernel)

 

 

4. 닫힘 Closing 연산

닫힘 연산은 팽창 연산 침식 연산을 수행합니다. 그림에서 같이 팽창의 효과로 전경의 잡음을 제거하고 침식을 통해 전경의 크기를 보전합니다.

 

Closing_Img = cv.morphologyEx(Input_Img, cv.MORPH_CLOSE, kernel)

 

 

5. Morphological Gradient

그림에서 같이 팽창 연산 결과와 침식 연산 결과의 차(뺄셈) 연산을 통해 관심 영역인 전경의 에지 Edge를 추출할 수 있습니다.

 

Gradient_Img = cv.morphologyEx(Input_Img, cv.MORPH_GRADIENT, kernel)

 

 

6. 커널 Structuring Element

보통 커널의 형태는 직사각형 모양입니다. 그러나 경우에 따라 타원형 또는 원형 커널이 필요할 수 있습니다. 이를 위해 OpenCV에는 cv.getStructuringElement()라는 함수가 있으며, 커널의 모양과 크기를 전달하면 원하는 커널을 얻을 수 있습니다. 그림은 Python 활용 예입니다.

 

728x90
728x90

(docs.opencv.org 참조)

 

이미지 개선의 가장 기본처리 방식으로 이미지 히스토그램을 활용합니다. 이미지 히스토그램 Image Histogram 이란 말 그대로 픽셀값의 분포를 나타냅니다. 이를 활용한 이미지 개선 방법 중 하나가 이미지 평활화 또는 균등화 Image Equalization이며 OpenCV 함수 EqualizeHist로 활용해 볼 수 있습니다.

 

 

히스토그램 평활화란 픽셀값 범위를 확장하여 이미지 대비를 개선하는 방법입니다. 그림에서처럼 원본 이미지의 픽셀값의 분포는 중간에 모여 있으며 이를 픽셀의 최소(0) 및 최대(255) 범위로 평활화 하게 되면 대비가 개선된 이미지를 얻을 수 있습니다. 조금 더 세부적으로 원본 이미지의 히스토그램의 누적분포함수(CDF)를 활용하여 픽셀값에 재맵핑 Remapping을 통해 균등화 할 수 있습니다. 수식은 생략하도록 하겠습니다.

 

활용 예)

(C/C++) equalizeHist( Input_Image, Result_Image);

(Python) Result_Image = cv.equalizeHist(Input_Image)

 

파라미터 Input_Image는 8-bit gray image로 결과 또한 8-bit gray 입니다. 

 

728x90
728x90

영상처리 알고리즘 Algorithm을 설계할 때 전처리 과정에 필수적으로 사용되는 것이 영상에 잡음 Noise을 제거하기 위한 함수 입니다. 몇 가지 명칭으로 사용되며 이미지 블러링 Image Blurring  또는 이미지 스무딩 Image Smoothing 등에  명칭으로 사용이 되는데 유사한 의미로 볼 수 있습니다. 신호처리에서 표현하는 저역통과 필터 Lowpass Filter로도 볼 수 있습니다. 평균 필터 Mean Filter, Averaging Filter에 명칭으로도 쓰이며 변형된 필터인 가우시안 필터 Gaussian Filter, 중간값 필터 Median Filter로도 응용될 수 있습니다. 원리로 보면 저역통과라는 단어에서 느껴지는 것처럼 고주파 신호를 제거하고 저주파 신호만 받아들이겠다는 의미이기도 합니다.

 

OpenCV에는 위에서 처럼 Smooth라는 함수로 구현이 되어 있습니다. Src는 원본영상, 그리고 Dst는 결과영상을 나타내고 기본적으로 8 bits 영상을 사용합니다. Size1Size2는 각 Aperture withheight를 나타내며, 다시 말해 적용하고자 하는 마스크 mask의 크기입니다. 예를 들어 3x3, 5x5, 7x7 등에 마스크 크기를 설정하면 되고, Smooth Type 중 가우시안 마스크 CV_GAUSSIANCV_BILATERAL을 이용 시에는 Sigma를 활용할 수도 있습니다. 마스크에 형태 Type CV_BLUR는 평균 필터, CV_MEDIAN은 중간값 필터를 의미합니다. 참고로 CV_BILATERALGaussian filter의 응용으로 Non-Linear한 특성과 경계선 보존 Edge-Preserving 특징을 가지는 필터로 영상처리기술에서 추가로 설명하도록 하겠습니다.

 

사용 예)

cvSmooth( Input_Image, Result_Image, CV_BLUR, 3, 3, 0, 0)

 

3x3 마스크 크기의 평균 필터를 적용하고자 할 때 위 예처럼 사용하면 됩니다. Smooth 함수는 알고리즘 개발에 전처리 과정에도 많이 사용되지만 그 자체 원리를 이용하여 디지털 카메라, 스마트폰 등에서 사진 블러효과 Blur Effect 로도 응용되고 실생활에서 자주 접할 수 있는 기능이기도 합니다.

728x90
728x90

윤곽선 검출 Edge Detection은 물체에 경계선을 추출하기 위한 기술 입니다. 우리가 알고자 하는 물체의 모양, 크기, 위치 등 정보를 확인하고자 할 때 사용되는 기술로 알고리즘의 전처리 과정에서 이용됩니다. 요즘 기술개발이 활발히 진행되고 있는 Machine Learning의 한 분야인 딥러닝 Deep Learning에서 영상의 특징점 Feature 을 정의하는데도 사용되는 전처리 기술 입니다.

Edge Detection 기술이 사용 된지는 오래됐으며 현재까지도 폭넓게 사용되고 있고, 수학적 접근방식에 따라 다양한 방법들이 존재합니다. 현 이야기에서는 OpenCV에서 제공되는 윤곽선 검출 도구 중 Sobel Edge Detection Canny Edge Detection 기술 사용법에 대한 설명입니다. Edge Detection에 앞에 붙어 있는 Sobel Canny는 개발자에 이름이며 WIKIPEDIA에 의하면Sobel Filter 1968년과 Canny Edge Detector 1986년에 개발되었습니다. 정말 오래된 기술 입니다만 현재도 상당히 많이 사용되고 있고, 이러한 윤곽선 검출 기술에 기본 원리는 미분 연산자 Differentiation Operator에 의한 밝기 변화 탐색 입니다. 다시 말해, 물체의 가장자리에서 밝기 변화가 크므로 Pixel 값에 차이를 통해 윤곽선 유무를 판단할 수 있다는 의미이기도 합니다.

위에서처럼 OpenCV에서 제공되는 Edge Detection 함수인 Sobel Canny를 볼 수 있습니다. 먼저 cvSobel 함수를 살펴보면 앞에서 설명한 기본 함수들에서처럼 입력영상인 “src”와 결과영상을 담기위한 “dst”가 보입니다. “aperture_size” Sobel Mask의 크기를 나타내고 “3”이면 3X3을 의미합니다. “xorder”“yorder” Sobel Mask 연산 시 결과값의 위치를 나타내는데 3X3의 경우 중심인 xorder = 1 yorder = 1 이 되며, Mask의 시작점은 (0, 0)이니 2가 아닌 1이 됩니다.

 

Sobel 사용 예)

cvSobel( Input_Image, Result_Image, 1, 1, 3)

cvConvertScaleAbs(Result_Image, Convert_Result_Image, 1, 0)

 

cvSobel 함수 사용은 위에 예처럼 사용하면 되는데 cvConvertScaleAbs()라는 함수가 더 있죠? cvSobel 함수 사용 시 주의할 점이 있습니다. 앞에서 언급했듯이 Edge Detection의 원리가 Pixel의 차이를 이용하기 때문에 결과에서는 마이너스 “-“ 값이 존재하고 Result_Image Depth는 보통 Input_Image Depthunsigned 8-bit 이니 signed 16-bit 으로 구성되어야 합니다. 따라서 최총 결과를 확인하기 위해서는 절대값을 이용하여 다시 unsigned 8-bit 으로 변환해 줘야 하고, 이때 사용되는 함수가 cvConvertScaleAbs() 입니다.

 

cvCanny 함수의 경우 입력영상인 “image”와 결과영상인 “edges”가 보이고 “apertureSize = 3” Sobel Mask   3X3을 의미합니다. 여기서 cvSobel 함수와 다르게 “threshold1”“threshold2”를 입력하게 되어 있는데, “threshold1” 이하는 외곽선이 아닌 영역으로 “threshold2” 이상은 외곽선인 영역, 그리고 “threshold1”“threshold2”의 사이는 Canny Procedure에 의해 외곽선 유무를 판단하게 됩니다.

 

Canny 사용 예)

cvCanny( Input_Image, Result_Image, 100, 200, 3 )

 

cvCanny 사용 예에서 threshold1threshold2 100 2000 ~ 255의 범위를 갖는 8-bit 영상 기준으로 설정된 값을 의미하고, Result_Image Input_Image와 동일한 속성으로 설정하면 됩니다. 앞에 설명과 그림 결과와 같이 8-bit Input_Image 기준으로 cvSobel 함수의 최종 Result_Image 8-bit 그레이 Gray 영상이며, cvCanny 함수의 경우 8-bit 바이너리 Binary 영상이니 이것만 주의해서 활용하면 됩니다. Sobel Canny의 수학적 또는 알고리즘 접근에 대해서는 영상처리기술에서 추가적으로 다뤄보도록 하겠습니다.

728x90
728x90

물체 추적 Object Tracking 분야에서 주요하게 사용되는 기술이 Pattern Matching 기술 입니다. 물체 추적이라는 단어에서 느껴지는 것처럼 실시간 영상처리에 한 분야 입니다. 보안 및 감시 카메라에서 출력되는 연속 영상 또는 동영상 처리 정도로 이해하면 됩니다. 알고리즘 구현에서는 크게 물체 탐색과 추적으로 나뉘고, 예를 들어 물체가 자동차라면 첫 영상에서 자동차를 추출하고 자동차 영역을 이용하여 다음 영상에 차량 위치를 탐색하고자 할 때 사용되는 기술이 Pattern Matching에 응용 입니다.

OpenCV 라이브러리에서는 Pattern Matching 함수를 제공하고 있습니다. 아래 그림에서처럼 Pattern(Template) 영상과 원본영상 Input Image 만 있으면 테스트를 해 볼 수 있고 이를 통해 다양한 실무 응용분야를 찾을 수 있습니다. 결과에서 Feature Map을 볼 수 있는데 Matching에 최종 결과로 볼 수 있고, Feature Map에 가장 높은 Pixel 값에 위치가 Template과 가장 유사한 영역으로 생각할 수 있습니다.

 

 

위 함수를 보면 C/C++ Python 언어로도 사용할 수 있게 제공되고 있습니다. 함수 사용시 입력 변수들 중 “Method” Matching에 사용되는 수식이라고 보면 되며 실제 다양한 수학적 접근 방법들이 사용되고 있습니다. OpenCV에서는 여섯 가지 방법들을 제공하고 있습니다. 그 중에 하나가 아래 사용 예에 “CV_TM_SQDIFF”가 있는데 Pixel 간 차이 제곱에 총합 Sum of Square Difference을 의미합니다. 수학적 방법에 상세한 내용은 영상처리기술에서 기술하도록 하겠습니다.

 

Template Matching 사용 예)

C: void cvMatchTemplate( Input_Image, Template, Feature_Map, CV_TM_SQDIFF )

 

cvMatchTemplate 함수 수행 후에 최종 결과인 Feature Map에서 최대값에 위치를 찾을 때 손수 코딩을 통해 찾을 수 있지만 OpenCV 함수에 하나인 cvMinMaxLoc을 사용할 수 있습니다. 이 함수를 이용하면 최대 및 최소값과 영상에서 그 위치를 추출할 수 있습니다.

실무에서는 Pattern Matching 기술 개발 시 OpenCV에서 사용되는 General Method도 사용할 수 있지만 물체에 특성에 맞게 Feature를 정의해서 직접 알고리즘을 구현하여 적용할 때도 있습니다. 하지만 기존 방법들의 답습을 통해 장단점을 파악하는 것도 자신만에 기술을 고도화 시킬 수 있는 방법이기도 하겠죠.

 

728x90
728x90

영상에 타원 형태에 물체를 찾고자 할 때 영상처리를 통해 외곽선을 추출하게 됩니다. 보통 에지탐색(Edge Detection) 기법들을 통해 외곽선을 탐색하게 되는데 주변 PixelIntensity 차이를 이용하는 (수학적으로 미분/Differential Equation 이라고 합니다.) 이러한 기법들은 영상의 화질 특성에 의해 완전한 외곽선이 아닌 끊겨 있거나 파편적인 선으로 추출되는 경우가 많습니다. 이 때 끊겨 있는 선들의 정보를 활용하여 타원을 추정할 때, OpenCV Ellipse Fitting 함수를 이용할 수 있습니다.

 

 

그림에서와 같이 fitEllipse 함수에 입력인 “points”은 영상에서 외곽선에 좌표를 의미합니다. C/C++ 함수의 출력인 “RotatedRect/CvBox2D”는 구조체로 중심좌표 Center, 크기 Size, 기울어진 각도 Angle 정보를 담고 있습니다. 타원 추정 결과를 확인 할 때는 앞에서 기술한 기본 그리기 함수인 아래 cvEllipse를 이용하면 됩니다. 입력 변수 중 “start_angle/end_angle”은 그려줄 각도를 나타내고 전체를 표시하고 싶으면 “start_angle = 0/end_angle = 360”으로 설정할 수 있습니다. 0~180으로 설정하면 타원에 반만 그려 주겠죠.

 

타원 그리기 함수 예)

C: void cvEllipse(CvArr* img, CvPoint center, CvSize axes, double angle,

     double start_angle, double end_angle,

     CvScalar color, int thickness=1, int line_type=8, int shift=0 )

 

실제 산업분야에서 타원이나 원을 추출할 때 다양하게 응용되고 있고 활용범위가 넓습니다. 타원 추정 이론과 함께 알아 두시면 많은 도움이 될 수 있습니다. OpenCV에 구현된 함수는 “A Buyer’s Guide to Conic Fitting”을 기반으로 하고 있습니다.

728x90
728x90

영상처리 오픈 라이브러리인 OpenCV를 사용하려면 기본적인 구조와 이용 방법을 알아야 합니다. 이번 이야기에서는 OpenCV 활용을 위해 가장 기본 함수 중 하나인 Line’에 대해 알아 보겠습니다. 단어에서처럼 한 점에서 다른 한 점까지 선을 긋는 함수이고 OpenCV 홈페이지에도 설명이 잘 되어 있습니다. 초기에는 C/C++ 언어만 제공되었는데 현재에는 Python에서도 활용될 수 있게 기능을 제공하고 있네요.

 

 

함수에 첫 번째 “img”는 선을 그릴 영상, “pt1 / pt2”는 선에 시작 점과 끝나는 점을, “color”는 선에 색깔을 나타냅니다. “thickness”는 선의 두께, “line type” 4, 8, CV_AA 중 하나를 선택할 수 있는데 선을 표현하는 거칠기 정도로 이해하면 됩니다. C 또는 C++ 함수를 사용 시 “img” 앞에 구조체 형이 다르기 때문에 주의해서 사용해야 되고 C 함수에서는 보통 “IplImage” 구조체를 사용합니다.

 

사용 예)

cvLine( Input_Image, cvPoint(10,10), cvPoint(100,100), CV_RGB(255, 255, 255), 1, CV_AA, 0);

 

C 함수 이용 시 위 예에서 처럼 활용할 수 있습니다. 설명했듯이 “Input_Image” IplImage 구조체이며 “pt1 / pt2” cvPoint(x, y)“color” CV_RGB(red, green, blue)로 대입 할 수 있습니다. CvPointOpenCV 사용에 맞게 정의된 구조체로 볼 수 있습니다. 몇 번 실습을 해보면 쉽게 익힐 수 있고, Line 함수를 활용할 수 있으면 Circle, Rectangle, Ellipse OpenCV에서 제공되는 기본 함수들을 쉽게 이용할 수 있게 됩니다.

경험 상 함수에 사용법은 개인 블로그 등을 참조하는 것보다 그 라이브러리 설명서를 이용하여 익히는 것이 가장 정확한 방법인 듯 합니다. 기본 함수를 잘 익히면 유사한 함수들은 쉽게 익힐 수 있고 응용이 가능해 질 수 있습니다.

728x90
728x90

(See below for English version.) 

 

영상처리 기술을 살아있는 기술로 만들기 위해서는 영상처리에 기본적인 지식과 프로그래밍 언어를 이용한 기술 구현능력이 필요합니다. 예를 들면 영상에서 꽃만 추출하고 싶다고 할 때 잡음 제거 (Noise Reduction)를 위한 필터링 (Filtering), 모양을 확인하기 위한 윤곽선 추출 (Edge Detection), 크기 및 위치 정보 확인을 위한 연결성분 해석 (Connected Component Analysis) 등등에 알고리즘이 필요합니다. 이 때 각 기능들을 하나씩 구현하고 검증하는 과정이 필요한데 상황에 따라 많은 시간을 필요로 하게 됩니다. 따라서 기본적인 알고리즘들이 기능으로 만들어져 있다면 개발 시간을 줄일 수 있을 뿐만 아니라 다양한 시험들을 해 볼 수 있을 겁니다. 이럴 때 주요하게 사용되는 것이 영상처리 라이브러리 입니다.

 

 

 

영상처리 라이브러리는 다양하게 존재합니다. 크게 유료와 무료로 구분되고 프로그래밍 언어 별로 최적화 되어 있습니다. MATLAB, MIL(Matrox Imaging Library), Cognex, Euresys 등에 유료 라이브러리 등이 있으며, OpenCV, ImageJ, PIL(Python Imaging Library) 등은 무료로 사용할 수 있는 라이브러리 들입니다개인적인 경험 상 활용도 측면에서 OpenCV와  MATLAB이 대표적으로 사용되는 유무료 라이브러리가 아닐까 생각됩니다.

산업적으로도 개발부분에 상당히 많이 활용되고 있어 프로그래밍 언어와 함께 라이브러리 쓰임새를 정확이 알아두면 영상처리 분야에 상당한 경쟁력이 될 수 있습니다.

 

영상처리 도구(Image Processing Tool) 이야기에서는 OpenCV(Open Source Computer Vision) 사용법에 대해 기술해 나갈 예정입니다. OpenCV는 실시간 영상처리를 위해 인텔(Intel)에서 개발되었고 인텔 CPU C/C++ 프로그래밍 언어에 최적화 되어 있습니다. 2000년 중 후반부터 활성화 되어 현재 버전 3.0이상까지 나와 있습니다.

라이브러리는 사용하기 쉽게 영상처리 기능들을 모아둔 것이기 때문에 응용을 위해서는 각 기능에 본질적인 원리 등을 정확히 파악하면서 사용하는 것이 실력과 경쟁력 향상에 시작일 수 있습니다.

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

For the development of image processing application, we need the basic knowledge and algorithm design ability for image processing. For example, if you want to detect the flower in an image, you can use the noise reduction filter, edge detector for shape feature and connected component analysis algorithm to find a flower position. In this case, the functions need to be implemented and verified one by one and these steps can take a lot of time. Therefore, if the basic algorithms are built as functions, we will not only reduce the development time, but try various tests. At this time, image processing library is mainly used. In the industry, image processing libraries are widely used and there are libraries such as Matlab, MIL, Cognex, Euresys, OpenCV, etc. In my experience, OpenCV and Matlab are probably the most popular and OpenCV is free to use library. In this blog, OpenCV usage will be explained. OpenCV was developed by Intel for real-time image processing and is optimized for Intel CPU and C/C++ programming language. And it has been develop since the mid 2000’s and is now available in version 3.0 and above. If you understand the principles of using OpenCV functions through this blog, your algorithm implementation skills and technical competitiveness will be improved.

728x90

+ Recent posts