[OpenLayers] 이미지 필터링

이미지 필터링은 이미지의 외곽선 추출이나 이미지의 잡음을 제거하기 위해 수행되는 NxN 행렬의 커널을 이미지의 각 화소에 연산하여 다시 조합하는 것을 말합니다. 이 글은 ol에서 받은 영상 데이터에 대한 외곽선을 추출하는 필터링 연산을 적용하여 그 결과를 실시간으로 살펴보는 것에 대한 내용을 정리합니다.

먼저 DOM을 아래처럼 구성합니다. 스타일과 함께 언급했고요..



    
        
        OpenLayers
        
    
    
        

지도에 대한 div 요소가 전부입니다. 이 div 요소가 바로 지도가 담길 DOM 입니다. js 코드를 순서대로 하나씩 살펴 보겠습니다. 먼저 필요한 모듈을 추가합니다.

import 'ol/ol.css';

import Map from 'ol/Map.js';
import View from 'ol/View.js';
import {Tile} from 'ol/layer.js';
import {XYZ} from 'ol/source.js';

그리고 필터링 대상이 되는 이미지를 VWorld의 항공영상 레이어로 사용할텐데.. 이에 대한 레이어 객체 imagery를 아래처럼 준비합니다.

let imagery = new Tile({
    source: new XYZ({
        url : 'http://xdworld.vworld.kr:8080/2d/Satellite/service/{z}/{x}/{y}.jpeg',
        crossOrigin: 'anonymous'
    }),
})

4번 코드를 보면 crossOrigin 옵션을 ‘anonymous’로 지정하고 있습니다. 이는 외부에서 가져온 이미지를 처리하여 다시 화면에 그릴때 발생하는 보안상의 오류를 방지합니다. 이제 생서한 레이어를 이용하여 지도 객체를 아래처럼 준비합니다.

let map = new Map({
    target: 'map',
    layers: [ 
        imagery
    ],
    view: new View({
        center:  [14128579.82, 4512570.74],
        zoom: 17,
        minZoom: 6
    })
});

외곽선 추출을 위한 커널로 3×3 행렬을 사용합니다. 가장 일반적인 외곽선 추출을 위한 3×3 행렬을 아래처럼 정의합니다.

let kernel = 
    [
        0,  1,  0,
        1, -4,  1,
        0,  1,  0
    ];

입력 데이터가 되는 항공영상 레이어에 postcompose 이벤트를 추가하여 입력 데이터가 완전이 준비되면 호출할 이벤트를 아래처럼 입력합니다.

imagery.on('postcompose', function(event) {
    convolve(event.context, kernel);
});

convolve 함수는 2개의 인자를 취하는데, 첫번째는 Canvas에 대한 context이고 두번째는 커널 행렬입니다. 첫번째 context의 Canvas에 입력 데이터에 대한 화소(Pixel 값으로써의 RGB)를 가지고 있습니다. 또한 이 Canvas에 다시 우리가 원하는 무언가를 그릴 수 있는데, 그리고자 하는 것은 필터링이 적용된 결과 이미지가 됩니다. convolve 함수는 다음과 같습니다.

function convolve(context, kernel) {
    let canvas = context.canvas;
    let width = canvas.width;
    let height = canvas.height;

    let size = Math.sqrt(kernel.length);
    let half = Math.floor(size / 2);

    let inputData = context.getImageData(0, 0, width, height).data;

    let output = context.createImageData(width, height);
    let outputData = output.data;

    for (let pixelY = 0; pixelY < height; ++pixelY) {
        let pixelsAbove = pixelY * width;
        for (let pixelX = 0; pixelX < width; ++pixelX) {
            let r = 0, g = 0, b = 0, a = 0;
            for (let kernelY = 0; kernelY < size; ++kernelY) {
                for (let kernelX = 0; kernelX < size; ++kernelX) {
                    let weight = kernel[kernelY * size + kernelX];
                    let neighborY = Math.min(height - 1, Math.max(0, pixelY + kernelY - half));
                    let neighborX = Math.min(width - 1, Math.max(0, pixelX + kernelX - half));
                    let inputIndex = (neighborY * width + neighborX) * 4;

                    r += inputData[inputIndex] * weight;
                    g += inputData[inputIndex + 1] * weight;
                    b += inputData[inputIndex + 2] * weight;
                    a += inputData[inputIndex + 3] * weight;
                }
            }

            let outputIndex = (pixelsAbove + pixelX) * 4;
            
            outputData[outputIndex] = r;
            outputData[outputIndex + 1] = g;
            outputData[outputIndex + 2] = b;
            outputData[outputIndex + 3] = 255;
        }
    }

    context.putImageData(output, 0, 0);
}

위의 코드의 핵심을 짚어보면 입력 데이터의 각 화소에 접근하여 커널 행렬을 적용하고, 그 결과 화소로 구성된 이미지를 다시 그려준다는 것입니다. 실행 결과는 아래와 같습니다.

위의 지역은 어딜까요...? 외곽선을 좀더 두드러지게 추출할 수 있는 다른 커널 행렬을 적용해 볼 필요가 있을듯합니다.

Python과 OpenCV – 3 : 코드로 이미지에 도형 그리기

참조한 글은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_gui/py_drawing_functions/py_drawing_functions.html 입니다.

이미지는 사진이나 동영상을 통해 얻을 수도 있지만 코드를 통해서도 생성할 수 있습니다. 아래의 코드는 512×512 크기의 3개의 채널로 되어 있으며 채널의 구성 데이터 타입은 unit8입니다.

import numpy as np
import cv2

img = np.zeros((512,512,3), np.uint8)

이 이미지 위에 다양한 도형을 그리는 코드를 정리해 볼텐데요. 먼저 두점으로 구성된 선분을 그리는 코드입니다.

cv2.line(img, (0,0), (512,512), (255,0,0), 5)

위의 코드는 좌표(0,0)에서 좌표(512,512)로 구성된 선분을 색상은 (B,G,R) 순서인 (255,0,0)인 파란색으로 굵기는 5pixel로 그립니다. 다음 코드는 사각형을 그리는 코드입니다.

cv2.rectangle(img, (384,0), (510,128), (0,255,0), 3)

위의 코드는 좌상단 (384,0)과 우하단 (510,128)인 지사각형을 초록색(0,255,0)으로 3Pixel 굵기로 그립니다. 다음은 원을 그리는 코드입니다.(

cv2.circle(img, (447,63), 63, (0,0,255), -1)

위의 코드는 중심점 (447,63)이고 반지름은 63인 원을 빨간색(0,0,255)으로 하여 굵기속성은 -1로 함으로써 도형을 채움형태로 그리는 코드입니다. 다음은 타원을 그리는 코드입니다.

cv2.ellipse(img, (256,256), (100,50), 45,0,180,(0,0,255),-1)

위의 코드는 중심점이 (256,256)이고 가로 반지름과 세로 반지름을 각각 (100,50)이며, 45도 시계방향으로 회전하고 시작각 0도 종료각 180도인 타원을 빨간색(0,0,255)로 채워진(-1) 타원을 그리는 코드입니다. 다음은 폴리라인을 그리는 코드입니다.

pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)
pts = pts.reshape((-1,1,2))
cv2.polylines(img, [pts], True, (0,255,255))

위의 코드는 총 4개의 좌표로 구성된 pts를 이용해 색상은 BRG값이 각각 (0,255,255)로 하여 폴리곤을 그리는 코드인데.. cv2.polylines의 3번째를 True로 지정하여 폐합(시작점과 끝점을 연결)하도록 그립니다. 마지막으로 문자를 그리는 코드입니다.

font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img, 'OpenCV', (10,500), font, 4, (255,255,255), 2)

위치 (10,500)에 OpenCV라는 문자열을 폰트 크기 4로 하여 백색(255,255,255)로 하여 굵기는 2로 그리는 코드입니다.

지금까지 그린 이미지를 화면에 표시하기 위해 아래의 코드를 덧붙입니다.

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

jetty의 ServletContextHandler에 CORS 지정하기

ServletContextHandler의 setResourceBase 함수를 통해 이미지나 html 등과 같은 리소스에 접근할 수 있는 일반적인 웹서버을 지정할 수 있는데, 이러한 리소스에 대한 접근 시 CORS를 설정하는 API입니다.

ServletContextHandler svltContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
svltContext.setResourceBase("d:/webHome");
        
// Start 
FilterHolder cors = svltContext.addFilter(CrossOriginFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
cors.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
cors.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
cors.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,POST,HEAD");
cors.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "X-Requested-With,Content-Type,Accept,Origin");
// End

개발성 향상을 위한 JavaScript의 주석

JavaScript는 type이 불명확하므로 문서화를 통해 그 상세정보를 남겨야 합니다. 그러나 소스코드와 분리된 문서는 사실상 그 활용가치가 매우 떨어집니다. 소스코드와 일원화된 방식으로 문서화가 되어야 하는데, 바로 주석입니다. 아래는 어떤 클래스의 특정함수에 대한 주석을 통한 문서화에 대한 간단한 예시입니다.

/**
 * 메뉴 항목을 추가한다.
 * @param {string} id 항목에 대한 고유 식별자 
 * @param {string} url 항목 아이콘 
 * @param {string} title 항목 타이틀
 * @param {function} callback 실행에 대한 호출 함수
 * @returns {boolean} 성공 여부
 */
addMenu(id, url, title, callback) {
   ...

IDE는 이 주석을 실시간으로 해석해 개발자에게 매우 직관적인 힌트를 제공합니다. 예를들어서 VSCode에서는 아래와 같은 힌트를 제공합니다.

아울러 이러한 주석은 다양한 문서 형식으로써, 예를들어 pdf, docx, html 등과 같은 별도의 문서를 자동으로 생성해 낼 수 있습니다.