앱개발

[C++] 맥에서 OpenCV로 영상처리하기 (XCode, C++, GUI앱 개발)

제갈티 2024. 9. 12. 13:11

만들려고 하는 앱 모습은 이런 느낌입니다.

- 맥오에스에서 화면에 빈 GUI 창을 하나 만들고 거기에 이미지 파일(png, jpg)을 드래그 앤 드롭하면 그림처럼 좌측에 이미지가 표시되며 마우스로 이미지 속 타깃 물체 주변을 드래깅 하여 빨간 바운딩박스를 실시간에 만든 후, 드래깅을 멈추면 박스 내 이미지 영역 안에서 수학적 영상처리 방식의 Segmentation 알고리즘인 Grabcut() 함수를 사용해 마스크를 만들고 그 마스크를 저장하는 기능을 만들려 합니다.

- 아시다시피 맥에서 C++ 이라는 언어로 GUI 프로그램을 만드는 방법은 몇 가지가 있지만 다들 매우 복잡합니다. Qt-Creator 나 CVUI 같은 걸 사용하는 방법이 있긴 하지만, 초보자가 넘볼만한 개발환경은 아닌 것 같습니다.

- 하지만 Openframeworks(줄여서 OF) 라는 오픈소스 개발 프레임웍을 사용하면 아주 쉽게 맥용 GUI앱을 C++로 만들 수 있습니다.

OF의 프로젝트 제너레이터의 모습

 - 맥용 OF 를 다운로드하여 설치하면 그 안에 프로젝트 제너레이터라는 앱이 보입니다. 실행하면 위 그림과 같이 프로젝트명과 경로, 애드온 같은 걸 선택하는 메뉴가 보이는데요..

- 애드온은 파이썬에서의 패키지(라이브러리) 같은 겁니다. 여기선 영상처리를 위해 ofxOpenCV 와 ofxCv를 선택했네요. 둘 다 OpenCV라이브러리의 C++용 Wrapper인데, ofxCv 가 좀 더 고급진(?) 기능을 가지고 있습니다. 참고로 ofxOpenCV는 OF에 내장된 애드온이고 ofxCv는 써드파티로 추가로 설치해야 하는 애드온입니다. 

- 연결되는 통합개발도구는 XCode로 고정되어 있지요. 마지막 생성 버튼을 누르면 XCode가 뜨면서 프로젝트가 열립니다.

- 맥용 OF 다운로드 주소: https://openframeworks.cc/download/ 

 

download | openFrameworks

linux arm openFrameworks for arm boards running linux like Raspberry Pi, Beaglebone (black), Pandaboard, BeagleBoard and others.We have setup guides for some of the most common boards but it should work on any armv6 and arm64 board.

openframeworks.cc

 

프로젝트 생성 성공했다네요, IDE 열기 버튼을 누릅니다.

 

앱의 헤더 파일

- 열린 Xcode 프로젝트는 main.cpp , ofApp.h, ofApp.cpp 3개의 파일로 구성됩니다.  우선 이건 헤더파일의 코드입니다. 앱클래스의 기본 메서드들의 프로토타입 들을 담고 있는데 setup(), update(), draw()가 핵심이고 나머지들은 다 이벤트 핸들러들입니다.

앱의 C++ 파일

- 여기서 영상처리나 그래픽 처리같은 중요한 기능들을 구현합니다.  setup()에서 각종 1회성 초기화 등을 구현하고 update()에서는 무한반복 갱신되는 코딩을 처리합니다. 그리고 update()가 한번 반복할 때마다 draw()를 호출해 그래픽 갱신을 처리합니다. 마치 Callback 함수로 실시간 그래픽을 처리하는 것과 비슷하지 않나요~?! 

- 맞습니다. OF는 별도로 코딩하지 않아도 자동으로 Callback기능을 수행하도록 작동합니다. OF 프레임웍이 실시간 인터랙션 아트미디어 전시물이나 반응형 고속 무한반복앱 개발에 많이 사용하는게 이런 특징 덕분이죠. 

- 저렇게 이미 짜여진 단일 앱 클래스에 사전 정의된 메써드를 사용자가 메써드 오버라이딩하면서 클래스를 변경해 주는 게 코딩의 전부입니다. 당연히 개발이 쉬워지겠죠?

- 저런 구조는 게임을 개발할때 쓰는 Unity 에도 들어있고 아두이노를 개발할 때 쓰는 Processing이라는 개발도구에도 들어있습니다. 실시간 반응이 중요하기 때문입니다. 저는 개인적으로 자율주행이나 제조업 현장에서의 딥러닝 추론도 이런 방식으로 코딩하고 또한 작동하여야 한다고 생각합니다. 

앱의 메인 메써드 파일

- 메인 메써드는 간단합니다. OpenGL 기반의 창을 하나 만들어서 보여주고 ofApp 클래스를 실행하여 무한루프를 돌리는게  전부죠.

- 참고로 맥에서 C++로 OpenGL기반으로 화면 그래픽을 처리하기 때문에 처리속도가 매우 빠른편입니다. 덩달아 영상처리 속도도 매우 빠르죠~!  그럼 이제 만들려고 하는 코드를 살펴볼까요?


#pragma once

#include "ofMain.h"
#include "ofxCv.h"
#include "ofxCsv.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();

		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void mouseEntered(int x, int y);
		void mouseExited(int x, int y);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
		
private:
    ofImage image;
    ofImage after;
    ofImage bin_mask;
    // Rect 
    int startX;
    int startY;
    int endX;
    int endY;
    //
    bool dragging;
    
    int ww, hh;
    
};

- 헤더 파일을 정의 합니다. 상단에 영상처리를 위해 ofxCv 헤더파일을 임포트 하는 게 보이죠?

- ofImage 는 OF 자체의 이미지 클래스 자료형입니다. 간단한 영상처리를 제공하지만 보통은 안 쓰고 복잡한 영상처리는 OpenCV에 맡길 것이므로 여기선 이미지 파일의 입출력 만을 담당하게 되죠.

 

#include "ofApp.h"

using namespace ofxCv;
using namespace cv;
using namespace std;
//--------------------------------------------------------------
void ofApp::setup(){

    //set to Image 2*size & 1*size
    ww= 256;
    hh= 256;
    
    ofSetWindowShape(2*ww, hh);
    
    ofSetBackgroundColor(50);
    //ofSetFrameRate(60);
    //ofSetVerticalSync(true);
    //ofEnableSmoothing();
    
    // Load the image
    image.load("/Users/user1/Documents/0309_graphCut/NG(99)(449) (1).png");
    
    // Set the initial values for the bounding box
    startX = 0;
    startY = 0;
    endX = 0;
    endY = 0;
    dragging = false;

}

- Cpp 파일중 setup() 메써드를 정의합니다. 이미지를 로딩하고 헤더에서 선언했던 각종 전역 멤버변수를 초기화합니다.

 

//--------------------------------------------------------------
void ofApp::update(){

}

//--------------------------------------------------------------
void ofApp::draw(){

    // Draw the image
    ofSetColor(255);
    image.draw(0, 0, ww, hh);
    ofSetColor(255);
    after.draw(ww, 0, ww, hh);
    
    // Draw the bounding box
    ofSetColor(255,0,0);
    ofNoFill();
    ofDrawRectangle(startX, startY, endX - startX, endY - startY);
}

- Cpp 파일 중 draw() 메써드를 정의합니다.

- 업데이트 메써드는 변화가 없지만 무한갱신하면서 드로우()를 호출하죠.

- 드로우()에서는 반복적으로 입력 이미지를 좌측에, 영상처리 후의 이미지결과를 우측에 그려줍니다.  그리고 바운딩 박스도 빨간색으로 그려주네요.

 

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){

    // When the mouse is dragged, update the end point of the bounding box
    if (dragging) {
        endX = x;
        endY = y;
    }
}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){


    // When the mouse is pressed, set the start point of the bounding box
    startX = x;
    startY = y;
    dragging = true;
}

- 이제 마우스 이벤트 핸들링 차례입니다. 드래깅과 마우스 프레스 이벤트를 처리합니다.

 

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){
    
    // GrabCut
    cv::Rect bbox(startX, startY, abs(endX-startX), abs(endY-startY));
    ofLog() << "bbox = " <<bbox;
    
    cv::Mat src= toCv(image);
    cv::Mat mask= cv::Mat::zeros(src.rows, src.cols, CV_8UC1);
    cv::Mat bgModel, fgModel;
    
    unsigned int iteration= 9;
    
    cv::grabCut(src, mask, bbox, bgModel, fgModel, iteration, GC_INIT_WITH_RECT);
    // binary mask + RGB color
    cv::Mat mask2= (mask ==1) + (mask ==3);
    // binary mask only
    //cv::Mat mask2= (mask ==1);
    
    // Threshold the mask to get the binary mask
    //cv::threshold(mask2, mask2, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);

    
    toOf(mask2, bin_mask);
    bin_mask.update();
    bin_mask.save("bin_mask.jpg");
    //
    cv::Mat dest;
    src.copyTo (dest, mask2);
    
    toOf(dest, after);
    after.update();
    after.save("seg_mask.jpg");
 
    // When the mouse is released, stop dragging
    dragging = false;
}

- 가장 중요한 마우스 릴리즈 이벤트를 처리합니다.

- 눈에 익숙한 OpenCV + C++  코드들이 보이죠? 이렇게 기존에 인터넷에 널려있는 C++ 코드들을 그대로 가져다 재활용이 가능합니다.

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){ 

    if( dragInfo.files.size() > 0 ){
        image.load(dragInfo.files[0]);
        image.setImageType(OF_IMAGE_COLOR);
    }
    ww=image.getWidth();
    hh=image.getHeight();
    ofSetWindowShape(2*ww, hh);
    //
    
    // Set the initial values for the bounding box
    startX = 0;
    startY = 0;
    endX = 0;
    endY = 0;
    dragging = false;
}

- GUI창에 새로운 이미지 로딩을 위한 드래그 앤 드롭 이벤트를 처리합니다.

코딩이 끝난 후 삼각형 실행버튼을 누르면 컴파일이 되면서 앱창이 뜹니다.

작 작동하네요~ 참 쉽죠?

- 창의 제목은 main.cpp 파일에서 변경하시면 됩니다.

OF라는 개발 프레임웍은 맥용 XCode 뿐 아니라 윈도에서 VStudio 로도 개발가능하고  리눅스에서 Qt-Creator 로도 개발이 가능합니다. 최근엔 VSCode 도 지원한다고 하는데 해보진 않았네요~ 당근 리눅스나 맥  터미널에서 CMake 로도 개발이 가능하고요.

엔비디아 젯슨 이나 라즈베리파이 같은 임베디드에서도 잘 돌아가며 개발이 가능하며 GUI 없는 run-level3 이하의 리눅스에서 CLI 모드로도 개발 및 GUI화면 로딩이 가능하죠!

OF는 별도의 OpenGL기반의 GUI 기능을 가지고 있어서 Cocoa 나 MFC 또는 Qt 같은 GUI프레임웍 과는 별도의 심플하고 빠른 GUI를 제공합니다. 

그리고 ofx로 시작하는 각종 써드파티 애드온들을 홈페이지나 깃허브에서 제공하기 때문에 OF 안에  addon 폴더에서 git clone 하기만 하면 설치가 되고 프로젝트 제너레이터에서 선택이 가능하게 됩니다. 홈페이지에 가보시면 컴퓨터비전은 물론 인공지능, 그래픽, 카메라제어, 수학, 운영체게, 네트워킹, 3차원 그래픽 등등 다양한 분야의 애드온들이 있으며 지금도 오픈소스로 만들어지고 있습니다.