윈도우 또는 리눅스환경에서 Qt프레임웍기반의 안드로이드를 위한 프로그램을 개발할 수 있다. 이글에서는 윈도우 환경에서 개발하는 방법을 설명한다.

 

먼저 안드로이드를 위한 컴파일된 Qt를 설치해야하는데 Qt 홈페이지를 방문해서 Qt Online Installer for Windows를 다운로드 받아 실행하거나 Qt가 설치되어 있다면 MaintenanceTool 을 실행한다.

 

Select Components에서 Android x86 및 Android ARMv7을 선택하고 설치한다.

 

안드로이드용 Qt설치가 끝났다면 안드로이드 개발관련 도구들을 다운로드받아야하는데 Android 스튜디오를 다운받을 수 도 있지만 이 글에서는 명령줄 도구로된 것을 사용할 것이다.

 

참고로 Android SDK 도구 버전 25.3.0 이상에서는 SDK 패키지 관리를위한 sdkmanager 와 AVD (Android Virtual Device) 관리를 위한 avdmanager를 명령 줄 도구로만 제공한다는 점을 알아두기 바란다.

 

그리고 한가지 더 중요한 사항은 Qt 버전을 v5.9 이하로 개발할 경우 SDK 도구 패키지 v25.2.5 이하를 사용한다.

 

Android SDK 다운로드 및 설치

 

Android NDK 다운로드 및 설치

Java JDK 다운로드 및 설치

 

윈도우에서는 기본 USB드라이버로 디버깅할 수 없으므로 Google USB드라이버를 설치한다.

CMD(명령프롬프트)를 실행하고 sdkmanager 폴더로 이동한 후 sdkmanager.bat "extras;google;usb_driver" 실행.

(리눅스나 맥에서는 필요치 않다. 자세한 내용은 이곳을 참고)

 

QtCreator설정

Option - Devices의 Android탭에서 JDK, SDK, NDK의 설치 및 폴더 경로를 설정한다. 

 

SDK 25.3 이후  SDK Manager, AVD Manager는 더이상 GUI를 제공하지 않지만 Qt Creator는 Android package목록을 제공하므로 간단하게 패키지를 설치할 수 있다.

 

패키지 설치 

 

이제 안드로이드 디바이스를 연결하고

다음과 같은 디버깅을 허용할지 묻는 팝업이 뜨면 확인을 누른다.

 

프로젝트를 만들때 kit은 아래처럼 처음에 설치한 Android kit을 선택한다.

 

프로젝트 생성 완료 후 Run을 실행하고 그림처럼 연결된 안드로이드 디바이스를 선택하면 잠시 후  선택한 장치에서 프로그램이 실행된다.

 

아래 캡쳐 이미지는 Qt기본 예제(webView)를 안드로이폰에서 실행한 것이다. 

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

전에 Qt4.8 버전에서의 정적빌드하는 방법에 대해 설명했는데,

이번에는 Qt5.4버전에서의 정적빌드에 대해 알아보겠습니다.

Qt4.8버전과 대략적으로 비슷하지만 약간 달라진 부분이 있습니다.

이제 설명을 시작하겠습니다.


http://www.qt.io/download-open-source/# 


먼저 위의 링크로 접속해서 Qt5.4 offline을 다운받아야 합니다.


 


하단에 Windows Host 제목의 Qt5.4.0 for Windows 32bit (MinGW 4.9.1, 852MB) 를 다운받아 설치합니다.

설치과정은 별다른 설정이 없으므로 생략합니다.


http://download.qt.io/official_releases/qt/5.4/5.4.0/single/

이제 위의 링크로 접속해서 소스코드를 다운받습니다.


qt-everywhere-opensource-src-5.4.0.zip


과 같은 이름의 파일이 있습니다.

해당 파일을 다운받습니다.


위의 Qt5.4 오프라인 버전을 설치하면 C:\Qt\Qt5.4.0\5.4폴더가 있습니다.

이 위치에 qt5-opensource-src-5.4.0.zip파일을 압축해제 해줍니다.


 


그러면 위와 같이 폴더가 존재하게 됩니다.

(위의 android_armv7은 아마 안보일겁니다. 제가 안드로이드버전을 설치해서 보이는 겁니다.)


C:\Qt\Qt5.4.0\5.4\qt-everywhere-opensource-src-5.4.0\qtbase\mkspecs\win32-g++


위의 경로에 보면 qmake.conf파일이 있습니다.

해당 파일을 메모장으로 열어줍니다.


중간에 QMAKE_LFLAGS가 있습니다.


QMAKE_LFLAGS            = -static -static-libgcc


다음과 같이 수정해줍니다.

그리고 저장한 후 닫아줍니다.


 


시작에서 Qt 프롬프트를 실행합니다.

위의 사진과 같은 곳에 Qt5.4.0 프롬프트가 존재할겁니다.(사진은 퍼온겁니다.)


콘솔창에 다음과 같이 입력합니다.


cd "C:\Qt\Qt5.4.0\5.4\qt-everywhere-opensource-src-5.4.0\qtbase"

그리고 다음을 입력합니다.


configure -static -release -opengl desktop -opensource


그리고 묻는 창에 y를 입력하여 configure를 진행합니다.

1분 내외로 작업이 끝나는데 이후에 다음과 같이 입력합니다.


mingw32-make sub-src


그러면 30분 내외의 시간동안 정적빌드에 들어갑니다.

 


전부 완료되면 다음과 같이 결과가 보입니다.


이제 창을 닫고 Qt Creator를 실행합니다.

메뉴바에서 Tool - options를 클릭합니다.




왼쪽의 Build & Run탭에서 Qt Versions 탭으로 간 다음, Add를 클릭합니다.



C:\Qt\Qt5.4.0\5.4\qt-everywhere-opensource-src-5.4.0\qtbase\bin


위의 경로에 보면 qmake.exe가 있습니다.

이것을 선택하고 열기를 누릅니다.

그리고 Version name을 적절히 변경해줍니다. 저는 Qt5.4.0(qt-static)으로 지었습니다.

 

다시 Kits 탭으로 가서 Add를 클릭합니다.

그리고 Name을 Qt5 static으로 입력하고, Qt version을 방금 만든 Qt5.4.0(qt-static)으로 선택하고 OK를 클릭합니다.


 

이제 새로운 프로젝트를 만들어서 Release모드로 실행해보면 다음과 같이 제대로 빌드 후 실행이 되는 것을 확인할 수 있습니다.


 


빌드된 폴더에 들어가서 실행파일을 보니 용량이 14메가바이트로 굉장히 늘었음을 확인할 수 있습니다.

이제 이 파일을 Qt를 설치하지 않은 다른 컴퓨터에서 원활하게 실행할 수 있게됩니다.

여기까지가 Qt5.4버전에서의 정적빌드 방법입니다.


여기서 조금 더 개선된 방식을 사용하자면 용량을 줄이는 방법이 있습니다.


제가 첨부파일로 올린 upx391w.zip을 다운받아 압축을 풉니다.

저는 이곳에 upx.exe파일이 있습니다.

C:\Users\remoc_000\Desktop\upx391w\upx391w\upx.exe


그리고 방금 빌드한 파일은 다음 경로에 있습니다.


C:\Users\유저\Documents\Qt\build-qt_static_001-Qt5_static-Release\release\qt_static_001.exe


이제 명령 프롬프트를 실행합니다.

명령프롬프트는 시작 - 보조프로그램에 있습니다.


명령 프롬프트에 다음과 같이 입력합니다.

"C:\Users\유저\Desktop\upx391w\upx391w\upx.exe" -9 -o "C:\Users\유저\Documents\Qt\build-qt_static_001-Qt5_static-Release\release\result.exe" "C:\Users\유저\Documents\Qt\build-qt_static_001-Qt5_static-Release\release\qt_static_001.exe"

 


그러면 다음과 같이 압축이 진행됩니다.

사용하지 않는 코드는 버리는 과정입니다.


 그러면 이렇게 완료가 됩니다. 


그리고 result.exe파일이 생성되면서 용량은 5.4메가바이트로 1/3로 줄어듭니다.

이제 네이버 블로그에도 올릴 수 있게되었네요.


여기까지가 Qt5.4 정적빌드 방법입니다.

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

OpenCV IplImage를 사용해서 Labeling 함수 구현


1. Labeling이란?

  우선 1-channel의 gray scale 영상이 필요합니다. 이진화된 아래의 이미지처럼 0 or 255(1-channel)의 값을 갖을 경우 인접한 영역끼리 그룹을 짓는 것을 Labeling(레이블링)이라고 합니다.

 

[그림 1]


아래의 그림처럼 인접 영역끼리 label Number를 매겨 그룹화를 하게됩니다.

 

[그림 2]


 여기서 인접한 pixel을 탐색할 때 8-neighbor 방식을 사용하였습니다. neighbor는 '이웃'이라는 뜻처럼 8-neighbor는 현재 pixel을 기준으로 몇 개의 이웃 pixel들을 탐색하는지를 뜻합니다. 아래의 왼쪽 그림은 현재 pixel을 기준으로 위, 아래, 왼쪽, 오른쪽 총 4방향을 탐색하는 4-neighbor 탐색입니다. 오른쪽 그림은 4-neighbor에 4개의 대각방향을 더 탐색하는 8-neighbor 탐색입니다. 저는 8-neighbor를 사용하였기 때문에 [그림 2]에서 label number가 '4'인 대각 픽셀이 그룹된 것을 확인할 수 있습니다. 이렇게 인접한 영역들을 Grouping한 것이 Labeling입니다.

[그림 3] 4-neighbor & 8-neighbor


2. Labeling 구현 전략

1) 이미지에서 화소 값(255)이 있는 지점까지 탐색

2) 이미 Labeling된 화소인지 확인

3) 아니라면 stack에 현재 x, y 좌표를 저장

4) 8-neighbor 탐색하면서 픽셀화소가 255이면서 Labeling이 안된 지역을     발견하면 stack에 모두 넣는다. (8-neighbor 모두 탐색한다.)

5) stack의 맨 위 좌표를 받아 4)번 과정을 반복한다.

6) 더이상 grouping할 행렬이 없을 때(stack이 비었을 때)까지 수행

7) 이미지의 다음 화소 값이 있는 곳까지 탐색하는 1) 과정부터 다시 시작


3. Labeling 구현 설명

제일 첫 좌표를 (0, 0)이라고 할 때 이미지에서 화소 값(255)이 있는 지점인 (3, 3)까지 오게된다. 이 점을 stack에 push한다.

[그림 4]


[그림 5] stack


  (3, 3)을 기준으로 8-neighbor 탐색을 한다. 해당 pixel에 값이 있고 Labeling이 안된 영역을 stack에 push 한다. [그림 6]을 보면 (3, 3)을 기준으로 3칸이 그림영역이고 현재 Labeling이 안되었기 때문에 [그림 7]처럼 stack에 push 한다.

[그림 6]


  stack의 top인 (4, 3)를 기준으로 다시 8-neighbor 탐색을 하면서 Label Number를 매기면 된다. 

[그림 7]


4. Iplimage로 구현한 Labeling 함수

stl stack을 사용하기 위해 #include <stack> 헤더파일을 선언한다. 
- 함수 호출방법: Labeling(src, dst);
- 함수 인자설명: src(원본이미지), dst(출력이미지)
(labelNumber 값이 출력이미지로 바로 출력되어 잘 안보일 수 있다. 눈에 보이게 하고 싶으면 labelNumber = 100; 이렇게 초기 값을 설정하면 잘 보일 것이다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stack>
 
void    Labeling    (const IplImage *src, IplImage *dst)
{
    // Only 1-Channel
    if( src->nChannels != 1 )
        return;
 
    // image size load
    int height    = src->height;
    int width    = src->width;
 
    // input image, result image를 image size만큼 동적할당
    unsigned char*    inputImage    = new unsigned char    [height * width];
    int*            resultImage    = new int            [height * width];    
 
    // before labeling prcess, initializing 
    forint y = 0; y < height; y++ ){
        forint x = 0; x < width; x++ ){
            // image copy
            inputImage[width * y + x] = src->imageData[width * y + x];
 
            // initialize result image
            resultImage[width * y + x] = 0;
        }
    }
 
    //// 8-neighbor labeling
    // Labeling 과정에서 stack overflow 방지를 위한 stl <stack>사용 
    stack<Point> st;
    int labelNumber = 0;
    forint y = 1; y < height - 1; y++ ){
        forint x = 1; x < width - 1; x++ ){
            // source image가 255일 경우 + Labeling 수행되지 않은 픽셀에서만 labeling process 시작
            if( inputImage[width * y + x] != 255 || resultImage[width * y + x] != 0 ) continue;
            
            labelNumber++;
            
            // 새로운 label seed를 stack에 push
            st.push(Point(x, y));
 
            // 해당 label seed가 labeling될 때(stack이 빌 때) 까지 수행
            while( !st.empty() ){
                // stack top의 label point를 받고 pop
                int ky = st.top().y;
                int kx = st.top().x;
                st.pop();
 
                // label seed의 label number를 result image에 저장
                resultImage[width * ky + kx] = labelNumber;
 
                // search 8-neighbor
                forint ny = ky - 1; ny <= ky + 1; ny++ ){
                    // y축 범위를 벗어나는 점 제외
                    if( ny < 0 || ny >= height ) continue;
                    forint nx = kx - 1; nx <= kx + 1; nx++ ){
                        // x축 범위를 벗어나는 점 제외
                        if( nx < 0 || nx >= width ) continue;
                        
                        // source image가 값이 있고 labeling이 안된 좌표를 stack에 push
                        if( inputImage[width * ny + nx] != 255 || resultImage[width * ny + nx] != 0 ) continue;
                        st.push(Point(nx, ny));
                        
                        // 탐색한 픽셀이니 labeling
                        resultImage[width * ny + nx] = labelNumber;
                    }
                }
            }        
        }
    }
 
    // dst image에 복사
    forint y = 0; y < height; y ++ ){
        forint x = 0; x < width; x++ ){
            dst->imageData[width * y + x] = resultImage[width * y + x];
        }
    }
 
    // 메모리 해제
    delete[] inputImage;
    delete[] resultImage;
}



출처: http://devmonster.tistory.com/22 [Dev.Monster]

출처: http://devmonster.tistory.com/22 [Dev.Monster]

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,