Eulerian Video Magnification
原文:http://www.hahack.com/codes/eulerian-video-magnification/
引言
人類的視覺感知存在有限的感知域。對于超出感知域的變化,我們無法感知。然而,這類信號卻可能蘊藏著驚人的秘密。
比如,血液循環使得人體的皮膚發生細微的周期性變化,這個裸眼無法感知的變化卻和人的心率非常吻合。2011 年,MIT 的一個亞裔學生 Mingzhe Poh 就利用這個微弱的信號設計了一個“魔鏡”[1]?—— 不僅能照出你的模樣,還能測出你的心率。
Mingzhe Poh 的這面神奇的鏡子的原理是利用了血液在人體內流動時光線的變化?[2]?:心臟跳動時血液會通過血管,通過血管的血液量越大,被血液吸收的光線也越多,人皮膚表面反射的光線就越少。因此,通過對圖像的時頻分析就可以估算出心率。
再比如,樂器之所以會發出聲音,是因為它發聲的部分在彈奏過程中發生了有規律的形變,而這個形變的振幅對應著樂器發聲的響度,快慢對應著樂器的音高。微弱信號所蘊藏的信息量是如此重大,無怪乎禪語有云:
一花一世界,一葉一菩提。
既然如此,能否將影像中的這些肉眼觀察不到的變化“放大”到裸眼足以觀察的幅度呢?這就是本文將要重點討論的問題。
在接下來的篇幅中,我將首先追溯最早的一個放大變化的實驗——卡文迪許實驗,然后引出適用于現代計算機的兩種視角下的影像放大方法。這兩種視角分別稱為拉格朗日視角(Lagrangian Perspective)和歐拉視角(Eulerian Perspective)。最后我將重點探討歐拉視角的算法實現細節。我所實現的一個歐拉影像放大算法程序在 Github 上開源。
附圖 1?Henry Cavendish
最早的放大:卡文迪許扭秤實驗
我所能追溯到的最早的將變化“放大”的實驗是 1797 年卡文迪許(H.Cavendish)的經典實驗——扭秤實驗。卡文迪許實驗是第一個在實驗室里完成的測量兩個物體之間萬有引力的實驗,并且第一個準確地求出了萬有引力常數和地球質量。
實驗的裝置由約翰·米切爾設計,由兩個重達350磅的鉛球和扭秤系統組成。
卡文迪許用兩個質量一樣的鉛球分別放在扭秤的兩端。扭秤中間用一根韌性很好的鋼絲系在支架上,鋼絲上有個小鏡子。用準直的細光束照射鏡子,細光束反射到一個很遠的地方,標記下此時細光束所在的點。用兩個質量一樣的鉛球同時分別吸引扭秤上的兩個鉛球。由于萬有引力作用。扭秤微微偏轉。但細光束所反射的遠點卻移動了較大的距離。他用此計算出了萬有引力公式中的常數?GG?。
卡文迪許實驗取得成功的原因,是將不易觀察的微小變化量,轉化(放大)為容易觀察的顯著變化量,再根據顯著變化量與微小量的關系算出微小的變化量 。
卡文迪許的實驗給了我們一個啟示:放大變化,就是要解決以下兩個問題:
?
?
不過,卡文迪許的這個實驗需要借助一個龐大的扭秤裝置,并不能直接用來放大影像中的變化。對于生活在二十一世紀的我們,最理想的方式當然是要借助計算機這個神器了。接下來將介紹兩種現代的技術方案,能夠讓計算機為我們放大影像中細微的變化,從而使我們具有這樣一對火眼金睛,去發現大自然隱藏的秘密。
拉格朗日視角
附圖 2?六祖慧能
時有風吹幡動。一僧云:風動。一僧云:幡動。議論不已。能進曰:不是風動,不是幡動,仁者心動。一眾駭然。
六祖慧能在初見五祖的時候,恰逢有風吹來,吹得幡動。于是一個和尚說是風在動,另一個和尚說是幡在動,而慧能卻一語道破:不是風動,也不是幡動,而是你的心在動。
看待一樣東西,視角不同,得出的結論也就不同。正如看待生活中的“變”,視角不同,也會得到不同的結果。
所謂拉格朗日視角,就是從跟蹤圖像中感興趣的像素(粒子)的運動軌跡的角度著手分析。打個比方,假如我們要研究河水的流速,我們坐上一條船,順流而下,然后記錄這條船的運動軌跡。
?
?
2005 年,Liu 等人最早提出了一種針對影像的動作放大技術[3],該方法首先對目標的特征點進行聚類,然后跟蹤這些點隨時間的運動軌跡,最后將這些點的運動幅度加大。
然而,拉格朗日視角的方法存在以下幾點不足:
- 需要對粒子的運動軌跡進行精確的跟蹤和估計,需要耗費較多的計算資源;
- 對粒子的跟蹤是獨立進行的,缺乏對整體圖像的考慮,容易出現圖像沒有閉合,從而影響放大后的效果;
- 對目標物體動作的放大就是修改粒子的運動軌跡,由于粒子的位置發生了變化,還需要對粒子原先的位置進行背景填充,同樣會增加算法的復雜度。
歐拉視角
不同于拉格朗日視角,歐拉視角并不顯式地跟蹤和估計粒子的運動,而是將視角固定在一個地方,例如整幅圖像。之后,假定整幅圖像都在變,只是這些變化信號的頻率、振幅等特性不同,而我們所感興趣的變化信號就身處其中。這樣,對“變”的放大就變成了對感興趣頻段的析出和增強。打個比方,同樣是研究河水的流速,我們也可以坐在岸邊,觀察河水經過一個固定的地方時的變化,這個變化可能包含很多和水流本身無關的成分,比如葉子掉下水面激起的漣漪,但我們只關注最能體現水流速的部分。
2012 年, Wu 等人從這個視角著手,提出了一種稱為歐拉影像放大技術(Eulerian Video Magnification)的方法[4],其流程如下:
在下一節我們將重點探討 Wu 等人所提出的這種線性的歐拉影像放大技術。之所以加上“線性”這個修飾詞,是因為 Wadhwa 等人在 2013 年對這項技術進行了改進,提出了基于相位的影像動作處理技術[5]。基于相位的歐拉影像放大技術在放大動作的同時不會放大噪聲,而是平移了噪聲,因而可以達到更好的放大效果。不過對它的討論超出了本文的篇幅,感興趣的讀者可以自己找來他們的 paper 閱讀。
算法細節
空間濾波
如前面所說,歐拉影像放大技術(以下簡稱 EVM )的第一步是對視頻序列進行空間濾波,以得到不同的空間頻率的基帶。這么做是因為:
由于空間濾波的目的只是簡單的將多個相鄰的像素“拼”成一塊,所以可以使用低通濾波器來進行。為了加快運算速度,還可以順便進行下采樣操作。熟悉圖像處理操作的朋友應該很快可以反應出來:這兩個東西的組合就是金字塔。實際上,線性的 EVM 就是使用拉普拉斯金字塔或高斯金字塔來進行多分辨率分解。
時域濾波
得到了不同空間頻率的基帶后,接下來對每個基帶都進行時域上的帶通濾波,目的是提取我們感興趣的那部分變化信號。
例如,如果我們要放大的心率信號,那么可以選擇 0.4 ~ 4 Hz (24~240 bpm )進行帶通濾波,這個頻段就是人的心率的范圍。
不過,帶通濾波器有很多種,常見的就有理想帶通濾波器、巴特沃斯(Butterworth)帶通濾波器、高斯帶通濾波器,等等。應該選擇哪個呢?這得根據放大的目的來選擇。如果需要對放大結果進行后續的時頻分析(例如提取心率、分析樂器的頻率),則應該選擇窄通帶的濾波器,如理想帶通濾波器,因為這類濾波器可以直接截取出感興趣的頻段,而避免放大其他頻段;如果不需要對放大結果進行時頻分析,可以選擇寬通帶的濾波器,如 Butterworth 帶通濾波器,二階 IIR 濾波器等,因為這類濾波器可以更好的減輕振鈴現象。
放大和合成
經過前面兩步,我們已經找出了“變”的部分,即解決了何為“變”這個問題。接下來我們來探討如何放大“變”這個問題。
一個重要的依據是:上一步帶通濾波的結果,就是對感興趣的變化的逼近。接下來將證明這個觀點。
這里以放大一維的信號為例,二維的圖像信號與此類似。
假定現在有個信號?I(x,t)I(x,t)?,在任意時刻?tt?,有:
eq: 1 ?
I(x,t)I(x,0)=f(x+δ(t))=f(x),t>0,t=0I(x,t)=f(x+δ(t)),t>0I(x,0)=f(x),t=0
其中,δ(t)δ(t)?是變化信號,在本例中就是一個位移函數。
我們希望得到這個變化放大?αα?倍后的結果,即:
eq: 2 ?
I^(x,t)=f(x+(1+α)δ(t))I^(x,t)=f(x+(1+α)δ(t))
為了將變化的部分分離出來,我們用一階泰勒級數展開來逼近公式 1 表示的信號2?2想起了高數老師在第一節課所說的話:高等代數,也就是微積分,研究的就是一個字:“變”。:
eq: 3 ?
I(x,t)≈f(x)+δ(t)?f(x)?xI(x,t)≈f(x)+δ(t)?f(x)?x
公式 2 中標藍的部分恰好就是變化的部分,而這個部分又和上一步的帶通濾波有著重要的聯系。下面分兩種情況討論。
理想情況
我們先考慮一種理想情況:假如所有的變化信號?δ(t)δ(t)?的頻率范圍恰好是我們進行帶通濾波時所選的頻帶范圍,那么帶通濾波結果?B(x,t)B(x,t)?應該恰好等于公式 2 中標藍的部分,即:
eq: 4 ?
B(x,t)=δ(t)?f(x)?xB(x,t)=δ(t)?f(x)?x
對公式 2 所逼近的信號進行放大,就是將這個變化的部分乘以一個放大倍數?αα?,再加回原來的信號中。即:
eq: 5 ?
I~(x,t)=I(x,t)+αB(x,t)I~(x,t)=I(x,t)+αB(x,t)
聯立公式 2~4 ,可以得到
eq: 6 ?
I~(x,t)≈f(x)+(1+α)δ(t)?f(x)?xI~(x,t)≈f(x)+(1+α)δ(t)?f(x)?x
在這種理想情況下,這個?I~(x,t)I~(x,t)?約等于我們希望得到的?I(x,t)I(x,t)?,即:
eq: 7 ?
I~(x,t)≈f(x+(1+α)δ(t))I~(x,t)≈f(x+(1+α)δ(t))
下圖演示了使用上面的方法將一個余弦波放大?αα?倍的過程和結果。其中,黑色的曲線表示原信號?f(x)f(x)?,藍色的曲線表示變化后的信號?f(x+δ)f(x+δ)?,青色的曲線表示對這個信號的泰勒級數逼近?f(x)+δ(t)?f(x)?xf(x)+δ(t)?f(x)?x,綠色的曲線表示我們分離出來的變化的部分。我們將這個部分放大?αα?倍再加回原信號就得到放大后的信號,圖中紅色的曲線表示這個放大后的信號?f(x)+(1+α)B(x,t))f(x)+(1+α)B(x,t))?。
非理想情況
不過,有些時候,我們并沒有那么幸運——變化信號?δ(t)δ(t)?的頻率范圍超出了我們所選的頻段范圍。這種情況下,應用帶通濾波意味著只是保留了一部分的變化信號,而其他頻率超出范圍的信號將會被減弱。因此,我們用?γk(t)γk(t)?來表示在?tt?時刻變化第?kk?個變化信號減弱的倍數(0≤γk≤10≤γk≤1)3?3它的值和所選的帶通濾波器有關。實際上論文原作者考慮這個情況僅僅是出于論證上的嚴密,在實現時我們不需要計算它,只需要得到 B(x,t) ,而這個值是上一步的帶通結果。,則有:
eq: 8 ?
B(x,t)=∑kγkδk(t)?f(x)?xB(x,t)=∑kγkδk(t)?f(x)?x
之后我們又要對它乘以放大倍數?αα?。既然兩步都是乘以一個倍數,為了方便起見,我們干脆把這兩個線性變化合為一步,即讓放大倍數?αk=γkααk=γkα?,則:
eq: 9 ?
I~(x,t)≈f(x+∑k(1+αk)δk(t))I~(x,t)≈f(x+∑k(1+αk)δk(t))
?
放大倍數限制
線性的 EVM 方法會在放大動作變化的同時放大噪聲,為了避免造成太大的失真,可以設置一個合理的放大倍數限制。假定信號的空間波長為?λ=2πωλ=2πω?,這個限制可以用公式 9 來表示:
eq: 10 ?
(1+α)δ(t)<λ8(1+α)δ(t)<λ8
?
當超出這個邊界的時候,我們可以讓?αα?維持在一個邊界值,如下圖所示:
不過,如果要放大的是顏色的變化,那么從視覺上看并不會很受影響(或者說這種顏色的失真就是我們想要的),這時候就可以不用對?αα?進行限制,或者使用一個更小的空間波長下限值。
算法實現
這一節將介紹我使用 OpenCV 實現線性歐拉影像放大算法的心得。
?
- 本文的源碼遵守 LGPL v3 協議,但這個技術已由原作者申請了專利,因此請勿直接用于商業用途。
- 動作放大的模塊引用了 Yusuke Tomoto 的?ofxEvm?項目的相關代碼。這個項目基于 EVM 實現了對動作的放大。對于初學者,我建議先閱讀他的代碼。
- 另外特別感謝?Daniel Ron?和?Aalessandro Gentilini?的大力援助,沒有他們我無法完成這個程序。
?
顏色空間轉換
顏色空間轉換在原論文中只是一筆帶過,但這一步對于放大動作還是非常有用的。在進入整個 framework 之前,作者建議先將圖像的顏色空間由 RGB 轉換到?YIQ?。YIQ 是 NTSC 電視機系統所采用的顏色空間,Y?是提供黑白電視及彩色電視的亮度信號,I?代表 In-phase,色彩從橙色到青色,Q?代表 Quadrature-phase,色彩從紫色到黃綠色。
采用這種顏色空間可以方便在后期使用一個衰減因子來減少噪聲:對于只想放大動作變化的情況,顏色就應該不會發生太大變化,所以我們可以用這個衰減因子來減小放大后的信號的 I 和 Q 兩個分量的值。然后再轉回 RGB 顏色空間44類似的顏色空間還有 Lab,經過我的測試,使用 Lab 顏色空間也有效。。兩個轉換函數實現如下:
|
? ? | void rgb2ntsc(const Mat_<Vec3f>& src, Mat_<Vec3f>& dst) { Mat ret = src.clone(); Mat T = (Mat_<float>(3,3) << 1, 1, 1, 0.956, -0.272, -1.106, 0.621, -0.647, 1.703); T = T.inv(); //here inverse! for (int j=0; j<src.rows; j++) { for (int i=0; i<src.cols; i++) { ret.at<Vec3f>(j,i)(0) = src.at<Vec3f>(j,i)(0) * T.at<float>(0.0) + src.at<Vec3f>(j,i)(1) * T.at<float>(0,1) + src.at<Vec3f>(j,i)(2) * T.at<float>(0,2); ret.at<Vec3f>(j,i)(1) = src.at<Vec3f>(j,i)(0) * T.at<float>(1.0) + src.at<Vec3f>(j,i)(1) * T.at<float>(1,1) + src.at<Vec3f>(j,i)(2) * T.at<float>(1,2); ret.at<Vec3f>(j,i)(2) = src.at<Vec3f>(j,i)(0) * T.at<float>(2.0) + src.at<Vec3f>(j,i)(1) * T.at<float>(2,1) + src.at<Vec3f>(j,i)(2) * T.at<float>(2,2); } } dst = ret; } void ntsc2rgb(const Mat_<Vec3f>& src, Mat_<Vec3f>& dst) { Mat ret = src.clone(); Mat T = (Mat_<float>(3,3) << 1.0, 0.956, 0.621, 1.0, -0.272, -0.647, 1.0, -1.106, 1.703); T = T.t(); //here transpose! for (int j=0; j<src.rows; j++) { for (int i=0; i<src.cols; i++) { ret.at<Vec3f>(j,i)(0) = src.at<Vec3f>(j,i)(0) * T.at<float>(0.0) + src.at<Vec3f>(j,i)(1) * T.at<float>(0,1) + src.at<Vec3f>(j,i)(2) * T.at<float>(0,2); ret.at<Vec3f>(j,i)(1) = src.at<Vec3f>(j,i)(0) * T.at<float>(1.0) + src.at<Vec3f>(j,i)(1) * T.at<float>(1,1) + src.at<Vec3f>(j,i)(2) * T.at<float>(1,2); ret.at<Vec3f>(j,i)(2) = src.at<Vec3f>(j,i)(0) * T.at<float>(2.0) + src.at<Vec3f>(j,i)(1) * T.at<float>(2,1) + src.at<Vec3f>(j,i)(2) * T.at<float>(2,2); } } dst = ret; } |
?
空間濾波
如前面所述,EVM 算法可以使用拉普拉斯金字塔和高斯金字塔來進行空間濾波。使用哪個金字塔得根據具體需求而定。如果要放大的是動作的變化,那么可以選擇拉普拉斯金字塔,構造多個不同空間頻率的基帶;如果要放大的是顏色的變化,不同基帶的 SNR 應該比較接近,因此可以選擇高斯金字塔,只取最頂層下采樣和低通濾波的結果。這兩個金字塔可以很容易地利用 OpenCV 的?cv::PyDown()?和?cv::PyUp()?兩個函數來構造:
| ? | /** * buildLaplacianPyramid - construct a laplacian pyramid from given image * * @param img - source image * @param levels - levels of the destinate pyramids * @param pyramid - destinate image * * @return true if success */ bool buildLaplacianPyramid(const cv::Mat &img, const int levels, std::vector<cv::Mat_<cv::Vec3f> > &pyramid) { if (levels < 1){ perror("Levels should be larger than 1"); return false; } pyramid.clear(); cv::Mat currentImg = img; for (int l=0; l<levels; l++) { cv::Mat down,up; pyrDown(currentImg, down); pyrUp(down, up, currentImg.size()); cv::Mat lap = currentImg - up; pyramid.push_back(lap); currentImg = down; } pyramid.push_back(currentImg); return true; } /** * buildGaussianPyramid - construct a gaussian pyramid from a given image * * @param img - source image * @param levels - levels of the destinate pyramids * @param pyramid - destinate image * * @return true if success */ bool buildGaussianPyramid(const cv::Mat &img, const int levels, std::vector<cv::Mat_<cv::Vec3f> > &pyramid) { if (levels < 1){ perror("Levels should be larger than 1"); return false; } pyramid.clear(); cv::Mat currentImg = img; for (int l=0; l<levels; l++) { cv::Mat down; cv::pyrDown(currentImg, down); pyramid.push_back(down); currentImg = down; } return true; } |
?
時域濾波
同樣,時域濾波可以根據不同的需求選擇不同的帶通濾波器。如果需要對放大結果進行后續的時頻分析,則可以選擇理想帶通濾波器;如果不需要對放大結果進行時頻分析,可以選擇寬通帶的濾波器,如 Butterworth 帶通濾波器,二級IIR 濾波器等。這里分別實現了二階 IIR 帶通濾波器和理想帶通濾波器:
?
|
? 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 | /** * temporalIIRFilter - temporal IIR filtering an image * (thanks to Yusuke Tomoto) * @param pyramid - source image * @param filtered - filtered result * */ void VideoProcessor::temporalIIRFilter(const cv::Mat &src, cv::Mat &dst) { cv::Mat temp1 = (1-fh)*lowpass1[curLevel] + fh*src; cv::Mat temp2 = (1-fl)*lowpass2[curLevel] + fl*src; lowpass1[curLevel] = temp1; lowpass2[curLevel] = temp2; dst = lowpass1[curLevel] - lowpass2[curLevel]; } /** * temporalIdalFilter - temporal IIR filtering an image pyramid of concat-frames * (Thanks to Daniel Ron & Alessandro Gentilini) * * @param pyramid - source pyramid of concatenate frames * @param filtered - concatenate filtered result * */ void VideoProcessor::temporalIdealFilter(const cv::Mat &src, cv::Mat &dst) { cv::Mat channels[3]; // split into 3 channels cv::split(src, channels); for (int i = 0; i < 3; ++i){ cv::Mat current = channels[i]; // current channel cv::Mat tempImg; int width = cv::getOptimalDFTSize(current.cols); int height = cv::getOptimalDFTSize(current.rows); cv::copyMakeBorder(current, tempImg, 0, height - current.rows, 0, width - current.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0)); // do the DFT cv::dft(tempImg, tempImg, cv::DFT_ROWS | cv::DFT_SCALE, tempImg.rows); // construct the filter cv::Mat filter = tempImg.clone(); createIdealBandpassFilter(filter, fl, fh, rate); // apply filter cv::mulSpectrums(tempImg, filter, tempImg, cv::DFT_ROWS); // do the inverse DFT on filtered image cv::idft(tempImg, tempImg, cv::DFT_ROWS | cv::DFT_SCALE, tempImg.rows); // copy back to the current channel tempImg(cv::Rect(0, 0, current.cols, current.rows)).copyTo(channels[i]); } // merge channels cv::merge(channels, 3, dst); // normalize the filtered image cv::normalize(dst, dst, 0, 1, CV_MINMAX); } /** * createIdealBandpassFilter - create a 1D ideal band-pass filter * * @param filter - destinate filter * @param fl - low cut-off * @param fh - high cut-off * @param rate - sampling rate(i.e. video frame rate) */ void VideoProcessor::createIdealBandpassFilter(cv::Mat &filter, double fl, double fh, double rate) { int width = filter.cols; int height = filter.rows; fl = 2 * fl * width / rate; fh = 2 * fh * width / rate; double response; for (int i = 0; i < height; ++i) { for (int j = 0; j < width; ++j) { // filter response if (j >= fl && j <= fh) response = 1.0f; else response = 0.0f; filter.at<float>(i, j) = response; } } } |
放大變化
根據前面的公式 5 和公式 8,可以設計如下的放大函數:
?
|
? 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 | /** * amplify - ampilfy the motion * * @param filtered - motion image */ void VideoProcessor::amplify(const cv::Mat &src, cv::Mat &dst) { float curAlpha; switch (spatialType) { case LAPLACIAN: // for motion magnification //compute modified alpha for this level curAlpha = lambda/delta/8 - 1; curAlpha *= exaggeration_factor; if (curLevel==levels || curLevel==0) // ignore the highest and lowest frequency band dst = src * 0; else dst = src * cv::min(alpha, curAlpha); break; case GAUSSIAN: // for color magnification dst = src * alpha; break; default: break; } } |
?
對于動作信號的放大,調用這個函數前需要先算出每一層基帶的?λλ?以及?δ(t)δ(t)?的值,以便于根據公式 10 來計算當前合理的放大倍數:
| 1 2 3 4 5 6 7 8 |
? delta = lambda_c/8.0/(1.0+alpha); // the factor to boost alpha above the bound // (for better visualization) exaggeration_factor = 2.0; // compute the representative wavelength lambda // for the lowest spatial frequency band of Laplacian pyramid lambda = sqrt(w*w + h*h)/3; // 3 is experimental constant |
注意:這里的?λcλc?就是前面所提的空間波長的下限值。對于?λ<λcλ<λc?的基帶,αα?值將被減弱。另外,還加了一個?exaggeration_factor?參數,它是一個魔數(magic number),用來將符合?λ>λcλ>λc?的基帶加倍放大。
合成圖像
先合成變化信號的圖像,再與原圖進行疊加。根據使用金字塔的類型,編寫對應的合成方法:
| 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 |
? /** * reconImgFromLaplacianPyramid - reconstruct image from given laplacian pyramid * * @param pyramid - source laplacian pyramid * @param levels - levels of the pyramid * @param dst - destinate image */ void reconImgFromLaplacianPyramid(const std::vector<cv::Mat_<cv::Vec3f> > &pyramid, const int levels, cv::Mat_<cv::Vec3f> &dst) { cv::Mat currentImg = pyramid[levels]; for (int l=levels-1; l>=0; l--) { cv::Mat up; cv::pyrUp(currentImg, up, pyramid[l].size()); currentImg = up + pyramid[l]; } dst = currentImg.clone(); } /** * upsamplingFromGaussianPyramid - up-sampling an image from gaussian pyramid * * @param src - source image * @param levels - levels of the pyramid * @param dst - destinate image */ void upsamplingFromGaussianPyramid(const cv::Mat &src, const int levels, cv::Mat_<cv::Vec3f> &dst) { cv::Mat currentLevel = src.clone(); for (int i = 0; i < levels; ++i) { cv::Mat up; cv::pyrUp(currentLevel, up); currentLevel = up; } currentLevel.copyTo(dst); } |
?
衰減 I、Q 通道
對于動作信號的放大,可以在后期引入一個衰減因子減弱 I、Q 兩個通道的變化幅度,最后才轉回 RGB 顏色空間。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /** * attenuate - attenuate I, Q channels * * @param src - source image * @param dst - destinate image */ void VideoProcessor::attenuate(cv::Mat &src, cv::Mat &dst) { cv::Mat planes[3]; cv::split(src, planes); planes[1] = planes[1] * chromAttenuation; planes[2] = planes[2] * chromAttenuation; cv::merge(planes, 3, dst); } |
結果
下面演示使用我的程序,對論文提供的 face 案例進行處理的結果。
所選參數如下:
| 動作變化放大 | 10 | 80 | 0.05~0.4 | 0.1 |
| 顏色變化放大 | 50 | - | 0.83~1.0 | - |
源碼和程序
-
?QtEVM 的源碼
-
?QtEVM 的 Win32 可執行程序
總結
以上是生活随笔為你收集整理的Eulerian Video Magnification的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: openCV——图像金字塔
- 下一篇: 遨博协作机器人高级编程 -AUBOPE