Python과 OpenCV – 1 : 이미지를 가지고 시작하기

원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_gui/py_image_display/py_image_display.html#display-image 입니다.

OpenCV는 단일 이미지나 동영상의 이미지를 원하는 결과를 분석 및 추출하기 위한 API입니다. OpenCV는 C/C++ 언어로 개발되었고, 이 API를 사용할 수 있는 언어는 C/C++, Java, Python 등입니다. 그리고 활용할 수 있는 플랫폼은 Windows, Linix, Android, iOS 등입니다. 이 글은 OpenCV를 Python 언어를 이용해 학습하면서 정리할 필요가 있다고 판단되는 핵심내용을 꼼꼼이 기록하기 위한 목적을 갖습니다. 각 글에 대한 원문은 글의 서문에 언급해 두고 있으니, 참고하시기 바랍니다. 아울리 이 글은 필자가 원문의 내용을 통해 학습하고 필자 나름대로 재해석하여 글을 작성하고 있습니다. 이 글과 이후의 Python과 OpenCV 관련글은 모두 이와 같습니다.

OpenCV와 Python의 설치 방법은 생략합니다. 검색을 통해 각자 자신에 맞는 환경에 설치하기 바랍니다.

OpenCV는 이미지를 대상으로 어떤 처리를 수행하므로 이미지를 읽고 화면에 표시하는 것은 매우 중요하고 기초적인 내용입니다. 이 글은 바로 이미지를 읽어 화면에 표시하는 내용에 대한 글입니다. 가장 먼저 작성된 코드는 다음과 같습니다.

import numpy as np
import cv2

img = cv2.imread('./data/quark-particle-flavors.jpg', cv2.IMREAD_GRAYSCALE)

cv2.imshow('image', img)

k = cv2.waitKey()

if k == ord('s'):
    cv2.imwrite('d:/z.jpg', img)

cv2.destroyAllWindows()

1,2번 코드는 OpenCV API 사용을 위한 모듈을 불러들이는 코드입니다. OpenCV는 이미지 데이터를 numpy의 자료구조를 사용합니다. 그러므로 OpenCV의 사용에 있어 numpy 모듈은 항상 함께 합니다.

실제 이미지를 불러 들이는 코드는 4번의 cv2.imread 함수인데, 첫번째 인자는 불러드릴 이미지 파일명이며 두번째는 해당 이미지를 불러와 어떤 포맷으로 메모리에 적재해 numpy 자료 구조의 객체를 생성할 것인지입니다. 위의 코드에서는 cv.IMREAD_GRAYSCALE로써 비록 해당 원본 이미지가 RGB의 칼라 이미지이지만 Gray 색상으로 해석해 이미지 객체를 반환하라는 것입니다. cv.IMREAD_GRAYSCALE 이외에도 사용할 수 있는 값은 다음과 같습니다.

  • cv2.IMREAD_COLOR : 별도로 지정하지 않을 경우 사용되는 기본값이며 칼라 이미지로 읽어드림. 이미지의 투명도값은 무시된다.
  • cv2.IMREAD_GRAYSCALE : 이미지를 Grayscale로 읽는다
  • cv2.IMREAD_UNCHANGED : 투명도인 Alpha 채널을 포함하여 읽는다.

반환된 이미지는 img 변수에 담기며 img 변수의 type을 확인해 보면 ‘numpy.ndarray’라는 타입으로 표시됩니다. 이제 이 이미지를 표시하기 위해 6번의 코드인 cv2.imshow 함수가 사용되는데 첫번째 인수는 이미지가 표시되는 Window의 식별자인 이름이고 두번째가 표시되는 이미지 객체입니다. 표시되는 이미지 객체는 4번 코드에서 정의된 변수입니다.

8번 코드는 사용자가 키 입력을 기다리는 함수인 cv2.waitKey로 인자값이 없거나 0일 경우 시간 제한없이 사용자의 키 입력을 기다립니다. 인자가 지정되면 해당값의 시간만큼만 키 입력을 기다립니다.>/p>

10번 코드는 입력된 키값이 소문자 s일 경우 img 객체의 저장된 이미지를 또 다른 파일로 저장하라는 cv2.imwrite 함수입니다.

13번은 프로그램의 종료를 위해 표시된 모든 Window을 닫으라는 코드입니다. 실행해보면 다음 화면과 같습니다.

이미지의 표시는 OpenCV 방식만 있는것이라 아니고 Matplotlib를 사용할 수도 있습니다. 이미지 처리의 결과를 그래프를 통해 효과적으로 표시하기 위해 Matplotlib를 사용합니다. Matplotlib를 이용한 이미지 표시의 코드는 아래와 같습니다.

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

img = cv2.imread('./data/quark-particle-flavors.jpg', 0)
plt.imshow(img, cmap = 'gray', interpolation = 'bicubic')
plt.xticks([]), plt.yticks([])
plt.show()

실행 결과는 다음과 같습니다.

[OpenCV] Channel의 분리와 조합

OpenCV에서 이미지에 대한 채널, 즉 일반적으로 Red, Green, Blue로 분리된 데이터 버퍼를 개별 요소로 분리하고, 이렇게 분리된 Channel들을 다시 하나의 이미지로써 묶는 경우 대한 기초적인 API에 대한 정리입니다.

정리를 위한 코드가 매우 짧은데, 아래와 같습니다.

import cv2
import numpy as np

src = cv2.imread('./data/lena.jpg')
cv2.imshow('src', src)

hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)

v2 = cv2.equalizeHist(v)
hsv2 = cv2.merge([h, s, v2])
dst = cv2.cvtColor(hsv2, cv2.COLOR_HSV2BGR)
cv2.imshow('dst', dst)

cv2.waitKey()
cv2.destroyAllWindows()

코드 4번에서 이미지 데이터를 읽어와 src에 저장하고, 7번에서 RGB 채널(OpenCV는 BGR 순서임)을 HSV 채널로 변환하여 hsv 버퍼에 저장합니다. 이 hsv 버퍼에 저장된 3개의 채널을 각각 h, s, v 버퍼에 저장 분리하는 코드가 8번인 cv2.split 입니다. 분리된 채널 중 v에 대해 Histogram 균등화를 수행하기 위해 10번이 실행되었고, 균등화된 v는 v2 버퍼에 저장됩니다. 다시 h, s, v2 버퍼에 저장된 3개의 Channel을 조합하기 위해 11번 코드의 cv2.merge API가 사용됩니다.

Javascript에서 객체의 key, value 순회하기

ES6에서 객체의 Key와 Key에 해당하는 Value을 순회하는 코드를 기록해 둡니다.

먼저 순회하려는 객체가 다음과 같다면..

let menus = {
    'menu1': {
        url: 'url(images/menu_icon1.png)',
        title: 'Menu Item 1',
            onclick: function() {
                alert('Menu 1');
            }
        },
    'menu2':  {
        url: 'url(images/menu_icon2.png)',
        title: 'Menu Item 2',
        onclick: function() {
            alert('Menu 2');
        }
    },

    'seprator1':  {},

    'menu3':  {
        url: 'url(images/menu_icon1.svg)',
        title: 'Menu Item 3',
        onclick: function() {
            alert('Menu 3');
        }
    },
    'menu4':  {
        url: 'url(images/menu_icon2.svg)',
        title: 'Menu Item 4',
        onclick: function() {
            alert('Menu 4');
        }
    },
};

순회하는 코드는 다음과 같습니다.

for (let [id, menu] of Object.entries(menus)) {
    if(menu.onclick) {
        this.addMenu(id, menu.url, menu.title, menu.onclick);
    } else {
        this.addSeparator(id);
    }
}

위의 코드에 대한 ES5에서의 동일한 코드는 다음과 같습니다.

for (let id in menus) {
    let menu = menus[id];

    if(menu.onclick) {
        this.addMenu(id, menu.url, menu.title, menu.onclick);
    } else {
        this.addSeparator(id);
    }
}

GeoServer의 Cross Domain 허용 (CORS)

GeoServer에서 Cross Domain을 허용하기 위한 설정 방법을 기록해 둡니다. 먼저 아래처럼 GeoServer의 환경설정 파일을 편집하기 위해 vi를 실행합니다. 환경 설정 파일은 설치되는 GeoServer의 디렉토리에 따라 달라집니다.

vi /usr/geoserver-2.14.1/webapps/geoserver/WEB-INF/web.xml

내용 중 CORS에 해당되는 2곳에 대한 주석을 아래 그림처럼 해제합니다.

만약 이미 GeoServer가 실행 중이라면 종료하고 재기동해야 합니다.

ES6의 Fetch API

ES6에서 지원하는 fetch API는 ES6의 Promise와 함께 AJAX를 Wrapping 해주는 매우 가독성이 뛰어난 방식입니다. fetch API은 내부적으로 AJAX API와 Promise를 사용하면서 외부로 들어내지 않습니다. 이 글은 fetch API에 대한 매우 기초적이지만 가장 일반적으로 많이 사용되는 내용을 정리합니다.

먼저 아래의 코드는 이미지를 fetch API를 통해 가져와 표시하는 코드 중 먼저 DOM 요소에 대한 코드입니다.


언급할 필요조차 없을 정도로 간단합니다. 다음은 실제 관심이 집중되는 fetch API 코드입니다.

fetch('lion.jpg')
    .then(function (response) {
        return response.blob();
    })
    .then(function (blob) {
        var objUrl = URL.createObjectURL(blob);
        myImage.src = objUrl;
    });

먼저 1번 코드의 fetch 함수를 통해 인자로 lion.jpg라는 상대 경로의 URL을 통해 (네트워크에서) 이미지 데이터를 가져와 2번 코드의 첫번째 then에 지정된 함수의 response에 그 결과를 인자로 담아 함수를 호출합니다. 호출된 함수에서 반환하는 결과에 대해 5번 코드의 then에 지정된 또 다른 함수의 인자에 담아 호출하게 됩니다. 하나의 fetch는 이처럼 2개의 연속된 then으로 구성됩니다.

API의 실행에 있어 중요한 것은 해당 API의 실패에 대한 처리입니다. 만약 lion.jpg에 대한 URL이 올바르지 않을 경우에 대한 처리가 필요합니다. 아래의 코드는 이러한 예외에 대한 경우의 코드까지 담고 있습니다.

fetch('lion.jpg')
    .then(function (response) {
        if (response.ok) {
            return response.blob();
        } else {
            alert('네트워크 오류');
        }
    })
    .then(function (blob) {
        var objUrl = URL.createObjectURL(blob);
        myImage.src = objUrl;
    })
    .catch(function (err) {
        alert(err)
    });

첫번째 then은 네트워크 통신 및 인자로 지정한 URL에 대한 문제가 있는지를 검사해야 하는데, 3번 처럼 response 인자의 ok 속성을 통해 확인 가능합니다. 두번째 then은 첫번째 then이 성공했던 실패했든지간에 두번째 then에서 지정한 함수의 실행 시에 어떤 문제가 발생하면 13번 코드의 catch의 함수가 호출됩니다.

아래는 POST 방식에 대한 fetch API 사용예입니다.

var params = {
    '_id': 'test.getPlayersWhere2',
    'age': 10,
    'name': '%'
};

fetch('http://localhost:8080/Bang?query',
    {
        method: 'POST',
        body: JSON.stringify(params)
    })
    .then(function (response) {
        if (response.ok) return response.text();
    })
    .then(function (text) {
        if (text) div.innerText = text;
        else alert('error');
     });

위의 사용예에 대한 jQuery 방식은 아래와 같습니다.

var params = {
    '_id': 'test.getPlayersWhere2',
    'age': 10,
    'name': '%'
};

$.ajax({
    url: 'http://localhost:8080/Bang?query',
    type: 'POST',
    data: JSON.stringify(params),
    dataType: "text",

    success: function (response) {
        div.innerText = response;
    },

    error: function (xhr, status) {
        alert('Error: ' + status);
    }
});