cyphen156

컴퓨터 비전#3 OpenCV 4 기본 사용법 본문

컴퓨터공학/컴퓨터 비전

컴퓨터 비전#3 OpenCV 4 기본 사용법

cyphen156 2024. 9. 6. 17:44

이 전글에서 이어서 계속 한다. 

#include <iostream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
    cout << "Hello CV2" << CV_VERSION << endl;
    string path = "Resources/test.png";
    Mat img = imread(path);
    imshow("img", img);
    waitKey(0);

}

 

1. string path를 통해 이미지 리소스를 불러와 스트링 데이터로 저장하고 있다.

2. cv:: mat 클래스와 imread 함수를 통해 path 경로로 찾아가 데이터를 행렬형식으로 img 변수에 저장한다. Mat클래스는 Matrix로 행렬 데이터 형식을 의미한다. 이 행렬형식은 1차원(그레이 스케일)부터 3차원(RGB) 이상의 데이터를 행렬 형식으로 저장

3.img에 저장된 데이터를 CV::imshow를 통해 사용자에게 그려서 보여줄 수 있다. 

img에 저장된 행렬 데이터를 나는 눈으로 보고 싶다. 

이미지 사이즈를 1/10으로 줄이고, 다시 격자를 그려 10 X 10의 구역으로 나눠 해당 구역의 중간지점 픽셀값을 벡터 자료형으로 변환해 다시 텍스트로 출력하기로 했다. 

대강 합쳐보면 다음과 같이 값이 출력된걸 알 수 있다.

// openCV.cpp : 이 파일에는 'main' 함수가 포함됩니다. 거기서 프로그램 실행이 시작되고 종료됩니다.
//

#include <iostream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
    cout << "Hello CV2" << CV_VERSION << endl;
    string path = "Resources/test.png";
    Mat img = imread(path);

    // 이미지 불러오기 실패 처리
    if (img.empty())
    {
        cerr << "img read failed" << endl;
        return -1;
    }

    // (입력데이터, 출력데이터, 사이즈기반 크기조정, X비율, Y비율) 원본사이즈 2060 X 2060;;;;
    resize(img, img, Size(), 0.1, 0.1); 

    // 이미지 크기 row, col
    int imsize[] = { img.rows, img.cols };
    cout << "행 크기 : " << imsize[0] << "\t" << "열 크기 : " << imsize[1] << endl;

    // 원본 크기가 너무 크기 때문에 출력문도 너무 많다. 그러니까 보기 쉽게 전체를 10등분 해서 보여준다.
    // 격자의 간격 206px
    int gridSize = imsize[0] / 10;

    // 수평선 그리기
    for (int i = 0; i <= imsize[0]; i += gridSize)
    {
        line(img, Point(0, i), Point(imsize[1], i), Scalar(0, 0, 0), 1); // 검은선, 두께 1
    }

    // 수직선 그리기
    for (int j = 0; j <= imsize[1]; j += gridSize)
    {
        line(img, Point(j, 0), Point(j, imsize[0]), Scalar(0, 0, 0), 1); // 검은 선, 두께 1
    }

    for (int i = gridSize / 2; i < imsize[0]; i += gridSize)
    {
        for (int j = gridSize / 2; j < imsize[1]; j += gridSize)
        {
            // 이미지가 컬러일 경우 BGR 픽셀 값을 추출
            Vec3b pixel = img.at<Vec3b>(i, j);

            // 텍스트로 표시할 내용 "(B, G, R)" 형식으로 변환
            // 흰색 공간은 \t로 무시하기
            if ((int)pixel[0] == 255 && (int)pixel[1] == 255 && (int)pixel[2] == 255)
            {
                cout << "\t";
            }
            else
            {
                string pixelText = to_string((int)pixel[0]) + ","
                    + to_string((int)pixel[1]) + ","
                    + to_string((int)pixel[2]);         
                cout << pixelText << "\t";
            }
        }
        cout << endl;
    }
    
    cv::imshow("img", img);
    waitKey(0);
}

// 프로그램 실행: <Ctrl+F5> 또는 [디버그] > [디버깅하지 않고 시작] 메뉴
// 프로그램 디버그: <F5> 키 또는 [디버그] > [디버깅 시작] 메뉴

// 시작을 위한 팁: 
//   1. [솔루션 탐색기] 창을 사용하여 파일을 추가/관리합니다.
//   2. [팀 탐색기] 창을 사용하여 소스 제어에 연결합니다.
//   3. [출력] 창을 사용하여 빌드 출력 및 기타 메시지를 확인합니다.
//   4. [오류 목록] 창을 사용하여 오류를 봅니다.
//   5. [프로젝트] > [새 항목 추가]로 이동하여 새 코드 파일을 만들거나, [프로젝트] > [기존 항목 추가]로 이동하여 기존 코드 파일을 프로젝트에 추가합니다.
//   6. 나중에 이 프로젝트를 다시 열려면 [파일] > [열기] > [프로젝트]로 이동하고 .sln 파일을 선택합니다.

위 실습에서 알게 된 점은 Mat데이터 형식이 행렬 데이터라 해서 바로 텍스트로 추출하지 못한다는 점이다. 텍스트로 추출하기 위해서는 at<Vec3D>와 같이 제너럴 형식을 통한 형변환을 통해 데이터를 가공해내야 한다는 것이다. 

 

주요 함수 설명

파일 읽기 함수 cv::Mat imread(const String& filename, int flags = IMREAD_COLOR);

openCV에 포함되어 있는 Mat 클래스이다. 

첫번째 인자상수형 문자열을 통해 읽기 전용으로 파일 경로를 불러온다. 

두번째 인자정수타입 열거형 상수로  파일 불러오기 옵션을 의미한다.

여기서 열거형 상수는 다음과 같이 존재한다.

반환값은 Mat클래스 데이터이다.

만약 제대로 읽어오지 못햇다면 Mat 클래스에 저장된 데이터의 행, 열 데이터가 0,0으로 빈 객체가 생성된다.

조금 더 들어가 보자면 둘 중 어느 하나라도 0이라면 파일을 읽어오는데 실패한 것이다. 제대로 읽어왔다면 반드시 행과 열 데이터가 1개씩 있어야 하기 때문이다 (1, 1) 행이 있는데 열정보가 없다면 그것은 예기치 못한 이유로 파일을 읽는데 실패한것이라는 의미다.

  • IMREAD_UNCHANGED : 파일 원본의 컬러 속성을 그대로 사용한다, 알파채널까지 1~4채널을 사용할 수 있다.
  • IMREAD_GRAYSCALE : 파일을 1채널 그레이 스케일로 변환하여 불러온다. 색상값이 변환되기 때문에 데이터 손실이 발생할 수 있다,
  • IMREAD_COLOR : 파일을 3채널 BGR(RGB아님!!주의) 영상으로 변환하여 불러온다. 그레이 스케일 영상을 변환하는 경우 추가적인 데이터 변환으로 인해 불러온 데이터의 크기가 커질 수 있다. 
  • IMREAD_REDUCED_GRAYSCALE_2 : 원본 영상의 크기를 1/2로 줄인 그레이 스케일로 출력한다. 실제로 사용되는 용량은 가로 x 세로 이므로 1/4보다 작게 된다.
    (원본 RGB = 1 X 1 X (1 or 3 or 4채널) => 1 X 1 X (1채널) => 0.5 x 0.5 X(1채널) => 출력데이터 <= 원본 데이터의 1/4크기
  • IMREAD_REDUCED_COLOR_2 : 원본 영상의 크기를 1/2로 줄인 BRG로 출력한다. 실제로 사용되는 용량은 가로 x 세로 이므로 1/4가 된다.
    (원본 RGB = 1 X 1 X (1 or 3 or 4채널) => 1 X 1 X (3채널) => 0.5 x 0.5 X(3채널) => 출력데이터 = 원본 데이터의 1/4크기
  • IMREAD_IGNORE_ORIENTATION : EXIF에 저장된 방향 정보를 사용하지 않습니다. -> 불러온 파일의 원본 즉, 처음 촬영하고 난 이후 추가적인 처리(이미지 회전, 반전 등)가 되어있는 내용들을 무시한다.
string path = "Resources/test.png";
Mat img = imread(path);

파일 쓰기 함수 bool imwrite(const String& filename, InputArray img, const std::vector<int>& params = std::vector<int>());

첫번째 인자는 저장할 파일의 이름을 의미한다.

두번째 인자는 저장할 파일의 데이터(Mat 객체)를 의미한다.

세번째 인자는 저장될 파일의 형식에 의존적인 파라미터 쌍(플래그 & 값)을 의미한다.

가령 파라미터는 다음과 같이 사용할 수 있다.

vector<int> params;
params.push_back(IMWRITE_JPEG_QUALITY);
params.push_back(95);
imwrite("sample.jpeg", img, params);

반환 값은 성공한다면 1 실패하면 0로 true false값을 반환하는 부울 대수를 의미한다.

빈 객체 확인함수 bool Mat::empty() const

호출했을 때 Mat객체 안의 rows 또는 cols 멤버 변수가 하나라도 0이거나  data 멤버 변수가 NULL인 경우를 확인한다. 

true가 반환된다면 객체가 비었다는 뜻이니 false가 되어야 한다. 

// 이미지 불러오기 실패 처리
if (img.empty())
{
    cerr << "img read failed" << endl;
    return -1;
}

영상 출력함수 void imshow(const String& winname, InputArray mat);

첫번째 인자는 영상을 보여줄 윈도우의 제목

두번째 인자는 해당 윈도우에서 보여줄 데이터를 가지고 있는 Mat 객체

Mat img = imread(path);

imshow("img", img);

윈도우 창을 따로 컨트롤 할 수 있게 만들어주는 void namedWindow(const String& winname, int flags = WINDOW_AUTOSIZE);

단순히 imshow함수를 사용한다면 윈도우를 원하는대로 조정할 수 없다. 그렇기 때문에 화면에 보여줄 윈도우를 사용자 마음대로 조절하기 위해 사용되는 함수 

이 함수를 통해 moveWindow, resizeWindow와 같은 함수를 사용할 수 있다.

첫번째 인자윈도우의 이름을 지정한다.

두번째 인자열거형 상수를 통해 윈도우와 영상 크기를 조절할 수 있다. 

  • WINDOW_NORMAL : 윈도우 크기에 맞게 영상크기가 조절된다. 윈도우 크기를 자유롭게 변경할 수 있게 해준다.
  • WINDOW_AUTOSIZE : 영상 크기에 맞게 윈도우가 조절된다. 윈도우 크기를 자유롭게 변경할 수 없다.
    -> imshow에 들어가는 기본 옵션이라고 볼 수 있다.
  • WINDOW_OPENGL : 영상 렌더링시 OpenGL을 통해 영상을 출력한다.
namedWindow("namedImg", WINDOW_NORMAL);

imshow("namedImg", img);

아래 함수들은 따로 실습은 하지 않도록 한다.

윈도우제어함수 void moveWindow(const String& winname, int x, int y);와 resizeWindow("winname", x, y);

두 함수는 사용되는 인자 값이 같다. 다만 moveWindow는 화면성의 x, y의 좌표를 의미하고, resizeWindow는 윈도우의 크기(width, height)를 의미한다. 하지만 namedWindow함수를 통해 윈도우를 만들었다면 보통 NORMAL옵션을 사용할테니 직접 조정하는게 편할 수도 있다. 

장점으로는 프로그램 자체에서 미리 크기를 재조정해서 사용자에게 보여준다는 점? 

생성한 윈도우를 종료시키는 void destoryWindow(const String& winname);, destroyAllWindows();

키 입력을 기다리는 int waitKey(int delay = 0);

delay 값 만큼 밀리초 단위를 기다린다. 기본값은 0이며, 인자가 0일시 무한히 기다린다.

반환값은 delay를 지나면 -1을 반환하거나 키 입력이 발생한 경우해당 키의 ASCII 값을 반환합니다.

    waitKey(0);

최종적으로 완성되어 실습에 사용한 코드는 다음과 같다,

// openCV.cpp : 이 파일에는 'main' 함수가 포함됩니다. 거기서 프로그램 실행이 시작되고 종료됩니다.
//

#include <iostream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
    cout << "Hello CV2" << CV_VERSION << endl;
    string path = "Resources/test.png";
    Mat img = imread(path);

    // 이미지 불러오기 실패 처리
    if (img.empty())
    {
        cerr << "img read failed" << endl;
        return -1;
    }

    // (입력데이터, 출력데이터, 사이즈기반 크기조정, X비율, Y비율) 원본사이즈 2060 X 2060;;;;
    resize(img, img, Size(), 0.1, 0.1); 

    // 이미지 크기 row, col
    int imsize[] = { img.rows, img.cols };
    cout << "행 크기 : " << imsize[0] << "\t" << "열 크기 : " << imsize[1] << endl;

    // 원본 크기가 너무 크기 때문에 출력문도 너무 많다. 그러니까 보기 쉽게 전체를 10등분 해서 보여준다.
    // 격자의 간격 206px
    int gridSize = imsize[0] / 10;

    // 수평선 그리기
    for (int i = 0; i <= imsize[0]; i += gridSize)
    {
        line(img, Point(0, i), Point(imsize[1], i), Scalar(0, 0, 0), 1); // 검은선, 두께 1
    }

    // 수직선 그리기
    for (int j = 0; j <= imsize[1]; j += gridSize)
    {
        line(img, Point(j, 0), Point(j, imsize[0]), Scalar(0, 0, 0), 1); // 검은 선, 두께 1
    }

    for (int i = gridSize / 2; i < imsize[0]; i += gridSize)
    {
        for (int j = gridSize / 2; j < imsize[1]; j += gridSize)
        {
            // 이미지가 컬러일 경우 BGR 픽셀 값을 추출
            Vec3b pixel = img.at<Vec3b>(i, j);

            // 텍스트로 표시할 내용 "(B, G, R)" 형식으로 변환
            // 흰색 공간은 \t로 무시하기
            if ((int)pixel[0] == 255 && (int)pixel[1] == 255 && (int)pixel[2] == 255)
            {
                cout << "\t";
            }
            else
            {
                string pixelText = to_string((int)pixel[0]) + ","
                    + to_string((int)pixel[1]) + ","
                    + to_string((int)pixel[2]);         
                cout << pixelText << "\t";
            }
        }
        cout << endl;
    }
    cv::imshow("img", img);

    namedWindow("namedImg", WINDOW_NORMAL);

    imshow("namedImg", img);

    waitKey(0);
    vector<int> params;
    params.push_back(IMWRITE_JPEG_QUALITY);
    params.push_back(95);
    imwrite("sample.jpeg", img, params);
}

다음에는 OpenCV에서 사용되는 핵심 클래스들을 공부하겠다.

공부하는데 사용된 책은 다음과 같습니다.

 

책정보, OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝 : 길벗, 이지톡 (gilbut.co.kr)

 

OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝

컴퓨터 비전 기초부터 딥러닝 활용까지!

www.gilbut.co.kr

 

모든 예제 파일은 다음 주소에 있습니다.

GitHub - sunkyoo/opencv4cvml: "OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝" (길벗, 2019) 책 소스 코드입니다.

 

GitHub - sunkyoo/opencv4cvml: "OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝" (길벗, 2019) 책 소스 코드입니

"OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝" (길벗, 2019) 책 소스 코드입니다. - sunkyoo/opencv4cvml

github.com

또는 제 깃허브에 올려둡니다.

Workspace/C++/openCV at main · cyphen156/Workspace (github.com)

 

Workspace/C++/openCV at main · cyphen156/Workspace

Studying . Contribute to cyphen156/Workspace development by creating an account on GitHub.

github.com