References
- https://github.com/opencv/opencv
- https://docs.opencv.org/
- OpenCV 4로 배우는 컴퓨터 비전과 머신러닝
Contents
- Vec 클래스
- Scalar 클래스
- InputArray와 OutputArray 클래스
지난 포스팅에 이어서 OpenCV에서 자주 사용되는 몇 가지 클래스들에 대해서 살펴보도록 하겠습니다.
Vec 클래스
하나의 행이나 하나의 열로만 구성된 행렬을 행 벡터/열 벡터라고 부릅니다. 그리고, 행 벡터/열 벡터를 합쳐서 벡터 또는 벡터 행렬이라고 부릅니다. 즉, 벡터는 같은 자료형을 가진 원소 몇 개로 구성된 데이터 형식이라고 볼 수 있습니다.
OpenCV에서는 이러한 벡터 데이터를 표현할 수 있는 Vec 클래스를 제공하며, 클래스 정의는 다음과 같습니다.
template<typename _Tp, int m, int n> class Matx
{
public:
enum {
rows = m,
cols = n,
channels = rows*cols,
#ifdef OPENCV_TRAITS_ENABLE_DEPRECATED
depth = traits::Type<_Tp>::value,
type = CV_MAKETYPE(depth, channels),
#endif
shortdim = (m < n ? m : n)
};
typedef _Tp value_type;
typedef Matx<_Tp, m, n> mat_type;
typedef Matx<_Tp, shortdim, 1> diag_type;
//! default constructor
Matx();
// ... 생략
_Tp val[m*n]; //< matrix elements
};
template<typename _Tp, int cn> class Vec : public Matx<_Tp, cn, 1>
{
public:
// ... 생략
/*! element access */
const _Tp& operator [](int i) const;
_Tp& operator[](int i);
const _Tp& operator ()(int i) const;
_Tp& operator ()(int i);
// ... 생략
};
위 코드에서 Matx 클래스는 작은 크기의 행렬을 표현하기 위해 만들어진 템플릿 클래스로, 이 클래스는 원소 데이터를 val 이라는 이름의 배열에 저장합니다. _Tp는 행렬 원소의 자료형이고, m과 n은 각각 행과 열 개수를 의미합니다.
Vec 클래스는 Matx 클래스를 상속받아 만들어지며, 열 개수가 1개로 특화된 벡터 클래스입니다. 이 클래스도 템플릿을 사용하기 때문에 실제 코드에서 사용할 때는 <> 괄호 사이에 데이터 타입과 개수를 명시해야 합니다. 예를 들어, 정수형 데이터 4개를 가지고 있는 벡터라면 Vec<int, 4> 타입을 사용하고;, uchar 타입 3개를 가지고 있는 벡터라면 Vec<uchar, 3> 타입으로 사용합니다. 특히 Vec<uchar, 3> 타입은 3채널 컬러 영상의 픽셀값을 표현하는 용도로 자주 사용됩니다.
cv::Vec<uchar, 3> p1, p2(0, 0, 255);
위 코드는 Vec<uchar, 3> 타입의 변수 p1, p2를 선언하는 예제 코드입니다.
하지만 매번 Vec<uchar, 3> 형태로 입력하는 것은 꽤 번거로울 수 있는데, 다행히 OpenCV에서는 자주 사용되는 자료형과 개수에 대한 Vec 클래스 템플릿의 이름 재정의를 제공하며 이를 이용하여 간단하게 원하는 크기와 자료형의 벡터를 표현할 수 있습니다. 제공되는 Vec 클래스 템플릿의 이름들은 다음 형식을 따릅니다.
Vec<num-of-data>{b|s|w|i|f|d}
<num-of-data> 위치에는 2, 3, 4 등 작은 숫자들을 지정할 수 있으며, {b|s|w|i|f|d} 부분에는 각 문자 중 하나를 지정합니다. 여기서 b는 uchar, s는 short, w는 ushort, i는 int, f는 float, d는 double 타입을 의미합니다. 실제 OpenCV에서 재정의된 벡터 클래스는 다음과 같습니다.
/** @name Shorter aliases for the most popular specializations of Vec<T,n>
@{
*/
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<ushort, 2> Vec2w;
typedef Vec<ushort, 3> Vec3w;
typedef Vec<ushort, 4> Vec4w;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<int, 6> Vec6i;
typedef Vec<int, 8> Vec8i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
예를 들어, 컬러 영상의 픽셀을 표현하고자 한다면 Vec<uchar, 3> 타입 대신 Vec3b 클래스를 사용할 수 있습니다.
cv::Vec3b p1, p2(0, 0, 255);
위와 같이 변수를 선언할 경우 p1과 p2는 각각 내부에 uchar val[3]; 타입의 멤버 변수를 가지고 있습니다. p1.val 배열 원소는 모두 0으로 초기화되고, p2의 경우에는 [0, 0, 255]로 초기화됩니다. 만약 p1 변수의 첫 번째 원소를 100으로 변경하고 싶다면 다음과 같이 코드를 작성할 수 있습니다.
p1.val[0] = 100;
그런데 Vec 클래스는 다음과 같이 [] 연산자가 오버로딩되어 있기 때문에 [] 연산자를 이용하여 멤버 변수 val 배열에 쉽게 접근할 수 있습니다.
template<typename _Tp, int cn> inline
_Tp& Vec<_Tp, cn>::operator [](int i)
{
CV_DbgAssert( (unsigned)i < (unsigned)cn );
return this->val[i];
}
따라서 이 연산자 오버로딩을 이용하여 p1 변수의 첫 번째 원소를 100으로 변경하려면 다음과 같이 작성해도 됩니다.
p1[0] = 100;
대부분의 OpenCV 클래스들이 std::cout << 연산자를 이용하여 간단한게 출력할 수 있듯이 Vec 클래스도 std::cout 을 사용하여 원소들을 간단하게 출력할 수 있습니다.
std::cout << "p1: " << p1 << std::endl;
std::cout << "p2: " << p2 << std::endl;
이외에도 다양한 멤버 함수와 +, - 연산자 등을 제공하는데, 자세한 사항들은 아래 공식 문서를 참조하시길 바랍니다.. !
https://docs.opencv.org/4.5.5/d6/dcf/classcv_1_1Vec.html
Scalar 클래스
OpenCV에서 Mat 클래스 다음으로 자주 사용되는 클래스는 Scalar 클래스입니다. 이 클래스는 4채널 이하의 영상에서 픽셀 값을 표현하는 용도로 자주 사용됩니다. Scalar 클래스는 Scalar_라는 이름의 클래스 템플릿의 이름 재정의이며 Scalar_ 클래스는 Vec 클래스를 상속받아 정의됩니다. 아래 코드는 Scalar_ 클래스를 간단하게 보여줍니다.
template<typename _Tp> class Scalar_ : public Vec<_Tp, 4>
{
public:
//! default constructor
Scalar_();
Scalar_(_Tp v0, _Tp v1, _Tp v2=0, _Tp v3=0);
Scalar_(_Tp v0);
Scalar_(const Scalar_& s);
Scalar_(Scalar_&& s) CV_NOEXCEPT;
Scalar_& operator=(const Scalar_& s);
Scalar_& operator=(Scalar_&& s) CV_NOEXCEPT;
template<typename _Tp2, int cn>
Scalar_(const Vec<_Tp2, cn>& v);
//! returns a scalar with all elements set to v0
static Scalar_<_Tp> all(_Tp v0);
//! conversion to another data type
template<typename T2> operator Scalar_<T2>() const;
//! per-element product
Scalar_<_Tp> mul(const Scalar_<_Tp>& a, double scale=1 ) const;
//! returns (v0, -v1, -v2, -v3)
Scalar_<_Tp> conj() const;
//! returns true iff v1 == v2 == v3 == 0
bool isReal() const;
};
typedef Scalar_<double> Scalar;
위 코드에서 정의된 Scalar_ 클래스는 Vec 클래스를 상속받았으며 4개의 원소를 가지고 있는 템플릿 클래스입니다. 따라서 다양한 자료형에 대해 정의할 수 있으며, 그중 특별히 double 타입을 사용하는 경우 Scalar라는 재정의된 이름으로 사용할 수 있습니다.
Scalar는 보통 4개 이하의 채널을 갖는 영상의 픽셀 값을 표현하는 용도로 많이 사용됩니다. grayscale인 경우 첫 번째 원소가 픽셀 밝기를 표현하고 나머지 3개의 원소는 0으로 설정됩니다. truecolor의 경우에는 처음 3개의 원소가 BGR 색상 성분을 표현하고 네 번째 원소는 보통 0으로 설정됩니다. 간혹 PNG 파일 형식처럼 투명도를 표현하는 알파 채널이 있는 경우, 네 번째 원소를 이용하기도 합니다.
아래 예제 코드는 Scalar 클래스를 이용하는 방법을 보여줍니다.
#include <iostream>
#include "opencv2/opencv.hpp"
int main(int argc, char* argv[])
{
cv::Scalar gray = 128;
std::cout << "gray: " << gray << std::endl;
cv::Scalar yellow{ 0, 255, 255 };
std::cout << "yellow: " << yellow << std::endl;
cv::Mat img1(256, 256, CV_8UC3, yellow);
for (int i = 0; i < 4; i++) {
std::cout << yellow[i] << std::endl;
}
}
OpenCV 함수 인자로 주로 InputArray와 OutputArray를 많이 사용하는데, InputArray는 주로 함수의 입력으로 사용되고 OutputArray는 함수의 출력으로 사용되는 인터페이스 클래스입니다.
InputArray 클래스
OpenCV 문서를 살펴보면 InputArray 타입의 인자를 사용하는 함수를 많이 볼 수 있습니다. 화면에 이미지를 출력해주는 imshow() 함수도 영상을 InputArray 타입으로 전달하도록 선언되어 있습니다.
InputArray 클래스는 Mat, vector<T> 등 다양한 타입을 표현할 수 있는 인터페이스 클래스로서 주로 OpenCV 함수의 입력 인자 타입으로 사용됩니다.
InputArray 클래스는 다음과 같은 형태로 이름이 재정의되어 있습니다.
typedef const _InputArray& InputArray;
InputArray 클래스를 완벽하게 이해하려면 _InputArray 클래스의 정의부터 살펴봐야 하지만, 사실 단순히 사용의 편의성을 위해 만들어진 구현체일 뿐이기 때문에 정의를 꼼꼼히 살펴볼 필요는 없습니다. 대신 OpenCV 함수 인자에서 InputArray 클래스가 사용될 때 사용자가 어떻게 해석해야 하는지에 대해서 이해하는 것이 중요합니다.
_InputArray 클래스는 Mat, Mat_<T>, Matx<T, m, n>, vector<T>, vector<vector<T>>, vector<Mat>, vector<Mat_<T>>, UMat, vector<UMat>, double 같은 다양한 타입으로부터 생성될 수 있는 인터페이스 클래스입니다. _InputArray 클래스는 OpenCV 라이브러리 내부에서 코드 구현 편의상 사용되며, 사용자가 명시적으로 _InputArray 클래스의 인스턴스 또는 변수를 생성하여 사용하는 것을 금지하고 있습니다. 따라서, OpenCV 문서에서 특정 함수의 인자가 InputArray 타입을 받도록 설명하고 있다면 Mat 클래스 객체 또는 vector<T> 타입의 변수를 전달하는 형태로 코드를 작성해야 합니다. 만약 InputArray 타입의 함수 인자에 비어 있는 행렬을 전달하려면 함수 인자에 noArray() 또는 Mat()을 입력해주어야 합니다.
만약 OpenCV에서 제공하는 함수처럼 사용자가 정의한 함수에서 Mat 객체뿐만 아니라 vector<T> 타입의 객체를 한꺼번에 전달받을 수 있게 만들고 싶다면 사용자 정의 함수 인자에 InputArray 타입을 사용할 수 있습니다. 그리고 실제 함수 본문에서는 _InputArray 클래스의 멤버 함수인 _InputArray::getMat() 함수를 사용하여 Mat 객체 타입 형태로 변환해서 사용해야 합니다.
아래 코드는 InputArray 클래스 타입의 인자를 사용하는 함수를 정의하는 방법과 실제 사용 방법을 보여줍니다.
#include <iostream>
#include <vector>
#include "opencv2/opencv.hpp"
void printMat(cv::InputArray _mat)
{
cv::Mat mat = _mat.getMat();
std::cout << mat << std::endl;
}
int main(int argc, char* argv[])
{
uchar data1[] = { 1, 2, 3, 4, 5, 6 };
cv::Mat mat1(2, 3, CV_8U, data1);
printMat(mat1);
std::vector<float> vec1 = { 1.2f, 3.4f, -2.1f };
printMat(vec1);
}
위 코드에서 printMat() 함수는 Mat 행렬의 원소 값을 출력하며, 함수의 인자로 전달받는 타입이 InputArray로 되어 있는 것을 볼 수 있습니다. main 함수에서는 printMat() 함수를 두 번 호출하며, 한 번은 Mat 객체를 전달하고 그 다음에는 vector<float> 객체를 전달합니다. printMat() 함수는 두 가지 경우에 대해 모두 getMat() 함수를 통해 Mat 객체를 생성하고, 이를 std::cout을 통해 화면에 출력합니다.
OutputArray 클래스
많은 OpenCV 함수는 영상을 입력으로 받아 처리하고, 그 결과를 다시 영상으로 생성하여 반환합니다. 이때 출력 영상을 함수의 return 구문으로 반환하는 것이 아니라 보통 OutputArray 클래스의 참조를 함수 인자로 사용하여 결과 영상을 전달합니다. OutputArray 클래스는 다음과 같이 재정의되어 있습니다.
typedef const _OutputArray& OutputArray;
_OutputARray 클래스는 _InputArray 클래스를 상속받아 만들어져있습니다. 따라서 _OutputArray 클래스도 Mat 또는 vector<T> 같은 타입의 객체로부터 생성될 수 있습니다. 다만 _OutputArray 클래스는 새로운 행렬을 생성하는 _OutputArray::create() 함수가 추가적으로 정의되어 있습니다. 따라서 OpenCV의 많은 영상 처리 함수는 결과 영상을 저장할 새로운 행렬을 먼저 생성한 후, 영상 처리 결과를 저장하는 형태로 구현되어 있습니다.
OutputArray 클래스도 InputArray 클래스와 마찬가지로 사용자가 직접 OutputArray 타입의 변수를 생성해서 사용하면 안됩니다.
참고로 영상에 그림을 그리는 몇몇 OpenCV 함수는 입력 영상 자체를 변경하여 다시 출력으로 반환하는 경우가 있는데, 이러한 함수는 InputOutputArray 클래스 타입의 인자를 사용합니다. 이름에서 알 수 있듯이 입력과 출력의 역할을 동시에 수행할 때 사용됩니다.
'프로그래밍 > OpenCV' 카테고리의 다른 글
[OpenCV] 직선, 도형, 문자 그리기 (0) | 2022.05.04 |
---|---|
[OpenCV] 카메라 & 동영상 파일 처리하기 (0) | 2022.05.03 |
[OpenCV] Mat 클래스 (0) | 2022.05.02 |
[OpenCV] 기본 자료형 클래스 (0) | 2022.05.01 |
[OpenCV] OpenCV 4 설치 (설치 파일 사용) (0) | 2022.04.30 |
댓글