IT 알쓸신잡

OpenCV를 Web에서 구현해보자 - 2 본문

Development

OpenCV를 Web에서 구현해보자 - 2

솦트웰러 2023. 2. 7. 15:00
728x90
반응형

OpenCV.js 최신버전을 다운받아 Script 호출한 후에는 Qt에서 사용하던 방식과 거의 동일하게 사용이 가능합니다.

 

let m_psACBlackImage = new cv.Mat(CAM_HEIGHT, CAM_WIDTH, cv.CV_8UC1);
let m_psACWhiteImage = new cv.Mat(CAM_HEIGHT, CAM_WIDTH, cv.CV_8UC1);
let m_psACPattnImage = new cv.Mat(CAM_HEIGHT, CAM_WIDTH, cv.CV_8UC1);
let m_psResultPatternImage = new cv.Mat(CAM_HEIGHT, CAM_WIDTH, cv.CV_8UC1);

위 코드 처럼 javascript 변수 선언 후 동적 할당으로 cv.Mat 하여 필요한 크기와 Depth로 Matrix를 할당하면 사용할 수 있습니다.

 

function ConvertToIplMonoImage(buf, size, result)
{
    if(size == CAM_WIDTH*CAM_HEIGHT*CAM_BPP)
    {
        let rb88Data = buf;

        for(let nV = 0; nV < CAM_HEIGHT; nV++)
        {
            for(let nH = 0; nH < CAM_WIDTH; nH++)
            {
                let pos = (nV*CAM_WIDTH + nH) * CAM_BPP;
                let r8 = rb88Data[pos];

                if (result == 101)
                  m_psACBlackImage.ucharPtr(nV, nH)[0] = r8;
                else if (result == 102)
                  m_psACWhiteImage.ucharPtr(nV, nH)[0] = r8;
                else if (result == 103)
                  m_psACPattnImage.ucharPtr(nV, nH)[0] = r8;
            }
        }
    }
    else if(size == CAM_WIDTH*CAM_HEIGHT)
    {
        let grayData = buf;

        for(let nV = 0; nV < CAM_HEIGHT; nV++)
        {
            for(let nH = 0; nH < CAM_WIDTH; nH++)
            {
                let pos = nV*CAM_WIDTH + nH;
                let gray = grayData[pos];

                if (result == 101)
                  m_psACBlackImage.ucharPtr(nV, nH)[0] = gray;
                else if (result == 102)
                  m_psACWhiteImage.ucharPtr(nV, nH)[0] = gray;
                else if (result == 103)
                  m_psACPattnImage.ucharPtr(nV, nH)[0] = gray;
            }
        }
    }
}

위 함수는 hid 통신으로 받은 데이터를 배열로 받은 후 Matrix 변수로 변환하는 함수입니다.

Grayscale 데이터 경우와 Color 데이터의 경우 두 가지로 변환이 가능합니다. (영상 처리에서 많이 사용되는 함수에요)

 

function recogMarker(psInImage, thr, Markers)
{   
    let BinImage = new cv.Mat(CAM_HEIGHT, CAM_WIDTH, cv.CV_8UC1);
    if (thr == -1)
        psInImage.copyTo(BinImage);
    else if (thr == 0)
    {
        cv.threshold(psInImage, BinImage, 0, 255, cv.THRESH_OTSU);
        if (cv.countNonZero(BinImage) == 0)
            return;
    }
    else
    {
        cv.threshold(psInImage, BinImage, thr, 255, cv.THRESH_BINARY);
        if (cv.countNonZero(BinImage) == 0)
            return;
    }

    let dictionary = new cv.Dictionary(cv.DICT_4X4_250);
    let markerIds = new cv.Mat();
    let markerCorners  = new cv.MatVector();

	cv.detectMarkers(BinImage, dictionary, markerCorners, markerIds);

    for(let i=0;i<markerIds.data32S.length;i++)
    {
      //console.log("marker ID : "+markerIds.data32S[i]);
      let markerCorner = markerCorners.get(i);
      //console.log("marker corner : "+markerCorner.data32S);
      Markers[i] = new vMarker();
      if (markerIds.data32S[i] == 18 || markerIds.data32S[i] == 19)
        Markers[i].id = markerIds.data32S[i] - 1;
      else
        Markers[i].id = markerIds.data32S[i];
        
      Markers[i].contourPoints = markerCorner.clone();
    }
 }

위 함수는  Aruco 마크를 인식하는 함수인데,

cv.threshold 로 이진 영상 변환 후, cv.detectMarkers 로 일치하는 마커를 찾고, 찾은 마커들을 배열에 저장합니다.

 

    let BinImage = new cv.Mat(CAM_HEIGHT, CAM_WIDTH, cv.CV_8UC1);
    cv.threshold(psInImage, BinImage, thr, 255, cv.THRESH_BINARY);

    let contours = new cv.MatVector();
    let hierarchy = new cv.Mat();
    cv.findContours(BinImage, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE);

    let contoursTrans = new Array();

    for(let i = 0; i < contours.size(); i++)
    {
        const VALID_CIRCLE_WIDTH_MINIMUM  = 24;
        const VALID_CIRCLE_WIDTH_MAXIMUM  = 160;

        const VALID_CIRCLE_HEIGHT_MAXIMUM = 160;
        const VALID_CIRCLE_AREA_MINIMUM   = 200;
        const VALID_CIRCLE_AREA_MAXIMUM   = 20000;

        // lower height threshold for UST, only for top side pattern (pos = 1, 2, 3)
        const VALID_CIRCLE_HEIGHT_MINIMUM_ORG = 24;
        let circle_height_min = 24;
        if(lens == LENS_TU)
          circle_height_min = 20;

        const VALID_CIRCLE_HEIGHT_MINIMUM = circle_height_min;

        let cnt = contours.get(i);
        let sRect = cv.boundingRect(cnt);
        let dblAreaOfContour = cv.contourArea(cnt);

        if( (sRect.width < VALID_CIRCLE_WIDTH_MINIMUM) ||
            (VALID_CIRCLE_WIDTH_MAXIMUM < sRect.width) ||
            (sRect.height < VALID_CIRCLE_HEIGHT_MINIMUM) ||
            (VALID_CIRCLE_HEIGHT_MAXIMUM < sRect.height) ||
            (dblAreaOfContour < VALID_CIRCLE_AREA_MINIMUM) ||
            (VALID_CIRCLE_AREA_MAXIMUM < dblAreaOfContour) )
        {
            continue;
        }

        let cOrg = new Point2D32f();
        cOrg.x = 0.0;
        cOrg.y = 0.0;
        
        let m = cv.moments(contours, false);
        cOrg.x = m.m10 / m.m00;
        cOrg.y = m.m01 / m.m00;
    }

detectMarkers 함수를 사용하기 전에는,

 

1. 이진화

2. cv.findContour

3. Contour의 예외 처리 (boundingRect, contourArea 등으로 비교 후 불필요한 Contour 제거)

4. moment 함수를 이용한 Contour의 중점 계산

 

단계를 거쳐야 하지만, detectMarkers 함수를 이용하면 일치하는 마커의 Contour까지 같이 검출이 됩니다.

또한 내가 출력한 마커의 ID를 알기 때문에 노이즈에 의한 Contour 제거를 원할하게 할 수가 있어요~

 

opencv.js 호출 후로는 javascript 변수 선언 방식과 함수 인자 전달 등의 세부적인 부분만 다를 뿐 거의 Qt에서 구현하던 C++ 기반으로 포팅이 원할하더군요.

 

OpenCV를 Web으로 포팅하면서, 이제 Web에서 제약사항이 점차 없어짐을 느꼈습니다.

1. USB Device와 HID 표준 프로토콜 통신이 가능하고,

2. 브라우저 GPU 가속을 통해 속도 및 퍼포먼스가 PC Application과 유사하며,

3. 네트워크 속도의 향상으로 서버와 데이터 송/수신이 훨씬 빨라졌습니다.

 

암튼 많은 것을 배우며 느꼈습니다.

 

그럼 이상으로 OpenCV를 Web으로 구현하는 포스팅을 마치도록 하겠습니다.

 

 

 

728x90
반응형
Comments