Python과 OpenCV – 11 : 이미지를 부드럽게 만들기

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_filtering/py_filtering.html#filtering 입니다.

OpenCV를 이용해 다양한 저주파 필터(Low-pass filter)를 이용해 이미지를 부드럽게, 즉 블러링(Bluring) 처리를 하는 내용입니다.

2D Convolution, 같은 의미로써 이미지 필터링(Filtering)은 고주파, 또는 저주파 필터를 이용해 이미지를 처리하는 것을 의미합니다. 여기서 필터는 예를들어 아래와 같은 행렬입니다.

위의 필터가 이미지의 각 픽셀을 순회하면서 해당 픽셀 주위의 픽셀들와 곱해져 더해집니다. 위의 필터에 대한 이미지 필터링에 대한 예제 코드는 다음과 같습니다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('./data/opencv_logo.png')

kernel = np.ones((5,5), np.float32) / 25
dst = cv2.filter2D(img, -1, kernel)

plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('Averaging')
plt.xticks([]), plt.yticks([])
plt.show()

위의 7번 코드가 앞의 필터 행렬을 생성하고 있고 8번코드의 cv2.filter2D 함수가 이미지에 대해 해당 필터를 적용하여 그 결과 이미지를 반환합니다. 실행 결과는 다음과 같습니다.

다시 이글의 주제로 돌아와서 이미지를 부드럽게 처리하는 LPF로써 OpenCV에서 제공하는 4가지 필터를 정리합니다. 먼저 평균 필터인데, 이는 이미 앞의 예제 코드와 동일하며 다음과 같은 코드 역시 동일한 결과를 생성합니다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('./data/opencv_logo.png')

blur = cv2.blur(img,(5,5))

plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()

다음은 가우시안 필터링(Gaussian Filtering)에 대한 예제입니다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('./data/opencv_logo.png')

blur = cv2.GaussianBlur(img,(5,5),0)

plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()

다음은 Median Filtering에 대한 예제인데, 이 필터는 잡음 제거 효과가 뛰어납니다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('./data/opencv_logo.png')

blur = cv2.medianBlur(img,5)
#
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()

다음은 Bilateral Filtering이며 잡음 제거 효과가 좋으면서, 가장자리를 보존하는데 효과적입니다. 단, 속도가 느리다는 단점이 있습니다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('./data/opencv_logo.png')

blur = cv2.bilateralFilter(img,9,75,75)

plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()

[OpenLayers] 지도 상에 10만개의 별 그리기에 대한 속도 테스트

OpenLayers의 지도 속도를 테스트 하기 위해 10만개의 별을 배경지도 상에 그려보는 코드를 작성해 보았습니다.

먼저 실행에 대한 동영상은 아래와 같습니다. 실행은 크롬에서 했고, 2016년 6월 경에 구입한 DELL 노트북인 XPS 9550을 사용했습니다.

웹임에도 상당이 빠릅니다. 이유는 소스코드를 살펴보면 파악할 수 있습니다. 먼저 index.html 입니다.



    
        
        OpenLayers
        
    
                    
        

index.js에 대한 코드의 설명은 하나씩 언급해 보면, 먼저 필요한 모듈을 선언합니다.

import 'ol/ol.css';
 
import Feature from 'ol/Feature.js';
import Map from 'ol/WebGLMap.js';
import View from 'ol/View.js';
import Point from 'ol/geom/Point.js';
import VectorLayer from 'ol/layer/Vector.js';
import VectorSource from 'ol/source/Vector.js';
import {Fill, RegularShape, Stroke, Style} from 'ol/style.js';
import {Tile} from 'ol/layer.js';
import {XYZ} from 'ol/source.js';

그리고 10만개의 포인트를 표시할 스타일로 별 모양으로 지정하는 코드입니다. 이미지가 아닌 실행중에 별형상을 생성해 스타일로 사용하는 것입니다.

var style = new Style({
    image: new RegularShape({
        points: 5,
        scale: 1,
        radius: 10,
        radius2: 4,
        fill: new Fill({
            color: 'rgba(255, 255, 0, 1)'
        }),
        stroke: new Stroke({
            color: 'rgba(0, 0, 0, 1)',
            width: 2
        })
    })
});

다음은 10만개의 포인트 피쳐를 생성합니다. 좌표는 여의도를 덮을 정도의 영역으로 잡았습니다.

var featureCount = 100000;
var features = new Array(featureCount);
var feature, geometry;

for (var i = 0; i < featureCount; ++i) {
    geometry = new Point([14078579 + Math.random()*100000, 4487570 +  Math.random()*50000]);
    feature = new Feature(geometry);
    feature.setStyle(style);
    features[i] = feature;
}

위의 피쳐 데이터를 담기 위한 데이터소스와 이 데이터소스를 표현하기 위한 레이어 객체를 생성합니다.

var vectorSource = new VectorSource({
    features: features
});

var vector = new VectorLayer({
    source: vectorSource
});

배경지도도 표현해줘야 의미있는 테스트가 될듯하여 VWorld의 배경지도를 살짝 가져다 썻습니다.

var base = new Tile({
    source: new XYZ({
      url: 'http://xdworld.vworld.kr:8080/2d/Base/service/{z}/{x}/{y}.png'
    })
});

앞서 생성한 레이어들을 조합해 지도 객체를 생성하면 끝입니다.

var map = new Map({
    layers: [base, vector],
    target: document.getElementById('map'),
    view: new View({
        center:  [14128579.82, 4512570.74],
        zoom: 15
    })
});

속도가 빠른 이유는 2가지입니다. 첫째는 IE가 아닌 크롬에서 테스트 했다는 것인데, 그렇다고 IE에서 실행해보면 아예 실행이 안되는 것은 아니고 크롬보다 살짝 느릴 뿐입니다. 하지만 향후 IE가 크롬의 렌더링 기술을 사용할 것이므로 IE도 크롬만큼 빨라질 것으로 기대합니다. 두번째 이유는 OpenLayers의 지도 객체가 WebGLMap 타입이라는 것입니다. 가장 처음 언급된 코드의 모듈 추가에서 ol/Map.js 대신 ol/WebGLMap.js에서 Map 클래스를 가져왔기 때문입니다.

Python과 OpenCV – 10 : 기하학 변환

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_geometric_transformations/py_geometric_transformations.html#geometric-transformations 입니다.

OpenCV는 cv2.wrapAffine, cv2.warpPerspective라는 2개의 변환 함수를 제공하는데 각각 2×3 행렬과 3×3 행렬을 인자로 받습니다. 이 함수를 통해 이미지에 대한 크기 변환, 이동, 회전, Affine 변환, Perspective 변환을 수행할 수 있습니다.

먼저 이미지 크기 변환인데, 이 경우 위의 함수를 통한 변환도 가능하지만 OpenCV에서는 cv2.resize 함수를 이용해 크기 변환을 자주 수행하며 예제는 아래와 같습니다.

import cv2
import numpy as np

img = cv2.imread('./data/Penguins.jpg', 0)

res1 = cv2.resize(img,None,fx=2, fy=2, interpolation = cv2.INTER_CUBIC)

height, width = img.shape[:2]
res2 = cv2.resize(img,(2*width, 2*height), interpolation = cv2.INTER_CUBIC)

cv2.imshow('res1', res1)
cv2.imshow('res2', res2)

cv2.waitKey()

cv2.destroyAllWindows()

6번과 9번 코드 모두 이미지를 2배 확대하는데, 9번 코드의 경우 이미지의 픽셀 크기로 확대정도를 지정하므로 소수점으로 확대, 축소는 할 수 없습니다.

다음은 이미지를 이동하는 변환입니다. 이동 변환을 위한 행렬은 다음과 같습니다.

위의 행렬을 적용하는 코드는 다음과 같은데, 이미지를 x축으로 100만큼, y축으로 50만큼 이동합니다. 이동되고 남은 공간은 0값으로 채워지므로 검정색으로 표시됩니다.

import cv2
import numpy as np

img = cv2.imread('./data/Penguins.jpg', 0)

M = np.float32(
    [
        [1, 0,100],
        [0, 1, 50]
    ]
)

rows,cols = img.shape

dst = cv2.warpAffine(img, M, (cols,rows))

cv2.imshow('img',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

다음은 회전입니다. 2차원에 대한 일반적인 회전 행렬은 다음과 같습니다.

이를 2×3인 행렬로 표현해야 Affine 변환이 가능한데, 이를 위해 OpenCV는 cv2.getRotationMatrix2D 라는 함수를 통해 회전 행렬을 얻을 수 있습니다. 이 함수는 장점은 회전값의 지정뿐만 아니라 크기 변환과 회전중심점도 지정할 수 있습니다. 이 함수를 통해 얻을 수 있는 행렬은 다음과 같습니다.

위의 기호에 대해서

예제는 다음과 같습니다.

import cv2
import numpy as np

img = cv2.imread('./data/Penguins.jpg', 0)

rows,cols = img.shape

M = cv2.getRotationMatrix2D((cols/2,rows/2), 45, 0.6)
print(M)
dst = cv2.warpAffine(img,M,(cols,rows))

cv2.imshow('img',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

8번 코드를 보면 회전 중심을 이미지의 중심 좌표로 했고, 회전각도는 45도, 크기변환 비율은 0.6으로 지정하여 원래 크기의 60%로 변환됩니다. 결과는 다음과 같습니다.

Affine 변환에 대해 살펴보겠습니다. Affine 변환은 크기변환, 이동변환, 회전변환이 발생해도 원래 평행했던 특성이 그대로 유지됩니다. 이러한 특성을 갖는 Affine 변환에 대한 행렬은 cv2.getAffineTransform을 통해 얻을 수 있으며 그 결과는 2×3 행렬입니다. 아래는 예제입니다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('./data/chessboard2.jpg')
rows,cols,ch = img.shape

pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[10,100],[200,50],[100,250]])

M = cv2.getAffineTransform(pts1,pts2)

dst = cv2.warpAffine(img,M,(cols,rows))

plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()

결과는 다음과 같은데..

위 코드에서 8번의 3개의 좌표가 9번의 3개의 각각의 좌표로 변환됨에 있어서 평행을 유지하도록 하는 행렬을 얻는다는 것입니다.

다음은 Perspective 변환, 즉 투영변환입니다. 이 변환은 지금까지의 2×3 행렬이 아닌 3×3 행렬입니다. 변환 후에 평행성은 더 이상 유효하지 않습니다. 예제는 다음과 같습니다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('./data/chessboard2.jpg')

rows,cols,ch = img.shape

pts1 = np.float32([[0,0],[368,52],[28,387],[389,390]])
pts2 = np.float32([[32,32],[300,0],[0,300],[300,300]])

M = cv2.getPerspectiveTransform(pts1,pts2)

dst = cv2.warpPerspective(img,M,(cols,rows))

plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()

결과는 다음과 같습니다.

9번에서 지정한 4개의 좌표가 10번에서 지정한 4개의 좌표로, 각각 매칭되어 변환이 이루어지는 행렬을 얻는 것이고 이렇게 얻은 행렬은 14번 코드의 cv2.warpPerspective 함수에 의해 변환이 수행됩니다.

Python과 OpenCV – 9 : Image Thresholding (2/2)

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html#thresholding 입니다.

이미지에 대한 Thresholding 연산을 수행할 때, 임계치 값을 개발자가 직접 지정해 주었는데 이를 자동으로 계산하기 위한 방법이 있습니다. 바로 Otsu의 이진화(Binarization)입니다. Otsu의 이진화(Binarization)가 가장 효과적으로 발휘될 수 있는 이미지는 Historam의 피크(Peak)가 2개 인 경우입니다. 피크가 2개가 아닌 경우 결과는 부정확할 수 있습니다. 이미지의 히스토그램에서 피크를 2개 갖도록 블러링을 통해 잡음을 제거하기도 합니다. 아래의 예제를 살펴보겠습니다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('./data/apple.jpg', 0)

# 임계치를 127로 지정한 경우
ret1,th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

# Otsu 방법으로 임계치를 내부적으로 계산하여 지정한 경우
ret2,th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)

# 잡음 감소를 위해 Gaussian filtering 연산 후 Otsu 방법으로 임계치를 계산한 경우
blur = cv2.GaussianBlur(img, (5,5), 0)
ret3,th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)

print(ret1, ret2, ret3)

# plot all the images and their histograms
images = [img, 0, th1,
          img, 0, th2,
          blur, 0, th3]

titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)',
          'Original Noisy Image','Histogram',"Otsu's Thresholding",
          'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]

for i in range(3):
    plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
    plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
    plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)
    plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
    plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
    plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])

plt.show()

결과는 아래와 같습니다.

동일한 이미지에 대해 3번의 Thresholding 연산을 수행한 결과입니다. 8번 코드는 임계치를 127로 명시하여 Thresholding 연산을 수행한 것이고 11번 코드는 임계치를 미지정(0)하고 4번째에 cv2.THRESH_OTSU 옵션을 지정하여 임계치를 자동으로 계산하도록 하였습니다. 이 경우 v2.threshold 함수는 ret2 변수에 계산된 임계치 값이 반환됩니다. 15번 코드는 이미지를 먼저 가우시안 블러링 처리를 수행하고 Othu 방식의 Thresholding을 수행합니다. 이미지에 잡음이 많은 경우 이 방식을 통해 Peak가 2개인 히스토그램을 생성합니다. 이미 원본 이미지의 히스토그램이 Peak를 2개 가지고 있으므로 3개의 결과가 큰 차이가 없지만 잡음이 매우 심한 이미지의 경우 가우시안 블러링 처리를 수행 후 Othu 방식의 Thresholding 연산은 큰 의미가 있습니다.