Window 10에서 OpenCV 3.1.0을 설치하여 Visual Studio 2019에 라이브러리를 포팅해 설치 확인 테스트까지 진행해보겠습니다.

 

 

1. OpenCV 3.1.0 Version 파일 설치

 

 

   - 구글에 OpenCV를 검색하시면 가장 상단에 공식 홈페이지가 나옵니다.

     (www.opencv.org)

 

구글에 OpenCV 검색

 

 

   - 홈페이지에 접속하면 아래와 같이 나오게 되며, 홈페이지 가장 하단에 Source Forge를 찾아 클릭합니다.

 

Source Forge 클릭

 

 

  - 설치하고 싶은 OpenCV Version를 찾습니다. (본 블로그에선 Window에 맞는 OpenCV 3.1.0 Version을 설치합니다.)

 

 

 

 

  - 설치 파일을 받은 후 경로를 지정하여 설치를 진행합니다.

 

 

 

 

  2. 환경 변수 설정

 

   - 키보드에서 윈도우+pause를 누르거나 검색하여 시스템 속성 창을 켜서 환경 변수를 클릭합니다.

 

 

 

  - 환경 변수에서 하단의 시스템 변수 속의 path를 찾아 아래와 같이 설치한 OpenCV에서의 파일 경로를 입력합니다.

 

 

 

 

3. Visual Studio

 

 - Visual Studio를 켜서 프로젝트를 생성합니다.

 - 프로젝트를 누른 뒤 alt + enter을 누르거나 상단 카테고리에서 프로젝트의 속성 페이지 창 찾아 띄웁니다.

 - 아래와 같이 설치한 OpenCV 경로에 맞춰 입력시켜줍니다(C/C++ - 일반, 링커 - 일반, 입력)

 

 

 

  - OpenCV 라이브러리가 잘 포팅되었는지 확인하도록 아래의 코드를 입력하여 이미지를 띄워봅니다.

    (.raw 파일이 아닌 png, jpg, jpeg, bmp 등의 포맷 형태의 이미지를 사용하시면 됩니다.)

 

 

 

- 만약 다음과 같은 에러가 발생한다면 에러 아래의 이미지를 따라해주세요.

- 해당 dll 파일을 OpenCV 폴더 속에서 찾아 Visual Studio 프로젝트의 경로에 복사해주세요.

 

 

 

  - 다시 위의 코드를 컴파일하면 경로에 맞는 이미지가 출력되는걸 확인할 수 있어, OpenCV 라이브러리가 잘 포팅되었다는 것을 알 수 있습니다.

 

 

 

오늘은 영상 속의 특징점(Keypoint)를 검출할 수 있는 Harris Corner에 대해 알아보겠습니다.

 

1998년도에 발표된 Harris Corner는 1980년도에 발표된 Moravec's Corner의 방법을 보안한 알고리즘으로서,

영상에서 코너의 특징점을 찾는 것입니다. 간략하게 말하자면 아래 그림과 같이 작은 영역을 이동시키며,

이동된 영역과 비교하여 얼마나 변화했는지 찾는 것입니다.

 

 

출처 : https://darkpgmr.tistory.com/131

 

아래 그림과 같이 이동되기 전과 이동된 영역의 변화의 차이인 R이 0보다 작으면 엣지(Edge), 둘다 0에 가까우면

플렛(Flat), 0보다 크면 코너점이 됩니다.

 

출처 : https://darkpgmr.tistory.com/131

 

 * 위 설명은 수학적 과정에 대한 언급 없이 표면적으로 간단하게 설명한 것입니다. 수학적인 전체의 과정은 추후에
충분히 이해한 뒤 내용을 추가해 작성하도록 하겠습니다.

 

 

 

아래 코드는 Opencv에서 제공하는 라이브러리 속 함수인 cornerHarris()를 사용한 예시입니다.

 

* 본 코드는 Opencv 3.1.0 Version에서 사용하였습니다.

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

using namespace cv;
using namespace std;

// Harris Corner thresh
int thresh = 120;

int main()
{
	Mat img = imread("D:\\sea.jpg");
	Mat gray;

	// Image resize
	resize(img, img, Size(500, 500), 0, 0, INTER_LANCZOS4);

	Mat img_copy = img.clone(); // 원본 이미지 복사
	Mat show_img = img.clone(); // 원본 이미지 복사

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

	Mat harris_c;
	Mat harris_norm;
	Mat cs_abs;

	harris_norm = Mat::zeros(gray.size(), CV_32FC1);
	
	// Harris Corner
	// cornerHarris(입력 이미지(Grayscale), 출력 이미지, 인접 픽셀 크기(Blocksize), Sobel ksize, Harris parameter, 픽셀 보간법)
	cornerHarris(gray, harris_c, 2, 3, 0.05, BORDER_DEFAULT);

	// 정규화(Normalizing)
	// normalize(입력 이미지, 출력 이미지, normalize range(low), normalize range(high), 픽셀 보간법) 
	normalize(harris_c, harris_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat());

	// 미분한 결과에 절대값을 적용하여 8bit의 unsigned int형으로 바꾸어 표현
	convertScaleAbs(harris_norm, cs_abs);

	// Drawing a circle around corners
	for (int j = 0; j < harris_norm.rows; j += 1)
		for (int i = 0; i < harris_norm.cols; i += 1)
			if ((int)harris_norm.at<float>(j, i) > thresh)
				circle(img_copy, Point(i, j), 7, Scalar(255, 0, 255), 0, 4, 0);

	imshow("Original Image", show_img);
	imshow("convertScaleAbs", cs_abs);
	imshow("Harris Corner Detection", img_copy);

	waitKey(0);
}

Original Image

 

cornerHarris() to convertScaleAbs()

 

Harris Corner Detection (코너점을 마젠타색 원으로 표현)

 

Harris Corner Detection은 영상의 평행이동, 회전변화에는 불변이고, affine 변화, 조명 변화에도 어느정도 강인한

특성을 가지고 있지만 영상의 크기 변화에는 민감함을 보입니다.

 

 

 

 

< 참고 및 인용 자료 >

 

1) https://darkpgmr.tistory.com/131

 

영상 특징점(keypoint) 추출방법

영상 특징점이 무엇이고 어떻게 뽑는지, 그리고 대표적인 방법에는 어떤 것들이 있는지 정리해 봅니다. 1. 영상 특징점이란? 2. Harris corner [1988] 3. Shi & Tomasi [1994] 4. SIFT - DoG [2004] 5. FAST [2006..

darkpgmr.tistory.com

2) http://egloos.zum.com/eyes33/v/6097465

 

Corner 검출 #1- Harris corner detector

이미지에서 특정 물체를 인식하거나, 추적할때 물체를 특징짖을 수 있는 주요 특징점을 매칭 시키면 쉽게 인식하거나, 추적할 수 있다. Harris Corner는 1980년 Moravec 의 아이디어를 수정 보완한것이다. Moravec은 작은 윈도우를 수직, 수평, 좌대각선, 우대각선 4개 방향으로 1 픽셀씩 이동시켰을 때의 변화하는 intensity 의

egloos.zum.com

3) https://leechamin.tistory.com/293

 

[OpenCV] 05-2. Harris Corner Detection

< Harris Corner Detection > " style="clear: both; font-size: 2.2em; margin: 0px 0px 1em; color: rgb(34, 34, 34); font-family: "Roboto Condensed", Tauri, "Hiragino Sans GB", "Microsoft YaHei",..

leechamin.tistory.com

 

 

어떠한 프로젝트를 진행하면서 픽셀의 값을 이용한 알고리즘으로 접근할 때, 이미지 전체가 아닌 특정 영역의 픽셀 값의

정보를 알고 싶은 경우가 있었습니다. 하지만 제가 아는 것은 이미지 전체의 픽셀에 접근하여 정보를 추출하거나 캡쳐

 

도구를 통해 수작업으로 이미지를 잘라서 처리하는 방법 뿐이여서 불편함을 많이 느꼈습니다.

 

오늘은 제가 느꼈던 불편함을 해소할 수 있는 방법으로 마우스 이벤트인 setMouseCallback 함수를 이용하여,

 

이미지 속에서 직접 지정한 특정 영역의 이미지와 픽셀 값의 정보를 추출하는 방법에 대해 소개하려합니다.

 

 

 

마우스 이벤트는 사용자가 함수를 설계하여 처리할 수 있습니다.

 

여기서 중요한 부분은 인터럽트처럼 사용자가 설계한 함수에서의 마우스 이벤트 발생했을 때

 

바로 처리가 가능하도록 콜백 함수(Event Handler)를 만들어 함수를 setMouseCallback()에 등록하는 겁니다.

 

 

setMouseCallback 함수에 대해 알아보겠습니다.

setMouseCallback 함수 설명

 

 

코드를 통해 살펴보도록 하겠습니다.

 

아래 코드는 입력된 이미지 창에서 마우스 이벤트인 setMouseCallback 함수를 이용하여 마우스를 통해 박스를 그리고

그려진 박스 영역의 이미지를 추출해 Grayscale하여 저장한다.

(마우스는 왼쪽 버튼을 누르면 박스가 그려지는 중이 되며 뗐을 때 완전한 박스가 그려진다.

 

또한, Grayscale이 적용된 이미지에서 픽셀 정보를 읽어 생성된 텍스트에 입력시켜 저장한다. 추가적인 옵션으로 이미지

창에서 키보드의 SPACE를 누를 때 그려진 박스들이 사라지고 EXC를 누르면 프로그램에 종료된다.

 

* 본 코드는 Opencv 3.1.0 Version에서 사용하였습니다.

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

void onMouse(int x, int y, int flags, int, void *param);      // 마우스 콜백 함수
void img_save(Mat img); // 이미지, 텍스트 저장

int x_1, x_2, y_1, y_2; // 그린 박스의 네 꼭짓점 좌표
int draw_start; 
int draw_save;
int index = 1;

int main()
{
	Mat image = imread("D:\\test.jpg");
	Mat image_copy;
	Mat crop_img;
	Mat img_backup;

	int pre_num = 0;
	int r, g, b; //RGB 색상

	resize(image, image, Size(500, 500), 0, 0, CV_INTER_LANCZOS4);
	img_backup = image.clone();

	while (1){
		// 원복 복사(박스가 자연스럽게 그려지는 것을 보여주기 위함)
		image_copy = image.clone();

		//박스 색상(그리는 중)
		r = 0;
		b = 255;
		g = 0;

		// 박스 좌측 상단에서 우측 하단으로 그리기
		if (x_1 < 0)
			x_1 = 0;
		if (x_2 > image_copy.cols)
			x_2 = image_copy.cols;
		if (y_1 < 0)
			y_1 = 0;
		if (y_2 > image_copy.rows)
			y_2 = image_copy.rows;

		// 박스 그리기
		if (draw_start == 1 && draw_save == 0)
			rectangle(image_copy, Point(x_1, y_1), Point(x_2, y_2), Scalar(b, g, r));
		else if(draw_start == 1 && draw_save == 1){	

				// 박스를 자연스럽게 그리기 위함
				if (x_1 > x_2) {
					pre_num = x_2;
					x_2 = x_1;
					x_1 = pre_num;
				}
				if (y_1 > y_2) {
					pre_num = y_2;
					y_2 = y_1;
					y_1 = pre_num;
				}

				// 이미지 저장 함수
				img_save(img_backup);

				// 초기화
				draw_start = 0;
				draw_save = 0;

				index++;

				// 박스 색상(그려진)
				r = 255;
				g = 0;
				b = 0;

				rectangle(image, Point(x_1, y_1), Point(x_2, y_2), Scalar(b, g, r));

				// 이미지 추출
				crop_img = img_backup(Range(y_1, y_2), Range(x_1, x_2));
				cvtColor(crop_img, crop_img, CV_RGB2GRAY);

				imshow("선택 영역", crop_img);
		}
		
		// 창에 텍스트 세기기
		putText(image, "Space : Delete all box", Point(10, 15), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255), 2);
		putText(image, "  ESC : End of Program", Point(10, 35), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255), 2);
		 

		imshow("Original", image_copy);
		setMouseCallback("Original", onMouse, 0);

		if (waitKey(10) == 32)
			image = img_backup.clone(); // 스페이스를 누를 시 그려진 박스 없앰

		if (waitKey(5) == 27) // ESC를 누를 시 종료
			exit(0);
	}
	//waitKey(0);
}

void img_save(Mat img)
{
	FILE *pixel;

	Mat save;

	char buf[256]; // 이미지 저장 경로 버퍼
	char buf2[256]; // 이미지 저장 경로 버퍼
	int pixel_info = 0;

	save = img(Range(y_1, y_2), Range(x_1, x_2));
	cvtColor(save, save, CV_RGB2GRAY);
	sprintf(buf, "D:\\%d.jpg", index);
	imwrite(buf, save);

	//픽셀 값 텍스트에 저장
	sprintf(buf2, "D:\\%d.txt", index);
	pixel = fopen(buf2, "w");

	fprintf(pixel, "이미지 사이즈(가로,세로)\n\n", save.cols, save.rows);
	fprintf(pixel, "%dx%d\n\n", save.cols, save.rows);

	for (int y = 0; y < save.rows; y++){
		for (int x = 0; x < save.cols; x++){
			pixel_info = save.at<uchar>(y, x);
			fprintf(pixel, "%03d ", pixel_info);
		}
		fprintf(pixel, "\n");
	}
	fclose(pixel);
}
void onMouse(int event, int x, int y, int flags, void * param)
{
	switch (event) //switch문으로 event에 따라 버튼 종류를 구분
	{
	case EVENT_MOUSEMOVE: // 마우스 이동
		x_2 = x;
		y_2 = y;
		break;

	case EVENT_LBUTTONDOWN:   // 마우스 왼쪽 버튼 누를 때
		x_1 = x;
		y_1 = y;
		draw_start = 1;
		break;

	case EVENT_LBUTTONUP:    // 마우스 왼쪽 버튼 뗄 때
		draw_save = 1;
		break;
	}
}

원본 이미지
영역을 지정하는 중 (파란색 박스)
지정된 영역(빨간색)
추출된 지정 이미지와 픽셀 정보(GrayScale이 적용됨)

 

 

 

추가적으로 본 코드의 onMouse 함수를 보게 되면 여러 인자가 들어가게 된다.

 

인자 값은 setMouseCallback를 통해서 정보를 가져온 것들이며, 자세한 내용은 아래와 같다.

 

 

 

마우스 이벤트 옵션는 아래와 같으니 필요 상황에 맞춰 사용하시면 된다.

 

 

 

위 표의 정보는 아래의 참고 문헌을 인용하여 작성하였습니다.

 

 

< 참고 문헌 >

 

정성환,배종욱 지음. 2017년. OpenCV로 배우는 영상 처리 및 응용. 생능출판사

 

 

 

동영상을 입력받아 전처리를 하여 동영상으로 저장하는 방법을 알아보도록 하겠습니다.

 

동영상을 저장하기 위해선 Opencv의 클래스인 VideoWriter를 사용해야합니다.

 

또한 저장시킬 동영상의 코덱, 프레임, 영상의 크기 등의 정보를 정해야 합니다.

 

 

 

아래 코드는 영상을 입력받아 크기를 500x500으로 줄이고 RGB에서 Grayscale을 적용한 뒤

 

동영상으로 저장하는 과정입니다. 

 

* 본 코드는 Opencv 3.1.0 Version에서 사용하였습니다.

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

using namespace cv;
using namespace std;


int main()
{
	Mat img;
	Mat pre_p;

	VideoCapture capture("D:\\ship.mp4"); // 입력 영상

	double fps = 35; // 영상 프레임
	int  fourcc = VideoWriter::fourcc('D', 'X', '5', '0'); // 코덱 설정


	VideoWriter  save;

	//save.open(입력 영상, 코덱, 프레임, 영상 크기, 컬러)
	save.open("D:\\save_Video.avi", fourcc, fps, Size(500, 500),0);

	while (1) {
		capture >> img;       // 카메라 영상받기
		if (img.empty())
			break;
		
		pre_p = img.clone(); // 영상 복사
		resize(pre_p, pre_p, Size(500, 500), 0, 0, CV_INTER_LANCZOS4); // 이미지 리사이징
		cvtColor(pre_p, pre_p, CV_RGB2GRAY); // Grayscale
		save << pre_p; // 프레임을 동영상으로 저장
		

		imshow("원본 영상", img);
		imshow("전처리 영상", pre_p);

		waitKey(10);
	}
}

 

원본 영상의 한 프레임

 

전처리를 거친 영상의 한 프레임

 

좌측 : 원본 영상 / 우측 : 저장된 영상

딥러닝 모델을 사용해보셨다면 라벨링이라는 단어를 많이 들어보셨을 겁니다.

 

라벨링이란 이미지 속 비슷한 무리의 픽셀끼리 그룹화 하여 구별이 가능하도록 이름을 새기는 것을 말합니다.

 

 

이진화 라벨링은 검은색과 흰색 픽셀로 이루어진 이미지를 라벨링하는 것입니다.

 

더욱 자세하게 아래 그림의 예로 설명하자면 아래의 이미지가 있을 때, 이미지(x, y)   (0,0) -> (1,0) -> (2,0) ... 순으로

 

픽셀을 지나치며 회색의 픽셀을 찾습니다. 회색의 픽셀을 찾으면 주변 픽셀들과의 상관 관계를 찾으며, 이때 4방향과

 

8방향 접근 방법을 사용합니다. 4방향 접근은 픽셀의 좌우, 상단의 상관 관계를 찾는 것이며, 8방향 접근은 좌우, 상단

 

그리고 대각선의 상관 관계를 찾습니다.

 

 

이진화로 된 이미지

 

4방향 접근 방법

 

8방향 접근 방법

 

Opencv에서 제공하는 connectedComponentsWithStats() 함수는 이진화로 표현된 이미지를 이용하여 라벨링을 처리가

 

가능하며, Opencv 3.0이상부터 본 함수를 제공해주고 있습니다.

 

 

connectedComponentsWithStats() 함수의 인자는 총 6가지를 입력해야 합니다.

 

1) 입력 이미지

 

2) 라벨링 결과 이미지

 

3) 라벨링 된 이미지의 정보

 

4) 라벨링 된 이미지의 중심 좌표

 

5) 4방향/8방향

 

6) 타입

 

 

아래 코드를 통해 connectedComponentsWithStats() 함수를 사용해보도록 하겠습니다.

 

* 본 코드는 Opencv 3.1.0 Version에서 사용하였습니다.

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

using namespace cv;
using namespace std;

int main()
{
	Mat image = imread("D:\\과일.jpg");
	Mat image_gray;
	Mat image_bi;
	Mat label_box;

	// 라벨 레이어 변수
	Mat img_label;
	Mat stats;
	Mat centroids;
	int label;


	//이미지 축소/확대
	resize(image, image, Size(500, 500));

	// 복사
	label_box = image.clone();

	//그레이스케일
	cvtColor(image, image_gray, CV_RGB2GRAY);

	//이진화
	threshold(image_gray, image_bi, 230, 255, CV_THRESH_BINARY);
	image_bi = ~image_bi;

	label = connectedComponentsWithStats(image_bi, img_label, stats, centroids, 8, CV_32S);
	for (int j = 1; j < label; j++) 
	{
		int area = stats.at<int>(j, CC_STAT_AREA);
		int left = stats.at<int>(j, CC_STAT_LEFT);
		int top = stats.at<int>(j, CC_STAT_TOP);
		int width = stats.at<int>(j, CC_STAT_WIDTH);
		int height = stats.at<int>(j, CC_STAT_HEIGHT);

		// 라벨링 박스
		rectangle(label_box, Point(left, top), Point(left + width, top + height), Scalar(0, 0, 255), 3);

	}

	imshow("original", image);
	imshow("Binary", image_bi);
	imshow("Label box", label_box);

	waitKey(0);
}

원본 이미지

 

이진화된 이미지

 

라벨링 이미지

 

 

 

< 참고 자료>
https://skkuassa.tistory.com/203

 

opencv , image processing , 2 pass labeling

살펴보기.. openCV에 신기하게도 라벨링이 없는듯하다. 라벨링 (Labeling) 이란 바이너리 이미지 ( 0과 1만 있는 이미지) 로 처리하는 과정 중에서 독립된 개체를 찾아서 라벨을 붙여주는 알고리즘이다. 여기서 배..

skkuassa.tistory.com

 

 

 

이전 글을 통해 이미지의 픽셀에 접근하는 방법을 알아보았습니다.

 

글을 작성하면서 조금 아쉽게 느껴졌던 게 "픽셀에 접근하는 방법을 알겠지만 이를 어떻게 응용할 수 있는 것인가?" 에

 

대한 질문을 나에게 던지면 막상 떠오르는 것이 없었던 것이였습니다. 그래서 며칠을 생각한 결과, 입력받은 텍스트를

 

이미지 속 픽셀값에 변조시켜 이미지에 텍스트를 숨길 수 있는 방법에 대하여 소개하려 합니다.

 

 

 

먼저 암호화 시키는 기술에 대해 간단히 알아보도록 하겠습니다.

 

전달하려는 정보를 이미지나 MP3 파일 등에 암호화 하여 숨기는 기술을 스테가노그래피[Steganographt]이라고 합니다.

 

이 기술은 크게 삽입 기법수정 기법으로 나뉘어집니다.

 

 

삽입 기법은 이미지 파일 데이터를 변경하지 않고 추가 데이터를 파일 앞이나 뒤에 붙이는 방식입니다.

다시 말해,  jpg, png 등의 파일에서 데이터의 시작을 알리는 SOI(Start Of Image) 이전에 또는 끝을 알리는

EOI(End Of Image) 뒤에 전달하려는 정보를 추가하는 것입니다.

 

이러한 방법은 정보를 숨기려는 이미지에 가시적인 영향을 주진 않지만 이미지의 크기가 커지게 됩니다.

 

 

수정 기법은 이미지 파일에서 RGB 값의 최하위 비트(LSB)를 수정하여 추가 데이터를 입력하는 기법입니다.

 

이는 육안으로 알아차리기 힘들기 때문에 많이 이용되는 방식입니다.

 

다시 말해, 픽셀 값의 표현 가능한 수의 범위인 8비트(0~255)에서 특정 값이 255일 경우 최하위비트를 수정해 254 변조시켜도 우리 눈에는 255과 254가 거의 동일하게 보인다는 것입니다.

 

 

 

아래 코드는 스테가노그래피의 수정 기법을 이용한 방법입니다.

 

* 본 코드는 Opencv 3.1.0 Version에서 사용하였습니다.

 

< Image Encording >

#include <opencv2/opencv.hpp> 
#include <iostream>
#include <time.h>
#include <math.h>

using namespace cv;
using namespace std;

int main(int argc, char *argv[]) {

	// 이미지 입력
	/*
	if (argc < 2) {
		printf("이미지 경로를 입력하세요.\n");
		printf("파일 이름, 이미지 경로\n\n");
		exit(0);
	}
	else if (argc > 2) {
		printf("인자를 초과하여 작성하였습니다.\n");
		printf("파일 이름, 이미지 경로\n\n");
		exit(0);
	}
	*/
	
	Mat img = imread("C:\\test\\bridge.bmp");

	int max_text = img.cols*img.rows;  // 텍스트 작성 최대 범위 변수
	int t_length; // 입력 텍스트 길이
	int t_length_clone = 0;
	int inf_lo = 0; // 위치 정보 변수
	int data_text = 0;
	int data_start = 0;
	int x = 0, y = 0; // 좌표
	int cnt = 0;

	char str[10000];
	char e_bit_text[8] = { 0, };
	char e_bit_lo[8] = { 0, };
	char text_length[14] = { 0, };

	if (max_text < 50 * 50) {
		printf("50x50 크기 이상의 이미지를 입력하세요.\n");
		exit(0);
	}

	// 최대 텍스트 입력 수
	if (((max_text - 14) / 25) > 10000)
		max_text = 10000;
	else
		max_text = (max_text - 14) / 25;

	max_text = max_text - 1; // 널문자 제외
	do {
		printf("입력 가능 텍스트 수(띄어쓰기 포함) : %d \n\n", max_text);
		printf("------------------------------------------------------------------------\n");
		printf("TEXT : ");
		fgets(str, sizeof(str), stdin);
		t_length = strlen(str);
		printf("\n\nText length : %d\n", t_length - 1);
		printf("------------------------------------------------------------------------\n");

		if (t_length == 1) {
			printf("텍스트를 입력하지 않았습니다.\n\n");
			printf("다시 입력해주세요.\n\n\n\n");
		}

		else if (t_length  > max_text + 1) {
			printf("입력 가능 텍스트 수를 초과하였습니다.\n\n");
			printf("다시 입력해주세요.\n\n\n\n");
		}
	} while ((t_length == 1) || (t_length > max_text + 1));



	///////////////////////////////////////
	// 14bit 내, 위치 정보 저장  (범위  0~37,777) //
	//////////////////////////////////////

	t_length = t_length - 1; // 널문자 제외
	t_length_clone = t_length; // 복사

    // 입력 텍스트 길이 2진수 14비트로 저장
	for (int t = 0; t < 14; t++) {
		if (t_length_clone > 0) {
			text_length[13 - t] = t_length_clone % 2;
			t_length_clone = t_length_clone / 2;
		}
		else
			text_length[13 - t] = 0;
	}

	// 입력 텍스트 길이 데이터 좌표에 입력
	for (int t = 0; t < 14; t++) {
		if (img.at<Vec3b>(y, x)[0] % 2 == 1) {
			if (text_length[x] == 0)
				img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] - 1;
		}
		else if (img.at<Vec3b>(y, x)[0] % 2 == 0) {
			if (text_length[x] == 1)
				img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] + 1;
		}

		x++;
		if (x == img.cols) {
			x = 0;
			y++;
		}
	}
	srand(time(NULL)); // 랜덤값

	while (cnt < t_length)
	{
		// 첫 번째 정보(텍스트 정보가 Y축에 어느 X축 위치인지에 대한 정보
		inf_lo = rand();
		while (inf_lo > 10)
			inf_lo = inf_lo / 10;
		//inf_lo = inf_lo % 10;
		inf_lo = 9;
		//printf("난수 %d \n", inf_lo);

		// 위치 정보와 텍스트 데이터 위치 사이 좌표 거리
		data_start = inf_lo;

		// 문자 1개 변수에 대입
		data_text = str[cnt];

		// 위치 정보, 문자 8비트로 표현
		for (int t = 0; t < 8; t++)
		{
			// 위치
			if (inf_lo > 0) {
				e_bit_lo[7 - t] = inf_lo % 2;
				inf_lo = inf_lo / 2;
			}
			else
				e_bit_lo[7 - t] = 0;

			// 문자
			if (data_text > 0) {
				e_bit_text[7 - t] = data_text % 2;
				data_text = data_text / 2;
			}
			else
				e_bit_text[7 - t] = 0;
		}

		// 위치 정보 입력 // (y,0)을 기준으로 8픽셀 맨 하위 비트에 입력
		for (int q = 0; q < 8; q++) {

			if (img.at<Vec3b>(y, x)[0] % 2 == 1) {
				if (e_bit_lo[q] == 0)
					img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] - 1;
			}
			else if (img.at<Vec3b>(y, x)[0] % 2 == 0) {
				if (e_bit_lo[q] == 1)
					img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] + 1;
			}

			x++;
			if (x == img.cols) {
				x = 0;
				y++;
			}
		}


		x += data_start;
		if (x >= img.cols) {
			x = x - img.cols;
			y++;
		}

		// 데이터 정보 입력 // (y,8+위치정보)를 기준으로 8픽셀 맨 하위 비트에 입력
		for (int q = 0; q < 8; q++) {

			if (img.at<Vec3b>(y, x)[0] % 2 == 1) {
				if (e_bit_text[q] == 0)
					img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] - 1;
			}
			else if (img.at<Vec3b>(y, x)[0] % 2 == 0) {
				if (e_bit_text[q] == 1)
					img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] + 1;
			}

			x++;
			if (x == img.cols) {
				x = 0;
				y++;
				if (y == img.rows)
					printf("텍스트 입력 가능 범위 초과\n");
			}
		}
		cnt++;
	}

	imwrite("Encording_image.bmp", img);

	printf("텍스트 인코딩 완료!!!\n\n");
}

 

입력 텍스트

 

원본 이미지

 

수정 기법이 적용된 이미지

 

< Image Decording>

#include <opencv2/opencv.hpp> 
#include <iostream>
#include <math.h>

using namespace cv;
using namespace std;

int main() {

	// 데이터 입력
	Mat img = imread("Encording_image.bmp");

	if (img.empty() == 1) {
		printf("이미지를 로드하지 못했습니다.\n\n");
		exit(0);
	}

	int text_length[14] = { 0, };
	int lo_inf[8] = { 0, };
	int text_inf[8] = { 0, };
	int length = 0;
	int cnt = 0;
	int x = 0, y = 0;
	int text = 0;
	int lo = 0;
	int data_start = 0;
	char str[10000] = { 0, };


	// 텍스트 길이 정보 비트 추출
	for (int t = 0; t < 14; t++) {
		if (img.at<Vec3b>(0, t)[0] % 2 == 1)
			text_length[t] = 1;
		else if (img.at<Vec3b>(0, t)[0] % 2 == 0)
			text_length[t] = 0;
	}

	// 텍스트 길이 값
	for (int t = 0; t < 14; t++)
		length += text_length[t] * pow(2, 13 - t);

	// 텍스트 길이가 포함된 14비트 길이
	x = 14;

	while (cnt < length)
	{
		// 위치 정보 비트 추출
		for (int t = 0; t < 8; t++) {
			if (img.at<Vec3b>(y, x)[0] % 2 == 1)
				lo_inf[t] = 1;
			else if (img.at<Vec3b>(y, x)[0] % 2 == 0)
				lo_inf[t] = 0;

			x++;
			if (x == img.cols) {
				x = 0;
				y++;
			}
		}

		// 위치 정보 값
		for (int t = 0; t < 8; t++)
			lo += lo_inf[t] * pow(2, 7 - t);


		x += lo;
		if (x >= img.cols) {
			x = x - img.cols;
			y++;
		}

		// 텍스트 정보 비트 추출
		for (int t = 0; t < 8; t++) {
			if (img.at<Vec3b>(y, x)[0] % 2 == 1)
				text_inf[t] = 1;
			else if (img.at<Vec3b>(y, x)[0] % 2 == 0)
				text_inf[t] = 0;

			x++;
			if (x == img.cols) {
				x = 0;
				y++;
				if (y == img.rows)
					printf("디코딩 오류..\n");
			}
		}

		//텍스트 정보 값
		for (int t = 0; t < 8; t++)
			text += text_inf[t] * pow(2, 7 - t);

		// 정보 저장
		str[cnt] = text;

		//초기화
		lo = 0;
		text = 0;

		cnt++;
	}
	printf("받은 텍스트 : ");
	for (int i = 0; i < length; i++)
		printf("%c", str[i]);
	printf("\n\n");
}

 

Image Decording으로 추출한 텍스트

 

 

코드를 살펴보면, Image Encording을 수행할 때 가장 먼저 텍스트를 입력받고 텍스트의 길이와 각각의 글자들을

바이너리로 표현한다. 이후 아래 그림과 같이 바이너리 값을 원본 이미지의 픽셀에 입력시켜 변조합니다.

 

아래 그림은 이미지의 픽셀단위로 표현한 것이며, 위 결과의 입력 텍스트인 "I am Groot!!!"에서 일부인 글자 I와

 

띄어쓰기가 입력된 바이너리 값을 나타 것입니다. 아래 그림을 참고하면 이해 하는 데 도움이 될 것입니다.

 

 

 

파란색은 입력 받은 텍스트의 길이 정보(14비트) 

 

노란색은 조금의 보안성을 높일 수 있도록 텍스트 데이터를 입력시킬 픽셀의 위치 정보(8비트)

* 위치 정보는 랜덤값을 호출하여 나타냄

 

초록색은 텍스트 데이터의 정보(8비트)

 

 

이미지 픽셀에 입력된 정보의 바이너리 값

 

 

 

아래 구글 드라이브 올라간 파일은 위 코드의 exe파일입니다

 

위 코드를 작성하여 확인하는 것 보다 실행파일을 받아 하는 게 더 빠르게 확인이 가능할 거 같아 올렸습니다.

 

cmd창에서 실행 파일 경로로 들어가 [Image Encording.exe] [입력시킬 bmp 이미지] 순으로 작성하여 실행해 

 

텍스트를 입력한다. 그러면 새로운 Encording_image.bmp 파일이 생성됩니다.

 

그리고 다시 cmd창에서 Image Decording.exe을 실행하면 같은 폴더에 있는 Encording_image.bmp를 입력받아

 

받은 텍스트를 나타냅니다.

 

https://drive.google.com/file/d/1DG-47-y3NrUEZinmrq8rYmONPyN7JQ86/view?usp=sharing

 

Hiding Text.zip

 

drive.google.com

 

 

< 정리글 >

   

   오늘은 이미지의 픽셀에 접근해 입력받은 텍스트를 암호화 시키는 코드를 작성해보았습니다.  약간의 고찰이 있었고,

 

바로 JPG 파일 형식의 이미지를 불러와 픽셀을 변조시킨 후 다시 저장을하면 픽셀 값이 내가 변조한 값과 다르게

나타난다는 것입니다. 확실히 파악하진 못했지만 Opencv에서 사진을 저장할 때 JPG 형식이 압축 기법이 들어간 것이기

 

때문이라고 짐작하고 있습니다.

 

참고로 위 방법을 사용하실거면 압축되지 않은 bmp 형식의 이미지를 이용해야 해야 하며, 중간에 오류가 발생하는

 

부분을 해결하지 못해 이미지의 크기는 50x50 이상인 경우에만 동작하도록 제한을 걸어놓았습니다.

 

추후에 디버깅을 하여 수정된 코드를 올리도록 하겠습니다.

 

 

 

 

< 참고 사이트 >

https://gnu-cse.tistory.com/36

 

이미지 파일 안에 file, text 숨기기 - 스테가노 그래피

사진, 음악, 동영상 등의 일반적인 파일안에 텍스트나 파일을 숨기는 암호화 기법을 steganography라고 한다. 이미지에 파일을 숨기는 경우 어떤 테크닉이 있는지 간단히 알아보자. 이미지 파일에 메세지를 숨기는..

gnu-cse.tistory.com

https://blue-shadow.tistory.com/21

 

스테가노그래피 (Steganography)

참고 링크 : - [보안뉴스] [보안용어 A to Z] 스테가노그래피(Steganography) (2015-08-26 18:01) - [위키백과] 스테가노그래피 스테가노그래피 데이터 은폐 기술 중 하나이며, 데이터를 다른 데이터에 삽입하는..

blue-shadow.tistory.com

 

 

 

오늘은 이미지 속 픽셀(pixel)에 접근하는 방법에 대해 알아보겠습니다.

 

그 전에 픽셀이 무엇인지에 대해서 살펴보도록 하지요.

 

픽셀이란 디지털 영상에서 더이상 쪼갤 수 없는 최소 단위를 뜻하며 픽셀 또는 화소로 얘기합니다.

 

이미지의 가로/세로 사이즈를 줄인다는 말에서 사이즈가 바로 픽셀을 말하는 거죠.

 

아래 사진을 예로 보시게 되시면 640x480 크기의 이미지가 있으며. 픽셀의 수는 640x480 = 307,200 개가 되네요.

 

640x480 이미지

 

 

 

우리가 흔하게 보는 이미지는 대게 RGB인 3개의 채널의 혼합으로 이루어진 이미지 입니다.

 

여기서 RGB는 색상인 Red, Green, Blue를 뜻합니다.

 

이미지속 한 픽셀마다 RGB의 3가지 값이 혼합되어있습니다.

 

이 내용은 아래의 앵무새 그림을 보고 이해하시면 됩니다.

 

 

RGB 벤다이어그램

 

RGB로 이루어진 이미지 

 

 

 

 

그렇다면 오늘의 주제인 이미지 픽셀에 접근하는 방법을 코드를 통해 알아보도록 하겠습니다.

 

* 본 코드는 Opencv 3.1.0 Version에서 사용하였습니다.

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main() {

	Mat img = imread("D:\\학교\\티스토리\\color\\mountain.jpg");

	Mat only_r = img.clone();
	Mat only_g = img.clone();
	Mat only_b = img.clone();

	for (int y = 0; y < img.rows; y++) {
		for (int x = 0; x < img.cols; x++) {	
			// B Channel => img.at<Vec3b>(y,x)[0]
			// G Channel => img.at<Vec3b>(y,x)[1]
			// R Channel => img.at<Vec3b>(y,x)[2]

			// Only Red 
			only_r.at<Vec3b>(y, x)[0] = 0; // zero B
			only_r.at<Vec3b>(y, x)[1] = 0; // zero G

			// Only Green
			only_g.at<Vec3b>(y, x)[0] = 0; // zero B
			only_g.at<Vec3b>(y, x)[2] = 0; // zero R

			// Only Blue
			only_b.at<Vec3b>(y, x)[1] = 0; // zero G
			only_b.at<Vec3b>(y, x)[2] = 0; // zero R
		}
	}

	imshow("원본 이미지", img);
	imshow("Only Red", only_r);
	imshow("Only Green", only_g);
	imshow("Only Blue", only_b);

	waitKey(0);
}

 

위 코드는 한 이미지를 불러와 RGB로 혼합되어 이루어진 이미지를 각각의 채널만을 남기고 나머지 채널은

0 값을 준 것입니다. 이렇게 코드를 돌려보면 위에 앵무새 그림과 동일하게 나오게 됩니다.

 

추가적으로 코드 내 Vec3b는 3 Byte로 된 벡터값을 의미합니다.

 

코드의 결과는 아래와 같습니다.

 

 

원본 이미지

 

Only Red
Only Green
Only Blue

 

 

 

< 정리글 >

 

오늘은 이미지 속 픽셀의 RGB 채널에 접근하는 방법을 알아보았습니다.

 

이 흐름을 이용하면 어떠한 영역에 접근하여 색상을 바꾸어 주거나 이미지 전체의 밝기를 조절할 수 있죠.

 

한가지 확실히 하고 넘어가야 되는건 위 코드의 결과는 RGB에서 1가지의 채널을 제외한 나머지 값을 0으로 만들어 나온

 

결과이며, 만약 각각의 채널을 따로 분리하여 1개 채널로 이미지를 보게 되면 회색 계열로 보이게 됩니다. 

 

 

 

 

우리가 이미지 속 특정 영역만을 추출해야되는 경우가 있습니다.

 

 

차량 번호판 검출를 예로 들겠습니다.

 

 

이미지 전체 탐색

 

이미지 속 특정 영역 탐색

 

첫 번째 그림과 같이 영상 속에서 차량 번호판을 검출하기 위해선

여러 알고리즘을 이용하여 이미지 전체를 탐색해야 됩니다. 

 

 

만약 아파트 입구에 설치된 주차 차단바의 상황라 가정하고 차량이 카메라 영역 속 특정 위치에 멈춰야 한다면

 

이미지 전체를 탐색하지 않고 두 번째 그림과 같이 이미지 속 특정 영역을 탐색해여 차량 번호판을 검출할 수 있으며,

처리 속도는 전체를 탐색하는 것 보다 월등히 차이가 나게 되죠.

 

 

 

 

바로 코드를 통해 Range() 함수를 사용해보겠습니다.

 

* 본 코드는 Opencv 3.1.0 Version에서 사용하였습니다.

#include <opencv2/opencv.hpp>  

using namespace cv;
using namespace std;

int main(){
	
	Mat img = imread("D:\\티스토리\\range\\벚꽃.jpg"); // 1000x1000 이미지
	Mat rect_img;

         // 영역 좌표
	int y1 = 300;
	int y2 = 600;
	int x1 = 250;
	int x2 = 750;

	rect_img = img(Range(y1, y2), Range(x1, x2));

	imshow("원본 이미지", img);
	imshow("결과 이미지", rect_img);

	waitKey(0);
}

 

원본 이미지

 

이미지 영역 분할 과정

 

 

결과 이미지

 

 

 

< 정리글 >

 

오늘은 Range() 함수에 대해서 알아보았습니다.

 

이 함수만을 이해하고 사용하시는 데 큰 어려움은 없을거라 생각합니다.

 

영상처리를 이용한 프로젝트를 하시게 되면 상황에 맞는 사용 이유와 응용 방법에 대해

잘 이해하실 수 있을거라 생각합니다

 

 

 

* 겨울이 다 지나고 벚꽃의 계절이 찾아온거 같네요.   

 

  코로나가 잠잠해지고 있는 것처럼 느껴지지만 이럴 시기일 수록 더욱 조심해야되지 않을까 싶습니다.

 

  모두가 힘든 시기를 잘 견뎌내어 이 상황을 이겨내길 바랍니다.

 

  (위에 벚꽃 사진은 2년전 군항제에 가서 찍은 사진입니다.)

+ Recent posts