Computer Vision and OpenCV [Day 2]

 Published On

3. OpenCV의 기본 자료 구조 및 실습[2]

3.8. Mat 클래스

  • 단일 채널 혹은 다중 채널의 값
  • 실수나 복소수로 구성된 벡터
  • 명암도 영상이나 컬러 영상 데이터
  • 점의 집합
  • 히스토그램 데이터
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;

void main() {


  CV_8U  : 8-bit unsigned integer: uchar ( 0..255 )
  CV_8S  : 8-bit signed integer: schar ( -128..127 )
  CV_16U : 16-bit unsigned integer: ushort ( 0..65535 )
  CV_16S : 16-bit signed integer: short ( -32768..32767 )
  CV_32S : 32-bit signed integer: int ( -2147483648..2147483647 )
  CV_32F : 32-bit floating-point number: float ( -FLT_MAX..FLT_MAX, INF, NAN )
  CV_64F : 64-bit floating-point number: double ( -DBL_MAX..DBL_MAX, INF, NAN )


  // Mat 객체 선언 방법
  Mat m1(2, 3, CV_8U, Scalar(0));
  cout << "[m1] = \n" << m1 << endl;
  cout << endl;

  Mat m2(2, 3, CV_8U, Scalar(300)); // 자료형을 8bit unsigned char형으로 선언했기 때문에, 최대가 255로 나옴
  cout << "[m2] = \n" << m2 << endl;
  cout << endl;

  Mat m3(2, 3, CV_16S, Scalar(300)); // 자료형을 16bit short형으로 선언했기 때문에, 최대가 65535이므로 300까지 제대로 나옴
  cout << "[m3] = \n" << m3 << endl;
  cout << endl;

  float data[] = {
    1.2f, 2.3f, 3.2f,
    4.5f, 5.f, 6.5f,
  };

  Mat m4(2, 3, CV_32F, data);
  cout << "[m4] = \n" << m4 << endl;
  cout << endl;

  // Size_ 객체로 Mat 객체 선언 방법
  Size sz(2, 3); // Size는 기본적으로 (width, height)이므로, 행렬로 보면 3 X 2 행렬이 됨
  cout << "size = " << sz << endl;
  cout << endl;

  Mat m5(Size(2, 3), CV_64F);
  Mat m6(sz, CV_32F, data);

  cout << "[m5] = \n" << m5 << endl;
  cout << endl;

  cout << "[m6] = \n" << m6 << endl;
  cout << endl;


  // 단위행렬이나 1로 구성된 행렬 등의 특수한 행렬을 생성하는 함수들
  1. ones()	: 행렬의 모든 원소 1인 행렬을 반환
  2. eye()	: 지정된 크기와 타입의 단위 행렬을 반환
  3. zeros()	: 행렬의 원소를 0으로 초기화


  Mat m7 = Mat::ones(3, 5, CV_8UC1);
  cout << "[m7] = \n" << m7 << endl;
  cout << endl;

  Mat m8 = Mat::zeros(Size(5, 3), CV_8UC1);
  cout << "[m8] = \n" << m8 << endl;
  cout << endl;

  Mat m9 = Mat::eye(3, 3, CV_8UC1);
  cout << "[m9] = \n" << m9 << endl;
  cout << endl;

}

3.9. Mat_ 클래스

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  double a = 32.12345678, b = 2.7;
  short  c = 400;
  float  d = 10.f, e = 11.f, f = 13.f;

  Mat_<int> 	m1(2, 4);
  Mat_<uchar> 	m2(3, 4, 100);
  Mat_<short>   	m3(4, 5, c);

  m1 << 1, 2, 3, 4, 5, 6;
  Mat m4 = (Mat_<double>(2, 3) << 1, 2, 3, 4, 5, 6);
  Mat m5 = (Mat_<float>(2, 3) << a, b, c, d, e, f);

  cout << "[m1]=" << endl << m1 << endl;
  cout << "[m2]=" << endl << m2 << endl << endl;
  cout << "[m3]=" << endl << m3 << endl << endl;
  cout << "[m4]=" << endl << m4 << endl;
  cout << "[m5]=" << endl << m5 << endl;
  return 0;
}

3.10. Matx 클래스

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int  main()
{
  // 기본 선언 방법
  Matx<int, 3, 3> 	 m1(1, 2, 3, 4, 5, 6, 7, 8, 9);
  Matx<float, 2, 3>	 m2(1, 2, 3, 4, 5, 6);
  Matx<double, 3, 5>	 m3(3, 4, 5, 7);

  cout << "[m1] = " << endl << m1 << endl;
  cout << endl;

  cout << "[m2] = " << endl << m1 << endl;
  cout << endl;

  cout << "[m3] = " << endl << m1 << endl;
  cout << endl;

  // 간편 선언 방법
  Matx23d  m4(3, 4, 5, 6, 7, 8);
  m4 << 1, 2, 3, 4, 5, 6;
  cout << "[m4] = " << endl << m4 << endl;
  cout << endl;

  Matx34d  m5(1, 2, 3, 10, 11, 12, 13, 14, 15); // 초기화되지 않은 값은 자동으로 0으로 할당
  Matx66d m6(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

  // 행렬 원소 접근
  cout << "m5(0, 0) =" << m5(0, 0) << " m5(1, 0) =" << m5(1, 0) << endl;
  cout << endl;

  cout << "m6(0, 1) =" << m6(0, 1) << " m6(1, 3) =" << m6(1, 3) << endl << endl;
  cout << endl;


  cout << "[m5] = " << endl << m5 << endl;
  cout << endl;
  cout << "[m6]= " << endl << m6 << endl;
  cout << endl;

  return 0;
}

3.11. Mat 클래스의 다양한 속성

Mat::dims			// 차원 수
Mat::rows			// 행의 개수
Mat::cols			// 열의 개수
Mat::data			// 행렬 원소 데이터에 대한 포인터
Mat::step			// 행렬의 한 행이 차지하는 바이트 수
Mat::channels()		// 행렬의 채널 수 반환
Mat::depth()		// 행렬의 깊이값 반환
Mat::elemSize()		// 행렬의 한 원소에 대한 바이트 크기 반환
Mat::elemSize1()	// 행렬의 한 원소의 한 채널에 대한 바이트 크기 반환
Mat::empty()		// 행렬 원소가 비어있는지 여부 반환
Mat::isSubmatrix()	// 참조 행렬인지 여부 반환
Mat::size()			// 행렬의 크기를 Size형으로 반환
Mat::step1()		// step을 elemSize1()로 나누어서 정규화된 step 반환
Mat::total()		// 행렬 원소의 전체 개수 반환
Mat::type()			// 행렬의 데이터 타입(짜료형 + 채널 수) 반환
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  Mat m1(4, 3, CV_32FC3);
  cout << "차원 수 = " << m1.dims << endl;
  cout << "행 개수 = " << m1.rows << endl;
  cout << "열 개수 = " << m1.cols << endl;
  cout << "행렬 크기 = " << m1.size() << endl << endl;

  // depth는 자료형을 구분하기 위한 상수

  CV_8U 		depth 0	unsigned char
  CV_8S 		depth 1	char
  CV_16U		depth 2	unsigned short int
  CV_16S		depth 3	short int
  CV_32S		depth 4	int
  CV_32F		depth 5	float
  CV_64F		depth 6	double


  cout << "depth(자료형을 구분) = " << m1.depth() << endl; // CV_32FC3를 반환
  cout << "전체 원소 개수 = " << m1.total() << endl;
  cout << "한 원소의 바이트 크기 = " << m1.elemSize() << endl;
  cout << "채널당 한 원소의 바이트 크기 = " << m1.elemSize1() << endl << endl;

  // 1 채널의 크기 = 4byte

  return 0;
}

3.12. Mat 클래스의 할당 연산자(=)

m1 = m2

m2 행렬이 m1 행렬에 복사되는 것이 아니라 m2 행렬을 m1 행렬이 공유한다.
따라서 m2 행렬의 원소가 변경되면, m1 행렬의 원소도 같이 변경된다.
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  Mat m1(2, 3, CV_8U, 2);
  Mat m2(2, 3, CV_8U, 10);

  // 행렬 연산
  Mat m3 = m1 + m2;
  Mat m4 = m1 * 2;
  Mat m5 = m1;

  cout << "[m2] =" << endl << m2 << endl;
  cout << "[m3] =" << endl << m3 << endl;
  cout << "[m4] =" << endl << m4 << endl << endl;

  // 공유 행렬 처리
  cout << "[m1] =" << endl << m1 << endl;
  cout << "[m5] =" << endl << m5 << endl << endl;

  // 포인터처럼 행렬의 값을 참조(복사 x)
  m1 = m2 * 10;

  cout << "[m1] =" << endl << m1 << endl;
  cout << "[m5] =" << endl << m5 << endl;
  return 0;
}

3.13. Mat 클래스의 크기 및 형태 변경 - I

resize(): 행의 개수를 기준으로 기존 행렬의 크기를 변경한다.
          기존 행렬의 개수보다 size가 작으면 하단 행을 제거하고, 크면 하단 행을 추가한다.

reshape(): 행렬 전체 원소 개수는 바뀌지 않으면서, 행렬의 모양을 변경하여 새 행렬을 반환한다.
           기존 행렬과 변경된 행렬의 전체 원소 개수(채널 수 x 행 수 x 열 수)가 일치하지 않으면 에러가 발생한다.

create(): 기존에 존재하는 행렬의 차원, 행, 열, 자료형을 변경하여 다시 생성한다.
          기존 행렬과 크기와 자료형이 다르면 기존 메모리를 해제하고 새로운 데이터를 생성한다.
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  Mat m = (Mat_<uchar>(2, 4) << 1, 2, 3, 4, 5, 6, 7, 8);
  cout << "[m] = " << endl << m << endl << endl;

  m.resize(1); // 1행으로 변경
  cout << "m.resize(1) = " << endl << m << endl;

  m.resize(3); // 3행으로 변경, 추가되는 값을 지정하지 않아 임의의 값으로 지정됨
  cout << "m.resize(3) = " << endl << m << endl << endl;

  m.resize(5, Scalar(50)); // 5행으로 변경, 추가된 값들을 50으로 지정
  cout << "m.resize(5) = " << endl << m << endl;

  return  0;
}

3.14. Mat 클래스의 크기 및 형태 변경 - II

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

void print_matInfo(string m_name, Mat m)
{
  cout << "[ " << m_name << " 행렬]" << endl;
  cout << "    채널수  = " << m.channels() << endl;
  cout << "    행 x 열 = " << m.rows << " x " << m.cols << endl << endl;
}

int main()
{

  // reshape(채널, 행, 열)

  Mat m1(2, 6, CV_8U);	   // 채널: 1, 행: 2, 열: 6
  Mat m2 = m1.reshape(2);    // 채널: 2로 변환, 행: 2, 열: 3로 변환(원소의 개수가 유지되어야 하므로)
  Mat m3 = m1.reshape(3, 2); // 채널: 3로 변환, 행: 2, 열: 2로 변환(원소의 개수가 유지되어야 하므로)

  print_matInfo("m1(2, 6)", m1);
  print_matInfo("m2 = m1_reshape(2)", m2); 
  print_matInfo("m3 = m1_reshape(3, 2)", m3);

  m1.create(3, 5, CV_16S);   // 완전히 새로운 값을 생성
  print_matInfo("m1.create(3, 5)", m1);
  return  0;
}

3.15. Mat 복사 및 자료형 변환

영상처리, 컴퓨터 비전 응용 시 원본 행렬 복사가 빈번히 발생한다.

Mat clone()		:	행렬 데이터와 같은 값을 복사해서 새로운 행렬을 반환한다.
void copyTo()	:	행렬 데이터를 인자로 입력된 mat 행렬에 복사한다.

    - copyTo(Mat &mat, Mat mask)
    - mat: 복사될 목적 행렬
    - mask: 연산 마스크(mask 행렬의 원소가 0이 아닌 위치만 복사가 수행됨)

void converTo()	:	행렬 원소의 데이터 타입을 변경하여 인수로 입력된 mat 행렬에 반환한다.

    - converTo(Mat &mat, int type, double alpha=1, double beta=0)
    - mat: 데이터 타입이 변경될 목적 행렬
    - type: 변경하고자 하는 데이터 타입(CV_8U, CV_16S, ...)
    - alpha: 원본 행렬의 원소값의 배율 지정
    - beta: 원본 행렬의 원소값에 대한 이동값
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  double data[] = {
    1.1, 2.2, 3.3, 4.4,
    5.5, 6.6, 7.7, 8.9,
    9.9, 10, 11, 12
  };
  Mat m1(3, 4, CV_64F, data);
  Mat m2 = m1.clone();	 // m1 행렬을 복사한 후, m2에 저장

  Mat m3, m4;
  m1.copyTo(m3);			 // m1 행렬을 복사한 후, m3에 저장
  m1.convertTo(m4, CV_8U); // m1 행렬의 데이터 타입을 uchar 행렬로 형변환 후, m4에 저장

  cout << "[m1] =\n" << m1 << endl;
  cout << "[m2] =\n" << m2 << endl;
  cout << "[m3] =\n" << m3 << endl;
  cout << "[m4] =\n" << m4 << endl;
  return 0;
}

3.16. vector 클래스

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main() {

  vector<Point> v1;
  v1.push_back(Point(10, 20));
  v1.push_back(Point(20, 30));
  v1.push_back(Point(50, 60));

  cout << "[v1] = \n" << (Mat)v1 << endl << endl; // 벡터는 Mat 형변환을 해야 출력이 가능하다

  vector<float> v2(3, 9.25);
  Size arr_size[] = { Size(2, 2), Size(3, 3), Size(4, 4) }; // 3행, 2열
  int arr_int[] = { 10, 20, 30, 40, 50 };

  cout << "[v2] = \n" << (Mat)v2 << endl << endl;
  cout << "[arr_size] = \n" << arr_size << endl << endl;
  cout << "[arr_int] = \n" << arr_int << endl << endl;

  // 배열 원소로 벡터 초기화
  vector<Size> v3(arr_size, arr_size + sizeof(arr_size) / sizeof(Size));
  vector<int> v4(arr_int + 2, arr_int + sizeof(arr_int) / sizeof(int));

  cout << "[v3] = \n" << (Mat)v3 << endl << endl; // 현재 3행 2열로
  cout << "reshape [v3] = \n" << ((Mat)v3).reshape(1, 1) << endl << endl; // reshape으로 (1채널, 1행, 6열)으로 변환

  cout << "[v4] = \n" << (Mat)v4 << endl << endl;
  cout << "reshape [v4] = \n" << ((Mat)v4).reshape(1, 1) << endl << endl;

}

3.17. vector 클래스의 사용 - I

1. 벡터 원소 접근 - 배열처럼 첨자 연산자([]) 이용
2. 벡터 할당된 용량 확인 - vector::capacity()
3. 벡터 메모리 확보 - vector::reserve()
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

void print_vectorInfo(string v_name, vector<int> v)
{
  cout << "[ " << v_name << " ] = ";
  if (v.empty()) 	cout << "벡터가 비어있습니다." << endl;
  else			cout << ((Mat)v).reshape(1, 1) << endl;

  cout << ".size() = " << v.size() << endl;
}

int main()
{
  int  arr[] = { 10, 20, 30, 40, 50 };
  vector<int>  v1(arr, arr + sizeof(arr) / sizeof(int)); // 포인터 형태와 비슷(arr는 arr배열의 초기값, sizeof(arr)/sizeof(int)는 배열의 크기)

  print_vectorInfo("v1", v1);
  cout << ".capacity() = " << v1.capacity() << endl << endl; // 할당된 용량 확인(처음에는 5)

  // 함수가 호출될 때마다 벡터의 용량이 재할당됨 -> 용량을 미리 설정하여 재할당이 안일어나도록 할 수 있음(reserve)
  v1.insert(v1.begin() + 2, 100); // 2 번째 원소의 앞에다 insert
  print_vectorInfo("v1, insert(2) ", v1);
  cout << ".capacity() = " << v1.capacity() << endl << endl; // 할당된 용량 확인(재할당 발생으로 용량 2 증가하여 7)

  v1.erase(v1.begin() + 3);
  print_vectorInfo("v1, erase(3) ", v1); // 앞에서부터 3 번째 원소(배열은 0부터 시작하므로, 4 번째)를 삭제
  cout << ".capacity() = " << v1.capacity() << endl << endl;

  v1.erase(v1.begin() + 3);
  print_vectorInfo("v1, erase(3) ", v1);
  cout << ".capacity() = " << v1.capacity() << endl << endl;

  v1.clear(); // 전체 삭제
  print_vectorInfo("v1, clear() ", v1);

  return 0;
}

3.18. vector 클래스의 사용 - II

#include <opencv2/opencv.hpp>
#include <time.h>
using namespace cv;
using namespace std;
int main()
{
  vector<double> v1, v2;
  v1.reserve(10000000); // 벡터 메모리 할당

  double start_time = clock();
  for (int i = 0; i < v1.capacity(); i++) {
    v1.push_back(i); // 할당된 용량만큼 원소값을 삽입
  }
  printf("reserve() 사용 %5.2f ms\n", (clock() - start_time));

  start_time = clock();
  for (int i = 0; i < v1.capacity(); i++) {
    v2.push_back(i); // v2는 할당되지 않았기에, 할당된 용량만큼 원소값을 삽입하면 남는 값이 생김(double형은 최대가 10^127이므로)
  }
  printf("reserve() 미사용 %5.2f ms\n", (clock() - start_time));
  return 0;
}

3.19. Mat의 원소 추가 및 삭제

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

void  print_matInfo(string name, Mat m)   	// 행렬의 정보와 원소 출력 함수
{
  string mat_type;
  if (m.depth() == CV_8U)	 		mat_type = "CV_8U";
  else if (m.depth() == CV_8S)	mat_type = "CV_8S";
  else if (m.depth() == CV_16U) 	mat_type = "CV_16U";
  else if (m.depth() == CV_16S)	mat_type = "CV_16S";
  else if (m.depth() == CV_32S) 	mat_type = "CV_32S";
  else if (m.depth() == CV_32F)	mat_type = "CV_32F";
  else if (m.depth() == CV_64F)	mat_type = "CV_64F";

  cout << name << " 크기 " << m.size() << ", ";
  cout << " 자료형 " << mat_type << "C" << m.channels() << endl;
  cout << m << endl << endl;
}

int main()
{
  Mat  m1, m2, m3, m4(2, 6, CV_8UC1);       // m1, m2, m3는 모두 1채널, 0행, 0열(초기화를 안했기에)
                        // m4는 uchar 1채널, 2행, 6열
  Mat  add1(2, 3, CV_8UC1, Scalar(100));    // uchar 1채널, 2행, 3열
  Mat  add2 = (Mat)Mat::eye(4, 3, CV_8UC1); // uchar 1채널, 4행, 3열
                        // 정방행렬이 아닌 대각행렬의 경우, 정방행렬인 곳은 대각행렬로, 나머지는 0으로 
  cout << "[add1] = " << add1 << endl << endl;
  cout << "[add2] = " << add2 << endl << endl;
  cout << "[m1] = " << m1.channels() << ", " << m1.rows << ", " << m1.cols << endl << endl;
  cout << "[m2] = " << m2.channels() << ", " << m2.rows << ", " << m2.cols << endl << endl;
  cout << "[m3] = " << m3.channels() << ", " << m3.rows << ", " << m3.cols << endl << endl;
  cout << "[m4] = " << m4.channels() << ", " << m4.rows << ", " << m4.cols << endl << endl;

  m1.push_back(100), m1.push_back(200);
  m2.push_back(100.5), m2.push_back(200.6);

  m3.push_back(add1);
  m3.push_back(add2);

  //m4.push_back(add1); // 행렬의 행과 열이 다르면 행렬에 행렬을 넣을 수 없다 -> reshape를 이용해 변환해서 사용 가능
  //m4.push_back(100);  // m4는 2행 6열이므로, add1은 2행 3열이기 때문에

  m4.push_back(add1.reshape(1, 1)); // 원래 add1(1채널, 2행, 3열) -> reshape add1(1채널, 1행, 6열)
  m4.push_back(add2.reshape(1, 2)); // 원래 add2(1채널, 4행, 3열) -> reshape add2(1채널, 2행, 6열)

  print_matInfo("m1", m1), print_matInfo("m2", m2);
  print_matInfo("m3", m3), print_matInfo("m4", m4);

  m1.pop_back(1); // pop하기 전에 m1은 [1x2], 마지막 원소(뒤에서부터) 1개 제거
  m2.pop_back(2); // pop하기 전에 m2는 [1x2], 마지막 원소(뒤에서부터) 2개 제거
  m3.pop_back(3); // pop하기 전에 m3는 [3x6], 마지막 원소(뒤에터부터) 3개 제거

  cout << "m1" << endl << m1 << endl;
  cout << "m2" << endl << m2 << endl;
  cout << "m3" << endl << m3 << endl;

  return 0;
}

3.20. Mat 행렬의 메모리 해제

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void  print_matInfo(string name, Mat m)   	// 행렬의 정보와 원소 출력 함수
{
  string mat_type;
  if (m.depth() == CV_8U)	 		mat_type = "CV_8U";
  else if (m.depth() == CV_8S)	mat_type = "CV_8S";
  else if (m.depth() == CV_16U) 	mat_type = "CV_16U";
  else if (m.depth() == CV_16S)	mat_type = "CV_16S";
  else if (m.depth() == CV_32S) 	mat_type = "CV_32S";
  else if (m.depth() == CV_32F)	mat_type = "CV_32F";
  else if (m.depth() == CV_64F)	mat_type = "CV_64F";

  cout << name << " 크기 " << m.size() << ", ";
  cout << " 자료형 " << mat_type << "C" << m.channels() << endl;
  cout << m << endl << endl;
}

int main()
{
  Mat m1(2, 6, CV_8UC1, Scalar(100)); // 행렬 선언
  Mat m2(3, 3, CV_32S);
  Range  r1(0, 2), r2(0, 2); // 관심영역 Range r1(0, 2) -> start는 포함, end는 포함 x -> 0, 1만
  Mat m3 = m1(r1, r2); // 관심 영역 참조

  print_matInfo("m1", m1); // 행렬 정보 출력
  print_matInfo("m3", m3);

  m2.release(); // 행렬 해제
  m3.release(); // 행렬 해제(부모 행렬 m1에는 영향이 없음)

  print_matInfo("m2", m2); // 행렬 정보 출력
  print_matInfo("m3", m3);
  print_matInfo("m1", m1);

  m1.release();
  print_matInfo("m1", m1);

  return 0;
}

3.21. 행렬 연산 함수

Mat cross(): 두 개의 3-원소 벡터들의 외적
double dot(): 두 벡터의 내적
MatExpr inv(): 역행렬

    - MatExpr inv(int method)
    - method 1) DECOMP_LU : 가우시안 소거법(LU분해)
             2) DECOMP_SVD : 특이치 분해 방법(역행렬이 존재하지 않아도 가능)
             3) DECOMP_CHOLESKY : 숄레스키 분해 방법(역행렬이 존재하는 정방행렬, 대칭행렬)
    
MatExpr mul(): 두 행렬의 각 원소 간 곱셈 수행
MatExpr t(): 전치행렬
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main() {

  float data[] = {
    1, 0, 2,
    -3, 4, 6,
    -1, -2, 3
  };

  float data2[] = { 6, 30, 8 };

  // m1 * x = m2
  // x = m1'(역행렬) * m2
  Mat m1(3, 3, CV_32F, data);
  Mat m2(1, 3, CV_32F, data2); // Matx13f m2(6, 30, 8);

  cout << "[m1] = " << endl << m1 << endl << endl;
  cout << "[m1의 역행렬] = " << endl << m1.inv(DECOMP_LU) << endl << endl;
  cout << "[m2의 전치행렬] = " << endl << m2.t() << endl << endl;

  Mat x = m1.inv(DECOMP_LU) * m2.t();
  cout << "연립방정식의 해 x1, x2, x3 = " << x.t() << endl << endl;

  return 0;

}

3.22. saturate_cast <_Tp>

표와 산술(saturation arithmetics) 연산

- saturate_cast() 메소드
- I(x, y) = min(max(round(r), 0), 255)
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  Matx<uchar, 2, 2> m1; // 2행 2열 행렬 선언
  Matx<ushort, 2, 2> m2;
    
  // uchar 행렬에 음수 -50 및 300 저장
  // 50의 2진수: 00110010
  // -50 = 50의 2의 보수: 11001110
  // 50의 2의 보수 -> 10진수: 206
  m1(0, 0) = -50;
  m1(0, 1) = 300;
  m1(1, 0) = saturate_cast<uchar>(-50);
  m1(1, 1) = saturate_cast<uchar>(300);

  m2(0, 0) = -50;
  m2(0, 1) = 80000;
  m2(1, 0) = saturate_cast<unsigned short>(-50);
  m2(1, 1) = saturate_cast<unsigned short>(80000);

  cout << "[m1] = " << endl << m1 << endl;
  cout << "[m2] = " << endl << m2 << endl;
  return 0;
}

3.23. 예외처리 매크로

CV_Assert()	: 실행시간에 조건을 체크하는 매크로, 조건이 false가 되면 예외발생
    - condition: 체크하려는 조건
    
CV_Error()	: 해당 에러 코드 발생 시, msg 문자열 출력
    - code: 에러 코드 상수. 음수값을 가짐.
        1) StsOk: 오류 아님
        2) StsBadArg: 함수의 인수 오류
        3) StsNullPtr: 널 포인터 오류
        4) StsVecLengthErr: 벡터 길이 오류
        5) StsBadSize: 자료형의 크기 오류
        6) StsDivByZero: 0으로 나누기 오류
        7) StsOutOfRange: 인수가 범위를 벗어난 오류
        8) StsAssert: CV_Assert()에서 조건이 거짓일 때
        
    - msg: 해당 에러 코드 발생시, args로 포맷 매칭하여 문자열 출력
    
CV_Error_()	: 해당 에러 코드 발생 시, args로 포맷 매칭하여 문자열 출력
    - code: 에러 코드 상수.
    - args: printf()와 비슷하게 포맷 매칭 사용
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main()
{
  string msg1 = "a is one.";
  string msg2 = "a is two.";
  string msg3 = "a is three.";
  int a;

  while (1) {
    cout << "input a : ";
    cin >> a;

    try {
      if (a == 0)
        CV_Error(Error::StsDivByZero, "a is zero.");

      if (a == 1)
        CV_Error(Error::StsBadSize, msg1);

      if (a == 2)
        CV_Error_(Error::StsOutOfRange, ("%s : %d", msg2.c_str(), a));

      if (a == 3)
        CV_Error_(Error::StsBadArg, ("%s : %d", msg3.c_str(), a));

      CV_Assert(a != 4);
    }
    catch (cv::Exception& e)
    {
      cout << "Exception code (" << e.code << "): " << e.what();
      cout << endl;
      if (e.code == Error::StsAssert)
        break;
    }
  }

  return 0;

}

3.24. 윈도우 창 제어

- namedWindow(): 윈도우의 이름을 설정하고, 해당 이름으로 윈도우를 생성한다.
  * flag
    1) WINDOW_NORMAL: 윈도우 크기의 재조정 가능
    2) WINDOW_AUTOSIZE: 표시된 행렬의 크기에 맞춰 자동 설정
    3) WINDOW_OPENGL: OpenGL을 지원하는 윈도우 생성

- imshow(): winname 이름의 윈도우에 mat 행렬을 영상으로 표시한다.
- destroyWindow(): 인수로 지정된 타이틀의 윈도우를 파괴한다.
- destroyAllWindows(): HighGUI로 생성된 모든 윈도우를 파괴한다.
- moveWindow(): winname 이름의 윈도우를 지정된 위치로 (x, y)로 이동시킨다.
                이동되는 윈도우의 기준 위치는 좌측 상단이다.
- resizeWindow(): 윈도우의 크기를 재설정한다.
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  Mat image1(300, 400, CV_8U, Scalar(255));
  Mat image2(300, 400, CV_8U, Scalar(100));
  string  title1 = "white창 제어";
  string  title2 = "gray 창 제어";

  namedWindow(title1, WINDOW_AUTOSIZE);
  namedWindow(title2, WINDOW_NORMAL);
  moveWindow(title1, 100, 200);
  moveWindow(title2, 300, 200);

  imshow(title1, image1);
  imshow(title2, image2);
  waitKey();
  destroyAllWindows();

  return 0;
}

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  Mat image(300, 400, CV_8U, Scalar(255)); // 1채널 uchar 행렬 ,255로 초기화
  string  title1 = "창 크기변경1 - AUTOSIZE";
  string  title2 = "창 크기변경2 - NORMAL";

  namedWindow(title1, WINDOW_AUTOSIZE); // 창 크기 변경 불가
  namedWindow(title2, WINDOW_NORMAL);	  // 창 크기 변경 가능
  resizeWindow(title1, 500, 200);
  resizeWindow(title2, 500, 200);

  imshow(title1, image);
  imshow(title2, image);
  waitKey(); // delay(ms) 시간만큼 키 입력 대기하고, 키 이벤트 발생하면 해당 키 값을 반환
  return 0;
}

3.25. 키보드 이벤트 제어

waitKey(): delay(ms) 시간만큼 키 입력 대기하고, 키 이벤트 발생하면 해당 키 값을 반환한다.
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main() {

  Mat image(200, 300, CV_8UC1, Scalar(255));
  namedWindow("키보드 이벤트", WINDOW_AUTOSIZE);
  imshow("키보드 이벤트", image);

  while (1) {

    int key = waitKey(100);

    if (key == 27) break;
    switch (key) {

      case 97: cout << "a키 입력" << endl; break;
      case 98: cout << "b키 입력" << endl; break;
      case 65: cout << "A키 입력" << endl; break;
      case 66: cout << "B키 입력" << endl; break;

      case 0x250000: cout << "왼쪽 화살표 키 입력" << endl; break;
      case 0x260000: cout << "왼쪽 화살표 키 입력" << endl; break;
      case 0x270000: cout << "왼쪽 화살표 키 입력" << endl; break;
      case 0x280000: cout << "왼쪽 화살표 키 입력" << endl; break;

    }

  }

  return 0;

}

3.26. 마우스 이벤트 제어

콜백 함수 - 개발자가 함수를 호출하는 것이 아니라, 
          어떤 이벤트가 발생하거나 특정 시점에 도달했을 때 시스템에서 개발자가 등록한 함수를 호출하는 방식

마우스 이벤트 등록: cv::setMouseCallback()

1. setMouseCallback(): 사용자가 정의한 마우스 콜백함수를 시스템에 등록하는 함수
2. (*MouseCallback)(): 발생한 마우스 이벤트에 대해서 처리 및 제어를 구현하는 콜백 함수

[event 옵션]
1) EVENT_FLAG_LBUTTON	: 왼쪽 버튼 누름
2) EVENT_FLAG_RBUTTON	: 오른쪽 버튼 누름
3) EVENT_FLAG_MBUTTON	: 중간 버튼 누름
4) EVENT_FLAG_CTRLKEY	: [Ctrl]키 누름
5) EVENT_FLAG_SHIFTKEY	: [Shift]키 누름
6) EVENT_FLAG_ALTKEY	: [Alt]키 누름

7) EVENT_MOUSEMOVE		: 마우스 움직임
8) EVENT_LBUTTONDOWN	: 왼쪽 버튼 누름
9) EVENT_RBUTTONDOWN	: 오른쪽 버튼 누름
10) EVENT_MBUTTONDOWN	: 중간 버튼 누름
11) EVENT_LBUTTONUP		: 왼쪽 버튼 떼기
12) EVENT_RBUTTONUP		: 오른쪽 버튼 떼기
13) EVENT_MBUTTONUP		: 중간 버튼 떼기
14) EVENT_LBUTTONDBLCLK	: 왼쪽 버튼 더블클릭
15) EVENT_RBUTTONDBLCLK	: 오른쪽 버튼 더블클릭
16) EVENT_MBUTTONDBLCLK	: 중간 버튼 더블클릭
17) EVENT_MOUSEWHEEL	: 마우스 휠
18) EVENT_MOUSEWHEEL	: 마우스 가로 휠
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

void onMouse(int event, int x, int y, int flags, void* param) {

  switch (event) {

    case EVENT_LBUTTONDOWN:
      cout << "마우스 왼쪽 버튼 누르기" << endl;
      break;
    case EVENT_RBUTTONDOWN:
      cout << "마우스 오른쪽 버튼 누르기" << endl;
      break;
    case EVENT_RBUTTONUP:
      cout << "마우스 오른쪽 버튼 떼기" << endl;
      break;
    case EVENT_LBUTTONDBLCLK:
      cout << "마우스 왼쪽 버튼 더블클릭" << endl;
      break;
  }

}

// 응용 실습: 두 상황의 감지 기능 추가
void onMouse2(int event, int x, int y, int flags, void* param) {

  if (flags == (EVENT_FLAG_SHIFTKEY + EVENT_FLAG_RBUTTON)) {
    cout << "shift키 + 오른쪽 버튼 누르기" << endl;
  }
  else if (flags == (EVENT_FLAG_CTRLKEY + EVENT_FLAG_LBUTTON)) {
    cout << "ctrl키 + 왼쪽 버튼 누르기" << endl;
  }

}

int main() {

  Mat image(200, 300, CV_8U);
  image.setTo(255);
  imshow("마우스 이벤트1", image);
  imshow("마우스 이벤트2", image);

  setMouseCallback("마우스 이벤트1", onMouse, 0);
  setMouseCallback("마우스 이벤트2", onMouse2, 0);
  waitKey(0);

  return 0;

}


3.27. 트랙바 이벤트 제어 - I

- 트랙바: 일정한 범위 내에서 특정한 값을 선택하고자 할 때, 사용하는 일종의 스크롤 바
- createTrackbar(): 트랙바를 생성하고, 그것을 지정된 윈도우 창에 추가하는 함수
- (*TrackbarCallback)(): 트랙바의 위치가 변경될 때마다 호출되는 콜백 함수
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

string  title = "트랙바 이벤트";
Mat  image;

void onChange(int  value, void* userdata)
{
  int  add_value = value - 130;
  cout << "추가 화소값 " << add_value << endl;

  Mat tmp = image + add_value;
  imshow(title, tmp);
}

int main()
{
  int  value = 130;
  image = Mat(300, 400, CV_8UC1, Scalar(130));

  namedWindow(title, WINDOW_AUTOSIZE);
  createTrackbar("밝기값", title, &value, 255, onChange);

  imshow(title, image);
  waitKey(0);
  return 0;
}




3.28. 트랙바 이벤트 제어 - II

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

string title = "밝기변경", bar_name = "밝기값";
Mat image;
int posX, posY;

void onChange(int  value, void* userdata)
{
  int  add_value = value - 130;
  cout << "추가 화소값 " << add_value << endl;

  Mat tmp = image + add_value;
  imshow(title, tmp);
}

void onMouse(int event, int x, int y, int flags, void* param) {

  // 오른쪽 버튼 클릭시 영상 밝기 10씩 증가
  if (event == EVENT_RBUTTONDOWN) {

    add(image, 10, image);
    setTrackbarPos(bar_name, title, image.at<uchar>(0));

    Mat tmp = image + 10;
    imshow(title, tmp);

  }
  else if (event == EVENT_FLAG_LBUTTON) {

    subtract(image, 10, image);
    setTrackbarPos(bar_name, title, image.at<uchar>(0));

    Mat tmp = image - 10;
    imshow(title, tmp);

  }

}

// 응용 실습
void onMouse2(int event, int x, int y, int flags, void* param) {

  if (event == EVENT_LBUTTONDOWN) {
    posX = x;
  }

  // 마우스 왼쪽 버튼을 누른 채로 
  if (flags == (EVENT_MOUSEMOVE + EVENT_FLAG_LBUTTON)) {

    // 오른쪽으로 움직이면 밝아지고(밝기가 1씩 증가)
    if (x > posX) {

      add(image, 1, image);
      setTrackbarPos(bar_name, title, image.at<uchar>(0));

      Mat tmp = image + 1;
      imshow(title, tmp);

    }
    // 왼쪽으로 움직이면 어두워지는(밝기가 1씩 감소)
    else if (x < posX) {

      subtract(image, 1, image);
      setTrackbarPos(bar_name, title, image.at<uchar>(0));

      Mat tmp = image - 1;
      imshow(title, tmp);

    }

  }

}
int main() {

  int value = 130;
  image = Mat(300, 500, CV_8UC1, Scalar(130));

  namedWindow(title, WINDOW_AUTOSIZE);
  createTrackbar("밝기값", title, &value, 255, onChange);
  setMouseCallback(title, onMouse2, 0);

  imshow(title, image);
  waitKey(0);

  return 0;

}

3.29. 직선 및 사각형 그리기

- 에일리어싱: 계단 현상(샘플링을 제대로 추출하지 못했을 때 발생하는 영향)
- 안티에일리어싱: 계단 현상을 감소시키기 위해, 중간값을 추가
- 푸리에 변환: 영상 -> 주파수

- line(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
  1) img: 그릴 대상 행렬
  2) pt1, pt2: 시작 좌표와 종료 좌표
  3) color: 선의 색상
  4) thickness: 선의 두께, -1이면 내부를 채움
  5) lineType: 선의 형태
     [옵션]
     5-1) LINE_4(4): 4-방향 연결선
     5-2) LINE_8(8): 8-방향 연결선
     5-3) LINE_AA(16): 계단현상 감소(안티에일리어싱) 선
  6) shift: 입력 좌표 (pt1, pt2)에 대해서 오른쪽 비트시프트 연산한 결과를 좌표로 지정해서 직선을 그림
            예를 들어 2가 되면, 1/2만큼 줄어듦
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  Scalar white(255, 255, 255), yellow(0, 255, 255), blue(255, 0, 0);
  Scalar red = Scalar(0, 0, 255);
  Scalar green = Scalar(0, 255, 0);

  Mat image(400, 600, CV_8UC3, white);
  Point pt1(50, 130), pt2(200, 300), pt3(300, 150), pt4(400, 50);
  Rect  rect(pt3, Size(200, 150));

  line(image, pt1, pt2, red, 1, LINE_4);
  imwrite("result1.jpg", image);
  line(image, pt1, pt2, red, 1, LINE_8);
  imwrite("result2.jpg", image);
  line(image, pt1, pt2, red, 1, LINE_AA);
  imwrite("result3.jpg", image);

  line(image, pt1, pt2, red);
  line(image, pt3, pt4, green, 2, LINE_AA); // 안티에일리어싱 적용
  line(image, pt3, pt4, green, 3, LINE_8, 1); // 8방향 연결선, 1비트 시프트(반씩 줄어듦)

  rectangle(image, rect, blue, 2);
  rectangle(image, rect, blue, FILLED, LINE_4, 1);
  rectangle(image, pt1, pt2, red, 3);

  imshow("직선 & 사각형", image);
  waitKey(0);
  return 0;
}


3.30. 글자 쓰기

- putText(Mat& img, const string& text, Point org, int fontFace, double fontScale,
          Scalar color, int thickness=1, int lineType=8, bool bottomLeftOrigin=false)
  1) img: 문자열을 작성할 대상 행렬(영상)
  2) text: 작성할 문자열
  3) org: 문자열의 시작 좌표, 문자열에서 가장 왼쪽 하단을 의미
  4) fontFace: 문자열에 대한 글꼴
  5) fontScale: 글자 크기 확대 비율
  6) color: 글자의 색상
  7) thickness: 글자의 굵기
  8) lineType: 글자 선의 형태
  9) bottomLeftOrigin: 영상의 원점 좌표
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  Scalar olive(128, 128, 0), violet(221, 160, 221), brown(42, 42, 165);
  Point pt1(20, 100), pt2(20, 200), pt3(20, 250);

  Mat image(300, 500, CV_8UC3, Scalar(255, 255, 255)); // 3채널 uchar 행렬 선언 및 흰색(255) 초기화

  putText(image, "SIMPLEX", Point(20, 30), FONT_HERSHEY_SIMPLEX, 1, brown);
  putText(image, "DUPLEX", pt1, FONT_HERSHEY_DUPLEX, 2, olive);
  putText(image, "TRIPLEX", pt2, FONT_HERSHEY_TRIPLEX, 3, violet);
  putText(image, "ITALIC", pt3, FONT_HERSHEY_PLAIN | FONT_ITALIC, 2, violet);

  imshow("글자쓰기", image);
  waitKey(0);
  return 0;
}


3.31. 원 그리기

- circle(Mat& img, Point center, int radius, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
  1) Mat& img				: 원을 그릴 대상 행렬
  2) Point center			: 원의 중심 좌표
  3) int radius			: 원의 반지름
  4) const Scalar& color	: 선의 색상
  5) int thickness = 1		: 선의 두께
  6) int lineType = 8		: 선의 형태
  7) int shift = 0			: 좌표에 대한 비트 시프트 연산
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  Scalar orange(0, 165, 255), blue(255, 0, 0), magenta(255, 0, 255);
  Mat image(300, 500, CV_8UC3, Scalar(255, 255, 255)); // 채널이 3개이므로, Scalar를 초기화할 때도 3개를 넣어야 함

  Point center = image.size() / 2;		// 영상의 중심점 구하기
  Point pt1(70, 50), pt2(350, 220);

  circle(image, pt1, 90, orange, 2, LINE_8, 0);
  circle(image, center, 100, blue, 2, LINE_8, 0);
  circle(image, pt2, 60, magenta, -1, LINE_8, 0); // 내부를 채우고 싶으면, thickness = -1로 설정

  int  font = FONT_HERSHEY_COMPLEX;
  putText(image, "center_blue", center, font, 1.2, blue);
  putText(image, "pt1_orange", pt1, font, 0.8, orange);
  putText(image, "pt2_magenta", pt2 + Point(2, 2), font, 0.5, Scalar(0, 0, 0), 2);
  putText(image, "pt2_magenta", pt2, font, 0.5, magenta, 1);

  imshow("원그리기", image);
  waitKey(0);
  return 0;
}


3.32. 타원 그리기

- ellipse(Mat& img, Point center, Size axes, double angle, double startAngle, double endAngle,
          const Scalar& color, int thickness=1, int lineType=8, int shift=0)
  1) img: 그릴 대상 행렬(영상)
  2) center: 원의 중심 좌표
  3) axes: 타원의 크기(x축 반지름, y축 반지름)
  4) angle: 타원의 각도(3시 방향이 0도, 시계방향 회전)
  5) startAngle: 호의 시작 각도
  6) endAngle: 호의 종료 각도
  7) color: 선의 색상
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
  Scalar orange(0, 165, 255), blue(255, 0, 0), magenta(255, 0, 255);
  Mat image(300, 700, CV_8UC3, Scalar(255, 255, 255));

  Point pt1(120, 150), pt2(550, 150);
  circle(image, pt1, 1, Scalar(0), 1);
  circle(image, pt2, 1, Scalar(0), 1);

  ellipse(image, pt1, Size(100, 60), 0, 30, 270, blue, 2, 8, 0);
  ellipse(image, pt1, Size(100, 60), 0, -90, 30, orange, 2, 8, 0);

  ellipse(image, pt2, Size(100, 60), 30, -30, 160, blue, 2, 8, 0);
  ellipse(image, pt2, Size(100, 60), 30, -200, -30, orange, 2, 8, 0);

  imshow("타원 및 호 그리기", image);
  waitKey(0);
  return 0;
}


과제

오른쪽 클릭할 때마다 사각형, 왼쪽 클릭할 때마다 원을 그리기


Tags: OpenCV

Comments:

comments powered by Disqus

© 2021 - MH.Ji. All rights reserved
Built using Jekyll