블로그 이미지
devtang
Instagram : @taebr0 devtaehyeong@gmail.com

calendar

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

Notice

2021. 3. 26. 15:11 개인 프로젝트

youtu.be/llFMVTj1pzg

실행영상

안녕하세요.

이번 프로젝트에서는 활성화 되어있는 카카오톡 채팅방에 원하는 메세지를 보낼 수 있는 프로그램을 제작하였습니다.

대화상자 기반 프로젝트를 전에도 몇번 진행해보면서 완벽하고 잘하진 않지만 사용할만한 프로그램 제작을 생각하다

카카오톡 전송 프로그램을 생각해보았습니다.

 

보통 대학교나 회사에서 단체로 공지를 보내야할 때 일일히 대화방에 복사+붙여넣기가 귀찮은 경우를 많이 보았기 때문에 만들어보게 되었습니다.

 

카카오톡에 메세지나 파일을 보내기 위해서는

카카오톡의 프로세스에 접근해서 키보드를 후킹하는 방식으로 진행하였습니다.

 

Visual Studio 에서는 Spy++라는 프로그램을 통해 현재 개인 컴퓨터내에서 작동하는 프로세스, 스레드 등을 확인 할 수 있습니다.

 

Spy++ 프로그램을 통해 카카오톡의 프로세스는 다음과 같습니다.

프로세스의 클래스ID, 캡션을 확인하여 카카오톡이 실행중인지 확인하는 함수에서 이용할 수 있습니다.

다음은 함수 코드중 일부입니다. FindWindow , FindWindowEx 함수를 통해서 카카오톡의 정상 구동을 확인하였습니다.

 

문자를 보내기 위해서는 카카오톡 대화상자의 프로세스를 확인해야합니다.

위의 사진과 같이 빨간색 테두리가 대화방의 부모클래스이며

노란색(RICHEDIT50W) 와 초록색Edit가 자식 클래스입니다.

 

카카오톡 메세지를 보내기 위해서는 프로세스에서 활성화 되어있는 대화방을 모두 찾고

각 대화방의 Edit Control 클래스에 보낼 메세지를 입력하는 방식으로 진행하여야 합니다.

 

카카오톡 대화방에 메세지를 전송하는 함수입니다.

대화방의 클래스 이름은 #32770이며 찾아낸 카톡방에 SendMessage , PostMessage를 이용하여

원하는 메세지를 입력시킨후 VK_RETURN이라는 가상 키보드 입력 이벤트를 작동시켜 전송하게 되는것입니다.

사용자가 메세지를 입력하고 엔터키를 입력하는 과정을 컴퓨터 프로세스를 통해 자동으로 동작하게 만들었습니다.

파일첨부기능역시 이를 약간 응용하면 됩니다.

카카오톡 대화방에서 Ctrl + T를 입력하면 파일 탐색기가 실행이되며 파일을 선택하여 첨부할 수 있습니다.

이때 위의 탐색기 창에서 파일 이름란에 파일의 경로를 입력하고 엔터를 누르면 첨부가 되는것을 확인하였습니다.

 

따라서 결론적으로 파일 전송을 위해서는

1. 대화방 프로세스에 접근한다.

2. Ctrl + T 키를 가상 키보드 이벤트 처리하여 입력한다.

3. 파일 탐색기 창의 Edit Control에 정해진 파일의 경로를 입력한다.

4. 가상 키보드 이벤트 처리를 통해 엔터키를 입력한다.

 

아래는 파일 첨부했을 때 전송 하는 함수입니다.

처음에는 메세지 전송함수와 비슷하게 카톡 대화방의 프로세스를 찾아냅니다.

이후에 Ctrl + T를 입력하는 가상 키보드 이벤트를 진행한 후에

열린 탐색기 창의 프로세스를 FindWindow 를 통해 탐색기의 Edit Control을 찾아낸 후 경로를 입력해줍니다.

이때 Sleep을 사용하지 않으면 탐색기가 마저 열리기도 전에 아래 코드가 실행되기 때문에 오작동되는 경우가

발생하여 사용하였습니다.

 

추가적으로 INFO 버튼을 클릭하면 저의 인스타그램과 티스토리에 접속할 수 있도록 하이퍼링크를 연결해보았습니다.

만들면서 귀찮고 어렵다고 생각하기 보단 모르던 기능들을 하나하나씩 찾아서 꾸며내는 재미로 만들었던 것 같습니다.

 

이번 프로젝트에서는 아쉽지만 카톡 대화방이 열려있어야하며, 특정 상대에게만 보내는 등의 기능을 구현하지 않았습니다.

 

다음에도 기회가 된다면 여기에 조금더 추가해서 다양한 기능을 만들어보았으면 좋겠습니다.

 

posted by devtang
2021. 1. 5. 20:41 개인 프로젝트

안녕하세요. 요새 코로나도 심해지는 와중에 건강 조심하시기 바랍니다.

집에서 코딩하는 것을 추천드려요~

 

 

 

이번 게시물에서는 딥러닝을 이용하지 않고

OpenCV를 이용하여 자동차 번호판 글꼴의 숫자를 인식하는 프로젝트를 작성합니다.

아직 자동차 번호판을 곧바로 인식하기엔 어려움이 있기 때문에 자동차 번호판의 숫자 글꼴을 인식하는 프로젝트로 진행하였습니다.

 

딥러닝을 이용해보신적이 있으시다면 숫자인식은 어떻게보면 "Hello World"같은 예제일정도로 쉬운 일입니다.

숫자나 어떤 이미지를 인식한다고 하면, 딥러닝에서는 데이터에서 특징을 추출하여 학습을 하여 인식을합니다.

그러나 OpenCV 라이브러리만을 이용하여 인식한다면 꽤나 생각해야할 부분이 많습니다..

본 게시물에서 진행한 숫자인식은 이런방법으로도 접근할 수 있구나라고 생각하고 봐주시면 감사하겠습니다.

 

제가 진행한 프로젝트의 흐름도는 다음과 같습니다.

우선 간단하게 요약하자면 숫자인식을 하기 위해 이미지 전처리, 관심영역 추출, 히스토그램 생성의 과정을 거칩니다.

 

표준 숫자 0~9 까지에 대해 위의 세과정을 거쳐서 표본을 만든후에

테스트할 숫자를 넣어 위와 같은 과정을 거쳐 표본 히스토그램의 픽셀수와 테스트 이미지 히스토그램의 픽셀수를 비교하여 가장 적게 차이나는 히스토그램을 가진 숫자를 인식된 숫자로 판정합니다. 

 

이미지 전처리

 

#define _CRT_SECURE_NO_WARNINGS

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main()
{
	Mat original_image; //image read(grayscale)
	Mat binary_image; //binary image


	original_image = imread("1.jpg", IMREAD_GRAYSCALE); //grayscale
	threshold(original_image, binary_image, 127, 255, THRESH_BINARY); //threshold

	imshow("grayscale", original_image);
	imshow("binary", binary_image);
	waitKey(0);
}

OpenCV를 이용하면 간단하게 이미지를 불러와서 그레이스케일 변환과 이진화를 진행할 수 있습니다.

 

이진화를 진행할때 threshold 값을 정해주어야 합니다. 그림에서 보이는 것과 같이 0이라는 숫자에

이미지의 quality가 좋지않아 noise가 발생되어있는데 0과 255 두개로만 픽셀을 깔끔하게 표현하기 위해서는

0의 윤곽쪽을 제외하고는 모두 255(흰색)으로 만들어줍니다. 

 

입력된 기본 original image를 그레이스케일 변환과 이진화를 진행한 사진입니다.

컬러 이미지를 흑백으로 전환하여 1채널(0~255)의 값으로 변경후 숫자 이미지의 픽셀을 0255로만 이루어지게 이진화를 거쳐 연산에 효율성과 인식율을 향상할 수 있었습니다.

 

이렇게 전처리를 진행함으로써 이미지의 픽셀에 접근하여 연산하는 과정 및 픽셀을 확인하는 과정을 효율적으로

진행할 수 있습니다. 

 

관심 영역 추출

이제 이진화까지의 과정을 거쳐서 0과 255로만 구성된 이미지를 얻었습니다.

다음 단계는 숫자 이외의 불필요한 배경들을 제거하여 숫자의 해당하는 픽셀의 연산에 조금더 정확하고 효율적으로

진행할 수 있도록 관심 영역의 x축 최소,최대 y축 최소,최대 좌표를 구하여 자르는 과정입니다. 

아래의 그림을 보면 이해가 쉽게 될 것 같습니다.

 

 

그림의 가로축을 X, 세로축 Y으로 놓았을때 좌측 상하단 우측 상하단을 순차적으로 돌면서 0의 라인에 해당하는

최솟값 최대값을 찾아냅니다. X-MIN ,X-MAX, Y-MIN , Y-MAX 값만 찾아내면 그값을 기준으로 자르면 되니까요.

 

#define _CRT_SECURE_NO_WARNINGS

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main()
{
	Mat original_image; //image read
	Mat binary_image; //binary image
	Mat roi_image; //roi image

	int x_count[200] = { 0, };
	int y_count[200] = { 0, };
	int x_min = 0, x_max = 0, y_min = 0, y_max = 0;
	int status = 0, pixel_count = 0;

	original_image = imread("0.jpg", IMREAD_GRAYSCALE); //grayscale
	threshold(original_image, binary_image, 127, 255, THRESH_BINARY); //threshold


 /************************** ROI 좌표 찾기 *****************************/
	for (int x = 0; x < binary_image.rows; x++) //x좌표 최솟값
	{
		for (int y = 0; y < binary_image.cols; y++)
		{
			if (binary_image.at<uchar>(y, x) == 0)
			{
				x_min = x;
				status = 1;
				break;
			}
			if (status == 1)
				break;
		}
	}
	status = 0;

	for (int y = binary_image.cols - 1; y >= 0; y--) //x좌표 최댓값
	{
		for (int x = binary_image.rows - 1; x >= 0; x--)
		{
			if (binary_image.at<uchar>(y, x) == 0)
			{
				x_max = x;
				y_max = y;
				status = 1;
				break;
			}
			if (status == 1)
				break;
		}
	}
	status = 0;

	for (int y = 0; y < binary_image.cols; y++) //y좌표 최솟값
	{
		for (int x = 0; x < binary_image.rows; x++)
		{
			if (binary_image.at<uchar>(y, x) == 0)
			{
				y_min = y;
				status = 1;
				break;
			}
			if (status == 1)
				break;
		}
	}
	status = 0;

	for (int x = binary_image.rows - 1; x >= 0; x--) //y좌표 최댓값
	{
		for (int y = binary_image.cols - 1; y >= 0; y--)
		{
			if (binary_image.at<uchar>(y, x) == 0)
			{
				if (y >= y_max)
					y_max = y;
				if (x >= x_max)
					x_max = x;
				status = 1;
				break;
			}
			if (status == 1)
				break;
		}
	}
	status = 0;
/************************** ROI 좌표 찾기 *****************************/

	roi_image = binary_image(Rect(Point(x_min, y_min), Point(x_max, y_max)));
	resize(roi_image, roi_image, Size(200, 200), 0, 0, INTER_LINEAR);

	rectangle(binary_image, Rect(Point(x_min, y_min), Point(x_max, y_max)), Scalar(0, 0, 100), 1, 4, 0);

	imshow("original", original_image);
	imshow("bin", binary_image);
	imshow("roi_image", roi_image);

	waitKey(0);
}

본 코드에는 나와있지 않지만 테스트겸 line을 그려주는 함수를 이용한 사진입니다.

 

위의 코드를 숫자 0을 입력하여 얻은 이미지입니다.

좌표가 올바르게 찾아졌는지 rectangle 함수를 이용하여 관심영역에 사각형을 그려보았습니다.

 

좌상하단 , 우상하단 = 4번의 연산이 필요합니다.

이중 for문을 이용하여 간단하게(?) 구현할 수 있습니다.

좌표를 찾았을때 break문을 걸어 탈출하며 flag를 바꾸는 방식으로 구현해보았습니다.

사실 코딩실력이 좋은편이 아니기 때문에 다소 난잡할 수 있으나,

구현적인 측면도 중요했기 떄문에 코드가 난잡하더라도 귀엽게 주니어 개발자를 봐주시면 감사하겠습니다.

	roi_image = binary_image(Rect(Point(x_min, y_min), Point(x_max, y_max)));
	resize(roi_image, roi_image, Size(200, 200), 0, 0, INTER_LINEAR);

 

주의할 코드부분은 관심영역을 자르고 나서 원본 사진의 해상도와 맞게 resize를 시켜주었습니다.

OpenCV에서는 resize 진행시 보간법(interpolation methods)을 지정해줄 수 있습니다. 
대표적으로 INTER_CUBIC ,INTER_LINEAR 등이 있습니다만, INTER_CUBIC을 사용하면 보다 더 선명한 이미지를 얻을수 있다고 합니다. 그러나 저는 INTER_LINEAR 방식으로 진행하였습니다.

 

 

히스토그램 생성

관심영역이 추출된 이미지를 가지고 본격적으로 인식에 가장 필요한 히스토그램을 생성하는 파트입니다.

 

히스토그램이란?

 

히스토그램(Histogram)은 표로 되어 있는 도수 분포를 정보 그림으로 나타낸 것입니다.

그러나 이미지 히스토그램은 이야기가 조금 다릅니다.

 

이미지 히스토그램은 가로축(x축) 에는 이미지의 픽셀 값을 나타내는 좌표값이고, 세로축(y축)으로는 픽셀의 수를 나타내는 좌표값입니다. 히스토그램으로 이미지의 대비(Contrast)나 빛의 강도 등을 나타낼 수도 있기 때문에, 이미지에서 어떤 특징점이 어느정도 분포하고 있는지 알 수 있는 그래프라고 볼 수 있습니다.

 

0과 9에대한 히스토그램... 별 차이가 없다.

저는 본 프로젝트를 진행하면서, 그레이스케일 변환한 숫자에 대해 히스토그램을이용하여 숫자 0~9 까지의 히스토그램을 모두 그려보았으나 뚜렷한 특징을 찾을 수가 없었습니다. 따라서 아래의 그림과 같이 진행하였습니다.

 

 

 

 

x축좌표에 대한 0에 해당하는 픽셀 개수를 표현

 

y축좌표에 대한 0에 해당하는 픽셀 개수를 표현

 

가로, 세로 축 좌표를 기준으로 숫자의 픽셀(검정색)의 개수를 카운팅하여, 빈 검정색 이미지에 line함수로 픽셀의 개수만큼 그려주었습니다. 축의 기준을 나눠서 히스토그램을 생성한 이유는 기존 히스토그램 사용시에, 밝은 픽셀과 어두운 픽셀의 분포를 표시하기 때문에 입력되는 이미지의 숫자의 크기에 따라 오인식 하는 확률이 크기 때문에 위와 같이 진행했었습니다.

 

x축 좌표에 대한 히스토그램 을 보면 가로축은 x가 0부터 image의 rows까지 , 세로축은 픽셀의 개수를 나타내었고

y축 좌표에 대한 히스토그램 역시 보면 가로축은 y가 0부터 image의 cols까지, 세로축은 픽셀의 개수를 나타내었습니다.

 

/************************** ROI 좌표 찾기 *****************************/

	roi_image = binary_image(Rect(Point(x_min, y_min), Point(x_max, y_max)));
	resize(roi_image, roi_image, Size(200, 200), 0, 0, INTER_LINEAR);

	Mat x_hist_image = Mat::zeros(roi_image.rows, roi_image.cols, CV_8U);
	Mat y_hist_image = Mat::zeros(roi_image.rows, roi_image.cols, CV_8U);
	Mat total_hist_image = Mat::zeros(roi_image.cols, roi_image.rows * 2, CV_8U);
	/**************************   histogram    ****************************/
	for (int y = 0; y < roi_image.rows; y++) //y축 히스토그램
	{
		pixel_count = 0;
		for (int x = 0; x < roi_image.cols; x++)
		{
			if (roi_image.at<uchar>(x, y) == 0)
			{
				pixel_count++;
			}
		}
		y_count[y] = pixel_count;

	}



	for (int x = 0; x < roi_image.cols; x++) //x축 히스토그램
	{
		pixel_count = 0;
		for (int y = 0; y < roi_image.rows; y++)
		{
			if (roi_image.at<uchar>(x, y) == 0)
			{
				pixel_count++;
			}
		}
		x_count[x] = pixel_count;
	}
	for (int x = 0; x < roi_image.rows; x++) //x축 히스토그램 그리기
		line(x_hist_image, Point(x, roi_image.rows), Point(x, roi_image.rows - x_count[x]), Scalar(255, 255, 255), 0);

	for (int y = 0; y < roi_image.cols; y++) //y축 히스토그램 그리기
		line(y_hist_image, Point(y, roi_image.cols), Point(y, roi_image.cols - y_count[y]), Scalar(255, 255, 255), 0);

	/* 통합 히스토그램 그리기 */
	for (int x= 0; x < roi_image.rows; x++) 
		line(total_hist_image , Point(x + 200, roi_image.rows), Point(x+200, roi_image.rows - x_count[x]), Scalar(255, 255, 255), 0);
	for (int y = 0; y < roi_image.cols; y++)
		line(total_hist_image, Point(y, roi_image.cols), Point(y, roi_image.cols - y_count[y]), Scalar(255, 255, 255), 0);

	/**************************   histogram    ****************************/

	imshow("x_hist", x_hist_image);
	imshow("y_hist", y_hist_image);
	imshow("original", original_image);
	imshow("bin", binary_image);
	imshow("roi_image", roi_image);
	imshow("total", total_hist_image);
	waitKey(0);
}

코드 중 가로 세로축 히스토그램을 그리는 코드의 일부입니다.

위의 코드 역시 이중 for문을 이용하여 이미지의 행 ,열을 훑어 연산하면서 픽셀의 카운트를 진행하였습니다.

테스트할 이미지의 데이터와 비교할 표준 숫자 데이터 0~9까지의 히스토그램을 모두 생성하여 저장하였습니다.

 

그림과 같이 각 숫자마다 모두 다른 히스토그램을 확인할 수 있습니다.

이제 표준 데이터의 히스토그램을 모두 저장하였으니,

테스트할 숫자 이미지 역시 지금까지 진행했던 방식으로 히스토그램을 생성합니다.

 

테스트할 숫자 데이터는 인터넷에서 자동차 번호판의 숫자를 찾아서 아래 그림과 같이 크기와 위치가 다르게 저장하여 진행하였습니다.

 

/* compare */
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			for (int y = 0; y < total_hist_image[i].cols; y++) //y축 히스토그램
			{
				for (int x = 0; x < total_hist_image[i].rows; x++)
				{
					if (total_hist_image[i].at<uchar>(x, y) != test_total_hist_image[j].at<uchar>(x, y))
						test_check_sum[i]++;
				}
			}
			if (check_sum[i] <= min)
				min = test_check_sum[i];
		}
		if (test_check_sum[i] == min)
			min = i;
		detect_num[i] = min;
	}
    
    /* compare */

표준 히스토그램 데이터와의 픽셀차이를 계산하는 코드중 일부입니다.

 

본 코드가 관심영역 추출이 올바르게 되는지, 글자의 크기에 영향을 미치지 않는지 테스트를 진행해보았습니다.

 

youtu.be/1li7GSmIwqk

 

 

테스트할 숫자 이미지가 입력이 되면 표준 숫자 데이터 0~9 를 각각 비교하면서 픽셀차이가 가장 적은 숫자를

인식하는 숫자로 판정하였습니다. 위의 사진과 같이 똑같은 글꼴이지만 위치와 숫자의 크기를 달리 하여도 표준 히스토그램의 특징과 뚜렷하게 다른점이 거의 없습니다.

 

테스트 숫자의 픽셀이 관심영역을 추출하면서 resize되어

글꼴이 깨져도 숫자의 본 모습은 크게 바뀌지 않아서 그런것 같습니다.

한계점..

 

본 프로젝트에서 진행한 숫자인식 방법은 가로 세로축을 기준으로 픽셀의 개수를 측정하였기 때문에 숫자가 조금만 회전이 되어도 오인식 하는 경우가 많았습니다..

 

회전이나 글꼴 차이에 해당하는 경우의 수는 고려하지 않고 반영하였기 때문에 본프로젝트는 추후에 더욱 좋은 구상을 통해 보완해야할 것 같습니다. 추후에 더욱 좋은 구상을 통해 보완하게 된다면 비교적 무거운 딥러닝을 사용하지 않고도 자동차 번호판을 인식할 수 있는 프로그램을 만들 수 있기 때문에 제한된 하드웨어 성능내에서 효율적인 시스템을 만들수 있을 것 같습니다.

 

본 프로젝트는 자동차 번호판 숫자 인식을 위한 프로젝트의 일부로 진행하였습니다.

막상 딥러닝을 이용할때는 몰랐는데 딥러닝의 필요성을 알게되는 프로젝트 중 하나였습니다. 영상처리에 대한 공부를 진행하면서 숫자 인식을 위해 진행했던 부분들이 흥미로운점이 많았던 것 같습니다.

 

긴 게시물 읽어주셔서 감사합니다.

 

posted by devtang
2020. 3. 11. 16:58 개인 프로젝트

*2020년도 10월 교내 캡스톤경진대회 제품제작에 성공하였습니다

youtu.be/OwrC1YFvGH0

 

 

마스크 학습관련내용은 아래 게시물에 있습니다.

https://ultrakid.tistory.com/15

 

[Windows] 마스크 착용 유/무 판단을 위한 YOLO 학습

요즘 코로나 바이러스로 인해 전세계가 혼란입니다. 그래서 마스크를 착용하지 않으면 주변 사람에게 피해가 가니 항상 착용하도록 합시다!! 다름이 아니라 연구실에서 공부겸 간단한 개인 프��

ultrakid.tistory.com

코로나 바이러스로 요즘 전세계적으로 혼란인 가운데,

마스크를 착용하지않으면 상대적으로 타인에게 불안감을 조성할 우려가 있어서

마스크를 착용했을때 문이 열리게 되는 프로젝트를 만들었습니다.

개인적으로 시간이 날때마다 짬짬히 만들어보았습니다.

우선 구현 영상입니다.

https://youtu.be/fcFa_WhXNIo

 

 

라즈베리파이와 컴퓨터간 실시간 소켓 통신을 이용하였으며

컴퓨터에서는 YOLO를 이용한 마스크 착용 인식, 라즈베리에서는 소켓으로 통신받아 GPIO를 제어하였습니다.

 

HC-SR04 초음파센서

우선 사람이 문앞에 있다고 가정하기 위해 초음파센서를 이용하였습니다.

라디오 같이 생긴 이 센서는 송신부인 Trigger와 수신부인 Echo로 나뉘어있는데 송신부에서 초음파를 발사하면 물체에 부딪쳐 나오는 진동을 Echo에서 받은 시간을 이용하여 거리를 측정하는 방식입니다.

이 센서는 최대 4m까지 거리측정이 가능합니다.

 

 

 라즈베리파이와 브레드보드를 이용하여 연결해놓고 잘 작동하는지 테스트해봤습니다.

 LED는 초음파센서와 물체와의 거리가 100cm 이하로 일정 count동안 존재할때 켜지도록 해놨습니다.

 

 서보모터는 SG-90을 이용했으며 서보모터에 나무젓가락 세개를 테이프로 이어붙혀서 만들었습니다.

 출입문 역할로 만들었으며, 마스크를 쓰면 서보모터를 회전시켜 지나갈 수 있도록 만들었습니다.

 이제 남은건 윈도우 환경에서 인식한 결과를 라즈베리에 전송시켜 마스크를 썼을때 서보모터가 움직이게

 두가지를 합쳤습니다.

 

YOLO의 darknet 소스코드찾다 image_opencv.cpp 파일에서 class를 검출하는 소스코드를 찾아냈습니다.

/*********************************************INSERT CODE LINE*******************************************************/
if (strcmp(detectname, names[j]) != 0)
	{
		fp = fopen("***TXT파일위치***", "w");
		fwrite(names[j], strlen(names[j]), 1, fp);
		fclose(fp);
	}
/*********************************************INSERT CODE LINE*******************************************************/

제가 추가한 소스코드입니다. names[j]에 인식된 class의 이름이 저장되있습니다.

TXT파일을 fopen 함수를 이용하여 열어서 fwrite 함수로 TXT파일에 검출된 class를 적게 구현했습니다.

이제 소켓으로 class name을 보내기만 하면 됩니다.

 

윈도우 소켓 코드중 일부입니다.

while (1) 
	{
		fp = fopen("***TXT파일 위치***", "r"); //YOLO에서 검출된name을 적은 TXT파일 열기
		fscanf(fp, "%s", message); // 문장을 복사
		fclose(fp); // 파일닫기
		if (strcmp(message, pre_message) == 0)
			continue;
		else {
			strcpy(pre_message, message);
			printf("SEND : %s\n", pre_message); 
			send(hSocket, message, sizeof(message) - 1, 0); //send를이용하여 라즈베리로 전송
			
		}

	}

 

YOLO와 소켓을 둘다 실행시켜놓고 소켓은 계속 텍스트를 읽어서 보내주기만 합니다.

 

라즈베리파이에서 GPIO를 다루는 코드 중 일부입니다.

if distance <= 100: # *초음파센서와 물체간 거리가 100cm 이하일때
			f = open("라즈베리파이 TXT파일 위치",'r') #윈도우에서 받은 name을 읽기
			read_yolo = f.readline() # read_yolo 변수에 문장을 복사
			f.close()
			right = 'MASK'	
			count += 1 # count가 증가함 
			if count >= 4 and read_yolo == right: # count가 4이상 and 'MASK'라는 name을 받게되면
				# print(2)
				p.ChangeDutyCycle(12) # 서보모터를 올림
				time.sleep(1)
				GPIO.output(LED, GPIO.HIGH) # LED를 켬
				time.sleep(2)	
				p.ChangeDutyCycle(7.5) # 2초후 서보모터를 내림
				time.sleep(1)
				count = 0 # count를 0으로 초기화
				GPIO.output(LED,GPIO.LOW) # LED를 끔
			
			else: # 마스크를 쓰지 않으면 서보모터를 제어하지 않는다.
				continue

일정시간동안 출입문 앞에서 마스크를 끼고 있지 않은 상태로 서있으면 야속하게 문이 열리지 않습니다.

간단하게 GPIO를 제어하여 마스크 착용시에만 문이 열리는 시스템을 만들어 봤습니다.

나중에 추가적으로 소스 코드 수정 및 기능을 개선,추가하여 더 좋은 퀄리티로 만들어 보고 싶습니다.

 

 

posted by devtang
2020. 3. 10. 14:41 개인 프로젝트

개인 프로젝트를 위해 저에게 필요한 단방향 소켓 통신방식을 만들어 봤습니다.

 

 

기본적인 세팅 구조

 

우선 라즈베리파이에 data.txt라는 텍스트 파일에 Random한 시간과 내용을 실시간으로 적게 되면,

윈도우에서 실시간으로 받은 문장을 server.txt라고 만든 텍스트 파일에 실시간으로 적어주는 방식입니다.

저번 출입알림시스템 때 이용해봤지만 리눅스-리눅스 환경의 소켓 통신이라 이번에는 서버코드를 약간 수정했습니다.

 

data.txt 파일에 ABCD를 적어서 저장하는 순간 윈도우내 지정된 server.txt 파일에 문자가 저장됩니다.

소켓으로 실시간 메모장을 열고 확인해야 하기 때문에 조금 비효율적이지만 

제가 배운 선에서 최대한 저한테 이용하기 편리하게 만들어놨습니다.

 

Windows 10 에서 구동한 소스 코드입니다. (Server)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma comment(lib,"ws2_32.lib")
#include <WinSock2.h>
#include <windows.h>


#define  BUF_SIZE   1024
void ErrorHandling(char *message);
int main(int argc, char *argv[])

{
   FILE *fp;
   WSADATA wsaData;
   SOCKET hServSock, hClntSock;
   SOCKADDR_IN servAddr, clntAddr;
   char message[BUF_SIZE];
   char write_memo[BUF_SIZE] = { 0, };
   int str_len, j;
   int szClntAddr;

   if (argc != 2)
   {
      printf("Usage:%s <port>\n", argv[0]);
      exit(1);
   }



   if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) //소켓 라이브러리 초기화
      ErrorHandling("WSAStartup() error!");

   hServSock = socket(PF_INET, SOCK_STREAM, 0); //소켓생성

   if (hServSock == INVALID_SOCKET)
      ErrorHandling("socket() error");

   memset(&servAddr, 0, sizeof(servAddr));
   servAddr.sin_family = AF_INET;
   servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
   servAddr.sin_port = htons(atoi(argv[1]));

   if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) //IP주소와 PORT 번호 할당
      ErrorHandling("bind() error");

   if (listen(hServSock, 5) == SOCKET_ERROR) //listen 소켓을 서버 소켓으로 완성
      ErrorHandling("listen() error");


   szClntAddr = sizeof(clntAddr);
   hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); // accept함수 호출
   if (hClntSock == INVALID_SOCKET)
      ErrorHandling("accept() error");

   while (1)
   {
      str_len=recv(hClntSock, message, sizeof(message)-1, 0); //클라이언트 메세지를 받음
      if (strcmp(write_memo, message) == 0) // *참고
         continue;
      else {
         fp = fopen("C:\\**TXT파일의위치**", "w"); //fopen함수 이용하여 메모장 쓰기모드로 열기
         fwrite(message, strlen(message), 1, fp); // 받은 메세지를 메모장에 쓰기
         fclose(fp); // 메모장을 닫는다.
         strcpy(write_memo, message); // *받은 메세지를 write_memo 변수에 복사해놓음(계속 쓰는것을 방지)
         printf("Message from Client : %s", message); //받은 메세지를 표시
      }
      for (j = 0; j < 100; j++) {
         message[j] = 0; //100글자까지 초기화
      }
         
   }
   closesocket(hClntSock);
   closesocket(hServSock);
   WSACleanup();
   return 0;
}

void ErrorHandling(char *message)
{
   fputs(message, stderr);
   fputc('\n', stderr);
   exit(1);
}

받은 message를 write_memo 라는 변수를 만들어서 복사해준 이유는 

라즈베리파이에서 예를들어 'ABCD'를 입력하면 한번만 보내는것이 아니라 무한적으로 ABCD를 계속 보내게 됩니다.

따라서 write_memo에 전에받았던 'ABCD'를 저장해놓으면 다음 while 루프때 문자가 바뀌지않으면

다음 문장을 입력받기 전까지 대기하게 할 수 있어서 만들어놨습니다.

 

아래는 RaspberryPi에서 구동한 소스 코드입니다.(Client)

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define  BUF_SIZE   1024
void error_handling(char *message);

int main( int argc, char *argv[])
{
   char send_socket[BUF_SIZE]= {0,};
   int sock;
   char message[BUF_SIZE];
   struct sockaddr_in serv_adr;
   FILE *fp;
   if(argc != 3) {
      printf("Usage : %s <IP> <PORT>\n", argv[0]);
      exit(1);
   }
   
   sock= socket( PF_INET, SOCK_STREAM, 0);
   if( -1 ==sock)
      error_handling("socket() error");

   memset( &serv_adr, 0, sizeof( serv_adr));
   serv_adr.sin_family     = AF_INET;
   serv_adr.sin_addr.s_addr= inet_addr(argv[1]);
   serv_adr.sin_port       = htons(atoi(argv[2]));
  

   if( -1 == connect(sock, (struct sockaddr*)&serv_adr, sizeof( serv_adr) ) )
   {
      printf( "접속 실패\n");
      exit( 1);
   }
   
   
   while(1){
   
      fp = fopen("**TXT파일 위치**","r"); // 읽기 모드로 TXT파일을 열기
      fscanf(fp, "%s",message); // TXT파일내 문장을 읽기
      //printf("MES : %s",message);
      fclose(fp);
      if(strcmp(message,send_socket)==0)
      {
    	continue;
      }
      else{
    strcpy(send_socket,message);
    printf("SEND : %s\n", send_socket);
    write(sock,message,sizeof(message)-1); // 서버로 message 전송
    }
      
      
   }
   close(sock);
   return 0;
}

void error_handling(char *message)
{
   fputs(message,stderr);
   fputc('\n',stderr);
   exit(1);
}

 

여기도 마찬가지로 제가 쓰던 코드라 조금 난잡할수도 있습니다. 위와 같은 방식으로 send_socket이라는 변수에 message를 저장시켜 문장이 변하지 않으면 계속 while 루프를 돌도록 구현했습니다.

 

아래는 프로그램 구현 동영상입니다.

 

 

 

참고도서 : 윤성우의 열혈 TCP/IP 소켓 프로그래밍

 

====== 오렌지 미디어 ======

             목록 서버1   서버2 강의시간 강의교안  PART 01 네트워크 프로그래밍의 시작    Chapter 01 네트워크 프로그래밍과 소켓의 이해        01-1. 네트워크 프로그래밍과 소켓의 이해① 27:43        01-1. 네트워크 프로그래밍과 소켓의 이해② 07:58        01-1. 네트워크 프로그래밍과 소켓의 이해③ 12:52        01-2. 리눅스 기반 파일 조작하기 27:27        01-3.

www.orentec.co.kr

 

posted by devtang
2020. 2. 25. 16:24 개인 프로젝트

 

연구실에서 개인적으로 진행해본 프로젝트 데모영상입니다.

 

출입알림시스템 데모 영상

 

 

계획하게된 계기

우선 저는 대학교 학부생으로 정보통신공학전공 연구실에 들어왔습니다.

라즈베리파이와 우분투 환경에대해 공부하던중에 ㅎㅎㅎ

교수님이 출입하시면 게임하다 걸리면 혼날거같아서 ㅎㅎㅎ

YOLO를 이용하여 연구실 인원 얼굴을 학습하여 출입시 누군지 인식하고 음성으로 알림 해주는 프로그램을 만들 계획을 짜게 되었습니다.

 

출입알림 시스템을 만들기 위해서 제가 간단히라도 공부했던 내용을 정리해보면

 

● YOLO 학습 ( 머신러닝의 원리, 데이터 학습원리 및 방법 )

● 라즈베리파이 

● OpenCV를 이용한 영상처리 기법

● TCP/IP 소켓 프로그래밍

● 카카오API, 구글 API를 활용한 TTS(Text to Speech), STT(Speech to Text)

 

2학년이 막 끝난 시점에 연구실에 처음 들어오게된 저로써는 , 처음에 그냥 공부하려니

되게 낯설었지만, 작은 프로젝트 하나라도 제대로 구현해보자 하는 마음으로 프로젝트에 

쓰일만한 내용을 학습했습니다.  특히~ 소켓 잡을때는 진짜 하기 싫었네요 ㅎㅎ

 

아무튼 어느정도 공부하면서 확실한 계획을 세웠습니다.

 

1단계 : 연구실 내의 모든 사람들의 얼굴을 학습한 후 실시간 감지 확인

 

 

첫번째로, 우리의 얼굴을 학습시킨후, 정상적으로 인식이 되는지 확인하였습니다.

 

YOLO는 Ubuntu환경에서 구동하였고, 1인당 1300~1400장의 얼굴 데이터를 학습시켰습니다.

 

 

얼굴 학습후 테스트 영상

YOLO 학습했던 내용은 추후에 추가 업로드 예정입니다.

 

2단계 : 양방향 소켓 통신 확인 (문자열 송수신)

 

소켓 프로그래밍은 도서관에서 책을빌려 병행하면서 공부했었네요

윤성우의 열혈 TCP/IP 프로그래밍

 

====== 오렌지 미디어 ======

             목록 서버1   서버2 강의시간 강의교안  PART 01 네트워크 프로그래밍의 시작    Chapter 01 네트워크 프로그래밍과 소켓의 이해        01-1. 네트워크 프로그래밍과 소켓의 이해① 27:43        01-1. 네트워크 프로그래밍과 소켓의 이해② 07:58        01-1. 네트워크 프로그래밍과 소켓의 이해③ 12:52        01-2. 리눅스 기반 파일 조작하기 27:27        01-3.

www.orentec.co.kr

 

라즈베리파이 <-> 우분투 간의 소켓 통신 확인후

YOLO에서 감지했을때 txt파일에 인식한 사람의 이름을 써주고

실시간으로 소켓으로 txt파일을 읽어서 라즈베리파이에 전송해주는 식으로 구상했습니다.

 

라즈베리파이로 출력하는 음성은 카카오 API 를 이용하여, 각 사람들의 음성파일을 만들었습니다.

 

MobaXterm을 이용한 음성파일 추출

소프트웨어적인 구현은 거의 완벽하게 진행되어있으나

하드웨어적 효율적 구현은 추후에 할예정입니다. 

 

YOLO가 인식하기에 카메라가 상당히 먼곳에 있어 인식이안되서 USB연장선으로 카메라를 땡겨왔습니다.

상당히 불편합니다..

USB 연장선을 더 추가하여 아예 벽쪽으로 몰아버리거나..

아니면 무선 영상 통신을 이용하여 영상을 받아 인식해주거나

생각좀 해봐야 할것 같네요.

posted by devtang
prev 1 next