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

        }

    }

}

 

 

오큘러스 고에서 검정 화면이 계속해서 나타났다.

시스템 오류로 인식하고 리셋 해버렸다

지금 보니 안면 인식 센서에서 얼굴 인식 실패 때문에 일어난 오류였다.

그래서 모든 세팅을 다시하고 유니티에서 빌드 하려는 순간 

유니티가 오큘러스 고를 찾지 못했다.

그 이유는 개발자 모드가 꺼저 있었기 때문이다. 

<개발자 모드 키는 방법>

오큘러스 고 앱 -> 설정 -> 연결된 기기 화살표 내림 -> 설정 더보기 -> 개발자 모드 

안녕하세요 이번 시간에는 저번 시간에 이어서 진행됩니다.

 

먼저 이번 시간을 통해 할 수 있는 것 들을 보여드리겠습니다.

 

 

위의 이미지에서 물뿌리개를 사용해 물을 주는 애니메이션과 비료통을 흔들어 비료를 주는 애니메이션을 

확인할 수 있습니다.

 

 

이번에는 목표지점으로 이동하기

 

위 xyz 화살표는 도착지라는 객체로 해당 지점으로 npc가 이동하게 됩니다. 

 

 

저번 시간에 만들었던 LookAtPlayer에 변화가 있습니다.

 

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class LookAtPlayer : MonoBehaviour
{
    // 밑의 변수로 플레이어가 근처에 있는지 알 수 있다.
    public static int player_in;
    public static bool off_panel;
    // 유니티 인스펙터 창에서 패널을 넣어주자
    public GameObject Intro_panel;
 
    private void OnTriggerEnter(Collider other)
    {
        if (other.tag == "Player" && farmer_animator.IsMove == false)
        {
            player_in = 1;
            Intro_panel.SetActive(true);
        }
    }
    private void OnTriggerStay(Collider other)
    {
        if (other.tag == "Player" && farmer_animator.IsMove == false)
        {
            // 플레이어를 바라보게 만든다
            transform.LookAt(other.transform);
            Intro_panel.SetActive(true);
 
        }
        else
        {
            Intro_panel.SetActive(false);
        }
 
    }
    private void OnTriggerExit(Collider other)
    {
        if (other.tag == "Player")
        {
            player_in = 0;
            Intro_panel.SetActive(false);
        }
    }
 
    
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter

 

IsMove라는 bool 변수가 트리거의 조건에 추가되었습니다.

위의 변수는 NPC농부가 이동할 때  TRUE가 되는 값입니다.

NPC가 이동 중에는 플레이어에 반응 하지 않도록 하였습니다.

 

NPC의 이동을 보여드리겠습니다.

 

이동하는 모습을 보면 방향 커서를 따라 이동하는 것을 볼 수 있습니다.

커서는 empty object로 태그를 target이라고 정했습니다.

 

 

움짤을 만들어서 올리니 생각난게 npc에 새로운 애니메이션을 추가한 게 있습니다.

따라서 모델을 다시 한번더 올리도록 하겠습니다.

 

farmer_02.fbx
0.44MB

추가된 애니메이션으로는 뛰기, 비료주기, 삽질하기, 물 주기입니다.

 

비료 통이나, 삽 같은 오브젝트도 같이 사용해보려 해 보았지만

시간만 날렸네요 ㅠㅠ 유니티에 따로 추가하도록 하겠습니다.

 

비료주기 

 

 

 

밑의 코드는 automove라는 코드입니다. 

영어 뜻 그대로 자동 움직임이라는 뜻인데

도착지를 정하면 도착지로 이동하는 코드입니다.

farmer_animator라는 스크립트에 있는 변수를 사용하고 있어서

밑의 스크립트를 먼저 만들게 되면 오류가 생기게 될 수 있습니다.

지금은 무시하셔도 됩니다.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class automove : MonoBehaviour
{
    public GameObject target;
 
    public GameObject Fertilize_box;
    public GameObject Water_box;
    public GameObject Shovel;
 
    // Start is called before the first frame update
    void Start()
    {
        
    }
 
    // Update is called once per frame
    void Update()
    {
        MoveToTarget();
        showing_Fertilize_Box();
        showing_Water_box();
    }
 
    void MoveToTarget()
    {
        if (target.transform.position != Vector3.MoveTowards(transform.position, target.transform.position, 1f))
        {
            transform.position = Vector3.MoveTowards(transform.position, target.transform.position, 1f); // 현위치, 도착점, 속도
 
            transform.LookAt(target.transform);
       
        }
        else
            farmer_animator.IsMove = false;
    }
 
    void showing_Fertilize_Box()
    {
        if (farmer_animator.Fertilize_on == true && Fertilize_box != null)
        {
            if (Fertilize_box.activeSelf == false)
                Fertilize_box.SetActive(true);
        }
        else
        {
            Fertilize_box.SetActive(false);
        }
    }
 
    void showing_Water_box()
    {
        if (farmer_animator.Water_on == true && Water_box != null)
        {
            if (Water_box.activeSelf == false)
                Water_box.SetActive(true);
        }
        else
        {
            Water_box.SetActive(false);
        }
    }
 
}
 
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs
 

우선 automove스크립트의 핵심 메서드 MoveToTarget에 대해 설명하겠습니다.

MoveToTarget은 영어 뜻 그대로 목표지점으로 이동하라는 메서드입니다.

핵심 코드를 살펴보겠습니다. 

 

transform.position = 

Vector3.MoveTowards(transform.position, target.transform.position, 1f); 

// 현 위치, 도착점, 속도

 

MoveTowards라는 내장 함수는 매개변수 값으로 받은 속도 값으로 도착점을 향해

지정된 오브젝트를 이동시킵니다.

그리고 밑에 LookAt은 npc가 이동할 때 목표점을 바라보며 이동할 수 있도록 해주기

위해서 추가되었습니다.

 

 

showing_Fertilize_box는 아까 비료주기 애니메이션에서 비료통이

없던걸 보셨습니다. 위의 함수는  

green_box라는 비료통을 애니메이션 실행에 맞추어

보여주는 함수입니다.

showing_Water_box()는 물뿌리개를 보여주는 함수입니다.

automove 스크립트는 farmer_controller에 추가해주세요

스크립트 설명은 여기까지 하고, 히어라키 창에 엠티 오브젝트를 생성하여서

목표점으로 사용하고 실행해서 되는지 안되는지 확인해주세요

 

green_box.fbx는 비료통입니다.  혹시 구현해볼 분은 사용해주세요

 

green_box.fbx
0.03MB

 

모델 안에 라이트랑 카메라가 포함돼있는데, 제거해주시면 됩니다.

 

<애니메이션 넣기>

 

 

이번엔 애니메이션을 넣도록 하겠습니다.

 

위의 이미지처럼 npc 모델 객체 자식으로 green_box와 water_can을 넣어주세요

water_can은 물뿌리개입니다. 물뿌리개는 없으면 큐브로 대체합니다.

 

애니메이션이 새로 추가되었기 때문에 새로운 모델을 사용해야 합니다.

기존 모델을 삭제하고 저번 시간에 했던 것처럼 Idle 애니메이션 까지는

이전 글을 참조해주세요

 

 

 

애니메이터에 애니메이션을 삽입하는 방법은 

프로젝트 창에서 모델 오른쪽 플레이 버튼을 클릭하여

모델을 펼치면 모델 구성 요소를 볼 수 있습니다.

거기서 애니메이션을 애니메이터에 드래그엔 드롭하시면 됩니다.

 

이번에는 애니메이터에 파라미터를 추가합니다.

 

 

애니메이터 좌측에 보면 파라미터를 추가할 수 있는 공간이

있습니다. 위의 이미지처럼 IsMove,..., Water_on을 bool 타입으로 추가해주세요

 

 

위의 이미지처럼 Armature|run 과 Idle 애니메이션 사이에 있는

화살표를 만들어 주세요 Idle 우클릭하면 make Transaction으로 추가 가능합니다.

다음으로 화살표를 더블클릭합니다.

인스펙터 창에 위와 같이 나타납니다.

Has Exit Time 체크를 해제합니다.

해제하는 이유는 체크돼있을 경우 실행되고 있는 애니메이션이 종료돼야

다음 애니메이션을 진행하게 됩니다.

예를 들어 목표점으로 이동하기 위해 뛰기 애니메이션을 실행해야 하는데

숨시기 애니메이션이 끝나지 않았으므로 끝날 때까지 기다린 후 

뛰기 애니메이션이 실행되게 됩니다. 그러면 npc가 이동 중인데

뛰지 않는 현상이 발생할 수도 있습니다.

has exit time 아래에 보면 조건을 지정할 수 있습니다.

Idle -> run 일 때는 IsMove값을 true

Idle <- run 일 때는 IsMove값을 false

넣어주시기 바랍니다.

마찬가지로

각 애니메이션에 화살표에도 이름에 해당하는 조건을 false로 지정해주세요

 

이제 run 애니메이션을 실행할 준비가 되었으니 밑의 코드를 통해서

애니메이션이 실행될 수 있도록 하겠습니다.

아래의 코드는 애니메이션을 수행하는 모델에 넣어주시기 바랍니다.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class farmer_animator : MonoBehaviour
{
    Animator animator;
    public static bool IsMove = false;
    public static bool Fertilize_on = false;
    public static bool Water_on = false;
    public static bool Shovel_on = false;
 
 
    // Use this for initialization
    void Start()
    {
        animator = GetComponent<Animator>();
    }
 
    // Update is called once per frame
    void Update()
    {
        animator.SetBool("IsMove", IsMove);
        animator.SetBool("Fertilize_on", Fertilize_on);
        animator.SetBool("Water_on", Water_on);
        animator.SetBool("Shovel_on", Shovel_on);
 
        PlayAnimation();
    }
 
    void PlayAnimation()
    {
        if (IsMove == false && Fertilize_on == false && Water_on == false && Shovel_on == false)
        {
            if (Input.GetKeyDown(KeyCode.X))
            {
                animator.Play("Armature|Fertilize"-10);
                Fertilize_on = true;
            }
            else if (Input.GetKeyDown(KeyCode.Z))
            {
                animator.Play("Armature|water"-10);
                Water_on = true;
            }
        }
    }
 
    public void invisible_Fertilize_box()
    {
        if (Fertilize_on)
        {
            Fertilize_on = false;
        }
    }
    public void invisible_Water_box()
    {
        if (Water_on)
        {
            Water_on = false;
        }
    }
 
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs

 

        animator.SetBool("IsMove", IsMove);

        animator.SetBool("Fertilize_on", Fertilize_on);

        animator.SetBool("Water_on", Water_on);

        animator.SetBool("Shovel_on", Shovel_on);

위의 코드는 아까 애니메이터에서 생성했던 파라미터의 값에 접근하게 해 줍니다.

 

여기서 run 애니메이션이 실행되는 원리를 말하자면 

automove 스크립트의 메서드인 MoveToTarget()에서 

이동시에 IsMove 값에 true를 넣게 됩니다. 그렇게 되면 

뛰기 애니메이션이 바로 실행 되게 됩니다.

목표점에 도달하거나 가만히 있게 되면 IsMove값에 false를 넣어 다시

숨쉬기 애니메이션으로 돌아오게 됩니다. 

 

다음으로, 메서드별로 설명을 하겠습니다.

첫 번째 메소드 PlayAnimation()은 실행되고 있는 애니메이션이 없을 때

애니메이션을 실행시키도록 하는 메서드입니다.

플레이어가 x를 눌렀을 때 실행되는 코드를 살펴보면

 animator.Play("Armature|Fertilize"-10);

이것은 

Armature|Fertilize라는 애니메이션을 실행하게 도와주는 메서드입니다.

 Fertilize_on = true; 이 코드를 실행시키면

비료 통이 보이게 됩니다.

그렇게 되면 비료주기 애니메이션이 실행되면서 비료통이 보이게 됩니다.

 

그다음에는

public void invisible_Fertilize_box() 라는 메서드가 있습니다.

밑의 애니메이션은 이제 비료주기 애니메이션이 끝났을 때

박스를 다시 안 보이는 상태로 변환하기 위해서 사용됩니다.

애니메이션이 끝났을 때 사용하기 위해서 콜백 메서드 형식으로 사용하게 됩니다.

 

<애니메이션에 특정 시점에 실행될 메소드 넣기>

다시 애니메이터로 돌아옵니다.

 

위의 버튼 Fertilize를 더블클릭하면

아래의 창이 나타납니다.

 

여기서 아래쪽으로 드래그합니다.

 

이벤트라는 항목이 있습니다.

펼쳐주고

위의 이모티콘을 눌러줍니다.

그러면 시간 줄에 커서 같은 게 생기는데 가장 오른쪽으로 당겨 둡니다.

그렇게 되면 애니메이션 종료 시점에서 이벤트가 실행되게 됩니다.

이번에는 function에다가 위의 이미지 함수 명처럼 넣어주세요

Float, Int, String은 매개변수가 있을 경우 사용합니다. 우리는 없으니 사용하지 않습니다.

이번에는 Object에다가 해당 메서드가 있는 스크립트를 넣습니다.

여기서 주의할 점은 스크립트가 애니메이션을 실행하는 모델에 속해있지

않으면 오류가 발생할 수 있습니다. 고로 해당 스크립트는 모델에 속해있어야 합니다.

이제 apply 버튼을 눌러 적용하면 됩니다.

invisible_Water_box()함수 또한 위처럼 적용해주시면 됩니다.

 

<비료통 & 물뿌리개 움직임 넣는 코드>

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class autoRotation : MonoBehaviour
{
 
    int i = 0;
    float x, y, z;
    // Start is called before the first frame update
    void Start()
    {
        x = transform.rotation.x;
        y = transform.rotation.y;
        z = transform.rotation.z;
    }
 
    // Update is called once per frame
    void Update()
    {
        if (i % 14 >= 7)
            transform.Rotate(00-4);
        else
            transform.Rotate(004);
        i++;
    }
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter

위의 코드를 비료통과, 물뿌리개에 적용 시켜주면 비료통과 물뿌리개가 움직이게 됩니다.

 

이제 모든 것이 완료되면

 

이렇게 됩니다.

+ Recent posts