[OpenCV] 1주차 실습

haeryong·2022년 12월 5일
0

2일차

영상 불러와서 출력하기

    cv::Mat img = cv::imread("lenna.bmp"); //이미지 불러오기. 흑백을 불러오려면 cv::IMREAD_GRAYSCALE 인자 추가.

    if (img.empty())
    {
        std::cerr << "Image load failed!" << std::endl; // 이미지 load에 실패했을 경우 에러메세지 출력 후 종료.
        return -1;
    }

    cv::namedWindow("Image"); // "Image"라는 이름의 window 생성
    cv::imshow("Image", img); // "Image" 윈도우에 img를 출력.

    cv::waitKey(0); // 키 입력을 입력한 시간(ms)만큼 기다림. 0초일 경우 무한대.
    cv::destroyAllWindows(); 
    return 0;

cv::Mat

행렬 타입
CV_8U : 8bit unsigned integer (uchar)
CV_8S : 8bit signed integer (schar)
CV_16U: 16bit unsigned integer (ushort)
CV_16S: 16bit signed integer (short)
CV_32S: 32bit signed integer (int)
CV_32F: 32bit floating-point num (float)
CV_64F: 64bit floating-point num (double)

행렬 타입 뒷부분에 channel의 개수를 붙여준다. (e.g. CV_8UC3, CV_8UC1)

    cv::Mat img = cv::imread("lenna.bmp");

    std::cout << "Width: " << img.cols << std::endl;
    std::cout << "Height: " << img.rows << std::endl;
    std::cout << "Channels: " << img.channels() << std::endl;

    if (img.type() == CV_8UC1)
        std::cout << "img is a grayscale image" << std::endl;
    else if(img.type() == CV_8UC3)
        std::cout << "img is a truecolor image" << std::endl;
output:
Width: 512
Height: 512
Channels: 3
img is a truecolor image

행렬 초기화


    cv::Mat img1; // 빈 행렬
    cv::Mat img2(480, 640, CV_8UC1); // 480행, 640행, 1채널(grayscale), uchar
    cv::Mat img3(cv::Size(640, 480), CV_8UC3); // width=640, height=480, 3채널, uchar
    cv::Mat img4(480, 640, CV_8UC1, cv::Scalar(128)); // 행렬 값 128로 초기화.
    cv::Mat img5(480, 640, CV_8UC3, cv::Scalar(0, 0, 255)); // 행렬 값 0, 0, 255 (red)로 초기화.

    cv::Mat mat1 = cv::Mat::zeros(3, 3, CV_32SC1); // 3 by 3, 1 channel, short, zero mat
    cv::Mat mat2 = cv::Mat::ones(3, 3, CV_32FC1); // 3 by 3, 1 channel, float, one matrix
    cv::Mat mat3 = cv::Mat::eye(3, 3, CV_64FC1); // 3 by 3, 1 channel, double, identity matrix.

    float data[] = {1, 2, 3, 4, 5, 6};
    cv::Mat mat4(2, 3, CV_32FC1, data);

    std::vector<float> vec1 = {1, 2, 3, 4, 5, 6};
    cv::Mat mat5(2, 3, CV_32FC1, vec1.data());

    cv::Mat mat6 = (cv::Mat_<float>(2, 3) << 1, 2, 3, 4, 5, 6);
    cv::Mat mat7 = cv::Mat_<uchar>({2, 3}, {1, 2, 3, 4, 5, 6});

    // 기존의 행렬을 지우고 새로운 행렬 생성.
    img1.create(480, 640, CV_8UC1); // 초기화는 불가능.
    img1 = cv::Scalar(255, 0, 0); // blue 컬러로 초기화.
    img1.setTo(1.f); // 모든 값을 1.0으로 초기화.

행렬 참조, 복사

    cv::Mat img1 = cv::imread("dog.bmp");


    // 참조
    cv::Mat img2 = img1; // 초기화 cv::Mat img2(img1);과 같다.
    cv::Mat img3;
    img3 = img1; // 대입

    // 복사
    cv::Mat img4 = img1.clone();
    cv::Mat img5;
    img1.copyTo(img5);


부분 영상 추출(image crop), ROI

    cv::Mat img1 = cv::imread("cat.bmp");
    cv::Mat img2 = img1(cv::Rect(220, 120, 340, 240)); // 참조
    cv::Mat img3 = img1(cv::Rect(220, 120, 340, 240)); // 복사

행렬 픽셀 값 접근하기


    cv::Mat mat1 = cv::Mat::zeros(10, 15, CV_8UC1);
    
    
	// cv::Mat::at()
    
    for(int y = 0; y < mat1.rows; y++)
    {
        for(int x = 0; x < mat1.cols; x++)
        {
            mat1.at<uchar>(y, x) += 5;
        }
    }
    
    
    // cv::Mat::ptr()
    
        for(int y = 0; y < mat1.rows; y++)
    {
        uchar* p = mat1.ptr<uchar>(y);

        for (int x = 0; x < mat1.cols; x++)
        {
            p[x] += 5;
        }
    }
    
    // cv::MatIterator_<T>

    for(cv::MatIterator_<uchar> it = mat1.begin<uchar>();
        it != mat1.end<uchar>(); ++it)
    {
        (*it) += 5;
    }
    

	// truecolor image 참조

    cv::Mat mat2 = cv::Mat::zeros(3, 3, CV_8UC3);
    for(int y = 0; y < mat1.rows; y++)
    {
        for(int x = 0; x < mat1.cols; x++)
        {
            cv::Vec3b& pixel = mat2.at<cv::Vec3b>(y, x);
            pixel[0] = 0;   R
            pixel[1] = 255; G 
            pixel[2] = 255; B
        }
    }

행렬 기본연산

    float data[] = {1, 2, 3, 4};
    cv::Mat mat1(2, 2, CV_32FC1, data);
    cv::Mat mat2 = mat1.inv();
    std::cout << "mat1:\n"  << mat1 << std::endl;
    
    std::cout << "mat2 = mat1 inverse:\n"  << mat2 << std::endl;

    std::cout << "mat1.t():\n"  << mat1.t() << std::endl;
    std::cout << "mat1 + 3:\n"  << mat1 + 3 << std::endl;
    std::cout << "mat1 + mat2:\n"  << mat1 + mat2 << std::endl;
    std::cout << "mat1 * mat2:\n"  << mat1 * mat2 << std::endl;
output:
mat1:
[1, 2;
 3, 4]
mat2 = mat1 inverse:
[-2, 1;
 1.5, -0.5]
mat1.t():
[1, 3;
 2, 4]
mat1 + 3:
[4, 5;
 6, 7]
mat1 + mat2:
[-1, 3;
 4.5, 3.5]
mat1 * mat2:
[1, 0;
 0, 1]

3일차

카메라와 동영상 처리

카메라, 동영상 불러와서 출력하기

 	cv::VideoCapture cap("test_video.mp4");
	//cv::VideoCapture cap(0); // camera
    
    if (!cap.isOpened())
    {
        std::cerr << "Camera open failed" << std::endl;
        return -1;
    }

    cv::Mat frame;

    while(cv::waitKey(1) != 'q')
    {
        cap >> frame;

        if (frame.empty())
        {
            std::cerr << "Frame empty!" << std::endl;
            break;
        }

        cv::imshow("frame", frame);
    }

카메라, 동영상 속성 값 참조와 설정
cv::VideoCaptureProperties
CAP_PROP_FRAME_WIDTH : 프레임 가로 크기
CAP_PROP_FRAME_HEIGHT : 프레임 세로 크기
CAP_PROP_FRAME_COUNT : 비디오 파일 총 프레임 수
CAP_PROP_FPS : 초당 프레임
CAP_PROP_POS_FRAMES : 현재 프레임 번호
CAP_PROP_EXPOSURE : 노출
...

    cv::VideoCapture cap("test_video.mp4");
	//cv::VideoCapture cap(0); // camera
    
    if (!cap.isOpened())
    {
        std::cerr << "Camera open failed" << std::endl;
        return -1;
    }

    int width  = cvRound(cap.get(cv::CAP_PROP_FRAME_WIDTH)); // 속성 get
    int height = cvRound(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
    double fps = cap.get(cv::CAP_PROP_FPS);
    
    cap.set(cv::CAP_PROP_FPS, 40); // 속성 set

동영상 저장하기, VideoWriter
fourcc : 압축 방식을 나타냄.

    cv::VideoCapture cap("test_video.mp4");

    int fourcc = cv::VideoWriter::fourcc('X', 'V', 'I', 'D');
    double fps = 15;
    int w = (int)cap.get(cv::CAP_PROP_FRAME_WIDTH);
    int h = (int)cap.get(cv::CAP_PROP_FRAME_HEIGHT);
    cv::Size sz(w, h);
    
    cv::VideoWriter outputVideo("output.avi", fourcc, fps, sz); // 마지막 인자로 false 추가 시 grayscale로 저장. 

    int delay = cvRound(1000 / fps);

    cv::Mat frame;
    while(true)
    {
        cap >> frame;

        outputVideo << frame;
        cv::imshow("frame", frame);

        if (cv::waitKey(delay) == 'q')
            break;
    }

그리기 함수

    cv::Mat img(480, 640, CV_8UC3, cv::Scalar(0, 0, 0));
    
    cv::Scalar red(0, 0, 255), green(0, 255, 0),blue(255, 0, 0);
    int thickness = 1;


    // line
    cv::Point pt1(50, 50), pt2(100, 100);
    cv::line(img, pt1, pt2, red, 1, cv::LINE_AA); // LINE_4, LINE_8, LINE_AA 중 지정.

    

    // rectangle
    cv::Rect rec(200, 150, 100, 50); // x, y, width, height
    cv::rectangle(img, rec, blue, thickness, cv::LINE_8); // thickness=-1일 경우 내부가 색칠된 사각형.



    // polylines
    std::vector<cv::Point> pts = {cv::Point(400, 200), cv::Point(400, 400), cv::Point(300, 300)};
    cv::polylines(img, pts, true, green, thickness, cv::LINE_8); // 3번째 인자로 true를 넣으면 폐곡선.

    // text
    cv::Point text_left_bottom(0, 450);
    int font = cv::HersheyFonts::FONT_HERSHEY_SIMPLEX;
    cv::putText(img, "putText", text_left_bottom, font, 1.0, red, thickness, cv::LINE_AA);

    cv::imshow("img", img);
    while(cv::waitKey(0) != 'q')
        continue;

이벤트 처리

마우스 이벤트 처리

MouseEventTypes, MouseEventFlags

enum MouseEventTypes
{
    EVENT_MOUSEMOVE     = 0, 
    EVENT_LBUTTONDOWN   = 1,
    EVENT_RBUTTONDOWN   = 2,
    EVENT_MBUTTONDOWN   = 3,
    EVENT_LBUTTONUP     = 4,
    EVENT_RBUTTONUP     = 5,
    EVENT_MBUTTONUP     = 6,
    EVENT_LBUTTONDBLCLK = 7, 
    EVENT_RBUTTONDBLCLK = 8,
    EVENT_MBUTTONDBLCLK = 9,
    EVENT_MOUSEWHEEL    = 10,
    EVENT_MOUSEHWHEEL   = 11,
};

enum MouseEventFlags // 2배씩 증가하므로 상태의 중첩이 가능하다.
{
    EVENT_FLAG_LBUTTON  = 1, // LBUTTON이 눌린 상태인지 
    EVENT_FLAG_RBUTTON  = 2,
    EVENT_FLAG_MBUTTON  = 4,
    EVENT_FLAG_CTRLKEY  = 8,
    EVENT_FLAG_SHIFTKEY = 16,
    EVENT_FLAG_ALTKEY   = 32,
};

마우스 이벤트, 플래그 예시

void my_mouse_callback(int event, int x, int y, int flags, void*)
{
    switch (event)
    {
    case cv::EVENT_LBUTTONDOWN:
        std::cout << "EVENT_LBUTTONDOWN" << x << ", " << y << std::endl;
        break;
    case cv::EVENT_MOUSEMOVE:
        if (flags & cv::EVENT_FLAG_LBUTTON || flags & cv::EVENT_FLAG_CTRLKEY)
            std::cout << "EVENT_MOUSEMOVE: " << x << ", " << y << std::endl;
        break;

    default:
        break;
    }
}

int main()
{
    cv::Mat src = cv::imread("lenna.bmp");

    cv::namedWindow("src");
    cv::setMouseCallback("src", my_mouse_callback);

    cv::imshow("src", src); 

    while(cv::waitKey(0) != 'q')
        continue;
    return 0;
}
L버튼 클릭 시 
output:
EVENT_LBUTTONDOWN316, 225
EVENT_LBUTTONDOWN312, 263
EVENT_LBUTTONDOWN433, 315
EVENT_LBUTTONDOWN218, 229
EVENT_LBUTTONDOWN216, 247

L버튼 또는 ctrl을 누른 채 드래그 시 
output:
EVENT_MOUSEMOVE: 246, 245
EVENT_MOUSEMOVE: 246, 246
EVENT_MOUSEMOVE: 247, 246
EVENT_MOUSEMOVE: 247, 246
EVENT_MOUSEMOVE: 247, 247
EVENT_MOUSEMOVE: 247, 248
EVENT_MOUSEMOVE: 247, 248
EVENT_MOUSEMOVE: 247, 249

이미지 위에 그림 그리기

cv::Point pt_prev;

void my_mouse_callback2(int event, int x, int y, int flags, void* userdata)
{
    switch (event)
    {
    case cv::EVENT_LBUTTONDOWN:
        pt_prev = cv::Point(x, y);
        break;
    case cv::EVENT_MOUSEMOVE:
        if(flags & cv::EVENT_FLAG_LBUTTON)
        {
            cv::Mat img = *(cv::Mat*)userdata;
            cv::line(img, pt_prev, cv::Point(x, y), cv::Scalar(0, 255, 255), 3, cv::LINE_AA);
            pt_prev = cv::Point(x, y);
            cv::imshow("src", img);
        }
    
    default:
        break;
    }
}


int main()
{
    cv::Mat src = cv::imread("lenna.bmp");

    cv::namedWindow("src");
    cv::setMouseCallback("src", my_mouse_callback2, (void*)&src);

    cv::imshow("src", src); 

    while(cv::waitKey(0) != 'q')
        continue;


    return 0;
}

L버튼 또는 Ctrl키를 누른 채로 마우스를 움직이면 line이 그려진다.

트랙바

void my_trackbar_callback(int pos, void* userdata)
{
    cv::Mat img = *(cv::Mat*)userdata;

    img.setTo(pos * 16);
    cv::imshow("src", img);
}

int main()
{
    cv::Mat src = cv::Mat::zeros(400, 400, CV_8UC1);

    cv::namedWindow("src");
    int tb_loc; // 트랙바 위치를 받아올 수 있다.
    int tb_max = 16; // 트랙바의 최대값을 설정 (최소는 0으로 고정)
    cv::createTrackbar("level", "src", &tb_loc, tb_max, my_trackbar_callback, (void*)&src);

    while(cv::waitKey(0) != 'q')
        continue;

    return 0;
}


OpenCV 함수

행렬의 합, 평균, 최대, 최소

    uchar data[] = {1, 2, 3, 4, 5, 6};
    cv::Mat mat1(2, 3, CV_8UC1, data);

    // mask 행렬을 인자로 추가 시 mask의 원소가 0인 부분은 제외하고 연산.
    int sum1 = (int)cv::sum(mat1)[0]; // 21
    double mean1 = cv::mean(mat1)[0]; // 10.5

    double minVal, maxVal; // 1, 6
    cv::Point minPoint, maxPoint; // [0, 0], [2, 1]

    cv::minMaxLoc(mat1, &minVal, &maxVal, &minPoint, &maxPoint);

영상의 속성 변환

자료형 변환

    cv::Mat img = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);

    cv::Mat dst;
    double alpha(1), beta(0); // 각 픽셀의 값이 val -> saturate(alpha*val + beta)로 변환.
    img.convertTo(dst, CV_32FC1, alpha, beta); // 

행렬의 정규화(원소 값 범위 정규화)

    cv::Mat img = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat dst;
    double alpha = 0; // 목표 norm값. (NORM_MINMAX타입인 경우 최솟값을 의미함.)
    double beta  = 255; // NORM_MINMAX타입인 경우에 사용되며 최대값을 의미함.
    int norm_type = cv::NORM_MINMAX; // 정규화 타입. NORM_L2, NORM_INF, NORM_L1, NORM_MINMAX
    cv::normalize(img, dst, alpha, beta, norm_type); 

색 변환
COLOR_BGR2GRAY / COLOR_GRAY2BGR
COLOR_BGR2HSV / COLOR_HSV2BGR
COLOR_BGR2YCrCb/ COLOR_YCrCb2BGR
...

    cv::Mat src = cv::imread("lenna.bmp");
    cv::Mat dst;
    cv::cvtColor(src, dst, cv::COLOR_BGR2GRAY);

채널 분리와 병합

    cv::Mat src = cv::imread("lenna.bmp");

    std::vector<cv::Mat> planes;

    cv::split(src, planes); 

    std::swap(planes[0], planes[2]); // BGR 순서에서 RGB 순서가 됨.

    cv::Mat dst;
    cv::merge(planes, dst);
    cv::imshow("src", src);
    cv::imshow("dst", dst);

    while(cv::waitKey(0) != 'q')
        continue;

연산 시간 측정

    cv::TickMeter tm;
    tm.start();

    // my function

    tm.stop();
    std::cout << "my function: " << tm.getTimeMilli() << "ms." << std::endl;

    tm.reset(); // reset 빼먹으면 위에서 걸린 시간도 추가됨.

    tm.start();

    // my function

    tm.stop();
    std::cout << "my function: " << tm.getTimeMilli() << "ms." << std::endl;

마스크 연산, ROI

    cv::Mat src  = cv::imread("airplane.bmp", cv::IMREAD_COLOR);
    cv::Mat mask = cv::imread("mask_plane.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat dst  = cv::imread("field.bmp", cv::IMREAD_COLOR);

    if(src.empty() || mask.empty() || dst.empty())
    {
        std::cerr << "Image load failed!" << std::endl; 
        return -1;
    }

    src.copyTo(dst, mask); // copyTo 외에도 mask 연산을 지원하는 다양한 함수가 존재함.

    cv::imshow("src", src);
    cv::imshow("dst", dst);
    cv::imshow("mask", mask);
    while(cv::waitKey(0) != 'q')
        continue;

4일차

영상의 밝기 조절

단순 덧셈

    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);

    if (src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }

    cv::Mat dst = src + 50; // 50 -> cv::Scalar(50)으로 변환됨.
	// dst = src - 50;
    // cv::add(src, 50, dst); 같은 결과.
    cv::imshow("src", src);
    cv::imshow("dst", dst);

    while(cv::waitKey(0) != 'q')
        continue;

반전

    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);

    if (src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }

    //cv::Mat dst = 255 - src;
    cv::Mat dst = ~src;
    cv::imshow("src", src);
    cv::imshow("dst", dst);

    while(cv::waitKey(0) != 'q')
        continue;

밝기 조절 구현

    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);

    if (src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }

    cv::Mat dst(src.rows, src.cols, CV_8UC1);

    for(int y = 0; y < src.rows; y++)
    {
        for (int x = 0; x < src.cols; x++)
        {
            //dst.at<uchar>(y, x) = src.at<uchar>(y, x) + 50;
            dst.at<uchar>(y, x) = cv::saturate_cast<uchar>(src.at<uchar>(y, x) + 50);
        }
    }

    cv::imshow("src", src);
    cv::imshow("dst", dst);    

    while(cv::waitKey(0) != 'q')
        continue;

saturate_cast를 하지 않으면 아래 그림처럼 된다.(픽셀 값이 255보다 크거나 0보다 작아지는 경우)

밝기 보정

    cv::Mat src = cv::imread("tiffany.bmp", cv::IMREAD_GRAYSCALE);

    if (src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }


    // 평균 밝기를 128로 보정.
    int m = cv::mean(src)[0];
    cv::Mat dst1 = src + (128 - m);


    cv::imshow("src", src);
    cv::imshow("128", dst1);    

    while(cv::waitKey(0) != 'q')
        continue;

영상의 명암비 조절


    cv::Mat src = cv::imread("columbia.bmp", cv::IMREAD_GRAYSCALE);

    if (src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }
	
	// 단순 contrast 조절    
    double alpha = 1.0;
    cv::Mat dst1 = src + (src - 128) * alpha;
	
    // mean을 이용한 contrast 조절
    int m = (int)cv::mean(src)[0];
	cv::Mat dst2 = src + (src - m) * alpha;
    
    
    
    cv::imshow("src", src);
    cv::imshow("dst1", dst1);    

    while(cv::waitKey(0) != 'q')
        continue;


히스토그램 분석

히스토그램 직접 구하기

    cv::Mat src = cv::imread("columbia.bmp", cv::IMREAD_GRAYSCALE);

    if (src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }
	
    int hist[256] = {0, };
    for(int y = 0; y < src.rows; y++)
    {
        for(int x = 0; x < src.cols; x++)
        {
            hist[src.at<uchar>(y, x)]++;
        }
    }

    //histogram 그리기
    int histMax = 0;
    for(int i = 0; i < 256; i++)
    {
        if(hist[i] > histMax) histMax = hist[i];
    }
    printf("%d", histMax);
    cv::Mat imgHist(100, 256, CV_8UC1, cv::Scalar(255));
    for(int i = 0; i < 256; i++)
    {
        int y_ = 100 - cvRound(hist[i] * 100 / histMax);
        cv::line(imgHist, cv::Point(i, 100), cv::Point(i, y_), cv::Scalar(0));
    }

    cv::imshow("src", src);
    cv::imshow("hist", imgHist);

    while(cv::waitKey(0) != 'q')
        continue;

calcHist 함수 이용
함수 인자
1. const Mat images : 입력 영상의 배열 또는 영상의 주소.
2. int nimages : 입력 영상의 개수
3. const int
channels: 정수형 배열. 히스토그램을 구할 이미지의 채널들.
4. InptArray mask
5. OutputArray hist : 히스토그램 이미지 출력.
6. int dims : 출력 히스토그램 차원.
7. const int* histSize: 히스토그램 각 차원의 크기를 나타내는 배열.
8. const float** ranges: 히스토그램 각 차원별 최대, 최소값을 원소로 갖는 배열의 배열.


cv::Mat calcGrayHist(const cv::Mat& img)
{
    CV_Assert(img.type() == CV_8UC1);

    cv::Mat hist;
    int channels[] = {0};
    int dims = 1;
    const int histSize[] = {256};
    float graylevel[] = {0, 256};
    const float* ranges[] = {graylevel};

    cv::calcHist(&img, 1, channels, cv::Mat(), hist, dims, histSize, ranges);

    return hist;
}

cv::Mat getGrayHistImage(const cv::Mat& hist)
{
    CV_Assert(hist.type() == CV_32FC1);
    CV_Assert(hist.size() == cv::Size(1, 256));

    double histMax = 0.;
    minMaxLoc(hist, 0, &histMax);

    cv::Mat imgHist(100, 256, CV_8UC1, cv::Scalar(255));
    for(int i = 0; i < 256; i++)
    {
        int y_ = 100 - cvRound(hist.at<float>(i) * 100 / histMax);
        cv::line(imgHist, cv::Point(i, 100), cv::Point(i, y_), cv::Scalar(0));
    }

    return imgHist;
}

int main()
{
    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
    
    cv::Mat hist = calcGrayHist(src);
    cv::Mat imgHist = getGrayHistImage(hist);
    cv::imshow("src", src);
    cv::imshow("hist", imgHist);

    while(cv::waitKey(0) != 'q')
        continue;

    return 0;
}

결과는 같다.

히스토그램 스트레칭, 평활화

스트레칭(stretching)


    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
    

    double gmin, gmax;
    cv::minMaxLoc(src, &gmin, &gmax);
    cv::Mat dst = (src - gmin) * 255 / (gmax - gmin);
	// cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX); // 같은 결과를 얻음.

    cv::Mat hist = calcGrayHist(src);
    cv::Mat imgHist = getGrayHistImage(hist);

    cv::Mat hist2 = calcGrayHist(dst);
    cv::Mat imgHist2 = getGrayHistImage(hist2);

    cv::imshow("src", src);
    cv::imshow("dst", dst);
    cv::imshow("hist", imgHist);
    cv::imshow("hist2", imgHist2);


    while(cv::waitKey(0) != 'q')
        continue;

평활화(equalization)


    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat dst(src.rows, src.cols, src.type());

    int hist_[256] = {0, };
    for(int y = 0; y < src.rows; y++)
    {
        for(int x = 0; x < src.cols; x++)
        {
            hist_[src.at<uchar>(y, x)]++;
        }
    }

    float cdf[256] = {};
    for(int i = 1; i < 256; i++)
    {
        cdf[i] = cdf[i-1] + float(hist_[i] / float(src.total()));
    }

    for(int y = 0; y < src.rows; y++)
    {
        for (int x = 0; x < src.cols; x++)
        {
            dst.at<uchar>(y, x) = uchar(cdf[src.at<uchar>(y, x)] * 255);
        }
    }

    //cv::equalizeHist(src, dst); // 같은 결과

    cv::Mat hist = calcGrayHist(src);
    cv::Mat imgHist = getGrayHistImage(hist);

    cv::Mat hist2 = calcGrayHist(dst);
    cv::Mat imgHist2 = getGrayHistImage(hist2);

    cv::imshow("src", src);
    cv::imshow("dst", dst);
    cv::imshow("hist_src", imgHist);
    cv::imshow("hist_dst", imgHist2);

    while(cv::waitKey(0) != 'q')
        continue;

영상의 산술, 논리연산

산술연산

    cv::Mat src1 = cv::imread("lenna256.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat src2 = cv::imread("square.bmp", cv::IMREAD_GRAYSCALE);

    if (src1.empty() || src2.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }

    if(src1.size() != src2.size() || src1.type() != src2.type())
    {
        std::cerr << "Images are different in size or type!" << std::endl;
        return -1;
    }

    cv::Mat dst1, dst2, dst3, dst4;

    cv::add(src1, src2, dst1); // 덧셈
    cv::subtract(src1, src2, dst2); // 뺄셈
    cv::addWeighted(src1, 0.5, src2, 0.5, 0.0, dst3); // 가중합
    cv::absdiff(src1, src2, dst4); // 차이

    cv::imshow("src1", src1);
    cv::imshow("src2", src2);
    cv::imshow("add", dst1);
    cv::imshow("subtract", dst2);
    cv::imshow("addWeighted", dst3);
    cv::imshow("absdiff", dst4);

    while(cv::waitKey(0) != 'q')
        continue;

논리연산


    cv::Mat src1 = cv::imread("lenna256.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat src2 = cv::imread("square.bmp", cv::IMREAD_GRAYSCALE);

    if (src1.empty() || src2.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }

    if(src1.size() != src2.size() || src1.type() != src2.type())
    {
        std::cerr << "Images are different in size or type!" << std::endl;
        return -1;
    }

    cv::Mat dst1, dst2, dst3, dst4;

    cv::bitwise_and(src1, src2, dst1);
    cv::bitwise_or(src1, src2, dst2);
    cv::bitwise_xor(src1, src2, dst3);
    cv::bitwise_not(src1, dst4); // dst4 = ~src1; 과 같음.

    cv::imshow("src1", src1);
    cv::imshow("src2", src2);
    cv::imshow("and", dst1);
    cv::imshow("or", dst2);
    cv::imshow("xor", dst3);
    cv::imshow("not src1", dst4);

    while(cv::waitKey(0) != 'q')
        continue;

5일차

영상 필터링

엠보싱 필터

    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);

    if(src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }
    float data[] = {-1, -1, 0, -1, 0, 1, 0, 1, 1};
    cv::Mat kernel(3, 3, CV_32FC1, data);

    cv::Mat dst;
    cv::filter2D(src, dst, -1, kernel, cv::Point(-1, -1), 128); 
    //6번째 인자에 128을 넣어 전체 이미지에 128만큼 더해준다.

    cv::imshow("src", src);
    cv::imshow("dst", dst);

    while(cv::waitKey(0) != 'q')
        continue;

블러링

평균값 필터

    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);

    if(src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }
    std::vector<float> data(9, 1.f/9.f);
    cv::Mat kernel(3, 3, CV_32FC1, data.data());
    // cv::Mat kernel = cv::Mat::ones(3, 3, CV_32FC1) / 9.f; // 같은결과.
    cv::Mat dst;
    cv::filter2D(src, dst, -1, kernel); 
	// cv::blur(src, dst, cv::Size(3, 3)); // 블러 함수를 사용할 수도 있다.
    
    
    cv::imshow("src", src);
    cv::imshow("dst", dst);

    while(cv::waitKey(0) != 'q')
        continue;

가우시안 블러

    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);

    if(src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }


    cv::Mat dst;
    cv::GaussianBlur(src, dst, cv::Size(), 2.0);
	// sigma = 1.0일 경우 커널사이즈는 7x7이고, sigma가 커질수록 커널사이즈도 늘어나고, 연산량도 늘어남.
    cv::imshow("src", src);
    cv::imshow("dst", dst);

    while(cv::waitKey(0) != 'q')
        continue;    

샤프닝

평균값 필터를 이용한 언샤프 마스크 필터링

    cv::Mat src = cv::imread("rose.bmp", cv::IMREAD_GRAYSCALE);

    if(src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }


    cv::Mat dst, blr;
	
    std::vector<float> data(9, -1.f/9.f);
    data[4] = 17.f/9.f;
    cv::Mat kernel(3, 3, CV_32FC1, data.data());
    cv::filter2D(src, dst, -1, kernel);
    
    //cv::blur(src, blr, cv::Size(3, 3)); 
    //dst = 2 * src - blr;  // 같은 결과를 얻는다.

    cv::imshow("src", src);
    cv::imshow("dst", dst);

    while(cv::waitKey(0) != 'q')
        continue;    


가우시안 블러를 이용한 언샤프 마스크 필터링, 가중치 alpha 도입

    cv::Mat src = cv::imread("rose.bmp", cv::IMREAD_GRAYSCALE);

    if(src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }

    cv::Mat srcf;
    src.convertTo(srcf, CV_32FC1);

    cv::Mat blr;
    cv::GaussianBlur(srcf, blr, cv::Size(), 1.0);

    float alpha = 1.f;
    cv::Mat dst = (1.f + alpha) * srcf - alpha * blr;

    dst.convertTo(dst, CV_8UC1);

    cv::imshow("src", src);
    cv::imshow("dst", dst);

    while(cv::waitKey(0) != 'q')
        continue;  

잡음 제거 필터

가우시안 잡음 생성

    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat noise(src.size(), CV_32S);
    cv::randn(noise, 0, 10);

    cv::Mat dst;
    cv::add(src, noise, dst, cv::noArray(), CV_8U);

    cv::imshow("src", src);
    cv::imshow("dst", dst);

    while(cv::waitKey(0) != 'q')
        continue;  

프로파일 출력 프로그램


cv::Mat src_, dst_, profile_;
int row_ = 0;

void on_trackbar(int, void*)
{
    src_.copyTo(dst_);
    profile_.setTo(255);

    uchar* pSrc = (uchar*)src_.ptr<uchar>(row_);
    uchar* pDst = (uchar*)dst_.ptr<uchar>(row_);

    for(int i = 1; i < src_.cols; i++)
    {
        cv::line(profile_, cv::Point(i-1, 255-pSrc[i-1]), cv::Point(i, 255-pSrc[i]), 0);
        pDst[i] = cv::saturate_cast<uchar>(pSrc[i] + 50);
    }

    cv::imshow("dst", dst_);
    cv::imshow("profile", profile_);
}

int main()
{
    src_ = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);

    if(src_.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }

    cv::namedWindow("dst");
    cv::namedWindow("profile");
    
    profile_.create(256, src_.cols, CV_8U);
    cv::createTrackbar("Profile", "dst", &row_, src_.rows - 1, on_trackbar);
    on_trackbar(0, 0);

    while(cv::waitKey(0) != 'q')
        continue;  
	
    return 0;
}

양방향 필터
에지는 보존하고 잡음을 제거하는 필터. 가우시안 블러에 비해 실행시간이 크다.

    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);

    if(src.empty())
    {
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }

    cv::Mat dst1, dst2;
    cv::GaussianBlur(src, dst1, cv::Size(), 5.0);

    cv::bilateralFilter(src, dst2, -1, 10, 5);

    cv::imshow("src", src);
    cv::imshow("gaussian", dst1);
    cv::imshow("bilateral", dst2);

    while(cv::waitKey(0) != 'q')
        continue;

히스토그램 스트레칭 개선

이미지의 픽셀의 최대, 최소값 대신 1%, 99% 값을 이용해 스트레칭.

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


void histogram_stretching_mod(const cv::Mat& src, cv::Mat& dst);

int main()
{
    cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat dst;

    histogram_stretching_mod(src, dst);

    cv::imshow("src", src);
    cv::imshow("dst", dst);

    while(cv::waitKey(0) != 'q')
        continue;

    return 0;
}


void histogram_stretching_mod(const cv::Mat& src, cv::Mat& dst)
{
    int hist[256] = {0, };

    for(int y = 0; y < src.rows; y++)
    {
        const uchar* p = src.ptr<uchar>(y);

        for(int x = 0; x < src.cols; x++)
        {
            hist[p[x]]++;
        }
    }

    int gmin  = 255, gmax = 0;
    int ratio = int(src.cols * src.rows * 0.01);

    for(int i = 0, s = 0; i <= 255; i++)
    {
        s += hist[i];
        if (s > ratio)
        {
            gmin = i;
            break;
        }
    }

    for(int i = 255, s = 0; i >= 0; i--)
    {
        s += hist[i];
        if (s > ratio)
        {
            gmax = i;
            break;
        }
    }

    dst = (src - gmin) * 255 / (gmax - gmin);
    //cv::normalize(src, dst, gmin, gmax, cv::NORM_MINMAX);
}

0개의 댓글