OpenCV

OpenCV - 입문

haniru 2025. 12. 18. 23:45

Open CV 다운로드

https://opencv.org/releases/

 

Releases

OpenCV Releases Are Brought To You By Intel Intel is a multinational corporation known for its semiconductor products, including processors that power a wide range of computing devices, from personal computers to servers and embedded systems. Read More Qua

opencv.org

 

환경변수 설정 (OpenCV 다운받은 폴더에 opencv\build\x64\vc16\bin 경로)

 

프로젝트 만들기

 

프로젝트 경로 설정: C:\OpenCV\opencv\build\include

 

추가 라이브러리 디렉터리 C:\OpenCV\opencv\build\x64\vc16\lib

 

추가 종속성 opencv_world4120d.lib

 

레나 이미지 로드하기

  1. imread: 저장 장치에서 이미지 파일을 읽어 메모리(cv::Mat)에 올린다.
  2. empty(): 파일이 없거나 형식이 잘못되어 로딩에 실패했는지 검사한다.
  3. namedWindow: 이미지를 보여줄 빈 창을 생성한다.
  4. imshow: 특정 창에 메모리의 이미지 데이터를 시각화한다.
  5. destroyAllWindows: 사용한 시스템 자원을 반납하고 창을 닫는다.
#include "opencv2/opencv.hpp"
#include <iostream>

int main()
{
    std::cout << "Hello OpenCV" << CV_VERSION << std::endl;

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

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

    cv::namedWindow("image");
    cv::imshow("image", img);

    cv::waitKey();
    return 0;
}

 

참고로 [파일 탐색기에서 열기] 경로에 lenna.png 을 넣어야 이미지가 로드된다

 

C++은 cv::Mat img;라고 선언하는 즉시 스택 메모리에 객체가 생성되고 기본 생성자가 호출된다. Java나 C#처럼 new 키워드를 명시적으로 사용하지 않아도 된다는 점이 C++의 특징이다.

std::cout vs std::cerr

  • std::cout: 표준 출력 스트림. 버퍼링을 사용하므로 효율적이지만, 프로그램이 비정상 종료될 때 출력 내용이 유실될 가능성이 있다.
  • std::cerr: 표준 오류 스트림. 버퍼를 거치지 않고 즉시 출력(unbuffered)하므로 에러 발생 시 상황을 즉각 확인해야 하는 디버깅 용도에 적합하다.

cv::waitKey(1000)

  • 단순히 1초를 기다리는 기능 외에도, 이 함수는 이벤트를 처리하는 역할을 한다.
  • imshow로 창을 띄웠을 때 화면이 갱신되려면 반드시 waitKey가 호출되어야 한다. 아무것도 안 넣으면 키 입력이 있을 때까지 무한정 대기한다.

 

Mat imread, Mat imwrite 예제

함수 설명 주요 옵션 (Flags)
cv::imread() 이미지 파일을 읽어 cv::Mat 객체로 반환 IMREAD_COLOR (컬러), IMREAD_GRAYSCALE (그레이스케일)
cv::imwrite() cv::Mat 객체를 파일로 저장 포맷은 파일 확장자(.jpg, .png)에 따라 자동 결정
#include "opencv2/opencv.hpp"
#include <iostream>

int main() {
    // 1. 이미지 읽기 (컬러 모드)
    cv::Mat img = cv::imread("lenna.png", cv::IMREAD_COLOR);

    if (img.empty()) {
        std::cerr << "이미지를 찾을 수 없습니다." << std::endl;
        return -1;
    }

    // 2. 이미지 처리 (예: 컬러를 그레이스케일로 변환)
    cv::Mat grayImg;
    cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY);

    // 3. 이미지 저장
    // 성공 시 true, 실패 시 false 반환
    bool isSaved = cv::imwrite("lenna_gray.jpg", grayImg);

    if (isSaved) {
        std::cout << "이미지가 성공적으로 저장되었습니다." << std::endl;
    }
    else {
        std::cerr << "이미지 저장에 실패했습니다." << std::endl;
    }

    // 4. 화면 출력 확인
    cv::imshow("Original", img);
    cv::imshow("Gray", grayImg);

    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

 

mwrite 에는 압축률을 지정할 수 있다

std::vector<int> params;
params.push_back(cv::IMWRITE_JPEG_QUALITY);
params.push_back(95); // 95% 화질로 저장
cv::imwrite("high_quality.jpg", grayImg, params);

 

윈도우 창 제어 함수 예제

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

int main() {
    cv::Mat img = cv::imread("lenna.png");

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

    // 1. 기본 창 생성 및 출력
    cv::namedWindow("First"); 
    cv::moveWindow("First", 50, 50); // 화면 좌상단 (50, 50) 위치로 이동
    cv::imshow("First", img);

    // 2. 크기 조절이 가능한 창 생성
    cv::namedWindow("Second", cv::WINDOW_NORMAL); 
    cv::resizeWindow("Second", 300, 300); // 창 크기를 300x300으로 강제 조정
    cv::moveWindow("Second", 600, 50);    // 첫 번째 창 옆으로 이동
    cv::imshow("Second", img);

    std::cout << "아무 키나 누르면 'First' 창만 닫힙니다." << std::endl;
    cv::waitKey(0);

    // 3. 특정 창만 닫기
    cv::destroyWindow("First");

    std::cout << "아무 키나 누르면 모든 창이 닫히고 종료됩니다." << std::endl;
    cv::waitKey(0);

    // 4. 모든 창 닫기
    cv::destroyAllWindows();

    return 0;
}
함수 기능 특징
namedWindow() 새로운 윈도우 창 생성 WINDOW_AUTOSIZE(기본값) 또는 WINDOW_NORMAL(크기 조절 가능) 설정 가능
imshow() 윈도우에 이미지 출력 첫 번째 인자인 창 이름이 없으면 namedWindow를 내부적으로 호출함
moveWindow() 윈도우의 위치 변경 모니터 좌측 상단을 (0, 0) 기준으로 좌표 지정
resizeWindow() 윈도우의 크기 변경 WINDOW_NORMAL로 생성된 창에서만 동작함
destroyWindow() 특정 윈도우 파괴 인자로 전달한 이름의 창만 닫음
destroyAllWindows() 모든 윈도우 파괴 프로그램 종료 전 열려 있는 모든 창을 한꺼번에 닫음

 

Point_ 클래스

2차원 평면 위의 점을 나타내는 템플릿 클래스

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

int main() {
    // 1. 다양한 생성 방식
    cv::Point pt1;          // (0, 0)으로 초기화
    pt1.x = 10; pt1.y = 20;

    cv::Point pt2(100, 200); // 생성과 동시에 초기화

    // 2. 연산자 오버로딩 활용
    cv::Point pt3 = pt1 + pt2; // (110, 220)
    cv::Point pt4 = pt2 * 2;   // (200, 400)

    // 3. 비교 및 관계 연산
    bool isEqual = (pt1 == pt2); // false

    // 4. 점 정보를 이용한 실습 (이미지에 점 그리기)
    cv::Mat img(400, 400, CV_8UC3, cv::Scalar(255, 255, 255)); // 흰색 배경

    cv::Point center(200, 200);
    // 점 위치에 반지름 5인 빨간색 원 그리기
    cv::circle(img, center, 5, cv::Scalar(0, 0, 255), -1);

    // 좌표 정보 출력
    std::cout << "pt1: " << pt1 << std::endl;
    std::cout << "pt3 (pt1 + pt2): " << pt3 << std::endl;
    std::cout << "dot product: " << pt1.dot(pt2) << std::endl; // 내적 연산

    cv::imshow("Point Example", img);
    cv::waitKey(0);

    return 0;
}

 

Size_ 클래스

Size_ 템플릿 클래스는 2차원 영역의 크기를 나타내기 위해 사용된다. 보통 너비(width)와 높이(height)를 멤버 변수로 가진다.

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

using namespace std;
using namespace cv;

int main() {
    // 1. 다양한 초기화 방법
    Size sz1(320, 240);          // 정수형 (Size2i)
    Size2f sz2(10.5f, 20.5f);    // 실수형 (float)
    Size sz3 = sz1;              // 복사 생성

    // 2. 멤버 변수 접근 및 출력
    cout << "sz1: " << sz1.width << "x" << sz1.height << endl;

    // 3. area() 메서드를 이용한 면적 계산
    cout << "sz1의 면적: " << sz1.area() << endl;

    // 4. 산술 연산
    Size sz4 = sz1 * 2;          // 모든 요소에 2를 곱함
    Size sz5 = sz1 + Size(10, 10); // 너비와 높이에 각각 10을 더함

    // 5. 비교 연산
    if (sz1 == sz3) {
        cout << "sz1과 sz3의 크기는 같다." << endl;
    }

    // 6. empty() 확인 (너비나 높이가 0 이하인 경우 true)
    Size sz6(0, 100);
    if (sz6.empty()) {
        cout << "sz6는 비어있는 크기다." << endl;
    }

    return 0;
}

 

Rect_ 클래스

Rect_ 클래스는 2차원 평면에서 사각형의 위치(좌표)와 크기(너비, 높이)를 나타내는 클래스다. Point_와 Size_ 클래스의 기능을 합친 형태라고 볼 수 있다.

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

using namespace std;
using namespace cv;

int main() {
    // 1. 다양한 초기화 방법
    Rect rc1(10, 10, 100, 100);           // (x, y, width, height)
    Rect rc2(Point(20, 20), Size(50, 50)); // Point와 Size 이용
    Rect rc3(Point(0, 0), Point(100, 100)); // 두 대각 점 이용

    // 2. 주요 멤버 변수 및 메서드
    cout << "rc1의 우측 하단 좌표: " << rc1.br() << endl; // Bottom-Right (110, 110)
    cout << "rc1의 좌측 상단 좌표: " << rc1.tl() << endl; // Top-Left (10, 10)
    cout << "rc1의 면적: " << rc1.area() << endl;         // 10000

    // 3. 사각형 포함 관계 확인
    Point pt(50, 50);
    if (rc1.contains(pt)) {
        cout << "점 pt는 rc1 내부에 있음" << endl;
    }

    return 0;
}

 

 

RotatedRect 클래스

RotatedRect 클래스는 중심점, 가로·세로 크기, 그리고 회전 각도를 이용해 회전된 사각형을 표현하는 클래스다. 일반적인 Rect_가 좌표축에 평행한 사각형만 다루는 것과 대조적이다.

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

using namespace cv;
using namespace std;

int main() {
    // 1. 객체 생성
    RotatedRect rr(Point2f(100, 100), Size2f(100, 50), 30.0f);

    // 2. 정점 좌표 추출
    Point2f pts[4];
    rr.points(pts);

    // 3. 이미지 생성 및 그리기
    Mat img = Mat::zeros(200, 200, CV_8UC3);

    // 회전된 사각형 그리기 (초록색)
    for (int i = 0; i < 4; i++) {
        line(img, pts[i], pts[(i + 1) % 4], Scalar(0, 255, 0), 2);
    }

    // 바운딩 박스 그리기 (빨간색)
    Rect br = rr.boundingRect();
    rectangle(img, br, Scalar(0, 0, 255), 1);

    // --- 추가된 부분: 화면 출력 ---
    imshow("RotatedRect Test", img); // 창을 띄워 이미지 표시
    waitKey(0);                     // 키 입력이 있을 때까지 대기 (이게 없으면 창이 바로 닫힘)
    // ----------------------------

    return 0;
}

 

 

Range 클래스

cv::Range 클래스는 행렬(cv::Mat)의 특정 행이나 열의 범위를 지정하여 **ROI(Region of Interest, 관심 영역)**를 추출할 때 주로 사용된다.

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

int main() {
    // 1. 10x10 크기의 0으로 채워진 행렬 생성
    cv::Mat mat = cv::Mat::zeros(10, 10, CV_8UC1);

    // 2. 2행부터 4행까지, 3열부터 7열까지 범위를 지정하여 부분 행렬 생성
    // (5행과 8열은 포함되지 않음)
    cv::Range row_range(2, 5);
    cv::Range col_range(3, 8);

    cv::Mat sub_mat = mat(row_range, col_range);

    // 3. 부분 행렬의 값을 255로 변경 (원본 mat에도 반영됨)
    sub_mat.setTo(255);

    std::cout << "Sub-matrix size: " << sub_mat.size() << std::endl;
    return 0;
}

 

String 클래스

OpenCV에서 과거에는 자체적인 cv::String 클래스를 사용했으나, 최신 버전(C++11 이후)에서는 표준 라이브러리인 std::string을 그대로 사용하도록 통합되었다. 따라서 OpenCV에서 문자열을 다루는 것은 일반적인 C++ 문자열 처리와 거의 동일하다.

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

int main() {
    // 1. 400x600 크기의 검은색 배경 생성 (3채널 컬러 이미지)
    cv::Mat img = cv::Mat::zeros(400, 600, CV_8UC3);

    // 2. 사용할 문자열 정의 (std::string 사용)
    std::string text = "Hello, OpenCV!";

    // 3. 이미지 위에 문자열 그리기
    // cv::putText(대상이미지, 문자열, 시작좌표, 폰트종류, 폰트크기, 색상, 두께)
    cv::putText(img, 
                text, 
                cv::Point(100, 200),      // 좌측 하단 시작점 (x, y)
                cv::FONT_HERSHEY_SIMPLEX, // 폰트 스타일
                1.5,                      // 폰트 크기 비율
                cv::Scalar(0, 255, 0),    // 색상 (B, G, R) -> 초록색
                2);                       // 선 두께

    // 4. 결과 확인
    cv::imshow("String Example", img);
    cv::waitKey(0);

    return 0;
}

 

Mat 클래스의 기본적인 생성, 복사, 속성 확인 및 출력 방법

 

  • image1.dims: 행렬의 차원을 나타낸다. 일반적인 2D 이미지는 2가 출력된다.
  • image1.cols / image1.rows: 영상의 가로(열)와 세로(행) 크기다.
  • image1.clone(): **깊은 복사(Deep Copy)**를 수행한다. image3은 image1과 동일한 데이터를 가지지만, 메모리 공간이 완전히 분리된 별개의 행렬이 된다.
  • cv::Scalar(i, i, i): B, G, R 값을 모두 동일하게 주면 검은색(0)에서 흰색(255)까지 변하는 그레이스케일 효과가 나타난다.
  • cv::waitKey(10): 밀리초(ms) 단위로 대기한다. 이 숫자가 작을수록 색상 변화가 빠르게 진행된다.
  • std::abs(255 - i): 수학적인 계산을 통해 밝기가 부드럽게 밝아졌다가 다시 어두워지는 왕복 효과를 구현했다.

 

Code1.cpp

#include "opencv2/opencv.hpp"

void show1() {
	cv::Mat image1 = cv::imread("lenna.png");
	cv::Mat image2 = cv::imread("dog.bmp");
	cv::Mat image3;

	if (image1.empty() or image2.empty()) {
		std::cerr << "파일들이 없습니다" << "\\n";
		return;
	}

	image3 = image1.clone();

	std::cout << "lena 는 몇 차원?: " << image1.dims << "\\n";
	std::cout << "lena 는 몇 컬럼?: " << image1.cols << "\\n";
	std::cout << "lena 는 몇 행?: " << image1.rows << "\\n";

	std::cout << "dog 는 몇 차원?: " << image2.dims << "\\n";
	std::cout << "dog 는 몇 컬럼?: " << image2.cols << "\\n";
	std::cout << "dog 는 몇 행?: " << image2.rows << "\\n";

	std::cout << "lena clone 는 몇 차원?: " << image3.dims << "\\n";
	std::cout << "lena clone 는 몇 컬럼?: " << image3.cols << "\\n";
	std::cout << "lena clone 는 몇 행?: " << image3.rows << "\\n";

	cv::namedWindow("LENA");
	cv::imshow("LENA", image1);

	cv::namedWindow("LENA CLONE");
	cv::imshow("LENA CLONE", image3);

	cv::namedWindow("DOG");
	cv::imshow("DOG", image2);

	cv::waitKey();
	cv::destroyAllWindows();
}

void show2() {
	cv::namedWindow("Color");
	for (int i = 0; i <= 255; ++i) {
		// cv::Mat image(512, 512, CV_8UC3, cv::Scalar(0, 0, i)); // 빨강
		cv::Mat image(512, 512, CV_8UC3, cv::Scalar(i, i, i));
		cv::imshow("Color", image);
		cv::waitKey(10);
	}
	cv::waitKey(0);

}

void show2_gray() {
	// cv::Mat image2(1024, 1024, CV_8UC1); // 회색
	// cv::Mat image2(1024, 1024, CV_8UC3); // 회색
	cv::Mat image2(cv::Size(512, 512), CV_8UC3); // 회색
	cv::imshow("Color", image2);
	cv::waitKey(0);
	cv::destroyAllWindows();
}

void show2_smooth() {
	cv::namedWindow("Color");
	// 0부터 510까지 순회 (255까지 내려갔다가 255만큼 다시 올라오는 거리)
	for (int i = 0; i <= 510; ++i) {
		// 255에서 i를 뺀 값의 절댓값을 취하면 255 -> 0 -> 255로 바뀜
		int brightness = std::abs(255 - i);

		cv::Mat image(512, 512, (CV_8U, 3), cv::Scalar(0, 0, brightness)); // 빨간색 왕복
		cv::imshow("Color", image);
		if (cv::waitKey(5) == 27) break;
	}
}

OpenCvProject.cpp

extern void show1();
extern void show2();
extern void show2_gray();
extern void show2_smooth();
#include <iostream>

int main()
{
    std::cout << "OpenCV!\\n";
    // show1();
    show2_gray();
    // show2_smooth();

}

 

Mat 클래스

1. Mat 클래스 정의

cv::Mat은 **n차원 행렬(Matrix)**을 표현하는 클래스다. 주로 2차원 이미지를 저장하는 용도로 쓰이지만, 일반적인 수치 데이터를 다루는 행렬로도 사용된다.

  • 헤더(Header): 행렬 크기, 행렬의 타입, 주소 등 정보를 담고 있다.
  • 데이터 포인터(Data Pointer): 실제 픽셀 값이나 수치 데이터가 저장된 메모리 공간을 가리킨다.
  • Mat은 헤더만 복사하는 얕은 복사를 기본으로 하여 대용량 이미지 처리 시 메모리 낭비를 줄인다.

2. 행렬의 깊이 (Depth)

행렬의 원소 하나가 사용하는 비트 수와 자료형을 의미한다. 이미지 처리에서 어떤 정밀도로 데이터를 저장할지 결정한다.

기호 데이터 타입 범위 용도
CV_8U 8비트 부호 없는 정수 0 ~ 255 일반적인 이미지 (uchar)
CV_8S 8비트 부호 있는 정수 -128 ~ 127  
CV_16U 16비트 부호 없는 정수 0 ~ 65,535 고해상도 의료 영상 등
CV_32F 32비트 실수 $-\infty$ ~ $\infty$ 정밀 연산, 필터링 (float)
CV_64F 64비트 실수 $-\infty$ ~ $\infty$ 고정밀 수치 계산 (double)

3. 채널 (Channel)

행렬의 한 칸(Pixel)이 몇 개의 성분으로 구성되어 있는지를 뜻한다.

  • 1채널 (C1): 그레이스케일(Grayscale) 이미지. 한 칸에 밝기 값 하나만 있음.
  • 3채널 (C3): 컬러 이미지. 한 칸에 B, G, R 세 가지 색상 값이 있음.
  • 4채널 (C4): 컬러 이미지 + 알파 채널(투명도).

4. 행렬 타입 (Type)

위에서 설명한 **깊이(Depth)**와 **채널(Channel)**을 합쳐서 하나의 상수로 표현한 것이다.

표기법: CV_<비트수><자료형>C<채널수>

  • CV_8UC1: 8비트 부호 없는 정수, 1채널 (일반 흑백 이미지)
  • CV_8UC3: 8비트 부호 없는 정수, 3채널 (일반 컬러 이미지 - BGR)
  • CV_32FC1: 32비트 실수, 1채널 (정밀한 계산용 데이터)
 

마스크

마스크 영상은 보통 **0(검은색)**과 **255(흰색)**로만 구성된 이진 영상(Binary Image)을 의미한다.

  • 1(혹은 255, 흰색): 연산을 수행할 부분 (관심 영역, ROI)
  • 0 (검은색): 무시할 부분

"곱하기"와 "모따기" (누끼 따기)

이미지 프로세싱에서 마스크를 곱한다는 것은 특정 영역만 남기고 나머지는 지워버리는 과정을 뜻한다.

  1. 원리: 원본 이미지의 픽셀 값에 마스크의 값을 곱한다.
    • (원본 픽셀 값) $\times$ 1 (흰색) = 원본 유지
    • (원본 픽셀 값) $\times$ 0 (검은색) = 0 (검은색으로 변함)
  2. 모따기(누끼 따기): 사과 사진에서 사과 모양의 마스크(흰색)를 만들어서 원본에 곱하면, 사과만 남고 배경은 모두 검은색(0)이 된다. 이렇게 **임의의 부분 영역(ROI)**만 골라내는 것을 "모따기" 혹은 "마스크 연산을 통한 ROI 추출"이라고 한다.

인식

  • 객체 검출: 영상 안에서 자동차, 컵, 키보드 같은 물체를 찾아내고 사각형(Bounding Box)으로 표시.
  • 얼굴 및 문자 인식: 사람의 얼굴을 찾거나(Face Detection), 이미지 속 글자를 읽어내는(OCR) 기술들이 모두 이 분야에 해당.