[2023-04-18] 4. Mat 클래스 기초 사용법
Mat 클래스 기초 사용법
영상의 생성과 초기화
- Mat 클래스 객체의 생성과 초기화 예제 코드
void MatOp1()
{
Mat img1; // empty matrix
Mat img2(480, 640, CV_8UC1); // unsigned char, 1-channel
Mat img3(480, 640, CV_8UC3); // unsigned char, 3-channels
Mat img4(Size(640, 480), CV_8UC3); // Size(width, height)
Mat img5(480, 640, CV_8UC1, Scalar(128)); // 초기값을 지정 128
Mat img6(480, 640, CV_8UC3, Scalar(0, 0, 255)); // 초기값을 지정 red
Mat mat1 = Mat::zeros(3, 3, CV_32SC1); // 0's matrix
Mat mat2 = Mat::ones(3, 3, CV_32FC1); // 1's matrix
Mat mat3 = Mat::eye(3, 3, CV_32FC1); // identity matrix
float data[] = {1, 2, 3, 4, 5, 6};
Mat mat4(2, 3, CV_32FC1, data);
Mat mat5 = (Mat_<float>(2, 3) << 1, 2, 3, 4, 5, 6);
Mat mat6 = Mat_<uchar>({2, 3}, {1, 2, 3, 4, 5, 6});
mat4.create(256, 256, CV_8UC3); // uchar, 3-channels
mat5.create(4, 4, CV_32FC1); // float, 1-channel
mat4 = Scalar(255, 0, 0);
mat5.setTo(1.f);
}
영상의 참조와 복사
- Mat 객체에 대해 = 연산자는 참조(얕은 복사)를 수행
- Mat::clone() 또는 Mat::copyTo() 함수를 이용하여 깊은 복사를 수행
void MatOp2()
{
Mat img1 = imread("dog.bmp");
Mat img2 = img1;
Mat img3;
img3 = img1;
// img1, img2, img3 은 같은 영상을 같게 됨(참조 형태)
Mat img4 = img1.clone(); // img1의 복사본을 만들어 img4에 대입하는 것이므로 오른쪽(img4)에서 왼쪽(img1)으로 복사가 됨
Mat img5;
img1.copyTo(img5); // img1의 img5에 복사하는 것이므로 왼쪽(img1)에서 오른쪽(img5)으로 복사가 됨
// img4, img5 모두 같은 영상을 갖게 됨(깊은 복사 형태)
img1.setTo(Scalar(0, 255, 255)); // yellow로 바꾸는데 img1, img2, img3가 노랑색으로 바뀌는 것을 확인 할 수 있음
// 참조(얕은 복사) 형태로 복사하면 원본이 변경되면 변경되지만, 깊은 복사 형태로 복사하면 변경되지 않음
imshow("img1", img1);
imshow("img2", img2);
imshow("img3", img3);
imshow("img4", img4);
imshow("img5", img5);
waitKey();
destroyAllWindows();
}
- img3는 clone 함수를 통해 완전히 메모리 공간을 새로 할당해서 픽셀 데이터를 복사함
- 복사된 메모리 데이터의 시작 주소를 img3가 가리키게 설정하므로 완전히 분리된 메모리 공간을 사용
Mat img1 = imread("dog.bmp");
Mat img2 = img1;
Mat img3 = img1.clone();
부분 영상 추출
- Mat 클래스 객체에서 부분 행렬 추출
- Mat 객체에 대해 () 연산자를 이용하여 부분 영상 추출이 가능함
- () 연산자 안에는 Rect 객체를 지정하여 부분 영상의 위치와 크기를 지정
- 참조를 활용하여 ROI 연산 수행이 가능함
void MatOp3()
{
Mat img1 = imread("cat.bmp");
if (img1.empty()) {
cerr << "Image load failed!" << endl;
return;
}
Mat img2 = img1(Rect(220, 120, 340, 240));
Mat img3 = img1(Rect(220, 120, 340, 240)).clone();
img2 = ~img2; // not 연산을 수행하여 영상을 반전함
// img2의 영상이 반전되면서 img1에서 참조한 부분도 반전됨
// img3는 clone을 통해 깊은 복사를 하였으므로 img2에서 영상이 바뀌어도 적용되지 않음
imshow("img1", img1);
imshow("img2", img2);
imshow("img3", img3);
waitKey();
destroyAllWindows();
}
영상의 픽셀 값 참조
-
Mat 영상의 픽셀 값 접근하기
-
OpenCV에서 제공하는 기능이 아닌 자신만의 새로운 기능을 추가해야 하는 경우에 유용함
-
기본적으로 Mat::data 멤버 변수가 픽셀 데이터 메모리 공간을 가리키지만, Mat 클래스 멤버 함수를 사용하는 방법을 권장
-
Mat::data는 메모리 연산이 잘못될 경우 프로그램이 비정상 종료할 수 있음
-
Mat::data :
(i : 행 번호, j : 열 번호. step[0] : 한 행의 바이트 크기, step[1 ]: 원소 1개의 byte 크기 )
-
Mat::at() 함수 : 좌표 지정이 직관적이고 임의 좌표에 접근할 수 있음
-
Mat::ptr() 함수 : Mat:at()보다 빠르게 동작하고, 행 단위 연산을 수행할 때 유리함
-
MatIterator_ 반복자 : 좌표를 지정하지 않아서 안전하지만 성능은 느린 편
-
-
Mat:at()
template<typename_Tp> _Tp& Mat::at(int x, int y)
- y : 참조할 행 번호
- x : 참조할 열 번호
- 반환값 : (_Tp& 타입으로 캐스팅된) 참조 형태의 y행 x열 원소 값
- 참조 형태로 반환하므로 픽셀 값을 반환하는 것 뿐만 아니라 픽셀 값을 설정할 수도 있음
Mat mat1 = Mat::zeros(3, 4, CV_8UC1); // 모든 원소의 값이 0으로 초기화되어있는 mat1 객체 생성
for (int y =0; y < mat1.rows; y++ ){
for (int x = 0; x < mat1.cols; x++){
mat1.at<uchar>(y, x)++;
}
}
Mat::ptr()
template<typename_Tp> _Tp& Mat::ptr(int y)
- y : 참조할 행 번호
- 반환값 : (_Tp* 타입으로 캐스팅된) y번 행의 시작 주소
for(int y = 0; y < mat1.rows; y++){
uchar* p = mat1.ptr<uchar>(y);
for(int x =0; x < mat1.cols; x++) {
p[x] ++;
}
}
MatIterator_ 반복자
- OpenCV는 Mat 클래스와 함께 사용할 수 있는 반복자 클래스 템플릿 MatIterator_를 제공
- MatIterator_는 클래스 탬플릿이므로 사용할 때에는 Mat 행렬 타입에 맞는 자료형을 명시해야 함
- Mat::begin() 함수는 행렬의 첫 번째 원소의 위치를 반환
- Mat::end() 함수는 행렬의 마지막 원소 바로 다음 위치를 반환
for (MatIterator_<uchar> it = mat1.begin<uchar>(); it != mat1.end<uchar>(); ++it){
(*it)++; // it가 가리키는 값(*it)을 1씩 증가함
}
void MatOp4()
{
Mat mat1 = Mat::zeros(3, 4, CV_8UC1); // 3행 4열의 uchar 타입의 행렬을 만들어 mat1 객체에 선언
for (int y = 0; y < mat1.rows; y++) {
for (int x = 0; x < mat1.cols; x++) {
mat1.at<uchar>(y, x)++;
}
}
// at 함수를 이용해 모든 원소의 값을 1씩 증가시킴
for (int y = 0; y < mat1.rows; y++) {
uchar* p = mat1.ptr<uchar>(y);
for (int x = 0; x < mat1.cols; x++) {
p[x]++;
}
}
// ptr 함수를 이용해 모든 원소의 값을 1씩 증가시킴
for (MatIterator_<uchar> it = mat1.begin<uchar>(); it != mat1.end<uchar>(); ++it) {
(*it)++;
}
// MatIterator_ 이용해 모든 원소의 값을 1씩 증가시킴
cout << "mat1:\n" << mat1 << endl; // mat1의 모든 원소를 출력함
}
- 3행 4열 0 값으로 채워진 행렬이 3행 4열 3 값으로 채워진 행렬로 바뀜
기초 행렬 연산
- Mat 클래스 기본 행렬 연산
void MatOp5()
{
float data[] = {1, 1, 2, 3};
Mat mat1(2, 2, CV_32FC1, data);
cout << "mat1:\n" << mat1 << endl;
Mat mat2 = mat1.inv(); // 역행렬을 구함
cout << "mat2:\n" << mat2 << endl;
cout << "mat1.t():\n" << mat1.t() << endl; // transpose
cout << "mat1 + 3:\n" << mat1 + 3 << endl; // 모든 원소에 숫자 3을 더함
cout << "mat1 + mat2:\n" << mat1 + mat2 << endl;
cout << "mat1 * mat2:\n" << mat1 * mat2 << endl; // 자기 자신과 자기 자신의 역행렬을 곱하여 단위행렬 결과가 나옴
}