OpenCV에 cv::applyColorMap 함수가 있습니다.
함수 사용 방법을 읽어보면, grayscale 뿐만 아니라 색상 영상도 제공되는 칼라맵을 적용하여 색감을 완전히 변화시킬 수 있습니다.
기본적으로 제공되는 칼라맵은 아래와 같습니다.
Class |
Scale |
|
COLORMAP_AUTUMN |
||
COLORMAP_BONE |
||
COLORMAP_COOL |
||
COLORMAP_HOT |
||
COLORMAP_HSV |
||
COLORMAP_JET |
||
COLORMAP_OCEAN |
||
COLORMAP_PINK |
||
COLORMAP_RAINBOW |
||
COLORMAP_SPRING |
||
COLORMAP_SUMMER |
||
COLORMAP_WINTER |
저는 색상 영상보다는 grayscale 영상을 주로 다루는데, 이 함수는 비교적 쓰임새가 많습니다.
픽셀값이 0-255 또는 0-65535, 심지어 0.0f-1.0f 범위의 grayscale 영상을 많이 다루는데,
이 함수를 적용하면 어떤 값이든 눈에 쉽게 보이므로 논문 및 프레젠테이션 작성 시 아주 유용합니다.
한 가지 예를 들자면, 저는 한 3D 모델에서 레퍼런스 모델과 편차(deviation)를 구하여 그 정밀도(accuracy, not precision)를 보여주고 있습니다.
쉽게, 3D 포인트마다 정밀도 값에 따라 colormap을 입히면 정밀도가 좋지 않은 부분을 한 눈에 찾을 수 있고, 그 형태를 쉽게 알 수 있죠.
보통 정밀도를 색으로 표현할 때, 정확한 부분을 초록색, 약간 벗어나는 부분을 노란색, -로 벗어나면 파란색, +로 벗어나면 빨간색으로 표시를 합니다.
OpenCV에서 제공하는 Jet 클래스를 사용할 수 있지만, 초록색 범위가 너무 적어 결과가 부정적으로 보이더군요.
Rainbow 클래스는 보라색을 포함하고 있어, 바로 가져다 쓸 수도 없네요.
게다가, 표현하고자 하는 정밀도 값의 step이 얼마나 될 지 모르니, lookup table 방법으로는 구현이 어렵습니다.
base color 맵을 lookup table로 정의하고 표현하고자 하는 크기에 따라 유동적으로 colormap 만드는 것이 필요합니다.
그냥 쉽게 만들어보죠. OpenCV는 오픈소스라구요!
ColorMap.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168 |
namespace colormap
{
using namespace cv;
static Mat linspace(float x0, float x1, int n)
{
Mat pts(n, 1, CV_32FC1);
float step = (x1 - x0) / (n - 1);
for( int i = 0; i < n; i++ )
pts.at<float>(i, 0) = x0 + i*step;
return pts;
}
//------------------------------------------------------------------------------
// cv::sortMatrixRowsByIndices
//------------------------------------------------------------------------------
static void sortMatrixRowsByIndices(InputArray _src, InputArray _indices, OutputArray _dst)
{
if( _indices.getMat().type() != CV_32SC1 )
CV_Error(Error::StsUnsupportedFormat, "cv::sortRowsByIndices only works on integer indices!");
Mat src = _src.getMat();
std::vector<int> indices = _indices.getMat();
_dst.create(src.rows, src.cols, src.type());
Mat dst = _dst.getMat();
for( size_t idx = 0; idx < indices.size(); idx++ ) {
Mat originalRow = src.row(indices[idx]);
Mat sortedRow = dst.row((int) idx);
originalRow.copyTo(sortedRow);
}
}
static Mat sortMatrixRowsByIndices(InputArray src, InputArray indices)
{
Mat dst;
sortMatrixRowsByIndices(src, indices, dst);
return dst;
}
static Mat argsort(InputArray _src, bool ascending = true)
{
Mat src = _src.getMat();
if( src.rows != 1 && src.cols != 1 )
CV_Error(Error::StsBadArg, "cv::argsort only sorts 1D matrices.");
int flags = SORT_EVERY_ROW | (ascending ? SORT_ASCENDING : SORT_DESCENDING);
Mat sorted_indices;
sortIdx(src.reshape(1, 1), sorted_indices, flags);
return sorted_indices;
}
template <typename _Tp> static
Mat interp1_(InputArray X_, InputArray Y_, InputArray XI_)
{
// sort input table
std::vector<int> sort_indices = argsort(X_);
Mat X = sortMatrixRowsByIndices(X_, sort_indices);
Mat Y = sortMatrixRowsByIndices(Y_, sort_indices);
Mat XI = XI_.getMat();
Mat yi = Mat::zeros(XI.size(), XI.type()); // interpolated values
for( int i = 0; i < XI.rows; i++ )
{
int low = 0;
int high = X.rows - 1;
// set bounds
if( XI.at<_Tp>(i, 0) < X.at<_Tp>(low, 0) )
high = 1;
if( XI.at<_Tp>(i, 0) > X.at<_Tp>(high, 0) )
low = high - 1;
// binary search
while( (high - low) > 1 )
{
const int c = low + ((high - low) >> 1);
if( XI.at<_Tp>(i, 0) > X.at<_Tp>(c, 0) ) {
low = c;
}
else {
high = c;
}
}
// linear interpolation
yi.at<_Tp>(i, 0) += Y.at<_Tp>(low, 0)
+ (XI.at<_Tp>(i, 0) - X.at<_Tp>(low, 0))
* (Y.at<_Tp>(high, 0) - Y.at<_Tp>(low, 0))
/ (X.at<_Tp>(high, 0) - X.at<_Tp>(low, 0));
}
return yi;
}
class ColorMap
{
public:
virtual ~ColorMap() {}
// Setup base map to interpolate from.
virtual void init(int n) = 0;
// Interpolates from a base colormap.
static Mat linear_colormap(InputArray X,
InputArray r, InputArray g, InputArray b,
int n) {
return linear_colormap(X, r, g, b, linspace(0, 1, n));
}
// Interpolates from a base colormap.
static Mat linear_colormap(InputArray X,
InputArray r, InputArray g, InputArray b,
float begin, float end, float n) {
return linear_colormap(X, r, g, b, linspace(begin, end, cvRound(n)));
}
// Interpolates from a base colormap.
static Mat ColorMap::linear_colormap(InputArray X,
InputArray r, InputArray g, InputArray b,
InputArray xi) {
Mat lut;
Mat planes[] = {
interp1_<float>(X, b, xi),
interp1_<float>(X, g, xi),
interp1_<float>(X, r, xi) };
merge(planes, 3, lut);
return lut; // for OpenGL renderer
}
};
class Rainbow2 : public ColorMap
{
public:
Rainbow2(int n) : ColorMap()
{
init(n);
}
void init(int n)
{
// breakpoints
Mat X = linspace(0, 1, 171);
// define the basemap
float r[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0.0235294, 0.0470588, 0.0705882, 0.0941177, 0.117647, 0.141176, 0.164706, 0.188235, 0.211765, 0.235294, 0.258824, 0.282353, 0.305882, 0.329412, 0.352941, 0.376471, 0.4, 0.423529, 0.447059, 0.470588, 0.494118, 0.517647, 0.541176, 0.564706, 0.588235, 0.611765, 0.635294, 0.658824, 0.682353, 0.705882, 0.729412, 0.752941, 0.776471, 0.8, 0.823529, 0.847059, 0.870588, 0.894118, 0.917647, 0.941177, 0.964706, 0.988235,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
float g[] = { 0, 0.0235294, 0.0470588, 0.0705882, 0.0941177, 0.117647, 0.141176, 0.164706, 0.188235, 0.211765, 0.235294, 0.258824, 0.282353, 0.305882, 0.329412, 0.352941, 0.376471, 0.4, 0.423529, 0.447059, 0.470588, 0.494118, 0.517647, 0.541176, 0.564706, 0.588235, 0.611765, 0.635294, 0.658824, 0.682353, 0.705882, 0.729412, 0.752941, 0.776471, 0.8, 0.823529, 0.847059, 0.870588, 0.894118, 0.917647, 0.941177, 0.964706, 0.988235,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0.988235, 0.964706, 0.941177, 0.917647, 0.894118, 0.870588, 0.847059, 0.823529, 0.8, 0.776471, 0.752941, 0.729412, 0.705882, 0.682353, 0.658824, 0.635294, 0.611765, 0.588235, 0.564706, 0.541176, 0.517647, 0.494118, 0.470588, 0.447059, 0.423529, 0.4, 0.376471, 0.352941, 0.329412, 0.305882, 0.282353, 0.258824, 0.235294, 0.211765, 0.188235, 0.164706, 0.141176, 0.117647, 0.0941177, 0.0705882, 0.0470588, 0.0235294, 0 };
float b[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0.988235, 0.964706, 0.941177, 0.917647, 0.894118, 0.870588, 0.847059, 0.823529, 0.8, 0.776471, 0.752941, 0.729412, 0.705882, 0.682353, 0.658824, 0.635294, 0.611765, 0.588235, 0.564706, 0.541176, 0.517647, 0.494118, 0.470588, 0.447059, 0.423529, 0.4, 0.376471, 0.352941, 0.329412, 0.305882, 0.282353, 0.258824, 0.235294, 0.211765, 0.188235, 0.164706, 0.141176, 0.117647, 0.0941177, 0.0705882, 0.0470588, 0.0235294,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
// now build lookup table
this->image_colorMap = ColorMap::linear_colormap(X,
Mat(171, 1, CV_32FC1, r).clone(), // red
Mat(171, 1, CV_32FC1, g).clone(), // green
Mat(171, 1, CV_32FC1, b).clone(), // blue
n);
}
public:
cv::Mat image_colorMap;
};
} |
cs |
Rainbow2 라는 클래스를 새로 만들고 초기화 함수에 원하는 색상 값을 base color로 정의합니다.
그리고 상속받은 ColorMap 클래스의 메서드를 이용하여 파라미터 n에 따라 lookup table을 만들면 끝입니다.
ColorMap 클래스와 미리 정의한 static 함수는 OpenCV 소스입니다. (필요없는 부분은 제거했습니다.)
저는 OpenGL 등의 renderer에서 사용할 목적이니, 127 라인대로 Rainbow2 클래스의 멤버 변수는 float 형식으로 r g b 값을 (CV_32FC3) 가지고 있습니다.
멤버 변수에 직접 인덱스로 엑세스하여 필요한 색상 값을 쉽게 얻어올 수 있겠네요.
아래는 400개의 Rainbow 색상을 만든 결과입니다.
질문은 언제든지 환영입니다!
참고자료:
[1] https://docs.opencv.org/2.4/modules/contrib/doc/facerec/colormaps.html