http://blog.csdn.net/zouxy09/article/details/17471401 html
基於感知哈希算法的視覺目標跟蹤算法
http://blog.csdn.net/zouxy09框架
偶然看到這三篇博文[1][2][3],提到圖片檢索網站TinEye和谷歌的類似圖片搜索引擎的技術原理。以圖搜圖搜索引擎的使命是:你上傳一張圖片,而後他們盡全力幫你把互聯網上全部與它類似的圖片搜索出來。固然了,這只是他們認爲的類似,因此有時候搜索結果也不必定對。事實上,以圖搜圖三大搜索引擎除了上面的老牌的TinEye和Google外,還有百度上線不算好久的新生兒:百度識圖。以前聽餘凱老師的一個Deep Learning的講座,裏面很大一部分就介紹了百度識圖這個產品,由於它是Deep Learning在百度成功上線的一個應用。裏面詳盡的把百度識圖和谷歌的PK了一番。若是我沒有聽錯和記錯的話,餘凱老師所介紹的百度識圖也是應用了卷積神經網絡CNN的,還有很是霸氣的一點是:餘凱老師說百度幾乎都是監督學習!在廈門仍是哪,有200人天天給百度標數據。這財力,氣度全在上面了,沒什麼好說的了。ide
跑題了,咱們回到這三篇博文提到的谷歌的以圖搜圖搜索引擎,博文中提到,這個網站提到了該引擎實現類似圖片搜素的關鍵技術叫作「感知哈希算法」(Perceptual hash algorithm),它的做用是對每張圖片生成一個「指紋」(fingerprint)字符串,而後比較不一樣圖片的指紋。結果越接近,就說明圖片越類似。(不知道是否是真的那麼簡單,哈哈)函數
但在這裏,我考慮的不是圖片檢索,而是跟蹤。由於既然它能夠衡量兩個圖片的類似性,那麼我就在想,那它就能夠拿來作目標跟蹤了,只要在每一幀找到和目標最類似的地方,那個就是目標了。這個和以前寫的模板匹配的原理是差很少的,只是以前模板匹配採用的類似度度量是兩個圖片的相關性,這裏用的是「hash指紋」。另外,詳細的描述請參考上面三篇博文,這裏先稍微總結下感知哈希算法的實現過程,而後給出本身簡單實現目標跟蹤的代碼。學習
1、感知哈希算法測試
一、基於低頻的均值哈希優化
一張圖片就是一個二維信號,它包含了不一樣頻率的成分。以下圖所示,亮度變化小的區域是低頻成分,它描述大範圍的信息。而亮度變化劇烈的區域(好比物體的邊緣)就是高頻的成分,它描述具體的細節。或者說高頻能夠提供圖片詳細的信息,而低頻能夠提供一個框架。
而一張大的,詳細的圖片有很高的頻率,而小圖片缺少圖像細節,因此都是低頻的。因此咱們平時的下采樣,也就是縮小圖片的過程,其實是損失高頻信息的過程。
均值哈希算法主要是利用圖片的低頻信息,其工做過程以下:
(1)縮小尺寸:去除高頻和細節的最快方法是縮小圖片,將圖片縮小到8x8的尺寸,總共64個像素。不要保持縱橫比,只需將其變成8*8的正方形。這樣就能夠比較任意大小的圖片,摒棄不一樣尺寸、比例帶來的圖片差別。
(2)簡化色彩:將8*8的小圖片轉換成灰度圖像。
(3)計算平均值:計算全部64個像素的灰度平均值。
(4)比較像素的灰度:將每一個像素的灰度,與平均值進行比較。大於或等於平均值,記爲1;小於平均值,記爲0。
(5)計算hash值:將上一步的比較結果,組合在一塊兒,就構成了一個64位的整數,這就是這張圖片的指紋。組合的次序並不重要,只要保證全部圖片都採用一樣次序就好了。(我設置的是從左到右,從上到下用二進制保存)。
計算一個圖片的hash指紋的過程就是這麼簡單。剛開始的時候以爲這樣就損失了圖片的不少信息了,竟然還能有效。簡單的算法也許存在另外一種美。若是圖片放大或縮小,或改變縱橫比,結果值也不會改變。增長或減小亮度或對比度,或改變顏色,對hash值都不會太大的影響。最大的優勢:計算速度快!
這時候,比較兩個圖片的類似性,就是先計算這兩張圖片的hash指紋,也就是64位0或1值,而後計算不一樣位的個數(漢明距離)。若是這個值爲0,則表示這兩張圖片很是類似,若是漢明距離小於5,則表示有些不一樣,但比較相近,若是漢明距離大於10則代表徹底不一樣的圖片。
二、加強版:pHash
均值哈希雖然簡單,但受均值的影響很是大。例如對圖像進行伽馬校訂或直方圖均衡就會影響均值,從而影響最終的hash值。存在一個更健壯的算法叫pHash。它將均值的方法發揮到極致。使用離散餘弦變換(DCT)來獲取圖片的低頻成分。
離散餘弦變換(DCT)是種圖像壓縮算法,它將圖像從像素域變換到頻率域。而後通常圖像都存在不少冗餘和相關性的,因此轉換到頻率域以後,只有不多的一部分頻率份量的係數纔不爲0,大部分系數都爲0(或者說接近於0)。下圖的右圖是對lena圖進行離散餘弦變換(DCT)獲得的係數矩陣圖。從左上角依次到右下角,頻率愈來愈高,由圖能夠看到,左上角的值比較大,到右下角的值就很小很小了。換句話說,圖像的能量幾乎都集中在左上角這個地方的低頻係數上面了。
pHash的工做過程以下:
(1)縮小尺寸:pHash以小圖片開始,但圖片大於8*8,32*32是最好的。這樣作的目的是簡化了DCT的計算,而不是減少頻率。
(2)簡化色彩:將圖片轉化成灰度圖像,進一步簡化計算量。
(3)計算DCT:計算圖片的DCT變換,獲得32*32的DCT係數矩陣。
(4)縮小DCT:雖然DCT的結果是32*32大小的矩陣,但咱們只要保留左上角的8*8的矩陣,這部分呈現了圖片中的最低頻率。
(5)計算平均值:如同均值哈希同樣,計算DCT的均值。
(6)計算hash值:這是最主要的一步,根據8*8的DCT矩陣,設置0或1的64位的hash值,大於等於DCT均值的設爲」1」,小於DCT均值的設爲「0」。組合在一塊兒,就構成了一個64位的整數,這就是這張圖片的指紋。
結果並不能告訴咱們真實性的低頻率,只能粗略地告訴咱們相對於平均值頻率的相對比例。只要圖片的總體結構保持不變,hash結果值就不變。可以避免伽馬校訂或顏色直方圖被調整帶來的影響。
與均值哈希同樣,pHash一樣能夠用漢明距離來進行比較。(只須要比較每一位對應的位置並算計不一樣的位的個數)
2、基於感知哈希算法的視覺跟蹤
和前面說的那樣,對於感知哈希算法的視覺跟蹤,思想很簡單,咱們把要跟蹤的目標保存好,計算它的hash碼,而後在每一幀來臨的時候,咱們掃描整個圖像,計算每一個掃描窗口的hash碼,比較它和目標的hash碼的漢明距離,漢明距離距離最小的掃描窗口就是和目標最類似的,也就是該幀的目標所在位置。爲了加速,咱們只在上一幀目標的周圍圖像區域進行掃描。爲了適應目標的變化,咱們還須要在成功跟蹤後的每一幀更新咱們要跟蹤的目標。
當時看到這個東西的時候,感受很簡單,而後就花了點時間動手寫了下代碼,不知道代碼是否正確,若有錯誤,還望你們不吝指點。個人代碼是基於VS2010+ OpenCV2.4.2的。基礎的均值哈希和pHash都實現了,切換隻須要在改變代碼裏面跟蹤的那個函數的flag便可。代碼能夠讀入視頻,也能夠讀攝像頭,二者的選擇只須要在代碼中稍微修改便可。對於視頻來講,運行會先顯示第一幀,而後咱們用鼠標框選要跟蹤的目標,而後跟蹤器開始跟蹤每一幀。對攝像頭來講,就會一直採集圖像,而後咱們用鼠標框選要跟蹤的目標,接着跟蹤器開始跟蹤後面的每一幀。具體代碼以下:
hashTracker.cpp
[cpp] view plaincopy
// Object tracking algorithm using Hash or pHash code
// Author : zouxy
// Date : 2013-12-21
// HomePage : http://blog.csdn.net/zouxy09
// Email : zouxy09@qq.com
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
// Global variables
Rect box;
bool drawing_box = false;
bool gotBB = false;
// bounding box mouse callback
void mouseHandler(int event, int x, int y, int flags, void *param){
switch( event ){
case CV_EVENT_MOUSEMOVE:
if (drawing_box){
box.width = x-box.x;
box.height = y-box.y;
}
break;
case CV_EVENT_LBUTTONDOWN:
drawing_box = true;
box = Rect( x, y, 0, 0 );
break;
case CV_EVENT_LBUTTONUP:
drawing_box = false;
if( box.width < 0 ){
box.x += box.width;
box.width *= -1;
}
if( box.height < 0 ){
box.y += box.height;
box.height *= -1;
}
gotBB = true;
break;
}
}
// calculate the hash code of image
Mat calHashCode(Mat image)
{
resize(image, image, Size(8, 8));
Scalar imageMean = mean(image);
return (image > imageMean[0]);
}
// calculate the pHash code of image
Mat calPHashCode(Mat image)
{
Mat floatImage, imageDct;
resize(image, image, Size(32, 32));
image.convertTo(floatImage, CV_32FC1);
dct(floatImage, imageDct);
Rect roi(0, 0, 8, 8);
Scalar imageMean = mean(imageDct(roi));
return (imageDct(roi) > imageMean[0]);
}
// get hamming distance of two hash code
int calHammingDistance(Mat modelHashCode, Mat testHashCode)
{
return countNonZero(modelHashCode != testHashCode);
}
// tracker: get search patches around the last tracking box,
// and find the most similar one using hamming distance
void hashTrack(Mat frame, Mat &model, Rect &trackBox, int flag = 0)
{
Mat gray;
cvtColor(frame, gray, CV_RGB2GRAY);
Rect searchWindow;
searchWindow.width = trackBox.width * 3;
searchWindow.height = trackBox.height * 3;
searchWindow.x = trackBox.x + trackBox.width * 0.5 - searchWindow.width * 0.5;
searchWindow.y = trackBox.y + trackBox.height * 0.5 - searchWindow.height * 0.5;
searchWindow &= Rect(0, 0, frame.cols, frame.rows);
Mat modelHashCode, testHashCode;
if (flag)
modelHashCode = calHashCode(model);
else
modelHashCode = calPHashCode(model);
int step = 2;
int min = 1000;
Rect window = trackBox;
for (int i = 0; i * step < searchWindow.height - trackBox.height; i++)
{
window.y = searchWindow.y + i * step;
for (int j = 0; j * step < searchWindow.width - trackBox.width; j++)
{
window.x = searchWindow.x + j * step;
if (flag)
testHashCode = calHashCode(gray(window));
else
testHashCode = calPHashCode(gray(window));
int distance = calHammingDistance(modelHashCode, testHashCode);
if (distance < min)
{
trackBox = window;
min = distance;
}
}
}
model = gray(trackBox);
cout << "The min hanming distance is: " << min << endl;
}
int main(int argc, char * argv[])
{
VideoCapture capture;
// from video
capture.open("david.mpg");
bool fromfile = true;
// from camera
//capture.open(0);
//bool fromfile = false;
//Init camera
if (!capture.isOpened())
{
cout << "capture device failed to open!" << endl;
return -1;
}
//Register mouse callback to draw the bounding box
cvNamedWindow("hashTracker", CV_WINDOW_AUTOSIZE);
cvSetMouseCallback("hashTracker", mouseHandler, NULL );
Mat frame, model;
capture >> frame;
while(!gotBB)
{
if (!fromfile)
capture >> frame;
imshow("hashTracker", frame);
if (cvWaitKey(20) == 'q')
return 1;
}
//Remove callback
cvSetMouseCallback("hashTracker", NULL, NULL );
Mat gray;
cvtColor(frame, gray, CV_RGB2GRAY);
model = gray(box);
int frameCount = 0;
while (1)
{
capture >> frame;
if (frame.empty())
return -1;
double t = (double)cvGetTickCount();
frameCount++;
// tracking
hashTrack(frame, model, box, 0);
// show
stringstream buf;
buf << frameCount;
string num = buf.str();
putText(frame, num, Point(20, 30), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255), 3);
rectangle(frame, box, Scalar(0, 0, 255), 3);
imshow("hashTracker", frame);
t = (double)cvGetTickCount() - t;
cout << "cost time: " << t / ((double)cvGetTickFrequency()*1000.) << endl;
if ( cvWaitKey(1) == 27 )
break;
}
return 0;
}
3、實驗結果
咱們仍是和以前同樣,用在目標跟蹤領域一個benchmark的視頻-david來測試下代碼的效果。以下圖因此,每幀的幀號在左上角所示。這裏的初始框是我隨意畫的,因此你的結果和個人有可能不一樣。下圖的結果是使用pHash的,pHash比均值hash要好,但耗時也增長了很多。另外,個人代碼沒有通過優化的,寫着玩嘛,哈哈。
4、思考
看到這個算法的時候,第一個感受就是,這太簡單了吧,它真的有效嗎?像下圖左那樣,它的hash值的圖壓根就看不出是個什麼東西了,竟然還能作類似的匹配,並且必定狀況下,仍是挺有效的。
這種簡單的比較獲得0和1編碼還讓我想到了經典的LBP特徵,如上圖右,不一樣在於LBP是每一個像素點與鄰域比較,而hash是與整幅圖的均值比較。因此LBP能夠保存明暗這種過渡的邊緣,而hash保存的是圖像總體的精簡版的低頻份量。
這也讓人困惑在簡單與複雜的抉擇之間,它們的考量也非三言兩語能避之。也許算法之美必定程度上能從其簡單和有效處得以瞥見吧。
另外,我還特地檢索了一下,暫時尚未搜到基於感知哈希算法的視覺跟蹤,不知道會不會對你們有所啓發。(發論文的,求掛名哦,哈哈^-^)
5、參考文獻:
[1] Google 以圖搜圖 - 類似圖片搜索原理 -Java實現
[2] 看起來像它——圖像搜索其實也不難
[3] 類似圖片搜索的原理
[4] 最簡單的目標跟蹤(模版匹配)