先把完成品貼上來給心急的人看一下
相對應的程式碼是
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 | #include <opencv2/opencv.hpp> #include <stdio.h> using namespace cv; #define WINDOW_NAME "Alpha" const int slider_max = 100; static void on_trackbar_alpha(int pos, void* userdata) { Mat* image = (Mat*) userdata; Mat new_image = image->clone(); float alpha = (float) pos / slider_max; for (int y = 0; y < image->rows; y++) { for (int x = 0; x < image->cols; x++) { for (int c = 0; c < 3; c++) { new_image.at<Vec3b>(y, x)[c] = saturate_cast<uchar>( (alpha * image->at<Vec3b>(y, x)[c]) * x / 512); } } } imshow(WINDOW_NAME, new_image); } int main_(int argc, char** argv) { Mat image = imread( argv[1] ); namedWindow(WINDOW_NAME, WINDOW_AUTOSIZE); // Create Window int init_alpha = 100; char TrackbarName[50]; sprintf(TrackbarName, "Alpha Max %d", slider_max); createTrackbar(TrackbarName, WINDOW_NAME, &init_alpha, slider_max, on_trackbar_alpha, &image); imshow(WINDOW_NAME, image); waitKey(0); return 0; } |
別被這密密麻麻的字嚇到, 讓喵來慢慢解說
Trackbar
首先我們將圖load進來, 然後創造一個window, 之後我們會使用 imshow來將兩個綁在一起
int main(int argc, char** argv) { Mat image = imread( argv[1] ); imshow(WINDOW_NAME, image); ... waitKey(0); return 0; }
接著中間放進去的code, 就是 trackbar相關的了
使用 createTrackbar來創造出一個trackbar, 並且要放入相對應的參數
createTrackbar的原始宣告如下
CV_EXPORTS int createTrackbar(const String& trackbarname, const String& winname, int* value, int count, TrackbarCallback onChange = 0, void* userdata = 0);
因此我們按照需求的填入
- trackbarname: Trackbar名稱
- winname: 視窗名稱
- value: 這邊value可以填0, 可以作為slider position初始值使用
- onChange: callback function
- userdata: userdata and value 都會在callback function當中傳入
當中需要說明一下的是callback function, 以下是他的原型
typedef void (*TrackbarCallback)(int pos, void* userdata);
因此需要"製作"這一個callback function
喵寫了一個如下
static void on_trackbar_alpha(int pos, void* userdata) { Mat* image = (Mat*) userdata; Mat new_image = image->clone(); float alpha = (float) pos / slider_max; for (int y = 0; y < image->rows; y++) { for (int x = 0; x < image->cols; x++) { for (int c = 0; c < 3; c++) { new_image.at<Vec3b>(y, x)[c] = saturate_cast<uchar>( (alpha * image->at<Vec3b>(y, x)[c]) * x / 512); } } } imshow(WINDOW_NAME, new_image); }
這邊開始就會跟Mat這物件打交道了
Mat
在一開始看到的畫面, 雖然是應用上了trackbar, 但是呈現出的效果卻是跟Mat這物件有比較大的相關
這段code包含了兩個部分
- 調整 alhpa
- 根據 x position, 逐漸調亮
先說第一個部分
這段code大致上是
new_image.at<Vec3b>(y, x)[c] = saturate_cast<uchar>( (alpha * image->at<Vec3b>(y, x)[c]));
先把 x position的部分去除掉了
這邊有幾個特別東西要介紹
<Vec3b>
這關於 C++語言的 template在這邊不多加描述, 不過這用法可以看一下
typedef Vec<uchar, 3> Vec3b;
因此可以知道Vec3b 是 Vec<uchar,3>
接著看一下 new_image.at<Vec3b>
template<typename _Tp> inline _Tp& Mat::at(int i0, int i1) { CV_DbgAssert(dims <= 2); CV_DbgAssert(data); CV_DbgAssert((unsigned)i0 < (unsigned)size.p[0]); CV_DbgAssert((unsigned)(i1 * DataType<_Tp>::channels) < (unsigned)(size.p[1] * channels())); CV_DbgAssert(CV_ELEM_SIZE1(DataType<_Tp>::depth) == elemSize1()); return ((_Tp*)(data + step.p[0] * i0))[i1]; }
因此這個式子 new_image.at<Vec3b>(y, x)
會return Vec3b的物件
接著再搭配上 [c]
這變成是去看 Vec3b[c] --> Vector<uchar,3>[c]
根據定義這個在 matx.hpp當中
template<typename _Tp, int cn> inline const _Tp& Vec<_Tp, cn>::operator [](int i) const { CV_DbgAssert( (unsigned)i < (unsigned)cn ); return this->val[i]; }
_Tp的類型是 uchar, 另外 val的定義是
_Tp val[m*n]; //< matrix elements
此時的 m = 3, n = 1, 所以上述式子相當於
uchar val[3*1]; //< matrix elements
至於這個 n=1的由來則是因為 Vec這物件本身是繼承 Matx, 並且維度為1
template<typename _Tp, int cn> class Vec : public Matx<_Tp, cn, 1>
根據這些定義, 我們可以理解到Matx在儲存 2D圖型pixel的方式是三個pixel放在一起
而他的排列方式是
val[0]: B
val[1]: G
val[2]: R
saturate_cast<uchar>
這本身沒什麼好多解釋的, 簡而言之就是把數值侷限到 uchar的範圍內更深入探討
理解完上述這些事項之後還有些事情是沒講述到的
new_image.at<Vec3b>(y, x)這裡有發現到 y放在前面, 而x放到後面
另外在之前的code當中有看到這一段
return ((_Tp*)(data + step.p[0] * i0))[i1];裡面多出了 step.p[0]這個東西
為了更多理解這個內容
我們把 setp.p[0]印出來之後會發現, 數值為 1536
這剛好是 512 * 3 = 1536
而 lena.png 是一個 512 x 512 pixel的圖案
因此就剛好回應 BGR 3個channel 乘上 一行有512個pixel
而一開始的程式碼, 喵把 x的位置加入做變化就是要特地去觀察
y放在前面, 而x放到後面的配置是否真的是正確 (正確!喵~)
另外這取值的方式, 有發現到 Vec3b取值的方法就像是直接在memory上面只分到3個 uchar
return ((_Tp*)(data + step.p[0] * i0))[i1];記得這 _Tp其實是 Vec3b
這也是Matx的一個巧思, 在Matx的定義當中, 大多數都是方法, 只有一個變量
那就是 _Tp val[m * n]
可以做個實驗去印出 Vec3b本身的address以及Vec3b的大小
這結果會讓人很吃驚
Vec3b test = Vec3b(1,2); printf("test addr 0x%x, 0x%x, size %d\n", &test, &test[0], sizeof(Vec3b));
結果:
test addr 0x55ae1390, 0x55ae1390, size 3
是不是開始有點佩服寫openCV的這些高手們~ 喵
這次對於code的闡述多了些
但希望每一個關節多闡述一點也可以讓大家一起欣賞這程式的美~
喵~
沒有留言:
張貼留言