OpenCV4 학습 가이드 1 - Mat: 가장 기본적인 이미지 컨테이너

1. 디지털 이미지 인식

현실 세계에서 디지털 이미지를 얻을 수 있는 방법에는 디지털 카메라, 스캐너, 컴퓨터 단층촬영, 자기공명영상 등 여러 가지가 있습니다. 어쨌든 우리(인간)가 보는 것은 이미지이다. 그러나 디지털 장치로 변환하면 기록되는 것은 이미지(디지털 이미지라고도 함)의 각 지점의 값입니다.

여기에 이미지 설명을 삽입하세요
예를 들어, 위 이미지에서 자동차의 거울은 모든 픽셀을 포함하는 강도 값의 행렬에 불과하다는 것을 알 수 있습니다. 픽셀 값을 가져오고 저장하는 방법은 필요에 따라 다를 수 있지만 결국 컴퓨터 세계의 모든 이미지는 숫자의 행렬과 행렬 자체를 설명하는 기타 정보로 축소될 수 있습니다. OpenCV는 이 정보를 처리하고 조작하는 것이 주요 목적인 컴퓨터 비전 라이브러리입니다. 따라서 가장 먼저 익숙해져야 할 것은 OpenCV가 이미지를 저장하고 처리하는 방법입니다.

2. 이력서의 발전 역사

OpenCV는 2001년부터 존재해 왔으며 최초의 CV 라이브러리는 C 인터페이스를 중심으로 구축되었습니다. 이미지를 메모리에 저장하기 위해 IplImage 라는 C 구조 가 사용되며 대부분의 오래된 튜토리얼과 교육 자료에서 볼 수 있습니다. 문제는 C 언어의 모든 단점을 포함하고 있다는 점인데, 그 중 가장 큰 것은 수동 메모리 관리가 필요하다는 것입니다. 사용자가 메모리 할당 및 할당 해제를 담당한다는 가정을 기반으로 구축되었습니다. 소규모 프로그램에서는 이것이 문제가 되지 않지만 일단 코드 기반이 커지면 개발 목표 달성에 집중하는 대신 이 모든 것을 처리하는 것이 훨씬 더 어려워집니다.

다행스럽게도 C++에서는 클래스 개념이 도입되어 자동 메모리 관리를 통해 사용자가 보다 쉽게 ​​사용할 수 있게 되었습니다. 좋은 소식은 C++가 C와 완벽하게 호환되므로 호환성 문제가 없다는 것입니다. 따라서 OpenCV 2.0에는 새로운 방식을 제공하는 새로운 C++ 인터페이스가 도입되었습니다. 즉, 메모리 관리에 어려움을 겪을 필요가 없어 코드를 간결하게 만들 수 있습니다(작성 ​​횟수는 줄이고 구현은 늘리기). C++ 인터페이스의 주요 단점은 현재 많은 임베디드 개발 시스템이 C만 지원한다는 것입니다. 따라서 임베디드 플랫폼을 대상으로 하지 않는 한 이전 방식을 사용하는 것은 의미가 없습니다(마조히즘적인 프로그래머가 아니고 문제를 일으키지 않는 한).

Mat 에 대해 가장 먼저 알아야 할 점 은 더 이상 메모리를 수동으로 할당하고 필요하지 않을 때 즉시 해제할 필요가 없다는 것입니다. 그렇게 하는 것이 여전히 가능하지만 대부분의 OpenCV 함수는 출력 데이터를 자동으로 할당합니다. 이미 존재하는 Mat 객체(매트릭스에 필요한 공간을 이미 할당함)를 전달하면 재사용된다는 점은 좋은 보너스입니다. 즉, 우리는 언제든지 작업을 수행하는 데 필요한 메모리만 사용합니다.

3. Mat의 기본 구조

Mat는 행렬 헤더( 행렬 크기, 저장 방법, 저장 행렬의 주소 등과 같은 정보를 포함하는 행렬 헤더)와 행렬에 대한 포인터( 포인터 ) 라는 두 가지 데이터 부분을 포함하는 클래스이며 , 행렬에는 다음이 포함됩니다. 픽셀 값. 매트릭스 헤더의 크기는 고정되어 있으나, 이미지 크기에 따라 매트릭스 자체의 크기가 달라질 수 있습니다.

OpenCV는 이미지 처리 라이브러리입니다. 여기에는 많은 수의 이미지 처리 기능이 포함되어 있습니다. 계산 문제를 해결하기 위해 대부분의 경우 라이브러리의 여러 기능을 사용하게 됩니다. 따라서 이미지를 함수에 전달하는 것은 일반적인 작업입니다. 우리는 종종 계산량이 상당히 많은 이미지 처리 알고리즘에 대해 이야기하고 있다는 사실을 잊어서는 안됩니다. 큰 이미지를 복사하면 프로그램 속도가 더욱 느려집니다.

이 문제를 해결하기 위해 OpenCV는 참조 카운팅 시스템을 사용합니다. 기본 아이디어: 각 Mat 객체에는 자체 헤드가 있지만 [ 행렬 포인터 ]가 동일한 주소를 가리키 도록 하여 두 Mat 객체 간에 행렬을 공유할 수 있습니다 . 또한 복사 연산자는 이미지 데이터 자체가 아닌 큰 행렬의 행렬 헤더와 포인터만 복사합니다.

3.1, 매트 - 얕은 사본

Mat A, C;                          // 仅仅创建矩阵头(matrix header)
A = imread(argv[1], IMREAD_COLOR); // 分配内存,存储图像数据
Mat B(A);                          // 使用复制构造器(copy constructor)
C = A;                             // 赋值操作符(Assignment operator)

위의 모든 개체(B, C)는 동일한 데이터 매트릭스를 가리키며, 그 중 하나를 수정하면 다른 모든 개체에 영향을 미칩니다 . 실제로, 서로 다른 객체는 동일한 기본 데이터에 대해 서로 다른 액세스 방법을 제공할 뿐입니다. 그러나 헤더는 다릅니다.

정말 흥미로운 부분은 전체 데이터의 일부만 참조하는 행렬 헤더를 만들 수 있다는 것입니다. 예를 들어, 이미지에 관심 영역(ROI)을 생성하려면 다음과 같이 새로운 경계가 있는 새 행렬 헤더를 생성하면 됩니다.

Mat D (A, Rect(10, 10, 100, 100) ); // using a rectangle
Mat E = A(Range::all(), Range(1,3)); // using row and column boundaries

다음 사항에 주목할 가치가 있습니다.

  1. A의 픽셀이 수정되면 D와 E가 모두 영향을 받습니다.
  2. 이것에 문제가 있습니까? 행렬 자체가 여러 Mat 개체에 속하는 경우 더 이상 필요하지 않을 때 정리하는 책임은 누구에게 있습니까? 대답은: 그것을 사용하는 마지막 개체입니다. 이는 참조 카운트 시스템을 사용하여 처리됩니다. Mat 개체의 헤드가 복사될 때마다 행렬의 카운터가 1씩 증가합니다. 매트릭스 헤드를 청소할 때마다 카운터는 1씩 감소합니다. 카운터가 0에 도달하면 매트릭스가 해제됩니다.
  3. 얕은 복사는 메모리 소모를 줄일 수 있지만 픽셀 값을 자주 수정해야 하는 경우 숨겨진 위험이 많이 발생합니다.

3.2, Mat - 전체 복사

원본 이미지를 수정하지 않으려면 전체 복사 방법을 사용할 수 있습니다. 코드 조각은 다음과 같습니다.

Mat F = A.clone();
Mat G;
A.copyTo(G);

위의 방법을 사용하면 F와 G의 데이터를 수정해도 A가 가리키는 이미지 데이터에는 영향을 미치지 않습니다. 몇 가지 참고할 사항이 있습니다.

  1. OpenCV 기능의 출력 이미지 할당은 자동으로 이루어집니다(달리 지정하지 않는 한).
  2. 메모리 관리에 특별한 주의를 기울이지 않고 OpenCV의 C++ 인터페이스를 사용하십시오.
  3. 할당 및 복사 생성자는 행렬 헤더만 복사합니다.
  4. 매트 딥 카피는 cv::Mat::clone()cv::Mat::copyTo();

4. 색상 표현 방법

픽셀 값을 저장하는 방법: 색상 공간( 색상 공간 )과 사용된 데이터 유형( 데이터 유형 )을 선택할 수 있습니다. 색상 공간은 색상 구성 요소가 결합되어 지정된 색상을 인코딩하는 방법을 나타냅니다. 가장 간단한 것은 회색조이고, 사용할 수 있는 색상은 흑백이며, 이러한 요소의 조합을 통해 다양한 회색 음영을 만들 수 있습니다.

더욱 다채로운 이미지를 만들기 위해 선택할 수 있는 방법이 더 많습니다. 우리는 일반적으로 이를 3~4개의 기본 구성 요소로 나누고 이러한 조합을 사용하여 다른 색상을 만듭니다. 가장 인기 있는 것은 RGB 색상 공간입니다. 그 이유는 RGB 색상 공간이 우리 눈이 색상을 구성하는 방식이기 때문입니다. 기본 색상은 빨간색, 녹색, 파란색입니다. 색상의 투명도를 코딩하기 위해 네 번째 요소인 알파(A)가 추가되는 경우도 있습니다.

그러나 다른 많은 색상 시스템( RGB, HSV/HLS, YCrCb, Lab )이 있으며 각각 고유한 장점이 있습니다.

  • RGB는 우리의 눈이 이와 유사한 것을 사용하기 때문에 가장 일반적입니다. 그러나 OpenCV 표준 디스플레이 시스템은 BGR 색상 공간(빨간색과 파란색 채널이 교체되는 위치)을 사용하여 색상을 구성한다는 점을 명심하세요.
  • HSV와 HLS는 색상을 색조, 채도, 명도/휘도의 세 부분으로 분해합니다. 이는 색상을 설명하는 보다 자연스러운 방법입니다. 예를 들어, 마지막 구성 요소를 무시하면 알고리즘이 입력 이미지의 조명 조건에 덜 민감하게 됩니다.
  • 널리 사용되는 JPEG 이미지 형식은 YCrCb를 사용합니다.
  • CIE L*a*b*는 지각적으로 균일한 색상 공간으로, 특정 색상과 다른 색상 사이의 거리를 측정해야 하는 경우 유용합니다.

각 빌드 구성 요소에는 고유한 유효한 도메인이 있습니다. 그러면 사용되는 데이터 유형이 생성됩니다. 구성 요소가 저장되는 방식은 유효한 도메인에 대한 제어를 정의합니다. 가능한 가장 작은 데이터 유형은 1바이트 또는 8비트를 의미하는 char이며, 부호 없는(0부터 255까지의 값을 저장할 수 있음) 또는 부호 있는(-127부터 +127까지의 값)일 수 있습니다. 세 가지 구성 요소(예: RGB)의 경우 이 너비는 이미 1,600만 가지의 가능한 색상을 제공하지만 더 미세한 제어를 위해 float(4바이트 = 32비트) 또는 double(8바이트 = 64비트) 데이터 유형을 사용할 수 있습니다. . 그러나 구성 요소의 크기를 늘리면 메모리 사용량도 늘어납니다.

5. Mat 객체 메소드 컬렉션 생성

Mat는 이미지 컨테이너로도 잘 작동하지만 범용 매트릭스 클래스이기도 합니다. 따라서 다차원 행렬을 만들고 조작할 수 있습니다. [ Mat 클래스 ] 의 멤버 함수를 이용하여 다양한 방법으로 [ Mat 객체 ]를 생성할 수 있으며 생성 방법은 다음과 같습니다.

5.1 일반적으로 사용되는 Mat 클래스의 멤버 함수

  • 매트(int 행, int 열, int 유형)

    CV::Mat 새 행렬을 생성하기 위한 첫 번째 오버로드된 멤버 함수입니다.
    매개변수 분석 :

    1. 행: 2D 배열의 행 수입니다.
    2. cols: 2D 배열의 열 수;
    3. 유형: CV_64FC4와 같은 배열 유형은 각 요소가 64비트(4바이트)를 차지하고 요소의 데이터 유형이 부동이며 4채널임을 의미합니다.

    공식 API
    여기 인용문이 있습니다

  • 매트(크기 크기, int 유형)

    기능 기능: 과부하 기능, 매트릭스 생성이 용이함.
    매개변수 분석:

    1. size: 2D 배열의 크기, Size(cols,rows). Size() 생성자 에서 행 역순입니다.
    2. 유형: 위의 소개를 참조하세요. 여기에서는 생략되었습니다.

    공식 API
    여기에 이미지 설명을 삽입하세요

5.2 Mat 클래스의 멤버 함수 예제

  • cv::Mat::Mat 생성자(생성자)

    Mat M(2,3, CV_8UC3, Scalar(0,0,255));
    cout << "M = " << endl << " " << M << endl << endl;
    M = 
     [  0,   0, 255,   0,   0, 255,   0,   0, 255;
    	0,   0, 255,   0,   0, 255,   0,   0, 255]
    
    1. 2,3: Mat 행렬의 행(2)과 열(3)을 지정합니다.
    2. CV_8UC3: 저장된 데이터의 유형, 각 매트릭스 포인트에 대한 채널 수를 지정합니다. 구조는 다음과 같습니다.CV_[항목당 비트 수][Signed 또는 Unsigned][Type Prefix]C[채널 번호]
    3. 구체적인 설명: [8]은 데이터를 저장하는 데 8비트(1바이트)의 길이가 필요함을 나타내고, [U]는 부호 없는 유형을 나타내고, [C]는 문자 유형(char)을 나타내고, [3]은 숫자를 나타냅니다. 채널;
    4. Scalar(0,0,255): 행렬 생성을 위한 초기화 값을 지정합니다.
  • C/C++ 배열 및 Mat 생성자 초기화

    Mat 클래스의 오버로드된 멤버 함수, 차이점은 매개변수 설정에 있습니다.

    1. ndims: 행렬의 차원
    2. 크기: 정수형의 다차원 배열
    3. 유형: 위의 정의와 동일
    4. 공식 오버로드된 함수는 아래 그림에 나와 있습니다.여기에 이미지 설명을 삽입하세요

    예:

    int sz[3] = {
          
          2,3,5};
    Mat L(3,sz, CV_8UC(1), Scalar::all(0));
    std::cout << "L.DIMS = " << " " << L.dims << std::endl;
    std::cout << "L.SIZE = " << " " << L.size << std::endl;
    
    // 输出如下
    L.DIMS =  3
    L.SIZE =  2 x 3 x 5
    

  • 이력서::매트::생성

    여기 인용문이 있습니다
    이 방법을 사용하기 위한 규칙:

    1. 현재 배열 형태와 유형이 새 배열과 일치하면 즉시 반환합니다. 그렇지 않으면 Mat::release를 호출하여 이전 데이터를 역참조하십시오.
    2. 새로운 매트릭스 헤더(header)를 초기화합니다.
    3. 새 데이터에 공간을 할당합니다. 공간 크기는 다음과 같습니다: total ( ) ✽ elem Size ( ) total()*elemSize()~ 까지 ( ) _ _ _*e l e m S i ze ( ) , 단위는 바이트(byte)입니다.
    4. 데이터와 관련된 새로운 참조 카운터를 할당하고 이를 1로 설정합니다.

    추가하려면 :

    1. 메모리 크기 = image.total() x image.elemSize();
    2. image.total() = 이미지.너비 x 이미지.높이;
    3. image.elemSize: 픽셀 값이 차지하는 바이트 수(16SC3 등), 기타 크기는 3xsizeof(short)
    // create function
    M.create(4,4, CV_8UC(2));
    cout << "N = "<< endl << " "  << M << endl << endl;
    std::cout << "M = " << std::endl << " " << &(M.data) << std::endl << std::endl;
    N = 
     [  0,   0,   0,   0,   0,   0,   0,   0;
        0,   0,   0,   0,   0,   0,   0,   0;
      216, 224,  38,   2,   0,   0,   0,   0;
       32, 225,  38,   2,   0,   0,   0,   0]
    
    M = 
     0x7ffd731496f0
    
  • Matlab 스타일 초기화

    Mat E = Mat::eye(4, 4, CV_64F);
    cout << "E = " << endl << " " << E << endl << endl;
    Mat O = Mat::ones(2, 2, CV_32F);
    cout << "O = " << endl << " " << O << endl << endl;
    Mat Z = Mat::zeros(3,3, CV_8UC1);
    cout << "Z = " << endl << " " << Z << endl << endl;
    
    // 输出内容如下
    E = 
     [1, 0, 0, 0;
      0, 1, 0, 0;
      0, 0, 1, 0;
      0, 0, 0, 1]
    
    O = 
     [1, 1;
      1, 1]
    
    Z = 
     [  0,   0,   0;
        0,   0,   0;
        0,   0,   0]
    
  • Mat_ 및 짧은 목록을 사용하여 Mat 초기화

    Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
    cout << "C = " << endl << " " << C << endl << endl;
    
    C = (Mat_<double>({
          
          0, -1, 0, -1, 5, -1, 0, -1, 0})).reshape(3);
    cout << "C = " << endl << " " << C << endl << endl;
    
    // 输出内容
    C = 
     [0, -1, 0;
     -1, 5, -1;
      0, -1, 0]
    
    C = 
     [0, -1, 0;
     -1, 5, -1;
      0, -1, 0]
    
  • cv::Mat::clone 또는 cv::Mat::copy
    기존 Mat 데이터를 기반으로 새 Mat 생성 C

    // 取C的第2行,创建新的Mat
    MatRowClone = C.row(1).clone();
    cout << "RowClone = " << endl << " " << RowClone << endl << endl;
    
    // 输出内容
    RowClone = [-1, 5, -1]
    
  • 임의의 값으로 행렬 초기화

    Mat R = Mat(3, 2, CV_8UC3);
    randu(R, Scalar::all(0), Scalar::all(255));
    
    // 输出内容
    R (default) = 
    	[ 91,   2,  79, 179,  52, 205;
    	 236,   8, 181, 239,  26, 248;
     	 207, 218,  45, 183, 158, 101]
    

6. 매트 출력 스타일

 출력 구조에서 볼 수 있듯이 기본 스타일(C++)은 C와 매우 유사하며 Python과 Numpy도 매우 유사합니다.

  • 기본

    cout << "R (default) = " << endl <<        R           << endl << endl;
    
    // 输出内容
    R (default) = 
    	[ 91,   2,  79, 179,  52, 205;
    	 236,   8, 181, 239,  26, 248;
    	 207, 218,  45, 183, 158, 101]
    
  • 파이썬

    cout << "R (python)  = " << endl << format(R, Formatter::FMT_PYTHON) << endl << endl;
    
    // 输出内容
    R (python)  = 
    	[[[ 91,   2,  79], [179,  52, 205]],
    	 [[236,   8, 181], [239,  26, 248]],
    	 [[207, 218,  45], [183, 158, 101]]]
    
  • CSV

    cout << "R (csv)     = " << endl << format(R, Formatter::FMT_CSV   ) << endl << endl;
    
    // 输出内容
    R (csv)     = 
     	91,   2,  79, 179,  52, 205
       236,   8, 181, 239,  26, 248
       207, 218,  45, 183, 158, 101
    
  • 넘피

    cout << "R (numpy)   = " << endl << format(R, Formatter::FMT_NUMPY ) << endl << endl;
    
    // 输出内容
    R (numpy)   = 
    	array([[[ 91,   2,  79], [179,  52, 205]],
      		   [[236,   8, 181], [239,  26, 248]],
     	 	   [[207, 218,  45], [183, 158, 101]]], dtype='uint8')
    
  • cout << "R (numpy)   = " << endl << format(R, Formatter::FMT_NUMPY ) << endl << endl;
     	
    // 输出内容
    R (c)       = 
       {
          
           91,   2,  79, 179,  52, 205,
        236,   8, 181, 239,  26, 248,
        207, 218,  45, 183, 158, 101}
    

7. 포인트 데이터 저장 구조

  • 포인트 유형
    여기에 이미지 설명을 삽입하세요

  • 예시 분석

    // 2D Point
    Point2f P(5, 1);
    cout << "Point (2D) = " << P << endl << endl;
    
    // 3D Point
    Point3f P3f(2, 6, 7);
    cout << "Point (3D) = " << P3f << endl << endl;
    
    // vector
    vector<float> v;
    v.push_back( (float)CV_PI);   v.push_back(2);    v.push_back(3.01f);
    cout << "Vector of floats via Mat = " << Mat(v) << endl << endl;
    
    // push 2d points
    vector<Point2f> vPoints(20);
    for (size_t i = 0; i < vPoints.size(); ++i)
        vPoints[i] = Point2f((float)(i * 5), (float)(i % 7));
    cout << "A vector of 2D Points = " << vPoints << endl << endl;
    
    // 输出内容
    Point (2D) = [5, 1]
    
    Point (3D) = [2, 6, 7]
    
    Vector of floats via Mat = [3.1415927;
     									2;
    								 3.01]
    
    A vector of 2D Points = [0, 0;
    						 5, 1;
    					 	10, 2;
    					 	15, 3;
    					 	20, 4;
    					 	25, 5;
    					 	30, 6;
    					 	35, 0;
    					 	40, 1;
    					 	45, 2;
    					 	50, 3;
    					 	55, 4;
    					 	60, 5;
    					 	65, 6;
    					 	70, 0;
    					 	75, 1;
    					 	80, 2;
    					 	85, 3;
    					 	90, 4;
    					 	95, 5]
    

8. 참고자료

Supongo que te gusta

Origin blog.csdn.net/kxh123456/article/details/130172623
Recomendado
Clasificación