이전 글에 이어서, OpenCV Mat 클래스를 사용하는 방법을 정리합니다.
이전글
6) Mat 객체 정보 확인
영상을 불러와서 Mat 객체에 할당한 후, Mat 객체의 정보를 확인해야 할 때가 있습니다. 아름다운 레나님의 영상으로 예제를 보여드리도록 하겠습니다.
※ Lenna Grayscale bmp image 출처: www.ece.rice.edu/~wakin/images/
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image = imread("lena512.bmp", IMREAD_GRAYSCALE);
// Size
cout << "Size: "<< image.size() << endl;
// 행 & 열
cout << "Row, Col:" << image.rows << ", " << image.cols << endl;
// 채널 수
cout << "Channel:" << image.channels() << endl;
// Depth - 원소(Element)의 자료형
cout << "Depth: " << image.depth() << endl;
// Matrix 원소의 전체 크기 / Matrix 원소의 1채널의 크기
// ex) CV_32FC3 - 전자는 4*3=12이고 후자는 4
cout << "ElementSize / ElementSize1: " << image.elemSize() << " / " << image.elemSize1() << endl;
// 전체 원소의 개수 (행*열)
cout << "Total: " << image.total() << endl;
// Matrix type (CV_8UC1 = 0)
cout << "Type: " << image.type() << endl;
// 각 행의 원소가 연속적으로 저장되어 있는지?
cout << "IsContinuous: " << image.isContinuous() << endl;
// 부분행렬인지?
cout << "IsSubmatrix: " << image.isSubmatrix() << endl;
imshow("imageWnd", image);
waitKey(0);
return 0;
}
실행 결과
7) Matrix 연산
OpenCV는 행렬의 사칙 연산을 직관적으로 수행할 수 있도록 Operator가 재정의되어 있습니다. 또한 함수를 사용하여 전치행렬, 역행렬 등을 쉽게 구할 수 있습니다. 아래 예제 코드를 확인해 주시기 바랍니다.
행렬은 곱셈만 조금 유의하면, 사용하는 데 큰 어려움은 없습니다. 교환법칙 성립 안된다는 것을 유념하시면 됩니다. 또한 수학적 곱셈 연산은 * 연산자를 재정의한 것을 사용하고, 같은 위치에 있는 원소끼리 곱셈해서 결과를 내는 것은 mul()함수를 사용한다는 차이 또한 기억하시면 좋겠습니다.
#include <iostream>
#include <opencv2/core/core.hpp>
using namespace cv;
using namespace std;
int main()
{
cout << "--------------------- 2 x 2 행렬 선언 ---------------------" << endl;
Mat mat1 = Mat_<float>({ 2, 2 }, { 1, 2, 3, 4 });
float data[] = { -1, 3, 2, -1 };
Mat mat2(2, 2, CV_32FC1, data);
cout << "mat1n" << mat1 << endl;
cout << "mat2n" << mat2 << endl;
cout << "--------------------- 행렬 & 숫자 연산 ---------------------" << endl;
cout << "행렬 + 숫자: n" << mat1 + 100 << endl;
cout << "행렬 - 숫자: n" << mat1 - 100 << endl;
cout << "행렬 * 숫자: n" << mat1 * 100 << endl;
cout << "행렬 / 숫자: n" << mat1 / 100 << endl;
cout << "--------------------- 숫자 & 행렬 연산 ---------------------" << endl;
cout << "숫자 + 행렬: n" << 100 + mat1 << endl;
cout << "숫자 - 행렬: n" << 100 - mat1 << endl;
cout << "숫자 * 행렬: n" << 100 * mat1 << endl;
cout << "숫자 / 행렬: n" << 100 / mat1 << endl;
cout << "--------------------- 행렬 & 행렬 연산 ---------------------" << endl;
cout << "행렬 + 행렬: n" << mat1 + mat2 << endl;
cout << "행렬 - 행렬: n" << mat1 - mat2 << endl;
cout << "행렬 * 행렬(수학적 행렬 곱셈 공식을 따름): n" << mat1 * mat2 << endl;
cout << "행렬 / 행렬(대응되는 원소끼리 나눗셈): n" << mat1 / mat2 << endl;
cout << "대응되는 원소끼리 곱셈(mul함수): n" << mat1.mul(mat2) << endl;
cout << "역행렬: n" << mat1.inv() << endl;
cout << "전치행렬: n" << mat1.t() << endl;
return 0;
}
실행 결과
8) Mat 객체 타입(Depth)/사이즈 변환
타입 변환
Mat 연산의 편의성 또는 정확성 향상을 위해 객체의 데이터 타입(Depth)를 변환할 필요가 가끔 있습니다. 아래는 Grayscale의 레나 이미지를 불러와서, UChar → Float으로 데이터 타입을 변환하는 예제입니다.
#include <iostream>
#include <opencv2/core/core.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image = imread("lena512.bmp", IMREAD_GRAYSCALE);
Mat image_float;
image.convertTo(image_float, CV_32FC1);
cout << "Depth: " << image.depth() << " vs " << image_float.depth() << endl;
return 0;
}
사이즈 변환
Mat 객체의 사이즈를 변환하는 방법을 소개합니다. 아래 예제에서 Original한 mat 객체는 2채널, 5*5 사이즈의 float 형 데이터 타입을 사용합니다.
reshape 함수는 행렬의 크기를 변환하며, 채널 수도 함께 고려됩니다. 예제는 5*5 사이즈의 2채널 행렬을 의미하였는데, 이를 1채널의 2*25 사이즈 행렬로 변환합니다. reshape 함수의 첫번째 파라미터는 채널 수, 두번째 파라미터는 행의 개수를 의미합니다. 만약 파라미터가 0이면 기존 채널 수 또는 행의 개수는 변경되지 않습니다. 또한 기존 mat의 객체를 참조하기 때문에, 필요에 따라 clone함수를 사용하여 복사해서 사용하는 것이 좋습니다.
resize 함수는 행을 늘리고 싶을 때 이용합니다. resize 함수의 첫번째 파라미터는 변경하고 싶은 전체 행의 개수를 의미하며, 늘리고 줄이는 것 둘 다 가능합니다. 아래 예제는 행렬이 5행에서 7행으로 늘어난 것을 나타냅니다. 두 번째 파라미터는 행이 늘어났을 때, 새로 추가된 행의 원소의 초기값을 뜻합니다. 예제의 mat 객체가 2채널이기 때문에 Scalar를 사용하였습니다.
push_back 함수는 Mat 객체에 새로운 행을 추가하고 싶을 때 사용합니다. 먼저 새로운 행(예제코드의 mat3)을 준비한 다음, push_back 함수를 호출하면 됩니다.
#include <iostream>
#include <opencv2/core/core.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat mat(5, 5, CV_32FC2, Scalar(1, 3));
cout << "Original: " << endl;
cout << mat << endl;
cout << "Mat size: " << mat.size() << endl;
cout << "Channel: " << mat.channels() << endl;
// Reshape
cout << "nnReshape: " << endl;
Mat mat_reshape = mat.reshape(1, 2);
cout << mat_reshape << endl;
cout << "Mat size: " << mat_reshape.size() << endl;
cout << "Channel: " << mat_reshape.channels() << endl;
// Resize (5행에서 7행으로 리사이즈)
cout << "nnResize: " << endl;
mat.resize(7, Scalar(7, 9));
cout << mat << endl;
cout << "Mat size: " << mat.size() << endl;
// Push back
cout << "nnPush Back: " << endl;
Mat mat3(1, 5, CV_32FC2, Scalar(99, 100));
mat.push_back(mat3);
cout << mat << endl;
cout << "Mat size: " << mat.size() << endl;
return 0;
}