2015년 7월 6일 월요일

제3장)바둑판에서 바둑돌 저장하기

 영상에서 바둑판도 입력받고 바둑알도 입력받을 수 있으니 이제 그 바둑돌의 좌표를 실제 2차원배열에 저장하면 영상처리의 일은 거의 끝났다고 봐도 과분하다.




















 나는 여기서 바둑돌의 좌표를 구할때 단순히 바둑판의 가로와 세로만을 계산하여 좌표를 구하면 될줄 알았지만 직접 해보니 오차가 어마어마하게 나타났다.
(실제로 19X19바둑판에서 중앙쪽을 제외한 곳에서 오차가 나타났다.)

그 문제는 영상에서의 바둑판은 실제로 정사각형이 아니기 때문이다.
카메라에 x축과 y축을 정확히 카메라기준으로 평행하게 맞출수가 없어 기울어진 사각형이 된다.
또한 영상에서의 바둑판은 3D -> 2D로 변환되면서 왜곡효과가 발생하여 정사각형이 아닌
휘어진 사각형? 으로 인식된다.

이러한 형태의 사각형을 19X19의 바둑판형태로 나타내기위해 약간의 수학적인 풀이를 하면 각각의 좌표를 얻을 수 있다.
설명하기쉽게 꼭지점 A,B,C,D의 좌표를 각각 x, y를 붙여 나타내겠다.










즉, 각각의 x,y좌표들 둘다 x,y축에 영향을 받는것을 알 수 있다.
이것을 이용하여 코드화를 하면 다음과 같다.
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
    #include "Header.h"using namespace cv;
int main(int argc, char** argv) {
    CvCapture *capture;
    CvMemStorage *storage = cvCreateMemStorage(0);
    CvMemStorage *storage_circle = cvCreateMemStorage(0);
    CvSeq *contour = 0;
    CvSeq *result = 0;
    CvSeq *Circle = 0;
    CvPoint *pt1; CvPoint *pt2; CvPoint *pt3; CvPoint *pt4;
    CvFont font;
    cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX, 0.60.6);    //FONT 설정
    IplImage* frame = 0;        //기본 윈도우 frame 설정
    IplImage* Output = 0;        //이진화 
    IplImage* Output_inverse = 0;        //역이진화
    IplImage* gray = 0;            //grayscale된후의 결과
    IplImage* Result = 0;            //추출된 값
    CvScalar value;    //픽셀값 직접접근
    
    int threshold = 200, count = 0 ,tmp, Doll_count = 0;    
    int x1 =0 , x2 = 0, x3 = 0, x4 = 0, y1= 0 , y2= 0, y3 = 0, y4 = 0;
    int rx1 = 0, rx2 = 0, rx3 = 0, rx4 = 0, ry1 = 0, ry2 = 0, ry3 = 0, ry4 = 0;
    int BlackDollX = 1, BlackDollY = 1, i, j, a = 0, b = 0, x, y;
    int board_arr[board_line][board_line * 2= { 0 },
        *Distinguish = (int*)calloc(3 * 40,sizeof(int));    //바둑판 배열 선언, 바둑돌가리기용 배열(x,y,r)설정 - 바둑알40개까지 
    double CircleX, CircleY,ArrCircleX[board_line][board_line*2],ArrCircleY[board_line][board_line*2],theta,garmma,alpha,beta;
    float *circle;
    char xy1[25],xy2[25],xy3[25],xy4[25];
    double len12, len23, len34, len41;
    cvNamedWindow("binary"0);    //이진화 윈도우 설정
    cvNamedWindow("binary2"0);    //역이진화 윈도우 설정
    cvNamedWindow("Black Tracking"0);    // 트래킹 윈도우 설정
    cvNamedWindow("Gray Scale"0);    //Gray Scale 윈도우 설정
    cvNamedWindow("Result"0);        //추출 윈도우 설정
    cvCreateTrackbar("T""binary", &threshold, 255, NULL);    //window 'Output' 의 트랙바 설정
    if (argc == 1) {
        capture = cvCreateCameraCapture(1);    //카메라가 한대있으면 0(anycamera) 여러대있을경우 ++1을 해준다.
    }
    assert(capture != NULL);    //assert란 주어진 조건이 거짓이면 오류메시지와 코어덤프를 출력하고 종료한다
    // 코어 덤프란 실행중인 프로그램에 대한 정보를 core라는 이름으로 파일로저장하는것
    while (1) {
        frame = cvQueryFrame(capture);    //cvGrabFrame() , cvretrieveFrame() 함수들을 결합해놓은 함수
        //        Colortracking(frame,font);            //ColorTracking - Black
        if (cvWaitKey(10>= 0break;
        if (!frame) break;
        if (!Output){
            gray = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 1);
            Output = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 1);
            Output_inverse = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 1);
            Result = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 1);
        }
        cvZero(Result);                //결과값의 Image data 0 => Black 화면 설정
        
        cvCvtColor(frame, gray, CV_RGB2GRAY);    //RGB색을 GrayScale 변환
        for (tmp = 0; tmp < count/3; tmp++){
            if (Distinguish[tmp*3> 0 && Distinguish[tmp*3+1]> 0 && Distinguish[tmp*3+2> 0){
                for (y = Distinguish[tmp*3+1- Distinguish[tmp*3+2]; y < Distinguish[tmp*3+1+ Distinguish[tmp*3+2]; y++){
                    for (x = Distinguish[tmp*3- Distinguish[tmp*3+2]; x < Distinguish[tmp*3+ Distinguish[tmp*3+2]; x++){
                        cvSet2D(gray, y, x, cvScalar(200200200));
                    }
                }
            }
        }
        cvThreshold(gray, Output, threshold, 255, CV_THRESH_BINARY);    //(입력값,결과값,임계값,임계값 최대치,이진화방식) 
        cvThreshold(gray, Output_inverse, threshold, 255, CV_THRESH_BINARY_INV);    //(입력값,결과값,임계값,임계값 최대치,이진화방식) 
        cvCanny(Output_inverse, Result, 252003);        //원본,결과,low Threshold,high Threshold , 필터크기
        cvFindContours(Result, storage, &contour, sizeof(CvContour), 12, cvPoint(00));
        
            while (contour){    //바둑판 좌표 구하기
                result = cvApproxPoly(contour, sizeof(CvContour), storage, CV_POLY_APPROX_DP, cvContourPerimeter(contour)*0.020);
                //윤곽선 근사화                        
                if (result->total == 4 && fabs(cvContourArea(result, CV_WHOLE_SEQ)) > 10000 && cvCheckContourConvexity(result)){    
                                                    //경계선을 포함한 영역 넓이를 계산        //경계선의 오목 볼록한지 확인한다.
                    pt1 = (CvPoint*)cvGetSeqElem(result, 0);
                    pt2 = (CvPoint*)cvGetSeqElem(result, 1);
                    pt3 = (CvPoint*)cvGetSeqElem(result, 2);
                    pt4 = (CvPoint*)cvGetSeqElem(result, 3);
                    x1 = pt1->x; x2 = pt2->x; x3 = pt3->x; x4 = pt4->x; y1 = pt1->y; y2 = pt2->y; y3 = pt3->y; y4 = pt4->y;
                    cvCircle(frame, cvPoint(x1, y1), 3, CV_RGB(25500), 380);
                    cvCircle(frame, cvPoint(x2, y2), 3, CV_RGB(25500), 380);
                    cvCircle(frame, cvPoint(x3, y3), 3, CV_RGB(25500), 380);
                    cvCircle(frame, cvPoint(x4, y4), 3, CV_RGB(25500), 380);
                    cvLine(frame, cvPoint(x1, y1), cvPoint(x2, y2), CV_RGB(00255), 280);
                    cvLine(frame, cvPoint(x2, y2), cvPoint(x3, y3), CV_RGB(00255), 280);
                    cvLine(frame, cvPoint(x3, y3), cvPoint(x4, y4), CV_RGB(00255), 280);
                    cvLine(frame, cvPoint(x4, y4), cvPoint(x1, y1), CV_RGB(00255), 280);
                
                        rx1 = x1 - x1; rx2 = (x2 - x1); rx3 = x3 - x2; rx4 = x4 - x1;
                        ry1 = y1 - y1; ry2 = y2 - y1; ry3 = y3 - y1; ry4 = y4 - y1;
                        len12 = sqrt(pow(rx1 - rx2, 2+ pow(ry1 - ry2, 2));
                        len23 = sqrt(pow(rx2 - rx3, 2+ pow(ry2 - ry3, 2));
                        len34 = sqrt(pow(rx3 - rx4, 2+ pow(ry3 - ry4, 2));
                        len41 = sqrt(pow(rx4 - rx1, 2+ pow(ry4 - ry1, 2));
                }
                contour = contour->h_next;
            }
            
            cvSmooth(gray, gray, CV_GAUSSIAN, 55);    //스무딩 
            Circle = cvHoughCircles(gray, storage_circle, CV_HOUGH_GRADIENT, 11020025415);    //스무딩하게만든 grayscale영상에서 원을 찾음
            //원본 이미지, storage, method(CV_HOUGH_GRADIENT) ,dp(해상도=1),인접한두원의 최소거리,임계값,임계값,최소반지름,최대반지름
        
            for (int k = 0; k < Circle->total; k++){
                circle = (float*)cvGetSeqElem(Circle, k);
                value = cvGet2D(frame, cvRound(circle[1]), cvRound(circle[0]));        //(원본,y,x)
                if (value.val[0< 100 && value.val[1< 100 && value.val[2< 100 && rx4 > 0){        //흑돌색출
                    cvCircle(frame, cvPoint(cvRound(circle[0]), cvRound(circle[1])), cvRound(circle[2]), CV_RGB(k * 30, k * 60, k * 60), 380);
                    
                    CircleX = (double)(cvRound(circle[0]) - x1);        
                    CircleY = (double)(cvRound(circle[1]) - y1);
                    garmma = acos(abs(rx2) / len12);
                    theta = acos(abs(rx4) / len41);
                    alpha = acos(abs(rx3 - rx2) / len23);
                    beta = acos(abs(rx4 - rx3) / len34);
                    for (j = 0; j < board_line; j++){
                        for (i = 0; i < board_line * 2; i += 2){
                            if (i > 17 * 2 && j > 17){
                                ArrCircleX[j][i] = rx3 - len23 * (board_line * 2 - (i + 2)) * cos(alpha) / (board_line * 2)
                                    + len34 * cos(beta)*(board_line - (j + 1)) / board_line - i / 2;
                                ArrCircleY[j][i] = ry3 - len23 * (board_line * 2 - (i + 2)) * sin(alpha) / (board_line * 2)
                                    - len34 * sin(beta)*(board_line - (j + 1)) / board_line - j;                
                            }
                            else{
                                ArrCircleX[j][i] = rx1 - len12 * j * cos(garmma) / board_line + len41 * i * cos(theta) / (board_line * 2+ i / 2;
                                ArrCircleY[j][i] = ry1 + len12 * j * sin(garmma) / board_line + len41 * i * sin(theta) / (board_line * 2+ j;
                            }
                            if ((abs(ArrCircleX[j][i] - CircleX) < 11) && (abs(ArrCircleY[j][i] - CircleY) < 11)){    //오차범위 <11>이내에있을때 (좌표찾기성공)
                                count += 3;
                                BlackDollX = i;
                                BlackDollY = j;
                                Distinguish[count - 3= cvRound(circle[0]);
                                Distinguish[count - 2= cvRound(circle[1]);
                                Distinguish[count - 1= cvRound(circle[2]);    //돌의 x,y,r 실제 콘솔좌표 대입
                                if (BlackDollX - a != 0 || BlackDollY - b != 0){
                                    system("cls");
                                    draw_board(board_arr, BlackDollX, BlackDollY);
                                }
                            }
                        }
                    }
                    
                }
                
            }
            
            a = BlackDollX; b = BlackDollY;
            sprintf(xy1, "%d,%d/1", rx1, ry1);
            cvPutText(frame, xy1, cvPoint(x1, y1), &font, CV_RGB(25500));
            sprintf(xy2, "%d,%d/2", rx2, ry2);
            cvPutText(frame, xy2, cvPoint(x2, y2), &font, CV_RGB(25500));
            sprintf(xy3, "%d,%d/3", rx3, ry3);
            cvPutText(frame, xy3, cvPoint(x3, y3), &font, CV_RGB(25500));
            sprintf(xy4, "%d,%d/4", rx4, ry4);
            cvPutText(frame, xy4, cvPoint(x4, y4), &font, CV_RGB(25500));
            cvShowImage("binary", Output);
            cvShowImage("binary2", Output_inverse);
            cvShowImage("Black Tracking", frame);
            cvShowImage("Gray Scale", gray);
            cvShowImage("Result", Result);
        }
        free(Distinguish);
        cvReleaseImage(&gray);
        cvReleaseImage(&Output);
        cvReleaseImage(&Output_inverse);
        cvReleaseImage(&Result);
        cvReleaseCapture(&capture);
        cvDestroyWindow("binary");    //window 이름을 이용하여 window 를 제거한다.
        cvDestroyWindow("binary2");
        cvDestroyWindow("Black Tracking");
        cvDestroyWindow("Gray Scale");
        cvDestroyWindow("Result");
        return 0;
}
cs



콘솔창에서 바둑판에서 바둑알이 한개만 있는 이유는 가장 최근에 놓은 돌만 인식하기때문이다.
그래도 2차원배열에는 놓은 바둑알들이 다 저장되어있다.
이제 인공지능 알고리즘을 넣고 블루투스로 mcu에 좌표data만 보내주면 70%는 된것이다. :)

댓글 없음:

댓글 쓰기