#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;


void maded_prewitt(const Mat& image, Mat& result, int thresh) {

	// 수직마스크
	Mat maskX = (Mat_<double>(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1);
	// 수평마스크
	Mat maskY = (Mat_<double>(3, 3) << 1, 1, 1, 0, 0, 0, -1, -1, -1);

	int filterOffset = 3 / 2;

	result = Mat::zeros(image.rows - filterOffset * 2, image.cols - filterOffset * 2, image.type());

	double sumEdgeX;
	double sumEdgeY;
	double magnitude;

	for (int yimage = filterOffset; yimage < image.rows - filterOffset; ++yimage) {
		for (int ximage = filterOffset; ximage < image.cols - filterOffset; ++ximage) {

			sumEdgeX = 0;
			sumEdgeY = 0;
			for (int ymask = -filterOffset; ymask <= filterOffset; ++ymask) {
				for (int xmask = -filterOffset; xmask <= filterOffset; ++xmask) {
					sumEdgeX += image.at<uchar>(yimage + ymask, ximage + xmask) * maskX.at<double>(filterOffset + ymask, filterOffset + xmask);
					sumEdgeY += image.at<uchar>(yimage + ymask, ximage + xmask) * maskY.at<double>(filterOffset + ymask, filterOffset + xmask);
				}
			}
			magnitude = sqrt(pow(sumEdgeY, 2) + pow(sumEdgeX, 2));
			result.at<uchar>(yimage - filterOffset, ximage - filterOffset) = ((magnitude > thresh) ? 255 : 0);
		}
	}

}

void maded_sobel(const Mat& image, Mat& result, int thresh) {

	// 수직마스크
	Mat maskX = (Mat_<double>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
	// 수평마스크
	Mat maskY = (Mat_<double>(3, 3) << 1, 2, 1, 0, 0, 0, -1, -2, -1);

	int filterOffset = 3 / 2;

	result = Mat::zeros(image.rows - filterOffset * 2, image.cols - filterOffset * 2, image.type());

	double sumEdgeX;
	double sumEdgeY;
	double magnitude;

	for (int yimage = filterOffset; yimage < image.rows - filterOffset; ++yimage) {
		for (int ximage = filterOffset; ximage < image.cols - filterOffset; ++ximage) {

			sumEdgeX = 0;
			sumEdgeY = 0;
			for (int ymask = -filterOffset; ymask <= filterOffset; ++ymask) {
				for (int xmask = -filterOffset; xmask <= filterOffset; ++xmask) {
					sumEdgeX += image.at<uchar>(yimage + ymask, ximage + xmask) * maskX.at<double>(filterOffset + ymask, filterOffset + xmask);
					sumEdgeY += image.at<uchar>(yimage + ymask, ximage + xmask) * maskY.at<double>(filterOffset + ymask, filterOffset + xmask);
				}
			}
			magnitude = sqrt(pow(sumEdgeY, 2) + pow(sumEdgeX, 2));
			result.at<uchar>(yimage - filterOffset, ximage - filterOffset) = ((magnitude > thresh) ? 255 : 0);
		}
	}

}

int main()
{
	Mat src, sobel_1;
	Mat grad, prewitt;

	src = imread("dog.png", IMREAD_GRAYSCALE);
	if (src.empty()) { return -1; }

	imshow("Image", src);
	maded_sobel(src, sobel_1, 100);
	imshow("sobel", sobel_1);
	maded_prewitt(src, prewitt, 100);
	imshow("prewitt", prewitt);

	waitKey(0);
	return 0;


}

왼쪽이 소벨 오른쪽이 프리윗 마스크가 적용된 이미지입니다.

 

 

#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;

Mat image, roi;
int mx1, my1, mx2, my2;
bool cropping = false;

void onMouse(int event, int x, int y, int flags, void* param)
{
	if(event == EVENT_LBUTTONDOWN) {
    	mx1 = x;
        my1 = y;
        cropping = true;
    }
    else if(event == EVENT_LBUTTONUP)
    {
    	mx2 = x;
        my2 = y;
        cropping = false;
        rectangle(image, Rect(mx1, my1, mx2 - mx1, my2 - my1), Scalar(0, 255, 0), 2);
        imshow("image", image);
    }
}

int main()
{
	image = imread("dog.png", IMREAD_COLOR);
    Mat clone = image.clone();
    
    if(image.empty())
    	cout << "영상을 읽을 수 없음" << endl;
    imshow("image", image);
    setMouseCallback("image", onMouse);
        
        while(1) {
        	int key = waitKey(100);
            
            if(key == 'q') break;
            else if(key == 'c') {
            roi = clone(Rect(mx1, my1, mx2 - mx1, my2 - my1));
            imshow("press_c", roi);
            }
            else if(key == 's'){
            	resize(image, image, Size(128, 128));
                imshow("image", image);
            }
            eles if(key == 'g') {
            	cvtColor(image, image, COLOR_BGR2GRAY);
                imshow("image", image);
            }
        }
        
        return 0;
 }

 

실행시켰을 경우

 

드래그 하였을 경우

c 클릭시 드래그 영역이 새로운 윈도우 창으로 나타난다.

g를 눌렀을 경우 색상이 컬러에서 흑백으로 바뀐다.

s를 눌렀을 경우 사이즈가 아까 드래그한 사각형의 창보다 작아진 것을 확인할 수 있다. 현재 위의 윈도우 크기는 128*128 이다.

 

 

1. 영상처리, 컴퓨터비전, 컴퓨터그래픽스의 관계에 대하여 설명하세요

영상 처리는 입력 영상을 처리하여 출력 영상을 얻는 기술이다.

컴퓨터 비전은 영상 처리된 영상을 처리하여 정보를 얻어내는 기술이다.

컴퓨터그래픽스 정보를 처리하여 이미지로 만드는 것이다.

 

샘플링과 영자화에 대하여 설명하세요

샘플링은 수많은 데이터로부터 유한한 개수의 데이터를 뽑아내는 것이다.

양자화는 샘플링한 아날로그 형태로되어 있는 신호나 정보를 디지털화하는 작업을 말한다.

 

컴퓨터 비전 응용분야

문자 인식, 생체 인식,  스마트 팩토리 불량 검사, 자율 주행 자동차

 

 

 

 

노이즈 만들기

Mat noise_img = Mat::zeros(src.rows, src.cols, CV_8U);
randu(noise_img, 0, 255);

Mat black_img = noise_img < 10; 
Mat white_img = noise_img > 245; 

Mat src1 = src.clone();
src1.seTo(225, white_img)
src1.seTo(0, black_img);
medianBlur(src1, dst, 5);

at()함수

#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("d:/lenna.jpg", IMREAD_GRAYSCALE);
    imshow("Original Image", img);
	
    for (int r = 0; r < img.rows; r++)
    	for(int c = 0; c < img.cols; ++c)
        	img.at<uchar>(r, c) = img.at<uchar>(r, c) + 30;
            
        imshow("New Image", img);
        waitKey(0);
        return 0;
}

위 코드는 오류가 발생한다.

화소의 값이 30이 더해지면 255를 넘게 되어서 오버플로우가 일어난다.

 

방지를 위해 img.at<uchar>(r, c) = saturate_cast<uchar>(img.at<uchar>(r, c) + 30);

saturate_cast<uchar> 을 사용한다.

 

영상의 밝기를 증가시키는 함수 convertTo()

#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("d:/lenna.jpg", IMREAD_GRAYSCALE);
    imshow("Original Image", img);
    
    Mat oimage;
    img.convertTo(oimage, -1, 1, 30);
    imshow("New Image", oimage);
    waitKey(0);
    return 0;
}

 

 

이진화

threshold(image, dst, threshold_value, 255, THRESH_BINARY);

 

반전 영상 만들기 LUT(Look Up Table)

Mat table(1, 256, CV_8U);

LUT(img1, table, img2);

 

영상 합성

dst = src1 + src2;

 두 영상을 더하면 된다.

 

선형영상합성

cout << "알파값을 입력하시오;
cin >> input;
beta = (1.0-alpha);
addWeight(src1, alpha, src2, beta, 0.0, dst);

 

논리적영상합성

bitwise_and(img1, mask, dst);

 

히스토그램 계산하기

int main()
{
	Mat src = imread("d:/lenna.jpg", IMREAD_GRAYSCALE);
    imshow("Input Image", src);
    int histogram[256] = {0};
    
    for (int y = 0; y < src.rows; y++)
    	for(int x = 0, x < src.cols; x++)
        	histogram[(int)src.at<uchar>(y,x)]++;
            
        for (int count : histogram)
        	cout << count << ",";
        waitKey(0);
        return 0;
}

 

 

히스토그램 그리기

// 히스토그램을 받아서 막대그래프로 그린다.
void drawHist(int histogram[])
{
	int hist_w = 512;  // 히스토그램 영상의 폭
    int hist_h = 400;  // 히스토그램 영상의 높이
    int bin_w = cvRound((double)hist_w / 256);  // 빈의 폭
    
    // 히스토그램이 그려지는 영상(컬러로 정의)선언
    Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(255, 255, 255));
    
    // 히스토그램에서 최대값을 찾는다.
    int max = histogram[0];
    for (int i = 1; i < 256; i++) {
    	if(max < histogram[i])
        	max = histogram[i];
     }
     // 히스토그램 배열을 최대값으로 정규화한다.(최대값이 최대 높이가 되도록).
     for (int i = 0; i < 255; i++) {
     	histogram[i] = floor(((double)histogram[i] / max)*histImage.rows);
     }
     // 히스토그램의 값을 빨강색 막대로 그린다.
     for (int i=0; i<255; i++) {
     	line(histImage, Point(bin_w*(i), hist_h),
        	Point(bin_w*(i), hist_h - histogram[i]), Scalar(0, 0, 255));
     }
     imshow("Histogram", histImage);
            
 }
 
 int main()
 {
 	Mat src = imread("lenna.jpg", IMREAD_GRAYSCALE);
    imshow("Input Image", src);
    int histogram[256] = {0};
    
    for (int y = 0; y <src.rows; y++)
    	for(int x=0; x<src.cols; x++)
        	histogram[(int)src.at<uchar>(y, x)]++;
            
    drawHist(histogram);
    waitKey(0);
    return 0;
 }

 

히스토그램 그리기(컬러)

calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);

 

히스토그램 스트레칭

int stretch(int x, int r1, int s1, int r2, int s2)
{
	float result;
    if (0 <= x && x <= r1) {
    	result = s1 / r1 * x;
     }
     else if (r1 < x && x <= r2) {
     	result = ((s2 - s1) / (r2 - r1)) * (x - r1) + s1;
     }
     else if (r2 < x && x <= 255) {
     	result = ((255 - s2) / (255 - r2)) * (x - r2) + s2;
     }
     return (int)result;
 }
 
 int main()
 {
 	Mat image = imrad("d:/lenna.jpg");
    Mat new_image = image.clone();
    
    int r1, s1, r2, s2;
    cout << "r1를 입력하시오: "; cin >> r1;
    cout << "r2를 입력하시오: "; cin >> r2;
    cout << "s1를 입력하시오: "; cin >> s1;
    cout << "s2를 입력하시오: "; cin >> s2;
    
    for (int y = 0; y < image.rows; y++) {
    	for (int x = 0; x < image.cols; x++) {
        	for (int c = 0; c < 3; c++) {
            	int output = stretch(image.at<Vec3b>(y, x)[c], r1, s1, r2, s2);
                new_image.at<Vec3b>(y, x)[c] = saturate_cast<uchar>(output);
                }
            }
        }
        ... //결과 영상 출력
  }

 

히스토그램 평활화

int main()
{
	Mat src = imread("d:/crayfish.jpg", IMREAD_GRAYSCALE);
    if (src.empty()) { return -1;}
    
    Mat dst;
    equalizeHist(src, dst);
    
    imshow("Image", src);
    imshow("Equalized", dst);
    waitKey(0);
    return 0;
}

 

전경과 배경 분리

using namespace std;
using namespace cv;

int main()
{
	Mat src, dst;
	src = imrad("d:/plane.jpg", IMREAED_GRAYSCALE);
    imshow("Image", src);
    if(!src.data) { return -1;}
    
    Mat threshold_image;
    threshold(src, threshold_image, 100, 255, THRESH_BINARY);
    imshow("Thresholded", threshold_image);
    waitkey(0);
    return 0;
}

 

향상된 이진화 방법

threshold(src, threshold_image, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

 

평균값 필터링의 구현

Mat mask(3, 3, CV_32F, weights);
Mat blur;
filter2D(image, blur, -1, mask);
blur.convertTo(blur, CV_8U);

 

평균값 필터링

blur(src, dst, Size(11, 11));

 

가우시안 필터링

#include <opencv2/opencv.hpp>
using namespace cv;

int main()
{
	Mat src = imraed("d:/lenna.jpg", 1);
    Mat dst;
    imshow("src", src);
    
    for (int i = 1; i<61; i = i + 2)
    {
    	GaussianBlur(src,dst,Size(i, i), 0, 0);
        imshow("Gaussian filter", dst);
        waitKey(1000);
     }
}

 

 

샤프닝

float weights1[9] = { -1, -1, -1, -1, 5, -1, -1, -1, -1};

float weights2[9] = { -1, -1, -1, -1, 9, -1, -1, -1, -1};



Mat mask1 = Mat(3, 3, CV_32F, weights1);

Mat mask2 = Mat(3, 3, CV_32F, weights2);



filter2D(src, dst1, -1, mask, Point(-1, -1), 0, BORDER_DEFAULT);

filter2D(src, dst2, -1, mask, Point(-1, -1), 0, BORDER_DEFAULT);

 

 

소벨 

int main()
{
    Mat src;
    Mat grad;
    int scale = 1;
    int delta = 0;
    src = imread("d:/lenna.jpg", IMREAD_GRAYSCALE);
    if (src.empty()) { return -1; }
    Mat grad_x, grad_y;
    Mat abs_grad_x, abs_grad_y;
    Sobel(src, grad_x, CV_16S, 1, 0, 3, scale, delta, BORDER_DEFAULT);
    Sobel(src, grad_y, CV_16S, 0, 1, 3, scale, delta, BORDER_DEFAULT);
    convertScaleAbs(grad_x, abs_grad_x);
    convertScaleAbs(grad_y, abs_grad_y);
    addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
    imshow("Image", src);
    imshow("Sobel", grad);
    waitKey(0);
    return 0;
 }

 

라플라시안

GaussianBlur(src, src, SIZE(3, 3), 0, 0, BORDER_DEFULAT);
Mat abs_dst;
Laplacian(src, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT);
convertScaleAbs(dst, abs_dst);

 

 

 

 

리지드 바디가 없을 때
리지드 바디가 적용된 경우

 

트리거 인식을 위해 객체는 리지드 바디 컴포넌트를 가지고 있어야 한다. 그렇지 않으면 트리거에 들어와도 인식되지 않는다.

 

 

유니티에서 UI버튼을 마우스를 이용해 클릭하기 위해서는 Standalone Input Module이 컴포넌트로 구현 되어 있어야 가능하다.

 

유니티 인스펙터 창에서 게임오브젝트 값을 넣을 수 있게 하기위해서는 2가지 방법이 있다.

하나는 접근 지정자를 public으로 하면 된다.

나머지 한가지 방법으로는 [SerializeField] 을 활용하면 priivate 접근지정자를 사용하면 된다. 

<화소처리>

화소의 값이 오버플로우 되지 않기 위해 saturate_cast<uchar>() 을 사용한다.

ex) saturate_cast<uchar>(img.at<uchar>(r.c) + 30);

 

영상의 밝기를 증가시키는 convertTo() 함수

ex) img.convert(oimage, -1, 1, 30);    1은 알파로 곱하기, 30은 베타로 더하기를 나타낸다.

 

이진화

double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)

매개 변수 설명 
src 입력 영상. (1채널이어야 한다)
dst 출력 영상
thresh 임계값(이 값을 기준으로 이진화 된다.)
maxval 가능한 최대 출력값
type 이진화 종류, 보통 THESH_BINARY

 

선형영상합성

ex) addWeighted(src1, alpha, src2, beta, 0,0, dst);

 

논리적인 영상 합성

ex) bitwise_and(평범한 사진, 검정색 배경에 하얀색 도형이 그려져 있는 사진, dst)

앤드 연산으로 인해 하얀색 도형의 부분만 평범한 사진의 일부를 나타낸다.

 

<히스토그램>

히스토그램

- 관측 값의 개수를 겹치지 않는 다양한 계급으로 표시하는 것

장점: 화소값들의 분포를 한눈에 볼 수 있다.

 

히스토그램 스트래징

- 히스토그램의 분포가 좁아서 영상의 대비가 좋지 않은 영상의 화질을 개선할 수 있는 알고리즘

 

 

히스토그램 평활화

- 화소값의 분포를 나타내는 히스토그램이 균일하게 되도록 변환하는 처리이다.

ex) equalizeHist(src,dst)

 

threshold에서 CV_THRESH_BINARY | CV_THRESH_OTSU

 

<공간 필터링>

 

공간 필터링

- 인접 화소들의 값을 참조하여 화소의 값을 변경하는 처리

 

컨벌루션

- 중심 화소의 값을 인접 화소값들의 가중 합으로 대체하는 연산이다.

 

자신에게 필요하여 선택된 정보를 전경, 배후에 있는 제외된 정보를 배경이라고 부른다.

 

미분 - 함수의 순간 변화율 

 

평균값 필터링

ex) blur(src, dst, Size(11, 11));

 

가우시안 필터링

ex) GaussianBlur(src, dst, Size(i, i), 0, 0);

 

샤프닝(sharpening)

- 출력화소에서 이웃 화소끼리 차이를 크게 해서 날카로운 느낌이 나게 만드는 것

- 영상의 세세한 부분을 강조할 수 있으며, 경계 부분에서 명암대비가 증가되는 효과

- 블러링은 부드러운 영상을 만듦. 명함대기가 감소

샤프닝 마스크

- 마스크 원소들의 값 차이가 커지도록 구성

- 마스크 원소 전체합이 1이 되어야 입력영상 밝기가 손실 없이 출력영상 밝기로 유지

 

에지 검출

에지는 영상에서 화소의 발기가 급격하게 변하는 부분이다.

 

밝기 변화율: 에지의 강도

기울기: 에지의 방향

 

그래디언트(gradient, 변화율)

- 그래디언트 방향은 경계선에 수직

 

1차 미분을 이용한 에지 검출

Roberts, Prewitt, Sobel

 

 

샤르 마스크(Scharr Mask)

소벨 연산자의 단점: 작은 크기의 커널을 사용할 때 정확도가 떨어짐. 큰 커널인 경우는 어느 정도 극복

3x3짜리 필터를 사용해서 영상측정값을 구할 땐, 항상 사용해야

 

2차 미분 마스크

라플라시안 에지 검출

 

LoG(Laplacian of Gaussian)

- 라플라시안은 노이즈에 민감한 단점

- 따라서, 먼저 노이즈를 제거 후 라플라시안을 수행 -> 노이즈에 강한 에지 검출이 가능

 

DoG(Difference of Gaussian)

-단순한 방법으로 2차 미분 계산

-가우시안 스무딩 필터링의 차이를 이용해서 에지를 검출하는 방법

 

주성분 분석

 

캐니 에지 검출

1. 블러링을 통한 노이즈 제거 (가우시안 필터링)

2. 화소 기울기(gradiant)의 강도와 방향 검출(소벨 마스크)

3. 비최대치 억제(non-maximum suppression)

4. 이력 임계값(hysteresis threshold)으로 에지 결정

 

 

Canny(src_image, detected_edges, lowThreshold, highThreshold, kernel_size);

 

입력 영상

출력 영상

하위 임계값

상위 임계값

3(내부적으로 사용되는 Sobel 마스크의 크기)로 정의한다.

 

 

<기하학적 변환>

 

기하학적 변환(geometric trasfortation)은 영상을 이동하거나 영상의 모양을 변형하는 처리

영상의 정확한 인식을 위해 기하학적 변환이 필요하다

 

역방향 사상

목적영상의 좌표를 중심으로 역변환을 계산하여, 그 좌표에 해당하는 입력 영상의 좌표를 찾아서 화소값을 가져오는 방식

 

역방향 사상 -> 영상을 축소할 때에 오버랩의 문제가 발생한다.

 

해결책: 보간법이 필요

목적영상에서 홀의 화소들을 채우고, 오버랩이 되지 않게 화소들을 배치하여 목적영상을 만드는 기법

 

최근접 이웃 보간법

변화된 위치와 가장 가까운 화소값을 사용하는 방법

 

선형 보간 사용하는 이유

최근접 이웃 보간법은 확대비율이 커지면, 모자이크 현상 혹은 경계부분에서 계단현상 발생

직선의 선상에 위치한 중간 화소들의 값은 직선의 수식을 이용해서 쉽게 계산

 

양선형 보간법 - 선형 보간을 두 번에 걸쳐서 수행하기에 붙여진 이름

 

warpAffine (src, dst, M, dsize, flags = INTER_LINEAR)

 

M: 어파인변환 행렬

INTER_NEAREST = 0  // 최근접 보간법

INTER_LINEAR = 1     // 양선형 보간법

INTER_CUBIC = 2     // 3차 보간법

 

int main()
{
	Mat src = imread("d:/lenna.jpg", IMREAD_COLOR);
	Mat dst = Mat();
	Size dsize = Size(src.cols, src.rows);
    Point center = Point(src.cols / 2.0, src.rows / 2.0);
    Mat M = getRotationMatrix2D(center, 45, 1.0);
    warpAffine(src, dst, M, dsize, INTER_LINEAR);
    imshow("Image", src);
    imshow("Rotated", dst);
    waitKey(0);
    return 1;
}

레나라는 사진이 45도 회전하게 된다.

 

원근감(Depth Feeling)

동일한 크기의 물체라도 시점으로부터 멀리 있는 것은 작게 보이고 가까운 것은 크게 보임

 

소실점(VP: Vanishing Point)

원근투상 결과 평행선이 만나는 점(시점 높이)

 

warp: 경사

 

 

warpPerspective(src, dst, M, dsize)

입력 영상
출력 영상

3x3 변환 행렬

dsize 출력 영상의 크기

 

int main()
{
	Mat src = imread("d:/book.jpg");
    
	Point2f inputp[4];
	inputp[0] = Point2f(30, 81);
	inputp[1] = Point2f(274, 247);
	inputp[2] = Point2f(298, 40);
	inputp[3] = Point2f(598, 138);
	Point2f outputp[4];
	outputp[0] = Point2f(0, 0);
	outputp[1] = Point2f(0, src.rows);
	outputp[2] = Point2f(src.cols, 0);
	outputp[3] = Point2f(src.cols, src.rows);
    
	Mat h = getPerspectiveTransform(inputp, outputp);
	Mat out;
	warpPerspective(src, out, h, src.size());
	imshow("Source Image", src);
	imshow("Warped Source Image", out);
	waitKey(0);
}

 

결과

<모폴로지> - 형태학

- 영상의 객체들의 형태(shape)를 분석하고 처리하는 기법

- 영상의 경계, 골격, 블록 등의 형태를 표현하는데 필요한 요소 추출함

- 영상 내에 존재하는 객체의 형태를 조금씩 변형시킴으로써 영상 내에서 불필요한 노이즈 제거하거나 객체를 뚜렷하게    함

 

 

침식 연산

- 객체를 침식시키는 연산

- 객체의 크기를 축소, 배경을 확장

- 영상 내에 존재하는 노이즈 같은 작은 크기의 객체 제거가 가능

- 소금-후추 노이즈와 같은 임펄스 노이즈를 제거

 

erode: 침식하다

int main()
{
	Mat src, dst, erosion_dst, dilation_dst;
    src = imread("d:/test.png", IMREAD_GRAYSCALE);
    
    threshold(src, dst, 127, 255, THRESH_BINARY);
    imshow("dst", dst);
    
    Mat element = getStructuringElement(MORPH_RECT,
    Size(3, 3),
    Point(-1, -1));
    
    erode(dst, erosion_dst, element);
    imshow("Erosion Demo", erosion_dst);
    waitKey(0);
    return 0;
}

결과를 통해 침식이 더 심해진 것을 확인할 수 있다.

팽창 연산

객체를 팽창시키는 연산

- 객체의 최외곽 화소를 확장시키는 기능 -> 객체 크기가 확대, 배경이 축소

- 객체 팽창으로 객체 내부의 빈 공간도 메워짐

    -> 객체 내부 노이즈 제거

 

열림 연산

침식 연산 -> 팽창 연산

 

int main() 
{
    Mat input_image = (Mat_<uchar>(8, 8) <<
    0, 0, 0, 0, 0, 255, 0, 0,
    0, 255, 255, 255, 0, 0, 0, 0,
    0, 255, 255, 255, 0, 0, 255, 255,
    0, 255, 255, 255, 0, 255, 255, 255,
    255, 255, 255, 0, 0, 255, 255, 255,
    0,0, 0, 0, 255, 255, 255, 255,
    0, 0, 0, 0, 255, 255, 255, 0, 
    0, 0, 0, 0, 0, 0, 0, 0 );
    Mat kernel = (Mat_<int>(3, 3) <<
    1, 1, 1, 1, 1, 1, 1, 1, 1);
    Mat output_image;
    morphologyEx(input_image, output_image, MORPH_OPEN, kernel);
    const int rate = 50;
    resize(input_image, input_image, Size(), rate, rate, INTER_NEAREST);
    imshow("Original", input_image);
    resize(output_image, output_image, Size(), rate, rate, INTER_NEAREST);
    imshow("Open", output_image);
    waitKey(0);
    return 0;
}

닫힘 연산

- 팽창 연산 -> 침식 연산

- 팽창 연산으로 객체가 확장되어서 객체 내부의 빈 공간이 메워짐

- 침식 연산으로 다음으로 확장되었던 객체의 크기가 원래대로 축소

 

 

형태학적인 그라디언트

외각선 추출

외곽선을 추출하려면 먼저 영상에 침식 연산을 적용한다. 앞에서 설명한 바와 같이 영상 내의 물체는 한 화소 축소된다.

그 다음에 원 영상에서 침식 영상을 뺀다. 그 결과는 물체의 외곽선만을 보여주는 영상이 된다.

 

int main()
{
    Mat src, dst, open, close;
    src = imread("d:/letterj.png", IMREAD_GRAYSCALE);
    imshow("src", src);
    Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
    morphologyEx(src, open, MORPH_GRADIENT, element);
    imshow("Gradient Demo", open);
    waitKey(0);
    return 0;
}

골격화

골격화는 골격선을 구하는 연산

간단하지만 결과가 좋지 않은 경우가 다수 있다.

 

<컬러 영상 처리>

 

BGR 영상에서 B, G, R을 분리하여 윈도우에 표시하려면 -> split() 함수를 사용한다.

 

int main()
{
	Mat image;
    image = imread("d:/rcube.jpg", CV_LOAD_IMAGE_COLOR);
    
    Mat bgr[3];
    split(image, bgr);
    
    imshow("src", image);
    imshow("blue", bgr[0]);
    imshow("green", bgr[1]);
    imshow("red", bgr[2]);
    waitKey(0);
    return 0;
}

 

cvtColor(src, dst, code);

src : 입력영상

dst: 출력 영상

code: 색상 공간 변환 코드이다.

COLOR_BGR2HSV: BGR 컬러 모델을 HSV 컬러 모델로 변경한다.

 

#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("d:/lenna.jpg");
	Mat img_color;
	applyColorMap(img, img_color, COLORMAP_HOT);
	imshow("img_color", img_color);
	waitKey(0);
}

컬러를 통한 객체 분할

int main()
{
    Mat img = imread("d:/image1.jpg", IMREAD_COLOR);
    if (img.empty()) { return -1; }
    Mat imgHSV;
    cvtColor(img, imgHSV, COLOR_BGR2HSV);
    Mat imgThresholded;
    inRange(imgHSV, Scalar(100, 0, 0),
    Scalar(120, 255, 255), imgThresholded);
    imshow("Thresholded Image", imgThresholded);
    imshow("Original", img);
    waitKey(0);
    return 0;
}

 

크로마키 기법

크로마키 합성(Chroma Key Composing)은 색조를 이용하여 2개의 영상 또는 비디오 스트림을 합성하는 기술이다.

 

int main()
{
    Mat img = imread("d:/chroma.jpg", IMREAD_COLOR);
    Mat img2 = imread("d:/beach.png", IMREAD_COLOR);
    Mat converted;
    cvtColor(img, converted, COLOR_BGR2HSV);
    Mat greenScreen = converted.clone();
    inRange(converted, Scalar(60-10, 100, 100), Scalar(60+10, 255, 255), 
    greenScreen);
    Mat dst, dst1, inverted;
    bitwise_not(greenScreen, inverted);
    bitwise_and(img, img, dst, inverted);
    bitwise_or(dst, img2, dst1, greenScreen);
    bitwise_or(dst, dst1, dst1);
    imshow("img", img);
    imshow("green", greenScreen);
    imshow("dst", dst);
    imshow("dst1", dst1);
    waitKey(0);
    return 0;
}

 

<주파수 영역 처리>

헤르츠(Hz) 

주파수를 표현하는 단위, 1초 동안에 진동하는 횟수

 

영상처리에서는 공간 주파수(spatial frequency)개념 사용

확장된 의미에서 주파수

이벤트가 주기적으로 재발생하는 빈도

 

공간 주파수 - 밝기의 변화 정도에 따라서 고주파 영역 / 저주파 영역으로 분류

 저주파 공간 영역

 - 화소 밝기가 거의 변화가 없거나 점진적으로 변화하는 것

 - 영상에서 배경 부분이나 객체의 내부에 많이 있음

 

고주파 공간 영역

- 화소 밝기가 급변하는 것

- 경계부분이나 객체의 모서리

 

고주파 성분 제거한 영상 -> 경계 흐려진 영상

고주파 성분만 취한 영상 -> 경계나 모서리만 포함하는 영상 즉, 에지 영상

 

푸리에 변환

- 시간에 따라 변화하는 함수를 분해하여 그 안에 들어있는 주파수 성분을 추출하는 변환

 

 

 

gotoPlayerAndFertilize() 함수 사용 모습

 

NPC에 적용한 스크립트와 Nav Mesh Agent

 

Nav Mesh Agent는 콜라이더 처럼 테두리가 있다.

테두리가 객체보다 크면 객체가 장애물을 테두리가 클 수록 크게 돌아간다.

사이즈를 적당하게 조절하지 않으면 객체가 공중에 떠서 이동하는 경우도 있었다.

 

Navigation_farmer

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class Navigation_farmer : MonoBehaviour
{
    Rigidbody myRigid;
    [SerializeField] private float moveSpeed;
    private static Vector3 originPos;
    private static NavMeshAgent agent;
    [SerializeField] private Transform tf_Destination;
    private bool justone;
    private static bool goto_on;

    DateTime StartTime;
    DateTime EndTime;

    // Start is called before the first frame update
    void Start()
    {
        myRigid = GetComponent<Rigidbody>();
        agent = GetComponent<NavMeshAgent>();
        originPos = transform.position;
        StartCoroutine(checkLotation());
        justone = true;

        gotoPlayerAndFertilize();
    }


    private IEnumerator checkLotation()
    {
        while(true)
        {
            if (Vector3.Distance(transform.position, originPos) < 0.5f)
            {
                if(LookAtPlayer.player_in_farmerTrigger == false)
                    transform.rotation = Quaternion.Euler(0, 90, 0);
                if (goto_on == false)
                    farmer_animator.IsMove = false;
                LookAtPlayer.ShowUI = true;
            }
            else
                LookAtPlayer.ShowUI = false;

            yield return new WaitForSeconds(1f);
        }
    }

    public  void gotoPlayerAndFertilize()
    {
        goto_on = true;
        farmer_animator.IsMove = true;
        agent.SetDestination(tf_Destination.position);
        
        StartCoroutine(waitMeeting());



    }

    static public void gotoOrigin()
    {
        goto_on = false;
        agent.SetDestination(originPos);
        farmer_animator.IsMove = true;
    }

    private IEnumerator waitMeeting()
    {
        while(true)
        {
            if(Vector3.Distance(transform.position, tf_Destination.position) < 0.3f)
            {
                farmer_animator.IsMove = false;
                farmer_animator.Fertilize_on = true;
                break;
            }
            yield return new WaitForSeconds(1f);
            
        }
    }
}

 

 

네비게이션을 사용하기 위해선 map 을 Navigation Static 설정해주어야 한다.

 

다음 bake를 통해 지형을 ai가 인식할 수 있도록 해야한다.

 

<bake를 하기위한 경로>

 

탭에서 Window를 선택 -> AI 를 선택 -> Navigation 선택 

눌렀으면 Inspector 창 옆에 Navigation이 생성된다.

 

 

 

 

<추가 정보>

 [SerializeField] 를 변수 앞에 붙이면 Unity Inspector 창에서 값을 볼 수 있게되며 설정 또한 할 수 있게된다.

 

Vector3.Distance(transform.position, originPos) < 0.5f

위 함수는 2가지 객체의 position 정보를 받아서 위치를 비교하여 값을 반환하는 함수이다.

 

 

agent = GetComponent<NavMeshAgent>();

agent.SetDestination(originPos);

 

위 agent 객체를 통해 목표점으로 이동할 수 있다.

 

 

비료를 준 후 원래 자리로 이동을 위해 아래 함수에 추가된 gotoOrigin()

    public void invisible_Fertilize_box()
    {
        if (Fertilize_on)
        {
            Fertilize_on = false;
            Navigation_farmer.gotoOrigin();
        }
    }

 

#include <opencv2/imgproc.hpp>

#include <opencv2/highgui.hpp>

#include <opencv2/imgproc/imgproc.hpp>

#include <iostream>

#include<time.h>

#include<conio.h>

 

using namespace cv;

using namespace std;

 

 

void put_string(Mat& frame, string text, Point pt)

{

    Point shade = pt + Point(2, 2);

    int font = FONT_HERSHEY_SIMPLEX;

    putText(frame, text, shade,font, 0.7, Scalar(0, 0, 0), 2);        // 그림자 효과

    putText(frame, text, pt, font, 0.7, Scalar(120, 200, 90), 2);   // 작성 문자

}

 

void put_circle(Mat& frame)

{

    int font = FONT_HERSHEY_SIMPLEX;

    circle(frame, Point(15, 240), 7, Scalar(0, 0, 255),-1);

}

 

int main(int argc, char** argv)

{

    

    VideoCapture capture;

    capture.open("video.mp4"); // 동영상 파일 개방

    CV_Assert(capture.isOpened());

    double frame_rate = capture.get(CAP_PROP_FPS); // 초당 프레임 수

    int delay = 1000 / frame_rate;        // 지연시간

    int frame_cnt = 0;

    Mat frame;

    time_t now;

    bool save_on = false;

    bool capture_on = false;

    bool stop_on = false;

    int key;

    int i = 0;

 

 

    Size size = Size((int)capture.get(CAP_PROP_FRAME_WIDTH), (int)capture.get(CAP_PROP_FRAME_HEIGHT));

    int fourcc = VideoWriter::fourcc('D', 'X', '5', '0');   // 압축 코덱 설정

 

    capture.set(CAP_PROP_FRAME_WIDTH, size.width);

    capture.set(CAP_PROP_FRAME_HEIGHT, size.height);

 

    VideoWriter writer; // 동영상 파일 저장 객체

    writer.open("video_file.avi", fourcc, CAP_PROP_FPS, size);

    CV_Assert(writer.isOpened());

 

 

 

 

    while (capture.read(frame)) // 프레임 반복 재생

    {

        now = time(NULL);

 

        key=waitKey(delay);        // 프레임간 지연시간 지정

 

        if (key == 'e')

        {

            save_on = true;

            stop_on = false;

            capture_on = false;

        }

        else if (key == 's')

        {

            save_on = false;

            stop_on = true;

            capture_on = false;

        }

        else if (key == 'c')

        {

            i++;

            string str = "capture" + to_string(i) + ".jpg";

            imwrite(str, frame);

            save_on = false;

            stop_on = false;

            capture_on = true;

 

        }

        else if (key == 27) break;

 

        if (frame_cnt < 100);

        else if ((frame_cnt < 200)) frame -= Scalar(0, 0, 100);

        else if ((frame_cnt < 300)) frame += Scalar(100, 0, 0);

        else if ((frame_cnt < 400)) frame = frame * 1.5;

        else if ((frame_cnt < 500)) frame = frame * 0.5;

 

 

 

 

        if (save_on)

        {

            writer << frame;

            put_string(frame, "SaveVideo", Point(10, 20));

        }

        else if(stop_on)

            put_string(frame, "StopSave", Point(10, 20));

        else if(capture_on)

            put_string(frame, "CaptureVideo", Point(10, 20));

 

        if (save_on && (int)now % 2 == 0)

        {

            put_circle(frame);

        }

 

        imshow("동영상 파일읽기", frame);

 

    }

 

    //waitKey(0);

}

 

OnEnable 정의

인스펙터뷰에서 체크를 하게되면 게임 오브젝트를 활성화 할 때 실행된다. 활성화 될 때마다 호출 됩니다.

따라서 위의 메소드는 객체를 비활성화 하거나 활성화로 상태를 자주 바꿀 때 유용하게 사용할 수 있습니다.

아래의 사이트를 통해 더 자세하게 공부 할 수 있습니다.

https://itmining.tistory.com/47

 

[유니티 기초] 유니티 생명주기 (Life Cycle)

이 글은 PC 버전 TISTORY에 최적화 되어있습니다. 유니티의 생명주기 유니티는 사용자가 호출하지 않아도 호출되는 함수들이 있습니다. 그 함수의 호출 주기를 유니티의 생명주기(LifeCycle)이라고 ��

itmining.tistory.com

 

코루틴 함수

요즘 유니티 코드를 작성하다 보면 게임을 조금씩 완성해 나가다 보면 Update에 코드를 많이 사용하게 되는데, 그럴 수록 프로세스에 부담이 갈 것을 느꼈다. 그러던 중 코루틴 함수를 만났다. 대부분의 코드가 메 프레임마다 상태를 업그레이드 할 필요는 없다. 따라서 코루틴 함수를 활용하는 것은 유니티로 게임 개발하는 사람들에게는 매우 중요하다 느낀다.

기본적으로 아래의 코드 처럼 작성해서 활용하면 된다.

 

private IEnumerator DelayEffect()

    {

        while (true)

        {

            method();

            /** 1초 딜레이 **/

            yield return new WaitForSeconds(1f);

        }

    }

 

코루틴 함수 사용 방법

StartCoroutine(Method());

 

자주 했던 실수

OnEnable()에 코루틴 함수를 적용 시키고 Start에 초기화를 진행하면 OnEnable()이 더 빨리 실행되기 때문에 코드가

재대로 동작 하지 않을 수 있다.

click Editor Type -> Outliner

Click Actions and select action which you want to delete

 

lookAt을 사용해 사용자를 npc가 바라보게 하였다.

그런데 한가지 문제가 발생했다.

npc가 플레이어를 잘 바라본다, 하지만 위를 바라보고 있었다.

하지만 정면을 바라봐야 제대로 되었다 할 수 있기 때문에 정면을 보게끔 코드를 수정하였다.

플레이어의 x,z축만 가져오고 y축은 사용하지 않는 것이다. 

 

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

 

public class LookAtPlayer : MonoBehaviour

{

    public GameObject target;

    private Vector3 targetPosition;

 

    private void OnTriggerStay(Collider other)

    {

        // other.gameObject == target.gameObject

        if (other.tag == "Player")

        {

            targetPosition = new Vector3(other.transform.position.x, transform.position.y, other.transform.position.z);

            transform.LookAt(targetPosition);

        }

    }

}

 

 

+ Recent posts