이미지 전처리 관련 함수
Lines 112 to 147 in 7e91ea7
//image preporcessing related functions | |
//1. GrayScale + Threshold | |
Mat grayThres(Mat numberimg) { | |
cvtColor(numberimg, numberimg, COLOR_BGR2GRAY); | |
threshold(numberimg, numberimg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); | |
return numberimg; | |
} | |
//2. Morphology | |
//3. Get number area | |
Mat get_numberArea(Mat preImg) { | |
Mat labels, stats, centroids; | |
int cnt = connectedComponentsWithStats(preImg, labels, stats, centroids); | |
if (cnt > 2) { | |
int largth = 7; | |
while (true) { | |
morphologyEx(preImg, preImg, MORPH_CLOSE, Mat(largth, largth, CV_8UC1)); | |
cnt = connectedComponentsWithStats(preImg, labels, stats, centroids); | |
if (cnt <= 2) break; | |
largth += 3; | |
} | |
} | |
int* p = stats.ptr<int>(1); | |
return preImg(Rect(p[0], p[1], p[2], p[3])).clone(); | |
} | |
//4. Resize to standard size | |
void sizerepair(Mat& preImg) { | |
resize(preImg, preImg, Size(250, 500)); | |
} | |
//Preporcessing function | |
Mat PretreatmentImg(Mat origin_numberimg) { | |
Mat preImg = grayThres(origin_numberimg); | |
morphologyEx(preImg, preImg, MORPH_CLOSE, Mat(10, 10, CV_8UC1)); | |
Mat numberImg = get_numberArea(preImg); | |
sizerepair(numberImg); | |
return numberImg; | |
} |
- 이미지 전처리 단계로 GrayScale 및 이진화를 진행해준다
- 모폴로지 연산을 통해 미약한 거리를 연결시킨다
- 숫자 객체의 영상을 가져오지만 객체가 여러개인 경우 모폴로지 연산을 반복하여 연결시킨 뒤 가져온다.
- 크기를 숫자의 일반적 크기의 비율로 조정한다.
Run이 작동되는 원리
const int rows = 10;
const int cols = 3;
int identify_number[rows][cols] = { 0 };
해당 코드는 rows가 숫자를 나타내는 클래스로 0~9까지의 숫자를 뜻하며 cols는 특징으로 늘이거나 줄임이 가능함. 1번 특성이 각 숫자에 대해 적합하다면 해당 배열값으로 1을 부여하고 해당 특성이 나타날 수 없는 숫자에 대해서는 -1를 해주어 우선 순위에서 제거 이후 각 열에 대해 해당 숫자를 합하여 최대 값을 가지는 숫자로 출력
Run에 사용되는 함수들
Lines 149 to 223 in 7e91ea7
//feature related functions | |
//1.Contours size | |
void contours_size(Mat img, bool main) { | |
vector<vector<Point>> contours; | |
findContours(img, contours, RETR_LIST, CHAIN_APPROX_NONE); | |
if(main == false) cout << "해당 숫자 객체에 대한 외각선 개수 : " << contours.size() << endl << endl; | |
if (contours.size() == 1) { | |
identify_number[1][0] = 1; | |
identify_number[2][0] = 1; | |
identify_number[3][0] = 1; | |
identify_number[4][0] = 1; | |
identify_number[5][0] = 1; | |
identify_number[7][0] = 1; | |
identify_number[6][0] = -1; | |
identify_number[9][0] = -1; | |
identify_number[8][0] = -1; | |
identify_number[0][0] = -1; | |
} | |
else if (contours.size() == 2) { | |
identify_number[4][0] = 1; | |
identify_number[6][0] = 1; | |
identify_number[9][0] = 1; | |
identify_number[0][0] = 1; | |
identify_number[7][0] = -1; | |
identify_number[1][0] = -1; | |
} | |
else { | |
identify_number[8][0] = 1; | |
identify_number[1][0] = -1; | |
identify_number[7][0] = -1; | |
} | |
} | |
//2. Erase Area | |
void erase_rArea(Mat img, bool main) { | |
Mat erase_area = img.clone(), erase_area_left = img.clone(), erase_area_right = img.clone(), lables; | |
erase_area_left(Rect(100, 0, 150, 500)) = Scalar(0, 0, 0); | |
erase_area(Rect(150, 0, 100, 500)) = Scalar(0, 0, 0); | |
erase_area_right(Rect(200, 0, 50, 500)) = Scalar(0, 0, 0); | |
int count = connectedComponents(erase_area, lables); | |
int count_l = connectedComponents(erase_area_left, lables); | |
int count_r = connectedComponents(erase_area_right, lables); | |
count -= 1; | |
count_l -= 1; | |
if (count == 1 && count_l == 1) { | |
if (main == false) cout << "우측 영역 제거시 생기는 외각선 개수 1개" << endl << endl; | |
identify_number[1][1] = 1; | |
identify_number[4][1] = 1; | |
identify_number[6][1] = 1; | |
identify_number[7][1] = 1; | |
identify_number[8][1] = 1; | |
identify_number[9][1] = 1; | |
identify_number[0][1] = 1; | |
identify_number[3][1] = -1; | |
identify_number[5][1] = -1; | |
} | |
else if (count_l == 2) { | |
if (main == false) cout << "우측 영역 제거시 생기는 외각선 개수 2개" << endl << endl; | |
identify_number[2][1] = 1; | |
identify_number[1][1] = 1; | |
identify_number[4][1] = 1; | |
identify_number[5][1] = 1; | |
if(identify_number[7][1] == 1 && count_r == 2) identify_number[7][1] = 1; | |
identify_number[8][1] = 1; | |
identify_number[6][1] = -1; | |
identify_number[9][1] = -1; | |
} | |
else if (count == 3) { | |
if (main == false) cout << "우측 영역 제거시 생기는 외각선 개수 3개" << endl << endl; | |
identify_number[3][1] = 1; | |
identify_number[2][1] = -1; | |
identify_number[4][1] = -1; | |
identify_number[5][1] = -1; | |
identify_number[8][1] = -1; | |
} | |
} |
1번 특징은 이미지 자체에 있어 외각선의 개수이다.
2번 특징은 우측 영역의 픽셀값을 0로 만든 이후 남는 영역에 대해서 나오는 객체의 개수이다.
Lines 283 to 420 in 7e91ea7
//Preporcessing learning related functions | |
Mat prepairImg_toDNN(Mat img) { | |
cvtColor(img, img, COLOR_BGR2GRAY); | |
threshold(img, img, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); | |
Mat labels, stats, centorids; | |
Rect all_img; | |
int cnt = connectedComponentsWithStats(img, labels, stats, centorids); | |
int* p = stats.ptr<int>(1); | |
if (cnt == 2) { | |
Mat tmp = img(Rect(p[0], p[1], p[2], p[3])); | |
if (p[2] < 40) { | |
Mat tmp_small(400, 300, CV_8UC1, Scalar(0, 0, 0)); | |
line(tmp_small, Point(150, 0), Point(150, 399), Scalar(255, 255, 255), 5); | |
return tmp_small; | |
} | |
resize(tmp, tmp, Size(300, 400)); | |
return tmp; | |
} | |
else { | |
int min_x, min_y, max_x, max_y; | |
int* p = stats.ptr<int>(1); | |
min_x = p[0]; min_y = p[1]; max_x = p[0] + p[2]; max_y = p[1] + p[3]; | |
for (int i = 2; i < cnt; i++) { | |
int* p = stats.ptr<int>(i); | |
min_x = min(min_x, p[0]); | |
min_y = min(min_y, p[1]); | |
max_x = max(max_x, p[0] + p[2]); | |
max_y = max(max_y, p[1] + p[3]); | |
} | |
if ((max_y - min_y) / (double)(max_x - min_x) > 2.5) { | |
min_x = max(min_x - 75, 0); max_x = min(max_x + 75, 300); | |
} | |
all_img = Rect(Point(min_x, min_y), Point(max_x, max_y)); | |
} | |
Mat tmp = img(all_img); | |
resize(tmp, tmp, Size(300, 400)); | |
return tmp; | |
} | |
void studyNumberData() { | |
for (int i = 0; i < 13; i++) { | |
string fileName = "./all_num_data/"; | |
if (i < 10) { | |
fileName += "0"; | |
fileName += to_string(i); | |
} | |
else fileName += to_string(i); | |
for (int j = 1; j <= 60; j++) { | |
string number_name = ""; | |
if (j < 10) number_name += ("-0" + to_string(j)); | |
else number_name += ("-" + to_string(j)); | |
number_name += ".png"; | |
string final_path = fileName + number_name; | |
Mat img = imread(final_path); | |
if (img.empty()) { cout << "image is empty pls check filename : " << final_path; } | |
Mat preImg = prepairImg_toDNN(img); | |
plus_learning_number(preImg, i); | |
} | |
} | |
learning_number[2](Rect(180, 40, 60, 200)) = 40; | |
learning_number[2](Rect(180, 320, 30, 40)) = 0; | |
for (int i = 0; i < 13; i++) { | |
threshold(learning_number[i], learning_number[i], 30, 255, THRESH_BINARY); | |
} | |
} | |
void plus_learning_number(Mat preImg, int i) { | |
int row = 40; | |
int col = 30; | |
Mat labels; | |
for (int x = 0; x < 10; x++) { | |
for (int y = 0; y < 10; y++) { | |
Rect rec(x * col, y * row, col, row); | |
int cnt = connectedComponents(preImg(rec), labels); | |
if (cnt >= 2) learning_number[i](rec) += 1; | |
} | |
} | |
} | |
void test_number_DNN(Mat img, bool main) { | |
int identify_number_D[13] = { 0, }; | |
int index_num[2] = { 0,0 }; | |
int row = 40; | |
int col = 30; | |
int count = 0; | |
Mat labels, preImg; | |
preImg = prepairImg_toDNN(img); | |
for (int x = 0; x < 10; x++) { | |
for (int y = 0; y < 10; y++) { | |
Rect rec(x * col, y * row, col, row); | |
int cnt = connectedComponents(preImg(rec), labels); | |
for (int k = 0; k < 13; k++) { | |
int ex_cnt = connectedComponents(learning_number[k](rec), labels); | |
if (ex_cnt >= 2 && cnt >= 2) identify_number_D[k] += 2; | |
else if (ex_cnt >= 2 && cnt < 2) identify_number_D[k] -= 1; | |
else if (ex_cnt < 2 && cnt >= 2) identify_number_D[k] -= 2; | |
} | |
} | |
} | |
int largest_index = -1; | |
int second_largest_index = -1; | |
int largest = INT_MIN; | |
int second_largest = INT_MIN; | |
for (int i = 0; i < 13; i++) { | |
if (identify_number_D[i] > largest) { | |
second_largest = largest; | |
second_largest_index = largest_index; | |
largest = identify_number_D[i]; | |
largest_index = i; | |
} | |
else if (identify_number_D[i] > second_largest) { | |
second_largest = identify_number_D[i]; | |
second_largest_index = i; | |
} | |
} | |
index_num[0] = largest_index; | |
index_num[1] = second_largest_index; | |
if (main == false) { | |
for (int i = 0; i < 13; i++) { | |
cout << i << " 숫자에 대한 점수 : " << identify_number_D[i] << endl; | |
} | |
cout << "가장 확률이 가까운 숫자 인덱스 2개 high : " << index_num[0] << ", sec high : " << index_num[1] << endl << endl; | |
} | |
if (index_num[0] == 10) index_num[0] = 4; | |
else if (index_num[1] == 10) index_num[1] = 4; | |
if (index_num[0] == 11) index_num[0] = 1; | |
else if (index_num[1] == 11) index_num[1] = 1; | |
if (index_num[0] == 12) index_num[0] = 2; | |
else if (index_num[1] == 12) index_num[1] = 2; | |
if (index_num[0] - index_num[1] < 5) { | |
identify_number[index_num[0]][2] = 1; | |
} | |
else { | |
identify_number[index_num[0]][2] = 1; | |
identify_number[index_num[1]][2] = 1; | |
} | |
} |
3번 특징으로 여러개의 이미지를 학습시키고 이에 대한 값을 저장한 이후 새로운 데이터가 들어오면 이전에 학습되어 있는 데이터를 기반으로 새로운 데이터의 숫자에 대해 추측한다. 이미지가 많아지고 영역을 세밀하게 조정할수록 정확도를 올릴 수 있다. 현 프로젝트는 숫자당 60개 총 600개의 숫자 이미지를 학습 데이터로 사용하였다. 4와 같은 숫자의 경우 내부 외각선이 존재하는 필기체와 없는 필기체가 있는데 4-1, 4-2처럼 따로 학습시켜 높은 정확도를 가지도록 몇가지 필기체를 추가하였다.
좌측이 여러개의 2의 숫자에 대해 데이터를 축척하여 2의 영역을 크게 잡아둔 영역을 표현하였고 우측은 test데이터가 들어갔을 때 얼마나 해당 영역에 들어가는지 영역을 넘어가는 픽셀이 존재하는지 확인하는 작업을 표현.
학습 데이터로 사용한 숫자 데이터들 중 일부
Lines 422 to 523 in 7e91ea7
void on_mouse(int event, int x, int y, int flags, void* userdata) { | |
static Point prePoint = Point(0, 0); | |
Rect save(500, 0, 148, 100), load(500, 100, 148, 100), clear(500, 200, 148, 100), run(500, 300, 148, 100), exit_f(500, 400, 148, 100); | |
Rect feature1(650, 0, 148, 100), feature2(650, 100, 148, 100), feature3(650, 200, 148, 100); | |
if (Point(x, y).inside(save)) { | |
turn_menu_color(*(Mat*)userdata); | |
copyimg(*(Mat*)userdata, save, save_red); | |
} | |
else if (Point(x, y).inside(load)) { | |
turn_menu_color(*(Mat*)userdata); | |
copyimg(*(Mat*)userdata, load, load_red); | |
} | |
else if (Point(x, y).inside(clear)) { | |
turn_menu_color(*(Mat*)userdata); | |
copyimg(*(Mat*)userdata, clear, clear_red); | |
} | |
else if (Point(x, y).inside(run)) { | |
turn_menu_color(*(Mat*)userdata); | |
copyimg(*(Mat*)userdata, run, run_red); | |
} | |
else if (Point(x, y).inside(exit_f)) { | |
turn_menu_color(*(Mat*)userdata); | |
copyimg(*(Mat*)userdata, exit_f, exit_red); | |
} | |
else if (Point(x, y).inside(feature1)) { | |
turn_menu_color(*(Mat*)userdata); | |
copyimg(*(Mat*)userdata, feature1, feature1_red); | |
} | |
else if (Point(x, y).inside(feature2)) { | |
turn_menu_color(*(Mat*)userdata); | |
copyimg(*(Mat*)userdata, feature2, feature2_red); | |
} | |
else if (Point(x, y).inside(feature3)) { | |
turn_menu_color(*(Mat*)userdata); | |
copyimg(*(Mat*)userdata, feature3, feature3_red); | |
} | |
else { | |
turn_menu_color(*(Mat*)userdata); | |
} | |
switch (event) { | |
case EVENT_LBUTTONDOWN: | |
prePoint = Point(x, y); | |
if (Point(x, y).inside(Rect(500, 300, 150, 100))) { | |
//Run | |
Mat preimg = PretreatmentImg((*(Mat*)userdata)(Rect(2, 2, 497, 497)).clone()); | |
contours_size(preimg, true); | |
erase_rArea(preimg, true); | |
test_number_DNN((*(Mat*)userdata)(Rect(2, 2, 497, 497)), true); | |
int identify_number = result_number(true); | |
cout << endl << "해당 숫자는 '" << identify_number << "' 입니다." << endl; | |
} | |
else if (Point(x, y).inside(Rect(500, 0, 150, 100))) { | |
//Save | |
savefile(*(Mat*)userdata); | |
} | |
else if (Point(x, y).inside(Rect(500, 100, 150, 100))) { | |
//Load | |
loadfile().copyTo((*(Mat*)userdata)(Rect(0, 0, 500, 500))); | |
} | |
else if (Point(x, y).inside(Rect(500, 200, 150, 100))) { | |
//Clear | |
(*(Mat*)userdata)(Rect(1, 1, 498, 498)) = Scalar(255, 255, 255); | |
} | |
else if (Point(x, y).inside(Rect(500, 400, 150, 100))) { | |
//Exit | |
exit(1); | |
} | |
else if (Point(x, y).inside(Rect(feature1))) { | |
//Contours Size | |
Mat preimg = PretreatmentImg((*(Mat*)userdata)(Rect(2, 2, 497, 497)).clone()); | |
contours_size(preimg, false); | |
} | |
else if (Point(x, y).inside(Rect(feature2))) { | |
//Erase Area | |
Mat preimg = PretreatmentImg((*(Mat*)userdata)(Rect(2, 2, 497, 497)).clone()); | |
erase_rArea(preimg, false); | |
} | |
else if (Point(x, y).inside(Rect(feature3))) { | |
//Similarity | |
test_number_DNN((*(Mat*)userdata)(Rect(2, 2, 497, 497)), false); | |
} | |
break; | |
case EVENT_MOUSEMOVE: | |
if (!Rect(1, 1, 498, 498).contains(Point(x, y))) break; | |
if (flags == EVENT_FLAG_LBUTTON) { | |
line(*(Mat*)userdata, prePoint, Point(x, y), Scalar(0, 0, 0), 3); | |
prePoint = Point(x, y); | |
} | |
else if (flags == EVENT_FLAG_CTRLKEY) { | |
line(*(Mat*)userdata, prePoint, Point(x, y), Scalar(255, 255, 255), 10); | |
prePoint = Point(x, y); | |
} | |
break; | |
case EVENT_LBUTTONUP: | |
prePoint = Point(x, y); | |
break; | |
default: | |
break; | |
} | |
imshow("NUMBER", *(Mat*)userdata); | |
} |
마우스가 이동 혹은 이벤트 발생 시 작동하는 함수이며 해당 영역 내에 들어갔을 때 이미지를 변환시키거나 각 기능을 실행시킨다.
최종 결과 영상