Skip to content

kCW-tb/number_recognization

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 

Repository files navigation

number_recognization

이미지 전처리 관련 함수

//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;
}

  1. 이미지 전처리 단계로 GrayScale 및 이진화를 진행해준다
  2. 모폴로지 연산을 통해 미약한 거리를 연결시킨다
  3. 숫자 객체의 영상을 가져오지만 객체가 여러개인 경우 모폴로지 연산을 반복하여 연결시킨 뒤 가져온다.
  4. 크기를 숫자의 일반적 크기의 비율로 조정한다.

작동 영상

Run이 작동되는 원리

const int rows = 10;
const int cols = 3;
int identify_number[rows][cols] = { 0 };

해당 코드는 rows가 숫자를 나타내는 클래스로 0~9까지의 숫자를 뜻하며 cols는 특징으로 늘이거나 줄임이 가능함. 1번 특성이 각 숫자에 대해 적합하다면 해당 배열값으로 1을 부여하고 해당 특성이 나타날 수 없는 숫자에 대해서는 -1를 해주어 우선 순위에서 제거 이후 각 열에 대해 해당 숫자를 합하여 최대 값을 가지는 숫자로 출력

image

Run에 사용되는 함수들

//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로 만든 이후 남는 영역에 대해서 나오는 객체의 개수이다.

//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데이터가 들어갔을 때 얼마나 해당 영역에 들어가는지 영역을 넘어가는 픽셀이 존재하는지 확인하는 작업을 표현.

image image

학습 데이터로 사용한 숫자 데이터들 중 일부

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);
}

마우스가 이동 혹은 이벤트 발생 시 작동하는 함수이며 해당 영역 내에 들어갔을 때 이미지를 변환시키거나 각 기능을 실행시킨다.

최종 결과 영상

최종 작동 영상

특징 출력과 결과 영상

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages