오늘의 주제에 대해서 설명하기 전에 딥러닝와 영상처리의 차이를 간단히 설명하고 넘어가려 합니다.

 

 

 

위 그림에는 바나나, 수박, 사과, 복숭아가 존재합니다.

 

이중 사과를 인식하기 위해선 어떻게 해야될까요?

 

가장 먼저 생각이 드는건 4가지 과일 중 "사과만이 가지고 있는 특징이 무엇일까?" 라는 겁니다.

 

예를 들어 사과의 색은 빨간색이고 사람의 주먹 정도의 크기이고 윗 부분은 파여있고

 

파여있는 부분에 꽁다리가 붙어있으며 사과 표면을 보면 노란색의 점들이 있는 등의 특징을 말할 수 있죠.

 

이러한 특징들을 이용하여 우리는 이것이 사과라는 것을 맞출 수 있습니다.

 

 

 

영상처리와 딥러닝에선 객체의 특징을 찾는 과정이 조금 다릅니다.

 

영상처리는 이러한 특징들을 찾기 위해 직접 코드로 구현해주어야 합니다.

 

예를 들어 사과를 인식하기 위해 "사과 이미지의 픽셀의 RGB 값의 범위는 어느정도로 범위로 줄 것인가?" 등이 있죠.

 

지금은 다른 것은 생각하지 말고 사과의 크기에 대해서만 생각해봅시다.

 

대부분의 사과는 동그랗기 때문에 일정 크기의 원의 범위를 이용하여 사과를 찾을 수 있습니다.

 

하지만 이 방법은 사과의 크기를 고정해야만 찾을 수 있죠.

 

다시말해 사과의 크기가 달라짐에 따라 위 방법을 이용하여 사과를 찾을 수 없게 됩니다.

 

 

 

딥러닝은 이러한 특징 찾기 위해 직접 코드로 구현하지 않아도 됩니다.

 

대체적으로 딥러닝을 사용하는 과정엔 훈련과 검출 및 인식으로 나뉘어져 있습니다.

 

가장 먼저 인식하고자 하는 이미지를 수집하고 이를 CNN(Convolution Nueral Network)에 입력시켜

 

Weight를 추출합니다. (딥러닝에 대한 자세한 설명은 추후에 글을 작성해 설명하겠습니다.)

 

최종적으로 나온 Weight가 바로 수집한 여러 이미지들 속 사과의 특징이 찾은것입니다.

 

다시말해 인식하고자 하는 객체의 이미지셋을 구축하여 CNN에 입력시키면 컴퓨터가 

 

사과의 특징을 찾아주게 됩니다.

 

 

 

위 내용을 통해 이론적으론 영상처리와 딥러닝의 차이를 간단하게 이해하셨을겁니다.

 

더 확실한 이해를 위해 영상처리와 딥러닝에 대해 각각의 방법으로 공통의 객체(숫자)를 인식해보도록 하겠습니다.

 

오늘은 영상처리를 이용한 객체 인식 방법에 대해 설명하고 딥러닝을 이용한 객체 인식 방법은

 

추후에 정리해서 글을 올리도록 하겠습니다.

 

 

 

 

 

 

 

이제 오늘의 주제인 히스토그램을 이용한 숫자 인식을 진행해보도록 하겠습니다.

 

그 전에 먼저 히스토그램의 개념을 알아야합니다.

 

히스토그램은 표로 되어있는 도수 분포를 막대선 그래프로 나타낸 것입니다.

 

 

           입력 영상           
히스토그램

( 히스토그램의 y축은 개수, x축은 숫자를 의미합니다.)

 

 

위 그림의 입력영상 표에 적힌 숫자의 갯수를 나타낸 것이 바로 히스토그램입니다.

 

다시 말하면 입력 영상의 픽셀 값의 분포를 나타낸 것이죠.

 

영상의 히스토그램을 쉽게 그릴 수 있도록 Opencv 라이브러리에서 함수를 제공하고 있으니 

찾아보시면 됩니다.

 

 

 

 

 

 

 

 

 

이제 본격적으로 숫자를 인식해봅시다.

 

전체적인 과정은 입력 이미지 -> GrayScale -> Binarization -> Historgram -> Histrogram comparison 입니다.

 

 

 

이미지는 대게 3개의 채널(3바이트)인 RGB로 표현됩니다.

 

GrayScale이란 한 이미지의 RGB인 3개의 채널의 각 픽셀값을 합쳐 3으로 나눠준 값이며,

 

8비트로 표현되는 회색 이미지입니다.

 

그렇다면 "우리가 이미지의 RGB 채널에 해당되는 픽셀에 접근해서 처리를 해야 되는거냐?" 라고 생각하실 수 있습니다.

 

네 맞습니다.. 하지만 Intel에서 개발한 영상처리에 특화된 라이브러리인 Opencv를 사용하면 쉽게 처리할 수 있습니다.

 

Opencv에선 RGB를 Gray로 변환해주는 함수가 존재하여서 함수와 매개변수를 조정하여 쉽게 변환할 수 있습니다.

 

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
	Mat img;
	Mat gray;

	//이미지 입력
	img = imread("C:\\숫자 인식\\7.jpg");

	// Grayscale
	cvtColor(img, gray, CV_RGB2GRAY);

	imshow("Original_img", img);
	imshow("Grayscale", gray);

	waitKey(0);
}

입력 이미지
출력 이미지

 

 

다음은 이진화(Binarization)입니다.

 

이진화는 영상의 픽셀을 0과 255인 흑과 백으로 표현하는 겁니다.

 

이 또한 Opencv에서 함수를 제공하고 있습니다.

 

이진화 함수의 매개변수에서 이진화를 시키기 위해 임계값을 설정해줘야 합니다.

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
	Mat img;
	Mat gray;
	Mat bi;

	//이미지 입력
	img = imread("C:\\숫자 인식\\7.jpg");

	// Grayscale
	cvtColor(img, gray, CV_RGB2GRAY);

	//Binarization
	threshold(gray, bi, 180, 255, THRESH_BINARY);

	imshow("Binarization", bi);

	waitKey(0);
}

 

이진화

 

 

 

 

다음은 이미지 속 숫자의 테두리 밖을 제거시켜줍니다.

 

테두리를 제거할 때 Y축 방향으로 (0,0) -> (0, img.cols)까지의 픽셀 값이 흰색인 개수를 파악합니다.

 

이를 (img.rows, 0) -> (img.rows, img.cols) 까지 반복해서 진행하며,

 

X축 방향일 때도 같게 진행합니다.

 

이렇게 진행하며 흰색 픽셀을 찾았을 경우의 우상단, 우하단, 좌상단, 좌하단의 좌표를 찾고 이미지를 자릅니다.

 

이는 추후에 히스토그램을 이용해 숫자 인식을 할 때 인식률을 향상시키기 위해 

 

어떠한 사진이든 동일한 위치에 두기 위함입니다.

 

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main() {

	Mat img;
	Mat gray;
	Mat bi;
	Mat cut_img;

	int zero_cnt = 0;
	int x1 = 0, x2 = 0;
	int y1 = 0, y2 = 0;

	//이미지 입력
	img = imread("C:\\숫자 인식\\7.jpg");

	// Grayscale
	cvtColor(img, gray, CV_RGB2GRAY);

	//Binarization
	threshold(gray, bi, 180, 255, THRESH_BINARY);

	// 왼쪽 Y축
	for (int x = 0; x < bi.cols; x++)
	{
		for (int y = 0; y < bi.rows; y++)
		{
			if (bi.at<uchar>(y, x) == 255)
				zero_cnt++;
		}

		if (zero_cnt > 10) {
			x1 = x;
			zero_cnt = 0;
			break;
		}
		zero_cnt = 0;
	}

	//오른쪽 Y축
	for (int x = 0; x < bi.cols; x++) {
		for (int y = 0; y < bi.rows; y++) {
			if (bi.at<uchar>(y, (bi.cols - 1) - x) == 255)
				zero_cnt++;
		}

		if (zero_cnt > 0) {
			x2 = (bi.cols - 1) - x;
			zero_cnt = 0;
			break;
		}
		zero_cnt = 0;
	}

	// 위 X축
	for (int y = 0; y < bi.rows; y++) {
		for (int x = 0; x < bi.cols; x++) {
			if (bi.at<uchar>(y, x) == 255)
				zero_cnt++;
		}

		if (zero_cnt > 0) {
			y1 = y;
			zero_cnt = 0;
			break;
		}
		zero_cnt = 0;
	}

	// 아래 X축
	for (int y = 0; y < bi.rows; y++) {
		for (int x = 0; x < bi.cols; x++) {
			if (bi.at<uchar>((bi.rows - 1) - y, x) == 255)
				zero_cnt++;
		}
		if (zero_cnt > 0) {
			y2 = (bi.rows - 1) - y;
			zero_cnt = 0;
			break;
		}
		zero_cnt = 0;
	}

	cut_img = bi(Range(y1, y2), Range(x1, x2));
	resize(cut_img, cut_img, Size(200, 200), 0, 0, INTER_LINEAR);

	imshow("테두리 제거", cut_img);

	waitKey(0);
}

 

테두리 제거

 

 

 

 

다음은 제거된 테두리 이미지를 이용하여 히스토그램을 그려보겠습니다.

 

히스토그램은 Opencv에서 제공하는 함수를 사용하지 않고 위의 테두리를 제거하는 것과 비슷하게

 

X축 또는 Y축을 기준으로 이미지 속 흰색 픽셀의 수를 카운팅하여 선을 그려주는 line() 함수를 이용해 그려주었습니다.

 

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main() {

	Mat img;
	Mat gray;
	Mat bi;
	Mat cut_img;
	Mat y_hist;
	Mat x_hist;
	Mat xy_hist;

	// 히스토그램 변수
	y_hist = Mat::zeros(200, 200, CV_8UC1);
	x_hist = Mat::zeros(200, 200, CV_8UC1);
	xy_hist = Mat::zeros(200, 400, CV_8UC1);
	
	// 이미지 속 x축 y축 라인의 픽셀 값 정보가 담길 변수
	int x_result[200] = { 0, };
	int y_result[200] = { 0, };

	int zero_cnt = 0;
	int x1 = 0, x2 = 0;
	int y1 = 0, y2 = 0;

	// 이미지 입력
	img = imread("C:\\숫자 인식\\7.jpg");

	//Grayscale
	cvtColor(img, gray, CV_RGB2GRAY);

	//Binarization
	threshold(gray, bi, 180, 255, THRESH_BINARY);
	bi = ~bi;



	// 오른쪽 Y축
	for (int x = 0; x < bi.cols; x++){
		for (int y = 0; y < bi.rows; y++){
			if (bi.at<uchar>(y, x) == 255)
				zero_cnt++;
		}

		if (zero_cnt > 0) {
			x1 = x;
			zero_cnt = 0;
			break;
		}
		zero_cnt = 0;
	}

	//왼쪽 Y축
	for (int x = 0; x < bi.cols; x++) {
		for (int y = 0; y < bi.rows; y++) {
			if (bi.at<uchar>(y, (bi.cols - 1) - x) == 255)
				zero_cnt++;
		}

		if (zero_cnt > 0) {
			x2 = (bi.cols - 1) - x;
			zero_cnt = 0;
			break;
		}
		zero_cnt = 0;
	}

	// 위 X축
	for (int y = 0; y < bi.rows; y++) {
		for (int x = 0; x < bi.cols; x++) {
			if (bi.at<uchar>(y, x) == 255)
				zero_cnt++;
		}

		if (zero_cnt > 0) {
			y1 = y;
			zero_cnt = 0;
			break;
		}
		zero_cnt = 0;
	}

	// 아래 X축
	for (int y = 0; y < bi.rows; y++) {
		for (int x = 0; x < bi.cols; x++) {
			if (bi.at<uchar>((bi.rows - 1) - y, x) == 255)
				zero_cnt++;
		}
		if (zero_cnt > 0) {
			y2 = (bi.rows - 1) - y;
			zero_cnt = 0;
			break;
		}
		zero_cnt = 0;
	}
	cut_img = bi(Range(y1, y2), Range(x1, x2));
	resize(cut_img, cut_img, Size(200, 200), 0, 0, INTER_LINEAR);



	//////////////
	// 히스토그램//
	/////////////

	// 세로 축 히스토그램
	for (int x = 0; x < cut_img.rows; x++){
		zero_cnt = 0;
		for (int y = 0; y < cut_img.cols; y++){
			if (cut_img.at<uchar>(y, x) == 255)
				zero_cnt++;
		}
		y_result[x] = zero_cnt;
	}
	for (int x = 0; x < y_hist.cols; x++)
		line(y_hist, Point(x, y_hist.rows - y_result[x]), Point(x, y_hist.rows), Scalar(255, 255, 255), 0);
	


	// 가로 축 히스토그램
	for (int y = 0; y < cut_img.rows; y++){
		zero_cnt = 0;
		for (int x = 0; x < cut_img.cols; x++){
			if (cut_img.at<uchar>(y, x) == 255)
				zero_cnt++;
		}
		x_result[y] = zero_cnt;
	}
	for (int y = 0; y < x_hist.rows; y++)
		line(x_hist, Point(0, y), Point(x_result[y], y), Scalar(255, 255, 255), 0);
	


	// 세로, 가로 축의 통합 히스토그램
	for (int a = 0; a < xy_hist.cols / 2; a++){
		line(xy_hist, Point(a, xy_hist.rows - y_result[a]), Point(a, xy_hist.rows), Scalar(255, 255, 255), 0);
		line(xy_hist, Point(a + 200, xy_hist.rows - x_result[a]), Point(a + 200, xy_hist.rows), Scalar(255, 255, 255), 0);
	}

	imshow("입력 이미지", cut_img);
	imshow("X축 히스토그램", x_hist);
	imshow("Y축 히스토그램", y_hist);
	imshow("통합 히스토그램", xy_hist);

	waitKey(0);
}

 

X, Y축의 히스토그램

 

톻합 히스토그램

 

 

 

이제 거의 다 왔습니다.

 

다음은 히스토그램을 이용해 숫자인식을 하기 위해서 입력데이터와 비교를 할 수 있도록

 

숫자 0~9까지의 표본 데이터가 필요합니다.

 

표본 데이터를 이용하여 위에 진행한대로 통합 히스토그램을 그립니다.

 

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main() {

	Mat img[10];
	Mat cut_img[10];
	Mat y_hist[10];
	Mat x_hist[10];
	Mat xy_hist[10];
	for (int a = 0; a < 10; a++) {
		y_hist[a] = Mat::zeros(200, 200, CV_8UC1);
		x_hist[a] = Mat::zeros(200, 200, CV_8UC1);
		xy_hist[a] = Mat::zeros(200, 400, CV_8UC1);
	}

	string img_path;

	int zero_cnt = 0;
	int x1 = 0, x2 = 0;
	int y1 = 0, y2 = 0;
	int x_result[10][200] = { 0, };
	int y_result[10][200] = { 0, };

	for (int i = 0; i < 10; i++) {

		// 변수 초기화
		zero_cnt = 0;
		x1 = 0, x2 = 0;
		y1 = 0, y2 = 0;

		img_path = format("C:\\숫자 인식\\표본 데이터\\%d.jpg", i);

		img[i] = imread(img_path, IMREAD_GRAYSCALE);

		threshold(img[i], img[i], 127, 255, THRESH_BINARY);
		img[i] = ~img[i];


		// 오른쪽 Y축
		for (int x = 0; x < img[i].cols; x++) {
			for (int y = 0; y < img[i].rows; y++) {
				if (img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				x1 = x;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		//왼쪽 Y축
		for (int x = 0; x < img[i].cols; x++) {
			for (int y = 0; y < img[i].rows; y++) {
				if (img[i].at<uchar>(y, (img[i].cols - 1) - x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				x2 = (img[i].cols - 1) - x;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		// 위 X축
		for (int y = 0; y < img[i].rows; y++) {
			for (int x = 0; x < img[i].cols; x++) {
				if (img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				y1 = y;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		// 아래 X축
		for (int y = 0; y < img[i].rows; y++) {
			for (int x = 0; x < img[i].cols; x++) {
				if (img[i].at<uchar>((img[i].rows - 1) - y, x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				y2 = (img[i].rows - 1) - y;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		cut_img[i] = img[i](Range(y1, y2), Range(x1, x2));
		resize(cut_img[i], cut_img[i], Size(200, 200), 0, 0, INTER_LINEAR);



		/////////////
		// 히스토그램//
		/////////////

		// 세로 축 히스토그램
		for (int x = 0; x < cut_img[i].rows; x++) {
			zero_cnt = 0;
			for (int y = 0; y < cut_img[i].cols; y++) {
				if (cut_img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			y_result[i][x] = zero_cnt;
		}
		for (int x = 0; x < y_hist[i].cols; x++)
			line(y_hist[i], Point(x, y_hist[i].rows - y_result[i][x]), Point(x, y_hist[i].rows), Scalar(255, 255, 255), 0);

		// 가로 축 히스토그램
		for (int y = 0; y < cut_img[i].rows; y++) {
			zero_cnt = 0;
			for (int x = 0; x < cut_img[i].cols; x++) {
				if (cut_img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			x_result[i][y] = zero_cnt;
		}
		for (int y = 0; y < x_hist[i].rows; y++)
			line(x_hist[i], Point(0, y), Point(x_result[i][y], y), Scalar(255, 255, 255), 0);

		// 세로, 가로 축의 total 히스토그램
		for (int a = 0; a < xy_hist[i].cols / 2; a++) {
			line(xy_hist[i], Point(a, xy_hist[i].rows - y_result[i][a]), Point(a, xy_hist[i].rows), Scalar(255, 255, 255), 0);
			line(xy_hist[i], Point(a + 200, xy_hist[i].rows - x_result[i][a]), Point(a + 200, xy_hist[i].rows), Scalar(255, 255, 255), 0);
		}

		String name1;
		name1 = format("%d", i);
		String name2;
		name2 = format("%d의 세로 히스토그램", i);
		String name3;
		name3 = format("%d의 가로 히스토그램", i);
		String name4;
		name4 = format("%d 세로,가로 통합 히스토그램", i);

		imshow(name1, cut_img[i]);
		//imshow(name2, y_hist[i]);
		//imshow(name3, x_hist[i]);
		imshow(name4, xy_hist[i]);

		//printf("%d 이미지의 흰색 픽셀 개수 %d\n", i, y_result);
	}
	waitKey(0);
}

 

표본 데이터

 

0~3까지의 통합 히스토그램

 

4~6까지의 통합 히스토그램

 

7~9까지의 히스토그램

 

 

 

마지막으로 비교하고싶은 숫자를 입력하여 통합 히스토그램을 추출하여

 

표본 숫자의 통합 히스토리와 가장 적게 차이나는 숫자를 찾도록 한다.

 

 

#include <opencv2/opencv.hpp>

#define PLUS 0

using namespace cv;
using namespace std;

int main() {

	// 표본 이미지
	Mat img[10];
	Mat cut_img[10];

	// 테스트 이미지
	Mat cp_img[10];
	Mat cp_cut_img[10];

	Mat y_hist[10];  // 세로 축
	Mat x_hist[10]; // 가로 축
	Mat xy_hist[10]; // 세로+가로
	Mat test_img_hist[10]; // 테스트 이미지 
	Mat dif_hist[10][10]; // 표본과 테스트의 차이

	// 다중 이미지를 위한 경로
	string img_path;
	string test_img_path;

	// 최종 결과
	int result[10][10] = { 0, };
	int clone_result[10][10] = { 0, };

	int zero_cnt = 0; // 흰색 픽셀 카운트 수
	int x1 = 0, x2 = 0;
	int y1 = 0, y2 = 0;
	int test_x[200] = { 0, }; // 카운트 수 결과
	int test_y[200] = { 0, }; // 카운트 수 결과

	int x_result[10][200] = { 0, };
	int y_result[10][200] = { 0, };

	int result_cnt = 0;
	int result_num = 0;

	// 0의 값을 가지고 있는 Mat 선언
	for (int a = 0; a < 10; a++) {
		y_hist[a] = Mat::zeros(200, 200, CV_8UC1);
		x_hist[a] = Mat::zeros(200, 200, CV_8UC1);
		xy_hist[a] = Mat::zeros(200, 400, CV_8UC1);
		test_img_hist[a] = Mat::zeros(200, 400, CV_8UC1);

		for (int b = 0; b < 10; b++)
			dif_hist[a][b] = Mat::zeros(200, 400, CV_8UC1);
	}

	// 표본 이미지
	for (int i = 0; i < 10; i++){

		// 변수 초기화
		zero_cnt = 0;
		x1 = 0, x2 = 0;
		y1 = 0, y2 = 0;

		// 이미지 포맷 변경
		img_path = format("C:\\숫자 인식\\표본 데이터\\%d.jpg", i);

		//  이미지 읽고 그레이 스케일 적용
		img[i] = imread(img_path, IMREAD_GRAYSCALE);

		// 이진화
		threshold(img[i], img[i], 127, 255, THRESH_BINARY);
		img[i] = ~img[i]; // 이미지 반전



		//////////////////////////////////////
		// 숫자 테두리를 따기 위한 잉여 배경을 제거//
		// 흰색 픽셀을 만날 경우를 판단                 //
		/////////////////////////////////////

		// 왼쪽 Y축
		for (int x = 0; x < img[i].cols; x++){
			for (int y = 0; y < img[i].rows; y++)
			{
				if (img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				x1 = x;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		//오 세로
		for (int x = 0; x < img[i].cols; x++) {
			for (int y = 0; y < img[i].rows; y++) {
				if (img[i].at<uchar>(y, (img[i].cols - 1) - x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				x2 = (img[i].cols - 1) - x;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		// 위 가로
		for (int y = 0; y < img[i].rows; y++) {
			for (int x = 0; x < img[i].cols; x++) {
				if (img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				y1 = y;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		// 아래 가로
		for (int y = 0; y < img[i].rows; y++) {
			for (int x = 0; x < img[i].cols; x++) {
				if (img[i].at<uchar>((img[i].rows - 1) - y, x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				y2 = (img[i].rows - 1) - y;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		// 숫자 테두리 영역 이미지 추출
		cut_img[i] = img[i](Range(y1 - PLUS, y2 + PLUS), Range(x1 - PLUS, x2 + PLUS));

		// 추출한 이미지 리사이징
		resize(cut_img[i], cut_img[i], Size(200, 200), 0, 0, INTER_LINEAR);




		////////////////////
		// 히스토그램 그리기//
		///////////////////

		// 세로 축 히스토그램
		for (int x = 0; x < cut_img[i].rows; x++){
			zero_cnt = 0;
			for (int y = 0; y < cut_img[i].cols; y++){
				if (cut_img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			y_result[i][x] = zero_cnt;
		}
		for (int x = 0; x < y_hist[i].cols; x++)
			line(y_hist[i], Point(x, y_hist[i].rows - y_result[i][x]), Point(x, y_hist[i].rows), Scalar(255, 255, 255), 0);
		


		// 가로 축 히스토그램
		for (int y = 0; y < cut_img[i].rows; y++){
			zero_cnt = 0;
			for (int x = 0; x < cut_img[i].cols; x++){
				if (cut_img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			x_result[i][y] = zero_cnt;
		}
		for (int y = 0; y < x_hist[i].rows; y++)
			line(x_hist[i], Point(0, y), Point(x_result[i][y], y), Scalar(255, 255, 255), 0);	

		// 세로, 가로 축의 통합 히스토그램
		for (int a = 0; a < xy_hist[i].cols / 2; a++){
			line(xy_hist[i], Point(a, xy_hist[i].rows - y_result[i][a]), Point(a, xy_hist[i].rows), Scalar(255, 255, 255), 0);
			line(xy_hist[i], Point(a + 200, xy_hist[i].rows - x_result[i][a]), Point(a + 200, xy_hist[i].rows), Scalar(255, 255, 255), 0);
		}
		//printf("%d 이미지의 흰색 픽셀 개수 %d\n", i, y_result);

		String name1;
		name1 = format("%d", i);
		String name2;
		name2 = format("%d의 세로 히스토그램", i);
		String name3;
		name3 = format("%d의 가로 히스토그램", i);
		String name4;
		name4 = format("%d 세로,가로 통합 히스토그램", i);

		//imshow(name1, cut_img[i]);
		//imshow(name2, y_hist[i]);
		//imshow(name3, x_hist[i]);
		//imshow(name4, xy_hist[i]);
	}



	// 테스트 이미지
	for (int i = 0; i < 10; i++){

		// 변수 초기화
		zero_cnt = 0;
		x1 = 0, x2 = 0;
		y1 = 0, y2 = 0;

		// 이미지 포맷 변경
		test_img_path = format("C:\\숫자 인식\\입력 데이터\\%d.jpg", i);

		//  이미지 읽고 그레이 스케일 적용
		cp_img[i] = imread(test_img_path, IMREAD_GRAYSCALE);

		// 이진화
		threshold(cp_img[i], cp_img[i], 127, 255, THRESH_BINARY);
		cp_img[i] = ~cp_img[i]; // 이미지 반전


		//////////////////////////////////////
		// 숫자 테두리를 따기 위한 잉여 배경을 제거//
		// 흰색 픽셀을 만날 경우를 판단                 //
		/////////////////////////////////////

		// 왼쪽 Y축
		for (int x = 0; x < cp_img[i].cols; x++)
		{
			for (int y = 0; y < cp_img[i].rows; y++)
			{
				if (cp_img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				x1 = x;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		//오른쪽 Y축
		for (int x = 0; x < cp_img[i].cols; x++) {
			for (int y = 0; y < cp_img[i].rows; y++) {
				if (cp_img[i].at<uchar>(y, (cp_img[i].cols - 1) - x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				x2 = (cp_img[i].cols - 1) - x;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		// 위 X축
		for (int y = 0; y < cp_img[i].rows; y++) {
			for (int x = 0; x < cp_img[i].cols; x++) {
				if (cp_img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				y1 = y;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}

		// 아래 X축
		for (int y = 0; y < cp_img[i].rows; y++) {
			for (int x = 0; x < cp_img[i].cols; x++) {
				if (cp_img[i].at<uchar>((cp_img[i].rows - 1) - y, x) == 255)
					zero_cnt++;
			}
			if (zero_cnt > 0) {
				y2 = (cp_img[i].rows - 1) - y;
				zero_cnt = 0;
				break;
			}
			zero_cnt = 0;
		}
		cp_cut_img[i] = cp_img[i](Range(y1 - PLUS, y2 + PLUS), Range(x1 - PLUS, x2 + PLUS));
		resize(cp_cut_img[i], cp_cut_img[i], Size(200, 200), 0, 0, INTER_LINEAR);


		////////////////////
		// 히스토그램 그리기//
		///////////////////

		// 세로 축 히스토그램
		for (int x = 0; x < cp_cut_img[i].rows; x++){
			zero_cnt = 0;
			for (int y = 0; y < cp_cut_img[i].cols; y++){
				if (cp_cut_img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			test_y[x] = zero_cnt;
		}

		// 가로 축 히스토그램
		for (int y = 0; y < cp_cut_img[i].rows; y++){
			zero_cnt = 0;
			for (int x = 0; x < cp_cut_img[i].cols; x++){
				if (cp_cut_img[i].at<uchar>(y, x) == 255)
					zero_cnt++;
			}
			test_x[y] = zero_cnt;
		}

		// 세로, 가로 축의 total 히스토그램
		for (int a = 0; a < test_img_hist[i].cols / 2; a++){
			line(test_img_hist[i], Point(a, test_img_hist[i].rows - test_y[a]), Point(a, test_img_hist[i].rows), Scalar(255, 255, 255), 0);
			line(test_img_hist[i], Point(a + 200, test_img_hist[i].rows - test_x[a]), Point(a + 200, test_img_hist[i].rows), Scalar(255, 255, 255), 0);
		}



		String name5;
		name5 = format("%d", i);
		String name6;
		name6 = format("%d의 세로 히스토그램", i);
		String name7;
		name7 = format("%d의 가로 히스토그램", i);
		String name8;
		name8 = format("%d 세로,가로 통합 히스토그램", i);

		//imshow(name5, cp_cut_img[i]);
		//imshow(name6, y_hist[i]);
		//imshow(name7, x_hist[i]);
		//imshow(name8, test_img_hist[i]);
	}


	/////////////////////////////////////
	// 표본과 테스트 이미지 히스토그램의 차이 //
	/////////////////////////////////////

	// 이미지 수 (표뵨 10개 / 테스트 10개)
	for (int i = 0; i < 10; i++){
		for (int j = 0; j < 10; j++){
			// 이미지 전체
			for (int y = 0; y < dif_hist[i][j].rows; y++)
			{
				for (int x = 0; x < dif_hist[i][j].cols; x++){
					if (xy_hist[i].at<uchar>(y, x) != test_img_hist[j].at<uchar>(y, x))
						result_cnt++;
				}
			}
			result[i][j] = result_cnt;
			clone_result[i][j] = result_cnt;

			// 비교 값 초기화
			result_cnt = 0;
		}
	}


	// 가장 비슷한 숫자 찾기
	for (int a = 0; a < 10; a++){
		for (int b = 0; b < 9; b++)
			if (clone_result[a][b] < clone_result[a][b + 1])
				clone_result[a][b + 1] = clone_result[a][b];

		for (int detect_num = 0; detect_num < 10; detect_num++)
			if (result[a][detect_num] - clone_result[a][9] == 0)
				result_num = detect_num;

		printf("(%d 번째 테스트)\n", a);
		printf("---------------------------------------------------\n");
		printf("숫자    :");
		for (int b = 0; b < 10; b++) {
			printf("   %d", b);
			//printf("%d\t", result[a][b]);
		}
		printf("\n");
		printf("---------------------------------------------------\n");
		printf("정답    :   ");
		for (int i = 0; i<result_num; i++)
			printf("    ");
		printf("%d\n\n\n", result_num);
	}
	waitKey(0);
}

 

입력 데이터

 

 

0~4까지의 히스토그램 비교 결과

 

5~9까지의 히스토그램 비교 결과

 

 

위 결과는 0.jpg부터 9.jpg가 차례대로 입력되어 나온 결과이다.

 

위의 결과를 보면 숫자가 잘 인식되는 것을 확인할 수 있다.

 

 

 

 

추가적으로 테스트를 진행해보면 위 방식의 히스토그램의 비교를 통한 숫자 인식은

 

인식이 되지 않지 경우가 더 많이 존재한다.

 

이러한 경우는 이미지 속 숫자가 조금 회전되어 있거나 글꼴이 다를 경우이다.

 

아래 그림은 숫자가 회전되어있는 이미지를 입력시킨 경우이다.

 

입력 데이터

 

 

 

0~4까지의 히스토그램 비교 결과

 

5~9까지의 히스토그램 비교 결과

 

위 결과에서 0의 숫자를 맞춘 경우는 10개중 3개입니다.

 

그리고 정답은 맞춘 3개는 회전이 조금 된 이미지이며, 나머지는 회전이 많이 된 이미지입니다.

 

* 파란 네모 : 정답

  빨간 네모 : 오답

 

 

 

 

 

 

<정리글>

 

오늘은 히스토그램을 이용하여 숫자를 인식해보았습니다.

 

대체적으로 숫자를 잘 인식하는 것처럼 보이지만, 이미지의 회전, 거리, 글꼴 등이 조금이라도 달라지면

 

오인식이 되는 문제가 발생하였습니다. 

 

오인식을 하지 않도록 하기 위해선 모든 상황에서의 각 숫자의 특징을 찾아야 하는 데, 이것은 쉬운일이 아닙니다...

 

만약 딥러닝을 이용하여 숫자를 인식하려 한다면 여러 이미지와의 공통된 특징점을 알아서 잡아주기 때문에

 

영상처리에서 어려웠던 일을 딥러닝 기술을 이용하면 쉬운일이 될 수 있습니다.

 

추후에 딥러닝을 이용한 숫자 인식을 진행하여 나온 결과를 통해 영상처리와 딥러닝을 비교해보도록 하겠습니다.

 

긴 글 읽어주셔서 감사합니다.

 

 

 

* 위 코드에서 반복되는 부분이 매우 많습니다. 추후에 정리하여 다시 올리도록 하겠습니다.

 

+ Recent posts