關於『數位影像分析之智慧型監視系統』的Q&A - Part 4
作者:Mr. Lai Tai-Yu, Averill, 赖岱佑, 賴岱佑
標題:關於『數位影像分析之智慧型監視系統』的Q&A - Part 4
由於有網友對於數位影像演算法及智慧型監視系統有疑問,因此整理如下:
P.S.整理得很粗糙,不好意思,凡是來信有帳號名稱、人名、稱謂、單位名稱或地名,都不包含在內。小弟儘量仔細檢查,將可以放上來的問題公佈出來,以供其他讀者參考。但若您認為不方便將問題公開,煩請來信告知,有冒犯之處,還請多多見諒。我會儘速處理,謝謝各位,靜祝各位萬事如意、鴻圖大展、健康快樂、心想事成。感謝。
P.S.小弟的回答,僅供參考,還需要各位先進指導,謝謝。
Q1:目前在想~在時間背景完成前~到底該用什麼來當作背景?當時間背景完成後~有物件在上面不更新背景這樣就可以了~可是在時間背景完成前~若是一開始就有車停在上面~那該怎麼辦?一開始就有車在上面~時間背景就會錯誤~快速背景也沒辦法取得動量標記物件~也沒辦法儲存一個固定背景~請問還有其他計算背景的方法嗎??
A1:先使用快速分割法取出動量,然後有動量的部分不要更新為背景,如此就是漸進式背景法。
一開始若有車輛停在螢幕內,那變成背景是無法避免的,重點是當車輛移走時,要抓住車輛並且將原來車輛的位置更新為背景。
先想想把時間背景法、快速分割法、漸進式背景法結合在一起,這樣就可以大致解決您的問題。
Q2:那8位元灰階是要用甚麼方式寫
A2:以下這段程式碼是處理8位元深度的BMP檔寫入方式,供您參考。
BITMAPFILEHEADER*bmpFH=newBITMAPFILEHEADER;
BITMAPINFOHEADER*bmpIH=newBITMAPINFOHEADER;
RGBQUAD*rgbQuad=newRGBQUAD[256];
bmpFH->bfType=0x4d42;
bmpFH->bfReserved1=0;
bmpFH->bfReserved2=0;
bmpFH->bfSize=(DWORD)sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+(sizeof(RGBQUAD)*256)+
(ulSRangeWidth*ulSRangeHeight);
bmpFH->bfOffBits=(DWORD)sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+(sizeof(RGBQUAD)*256);
bmpIH->biSize=sizeof(BITMAPINFOHEADER);
bmpIH->biWidth=(LONG)(ulSRangeWidth);
bmpIH->biHeight=(LONG)(ulSRangeHeight);
bmpIH->biPlanes=1;
bmpIH->biBitCount=8;
bmpIH->biCompression=BI_RGB;
bmpIH->biSizeImage=(DWORD)(ulSRangeWidth*ulSRangeHeight);
bmpIH->biXPelsPerMeter=0;
bmpIH->biYPelsPerMeter=0;
bmpIH->biClrUsed=0;
bmpIH->biClrImportant=0;
for(intiI=0;iI<256;iI++)
{
rgbQuad[iI].rgbBlue=iI;
rgbQuad[iI].rgbGreen=iI;
rgbQuad[iI].rgbRed=iI;
rgbQuad[iI].rgbReserved=0;
}
charpcFileName[100];
sprintf_s(pcFileName,"C:\\[目錄]\\[檔名].bmp");
ulCountLPRIndex++;
FILE*pBMPFile=fopen(pcFileName,"wb");
if(pBMPFile)
{
fwrite(bmpFH,sizeof(BITMAPFILEHEADER),1,pBMPFile);
fwrite(bmpIH,sizeof(BITMAPINFOHEADER),1,pBMPFile);
fwrite(rgbQuad,sizeof(RGBQUAD)*256,1,pBMPFile);
fwrite(pbyBitmap_G8,ulSRangeWidth*ulSRangeHeight,1,pBMPFile);
fclose(pBMPFile);
}
SAFE_DELETE(bmpFH);
SAFE_DELETE(bmpIH);
SAFE_DELETE_ARRAY(rgbQuad);
SAFE_DELETE_ARRAY(pbyBitmap_G8);
Q3.目前正處於學習影像處理,
A3.很高興您對於數位影像處理有興趣。
Q4.我想嘗試寫即時視訊瞳孔追蹤的程式,我想了幾個步驟流程,先搜尋臉->再找到眼部->最後找到瞳孔位置,目前正試寫搜尋臉,我是以膚色偵測的方式來偵測臉部區塊,但有誤判雜點的情形,這部分應如何處理? 另外如何從膚色像素進一步來找出臉部區塊?
A4.在我的拙作『數位影像分析之智慧型監控系統』一書就提到膚色偵測法目前在學術界上仍是個爭議的問題,大家在探討何謂膚色,如何定義膚色,各問論文所提出的膚色都是經過樣本統計下的結果,而非一個完美的數學解來定義膚色。所以使用膚色本來就會有很多問題。
In Addition, 一般做瞳孔偵測,都是只針對瞳孔影像作分析,而且瞳孔位於臉部屬於較小範圍,沒有百萬像素以上等級,很容易將眉毛與眼睛部分誤判。這點可以參考華碩ASUS的LifeFrame產品,就有這樣的誤偵測。我的建議是要做瞳孔就針對瞳孔影像作分析,而不要從人臉開始。應該要反推回來,就是先把瞳孔分析做好,再退一步做到人臉定位瞳孔,這樣會比先偵測臉部再找瞳孔來的有效率。
Q5.在偵測人臉的研究中,有Haar like方式偵測人臉,但對這個方法不了解,比較膚色偵測的方法與Haar like,似乎是Haar like比較符合即時的效率來達到偵測人臉,想請教什麼是Haar like?
A5.Haar Like牽涉到影像積分,以及特徵建立,Haar Like的方法的確優於膚色偵測法,除了傳統的Haar Like慢速的積分及辨識,現在還有快速Haar Like積分及辨識法。最最簡單的概念就是計算灰度的特徵,進而找到符合人臉灰度特徵。但實際上還要更複雜一些,並非三言兩語就可以解釋清楚,因為Haar Like不斷被應用及進步,他還搭配其他分類器,使得辨識率及效能更高更快,例如:請搜尋關於adaboost haar like的關鍵字,您會有很多收穫。
Q6.還是老師有更好的方法及流程,懇請老師指導。
A6.我並沒有跟您詳談過,我也不知道您教授的研究方向,也不知道您的整個細節,因此無法有更多的建議,怕會誤導您的方向,導致您無法畢業。
我把我寫的人臉偵測程式寄給您一份做為參考,如附件:FaceDetectionRelease.rar,解壓縮後點選執行檔,就可以由Web Camera獲得影像,並將人臉框出。請參考。
Q7.我想對04-06_長條圖強化 書中範例改寫成開擋讀圖方式,我用MFC 精靈產程式碼 跟 書程式碼是不一樣的,HistogramEqualization.cpp ,我看長條圖強化程式碼主要是寫在 ,HistogramEqualizationDlg.cpp 是OK 那按鈕?
void CHistogramEqualizationDlg::OnBnClickedOk() // 點 OK 按鈕就 call 他嗎?
1. 想請問一下OK 按鈕// 請問你這檔是怎麼產生出來的?用資源建出來的嗎?
2. 要改成由開檔方式BMP 要怎麼要寫長條圖強化範例?
3. 在MFC 裡面有哪個物件可以開很多種圖檔格式? 例如 jpg ,gif , bmp都可以開?
A7.
>>我用MFC 精靈產程式碼 跟 書程式碼是不一樣的
那是因為你勾選的步驟與我不同。
>>我看長條圖強化程式碼主要是寫在 HistogramEqualizationDlg.cpp 是OK 那按鈕?
是的。
>> void CHistogramEqualizationDlg::OnBnClickedOk() // 點 OK 按鈕就 call 他嗎?
點了OK鈕就執行這個函式。
>>1. 想請問一下OK 按鈕// 請問你這檔是怎麼產生出來的?用資源建出來的嗎?
點選ViewResource View找到Dialog就可透過拖拉的方式將按鈕放上去。
>>2. 要改成由開檔方式BMP 要怎麼要寫長條圖強化範例?
使用CFileDialog就可以有你所謂開檔的功能。以下的Code給你參考。
CFileDialog fd( TRUE, NULL, NULL, OFN_EXPLORER, _T("Bitmap (*.bmp)|*.bmp\0"), NULL, 0 );
if( fd.DoModal() == IDOK )
{
CString cstrPathName = fd.GetPathName();
int intLengthPathName = cstrPathName.GetLength();
CHAR strPathName [ 1024 ];
memset ( &strPathName, 0x00, 1024 * sizeof(CHAR) );
for (int i = 0; i < intLengthPathName; ++i)
{
strPathName[i] = (CHAR)cstrPathName[i];
}
memcpy ( &Image1InPath, &strPathName, intLengthPathName );
GetDlgItem(IDC_editImageIn1)->SetWindowText(strPathName);
}
else
{
memset ( &Image1InPath, 0x00, MAX_PATH * sizeof(char) );
}
>>3. 在MFC 裡面有哪個物件可以開很多種圖檔格式? 例如 jpg ,gif , bmp
使用GDI+就可以開啟很多種格式。
A8. 請問用 windows Form 跟用 MFC 寫優缺點在哪?
Q8. 這是一個很多解釋的問題,從不同面向回答的答案都不同。
我猜測您關心的想法,嘗試做回答。請當參考。
您使用Windows Form就是在Net. Framework下寫程式。
NET. Framework是 Base Class Library,最主要的特色,就是架構在 CLR (Common Language Runtime )之上。
MFC 則是在 Windows早期為方便 C++ 程式設計師撰寫 Windows 應用程式,設計的C++ Class Library。
就效能來說,.NET Managed 應用程式和MFC效能的差別是在第一次執行時,Managed程式必須被CLR JIT Compiler編譯,所以比較慢,基本上小弟實驗的結果,編譯完成後就和MFC應用程式效能差不多了。
A9.我這兩天拜讀您寫的書。看到High pass或Low pass等Filter範例,範圍都是從1開始到width-1。然後去和3*3矩陣(Filter)做處理。最後將新值存在新的image上,然後輸出。但是0和width等,邊緣線似乎沒被處理到。是否有其他目的或疏漏之處,還望指教一二。
A9. 關於您的問題,這是因為在邊緣處沒有辦法取得3x3的資料,因此通常在處理時,有兩種方法:
第一種方法就是忽略最邊緣處,直接填上黑色(0),就如同本書的作法。
第二種作法就是處理到邊緣時,沒有資料的部分,直接以(0)代替。
您對於數位影像有興趣,因此我另外附上一些資料給您參考。這些資料與小弟拙作有關『數位影像處理技術手冊』與『數位影像分析之智慧型監視系統』。
Q10.由於專題想要帶與ip cam有關的應用(遠端機器人控制),在網站爬了許多文,但沒找到完整資訊,正巧看到您的大作,馬上就到博客來訂購您的書,由於本身興趣,工作相關及希望能深入了解及實作,可否冒昧的向您請求相關的程式碼及論文,謝謝!
A10,由於我不了解您的需求,是有關於數位影像處理或是視訊影像處理,還是機器人控制,所以我盡我所能把相關的程式原始碼寄給您,但由於我不了解機器人控制,在這方面我還得多多學習。若您的問題是關於數位影像或視訊處理,我相信我是可以幫得上忙,但我需要您再仔細描述需求,否則這方面的資料很多,會不知道您所需要的方向。
附件有五支程式原始碼,對於即時處理視訊影像有所幫助,但卻不知道是否是您所需要的資料。請先參考。
Q11. 關於不同位元深度的BMP檔讀寫
A11. 請參考這段Code。這段Code附有調色盤的部分,依照不同位元深度,自行修改,就可以讀任何位元深度的BMP圖檔了。
int iSaveRangeSX = 0;
int iSaveRangeEX = 0;
int iSaveRangeSY = 0;
int iSaveRangeEY = 0;
iSaveRangeSX = iRangeSX;
iSaveRangeEX = iRangeEX;
iSaveRangeSY = iRangeSY;
iSaveRangeEY = iRangeEY;
if ( ( iSaveRangeEX - iSaveRangeSX + 1 ) % 4 != 0 )
{
int iTempA = 0;
int iTempB = 0;
iTempA = ( ( iSaveRangeEX - iSaveRangeSX + 1 ) / 4 ) * 4;
iTempB = ( iSaveRangeEX - iSaveRangeSX + 1 ) - iTempA;
iSaveRangeEX -= iTempB;
iSaveRangeEX += 4;
}
int iSaveRangeWidth = 0;
int iSaveRangeHeight = 0;
iSaveRangeWidth = ( iSaveRangeEX - iSaveRangeSX + 1 );
iSaveRangeHeight = ( iSaveRangeEY - iSaveRangeSY + 1 );
BYTE* pbySaveTemp_G8 = new BYTE [ iSaveRangeWidth * iSaveRangeHeight * sizeof(BYTE) ];
memset ( pbySaveTemp_G8, 0x00, iSaveRangeWidth * iSaveRangeHeight * sizeof(BYTE) );
int iYS = 0;
int iXS = 0;
for ( int iYY = iSaveRangeSY; iYY <= iSaveRangeEY; iYY++ )
{
for ( int iXX = iSaveRangeSX; iXX <= iSaveRangeEX; iXX++ )
{
int iIndex = ( iYY * iMotionROI_Width ) + ( iXX );
int iIndexS = ( iYS * iSaveRangeWidth ) + iXS;
*( pbySaveTemp_G8 + iIndexS ) = *( pbyMarkObject_G8 + iIndex );
iXS++;
}
iXS = 0;
iYS++;
}
unsigned long ulSRangeHeight = ( iSaveRangeEY - iSaveRangeSY + 1 );
unsigned long ulSRangeWidth = ( iSaveRangeEX - iSaveRangeSX + 1 );
BYTE* pbyBitmap_G8 = new BYTE[ ulSRangeWidth * ulSRangeHeight * sizeof(BYTE) ];
memset ( pbyBitmap_G8, 0, ulSRangeWidth * ulSRangeHeight * sizeof(BYTE) );
unsigned long ulTempY = 0;
unsigned long ulTempX = 0;
for ( int iY = iSaveRangeHeight - 1; iY >= 0; iY-- )
{
for ( unsigned long ulX = 0; ulX < (unsigned long)iSaveRangeWidth; ulX++ )
{
unsigned long ulSourceIndex = ( ulX ) + ( (unsigned long)iY * iSaveRangeWidth );
unsigned long ulDestinationIndex = ( ulTempX ) + ( ulTempY * ulSRangeWidth );
*( pbyBitmap_G8 + ulDestinationIndex ) = *( pbySaveTemp_G8 + ulSourceIndex );
ulTempX++;
}
ulTempX = 0;
ulTempY++;
}
BITMAPFILEHEADER *bmpFH = new BITMAPFILEHEADER;
BITMAPINFOHEADER *bmpIH = new BITMAPINFOHEADER;
RGBQUAD *rgbQuad = new RGBQUAD[256];
bmpFH->bfType = 0x4d42;
bmpFH->bfReserved1 = 0;
bmpFH->bfReserved2 = 0;
bmpFH->bfSize = (DWORD) sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + ( sizeof(RGBQUAD) * 256 ) +
( ulSRangeWidth * ulSRangeHeight );
bmpFH->bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + ( sizeof(RGBQUAD) * 256 );
bmpIH->biSize = sizeof(BITMAPINFOHEADER);
bmpIH->biWidth = (LONG) ( ulSRangeWidth );
bmpIH->biHeight = (LONG) ( ulSRangeHeight );
bmpIH->biPlanes = 1;
bmpIH->biBitCount = 8;
bmpIH->biCompression = BI_RGB;
bmpIH->biSizeImage = (DWORD) ( ulSRangeWidth * ulSRangeHeight );
bmpIH->biXPelsPerMeter = 0;
bmpIH->biYPelsPerMeter = 0;
bmpIH->biClrUsed = 0;
bmpIH->biClrImportant = 0;
for ( int iI = 0; iI < 256; iI++ )
{
rgbQuad[iI].rgbBlue = iI;
rgbQuad[iI].rgbGreen = iI;
rgbQuad[iI].rgbRed = iI;
rgbQuad[iI].rgbReserved = 0;
}
char pcFileName[100] ;
sprintf_s( pcFileName, "C:\\LP\\LPR__%d__%d.bmp", lPixelChangeDistance, ulCountLPRIndex );
ulCountLPRIndex++;
FILE *pBMPFile = fopen( pcFileName, "wb" );
if( pBMPFile )
{
fwrite( bmpFH, sizeof(BITMAPFILEHEADER), 1, pBMPFile );
fwrite( bmpIH, sizeof(BITMAPINFOHEADER), 1, pBMPFile );
fwrite( rgbQuad, sizeof(RGBQUAD) * 256, 1, pBMPFile );
fwrite( pbyBitmap_G8, ulSRangeWidth * ulSRangeHeight, 1, pBMPFile );
fclose( pBMPFile );
}
SAFE_DELETE (bmpFH);
SAFE_DELETE (bmpIH);
SAFE_DELETE_ARRAY ( rgbQuad );
SAFE_DELETE_ARRAY ( pbyBitmap_G8 );
SAFE_DELETE_ARRAY ( pbySaveTemp_G8 );
Q12. 前您提到~有物件在上面時候~就不要更新時間背景~那應該要用哪一個物件提取的結果來參考呢?一個是影像減去時間背景(可是當時間背景還沒學完時物件資訊並不正確)另一個是快速背景法(可是這物件資訊並沒有很正確)另一個是影像減去預先儲存背景(這最好可是我沒辦法預先取得一個OK的背景)還是有另外的方法比較OK呢??
A12. 呵呵~您真的很用心喔,這就是關鍵。用教科書的背景法絕對不實用ㄉ,有沒有想過把三個背景加再一起呢?互補各背景法的不足呢?這個方向才對喔~。目前沒還有任何一種背景法可以解決所有問題。您真的很用心在做,思考一下吧,這三個要怎麼結合。
Q13. 目前在想~在時間背景完成前~到底該用什麼來當作背景?當時間背景完成後~有物件在上面不更新背景這樣就可以了~可是在時間背景完成前~若是一開始就有車停在上面~那該怎麼辦?一開始就有車在上面~時間背景就會錯誤~快速背景也沒辦法取得動量標記物件~也沒辦法儲存一個固定背景~ 請問還有其他計算背景的方法嗎??
A13. 先使用快速分割法取出動量,然後有動量的部分不要更新為背景,如此就是漸進式背景法。一開始若有車輛停在螢幕內,那變成背景是無法避免的,重點是當車輛移走時,要抓住車輛並且將原來車輛的位置更新為背景。先想想把時間背景法、快速分割法、漸進式背景法結合在一起,這樣就可以大致解決您的問題。
Q14. 請教您一下~關於背景~我有想到個方法~先用人工方式取得一張完成的時間背景~將這張完成的時間背景暫時當作程式一開始的時間背景~之後有物件在上面~就不更新時間背景~這樣的方法可以解決~如果有車停在上面很久~會變成背景的問題~而且背景依舊可以一直更新~不知道這個方法正確嗎?有沒有更好的其他方法?(因為還是一定要去現場利用人工取的一張的背景)這個方法不知道還有哪些我還沒想到的問題?或是有更棒的取得背景方式?
A14. 先用人工取得一張背景的話,那就需要清場,學術研究是可行的,但實際應用是不可行的。現在的重點不是車子變成背景,而是車子由背景離開時,能夠抓到車子,並且將車子移開的位置更新為背景。所以您的問題要換個角度思考,才會有解。嘗試漸進式背景法+時間背景法+快速分割法,您會有收穫的。
Q15. 這邊是依照您給的 VC 相似度 code 改的 Pxy 會大於1 這正確嗎 ??
Public Function HistogramSimilarity( _
ByRef Histogram1() As Integer, _
ByRef Histogram2() As Integer) As Double
Dim dbA As Double = 0, dbB As Double = 0
Dim dbC As Double = 0, dbP As Double = 0
For IntI As Integer ......
A15.
要做比較的話,第一筆資料要少於或小於第二筆資料,或者兩筆資料是相等長度。這樣才能正確比較。因此您可能第一筆資料大於或多於第二筆資料,所以才會產生大於1的情形,也就是超過100%。
不過由於您是使用長條圖,照理來說資料量是相當的,您所謂的大於1 ,有點奇怪。
試著將For IntI As Integer ......= 0 To 256 * 3
改成For IntI As Integer ......= 0 To 255 * 3
好嗎?
Q16. 請教您一下~假設相似度函式正確~利用相似度去與上一張Frame中的所有物件去比對~最像的就是上一個物件~但是最像不代表一定是耶~假設目前的 Frame 中有一個新的物體進入或是離開~那該怎麼辦呢?還有當物體離開或進入畫面一半~那這樣也沒辦法計算正確丫??將目前 Frame 中所有的物件去跟上一張所有 Frame中比對~假設從第一個物件開始比對~比對到與上一張第1個物件最像~第二個物件開始比對也發現與第1個物件最像~那怎麼辦呢?>"< 怎麼覺得越後面的問題越困難~都完全想不到一點頭緒~前面的問題還有辦法去找資源來學來處理~後面的都沒地方能學到如何解決!
A16. 先用相似度來做比較,這是比較簡單的做法。
當有新的物體進入,相似度就會很差,因此應該建立新物件資訊。
若有兩個物件相似度都很高,當然取最高相似度為主。
除了相似度之外,還有距離量測也是很簡單的做法。
當物體移動位置很接近時,也可以直接給予。......
Q17. 您好~又要請教您一下問題了~我目前遇到的問題是想要利用兩支以上同型號的webcam 同時拍攝BMP影像存入電腦的不同資料夾內~解析度也需要1600*1200由於我並沒有C語言的基礎所以這兩三個禮拜都在鬼打牆= =||~希望賴老師能給些建議與指導~謝謝
A17. 我了解您沒有C語言基礎要做影像處理的難處,因此我提供以下的程式碼,藉由OpenCV的幫助,您只需要寫以下不到20行的程式,就可以做到雙Camera的影像擷取了。嘗試看看吧。
#include <cv.h>
#include <highgui.h>
int main()
{
CvCapture *capture1,*capture2;
IplImage *frame1,*frame2;
capture1 =cvCaptureFromCAM(0);
capture2 =cvCaptureFromCAM(1);
cvNamedWindow("C1",1);
cvNamedWindow("C2",1);
while(true)
{
frame1 = cvQueryFrame(capture1);
frame2 = cvQueryFrame(capture2);
cvShowImage("C1",frame1);
cvShowImage("C2",frame2);
if(cvWaitKey(10)>=0)
break;
}
}
標題:關於『數位影像分析之智慧型監視系統』的Q&A - Part 4
由於有網友對於數位影像演算法及智慧型監視系統有疑問,因此整理如下:
P.S.整理得很粗糙,不好意思,凡是來信有帳號名稱、人名、稱謂、單位名稱或地名,都不包含在內。小弟儘量仔細檢查,將可以放上來的問題公佈出來,以供其他讀者參考。但若您認為不方便將問題公開,煩請來信告知,有冒犯之處,還請多多見諒。我會儘速處理,謝謝各位,靜祝各位萬事如意、鴻圖大展、健康快樂、心想事成。感謝。
P.S.小弟的回答,僅供參考,還需要各位先進指導,謝謝。
Q1:目前在想~在時間背景完成前~到底該用什麼來當作背景?當時間背景完成後~有物件在上面不更新背景這樣就可以了~可是在時間背景完成前~若是一開始就有車停在上面~那該怎麼辦?一開始就有車在上面~時間背景就會錯誤~快速背景也沒辦法取得動量標記物件~也沒辦法儲存一個固定背景~請問還有其他計算背景的方法嗎??
A1:先使用快速分割法取出動量,然後有動量的部分不要更新為背景,如此就是漸進式背景法。
一開始若有車輛停在螢幕內,那變成背景是無法避免的,重點是當車輛移走時,要抓住車輛並且將原來車輛的位置更新為背景。
先想想把時間背景法、快速分割法、漸進式背景法結合在一起,這樣就可以大致解決您的問題。
Q2:那8位元灰階是要用甚麼方式寫
A2:以下這段程式碼是處理8位元深度的BMP檔寫入方式,供您參考。
BITMAPFILEHEADER*bmpFH=newBITMAPFILEHEADER;
BITMAPINFOHEADER*bmpIH=newBITMAPINFOHEADER;
RGBQUAD*rgbQuad=newRGBQUAD[256];
bmpFH->bfType=0x4d42;
bmpFH->bfReserved1=0;
bmpFH->bfReserved2=0;
bmpFH->bfSize=(DWORD)sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+(sizeof(RGBQUAD)*256)+
(ulSRangeWidth*ulSRangeHeight);
bmpFH->bfOffBits=(DWORD)sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+(sizeof(RGBQUAD)*256);
bmpIH->biSize=sizeof(BITMAPINFOHEADER);
bmpIH->biWidth=(LONG)(ulSRangeWidth);
bmpIH->biHeight=(LONG)(ulSRangeHeight);
bmpIH->biPlanes=1;
bmpIH->biBitCount=8;
bmpIH->biCompression=BI_RGB;
bmpIH->biSizeImage=(DWORD)(ulSRangeWidth*ulSRangeHeight);
bmpIH->biXPelsPerMeter=0;
bmpIH->biYPelsPerMeter=0;
bmpIH->biClrUsed=0;
bmpIH->biClrImportant=0;
for(intiI=0;iI<256;iI++)
{
rgbQuad[iI].rgbBlue=iI;
rgbQuad[iI].rgbGreen=iI;
rgbQuad[iI].rgbRed=iI;
rgbQuad[iI].rgbReserved=0;
}
charpcFileName[100];
sprintf_s(pcFileName,"C:\\[目錄]\\[檔名].bmp");
ulCountLPRIndex++;
FILE*pBMPFile=fopen(pcFileName,"wb");
if(pBMPFile)
{
fwrite(bmpFH,sizeof(BITMAPFILEHEADER),1,pBMPFile);
fwrite(bmpIH,sizeof(BITMAPINFOHEADER),1,pBMPFile);
fwrite(rgbQuad,sizeof(RGBQUAD)*256,1,pBMPFile);
fwrite(pbyBitmap_G8,ulSRangeWidth*ulSRangeHeight,1,pBMPFile);
fclose(pBMPFile);
}
SAFE_DELETE(bmpFH);
SAFE_DELETE(bmpIH);
SAFE_DELETE_ARRAY(rgbQuad);
SAFE_DELETE_ARRAY(pbyBitmap_G8);
Q3.目前正處於學習影像處理,
A3.很高興您對於數位影像處理有興趣。
Q4.我想嘗試寫即時視訊瞳孔追蹤的程式,我想了幾個步驟流程,先搜尋臉->再找到眼部->最後找到瞳孔位置,目前正試寫搜尋臉,我是以膚色偵測的方式來偵測臉部區塊,但有誤判雜點的情形,這部分應如何處理? 另外如何從膚色像素進一步來找出臉部區塊?
A4.在我的拙作『數位影像分析之智慧型監控系統』一書就提到膚色偵測法目前在學術界上仍是個爭議的問題,大家在探討何謂膚色,如何定義膚色,各問論文所提出的膚色都是經過樣本統計下的結果,而非一個完美的數學解來定義膚色。所以使用膚色本來就會有很多問題。
In Addition, 一般做瞳孔偵測,都是只針對瞳孔影像作分析,而且瞳孔位於臉部屬於較小範圍,沒有百萬像素以上等級,很容易將眉毛與眼睛部分誤判。這點可以參考華碩ASUS的LifeFrame產品,就有這樣的誤偵測。我的建議是要做瞳孔就針對瞳孔影像作分析,而不要從人臉開始。應該要反推回來,就是先把瞳孔分析做好,再退一步做到人臉定位瞳孔,這樣會比先偵測臉部再找瞳孔來的有效率。
Q5.在偵測人臉的研究中,有Haar like方式偵測人臉,但對這個方法不了解,比較膚色偵測的方法與Haar like,似乎是Haar like比較符合即時的效率來達到偵測人臉,想請教什麼是Haar like?
A5.Haar Like牽涉到影像積分,以及特徵建立,Haar Like的方法的確優於膚色偵測法,除了傳統的Haar Like慢速的積分及辨識,現在還有快速Haar Like積分及辨識法。最最簡單的概念就是計算灰度的特徵,進而找到符合人臉灰度特徵。但實際上還要更複雜一些,並非三言兩語就可以解釋清楚,因為Haar Like不斷被應用及進步,他還搭配其他分類器,使得辨識率及效能更高更快,例如:請搜尋關於adaboost haar like的關鍵字,您會有很多收穫。
Q6.還是老師有更好的方法及流程,懇請老師指導。
A6.我並沒有跟您詳談過,我也不知道您教授的研究方向,也不知道您的整個細節,因此無法有更多的建議,怕會誤導您的方向,導致您無法畢業。
我把我寫的人臉偵測程式寄給您一份做為參考,如附件:FaceDetectionRelease.rar,解壓縮後點選執行檔,就可以由Web Camera獲得影像,並將人臉框出。請參考。
Q7.我想對04-06_長條圖強化 書中範例改寫成開擋讀圖方式,我用MFC 精靈產程式碼 跟 書程式碼是不一樣的,HistogramEqualization.cpp ,我看長條圖強化程式碼主要是寫在 ,HistogramEqualizationDlg.cpp 是OK 那按鈕?
void CHistogramEqualizationDlg::OnBnClickedOk() // 點 OK 按鈕就 call 他嗎?
1. 想請問一下OK 按鈕// 請問你這檔是怎麼產生出來的?用資源建出來的嗎?
2. 要改成由開檔方式BMP 要怎麼要寫長條圖強化範例?
3. 在MFC 裡面有哪個物件可以開很多種圖檔格式? 例如 jpg ,gif , bmp都可以開?
A7.
>>我用MFC 精靈產程式碼 跟 書程式碼是不一樣的
那是因為你勾選的步驟與我不同。
>>我看長條圖強化程式碼主要是寫在 HistogramEqualizationDlg.cpp 是OK 那按鈕?
是的。
>> void CHistogramEqualizationDlg::OnBnClickedOk() // 點 OK 按鈕就 call 他嗎?
點了OK鈕就執行這個函式。
>>1. 想請問一下OK 按鈕// 請問你這檔是怎麼產生出來的?用資源建出來的嗎?
點選ViewResource View找到Dialog就可透過拖拉的方式將按鈕放上去。
>>2. 要改成由開檔方式BMP 要怎麼要寫長條圖強化範例?
使用CFileDialog就可以有你所謂開檔的功能。以下的Code給你參考。
CFileDialog fd( TRUE, NULL, NULL, OFN_EXPLORER, _T("Bitmap (*.bmp)|*.bmp\0"), NULL, 0 );
if( fd.DoModal() == IDOK )
{
CString cstrPathName = fd.GetPathName();
int intLengthPathName = cstrPathName.GetLength();
CHAR strPathName [ 1024 ];
memset ( &strPathName, 0x00, 1024 * sizeof(CHAR) );
for (int i = 0; i < intLengthPathName; ++i)
{
strPathName[i] = (CHAR)cstrPathName[i];
}
memcpy ( &Image1InPath, &strPathName, intLengthPathName );
GetDlgItem(IDC_editImageIn1)->SetWindowText(strPathName);
}
else
{
memset ( &Image1InPath, 0x00, MAX_PATH * sizeof(char) );
}
>>3. 在MFC 裡面有哪個物件可以開很多種圖檔格式? 例如 jpg ,gif , bmp
使用GDI+就可以開啟很多種格式。
A8. 請問用 windows Form 跟用 MFC 寫優缺點在哪?
Q8. 這是一個很多解釋的問題,從不同面向回答的答案都不同。
我猜測您關心的想法,嘗試做回答。請當參考。
您使用Windows Form就是在Net. Framework下寫程式。
NET. Framework是 Base Class Library,最主要的特色,就是架構在 CLR (Common Language Runtime )之上。
MFC 則是在 Windows早期為方便 C++ 程式設計師撰寫 Windows 應用程式,設計的C++ Class Library。
就效能來說,.NET Managed 應用程式和MFC效能的差別是在第一次執行時,Managed程式必須被CLR JIT Compiler編譯,所以比較慢,基本上小弟實驗的結果,編譯完成後就和MFC應用程式效能差不多了。
A9.我這兩天拜讀您寫的書。看到High pass或Low pass等Filter範例,範圍都是從1開始到width-1。然後去和3*3矩陣(Filter)做處理。最後將新值存在新的image上,然後輸出。但是0和width等,邊緣線似乎沒被處理到。是否有其他目的或疏漏之處,還望指教一二。
A9. 關於您的問題,這是因為在邊緣處沒有辦法取得3x3的資料,因此通常在處理時,有兩種方法:
第一種方法就是忽略最邊緣處,直接填上黑色(0),就如同本書的作法。
第二種作法就是處理到邊緣時,沒有資料的部分,直接以(0)代替。
您對於數位影像有興趣,因此我另外附上一些資料給您參考。這些資料與小弟拙作有關『數位影像處理技術手冊』與『數位影像分析之智慧型監視系統』。
Q10.由於專題想要帶與ip cam有關的應用(遠端機器人控制),在網站爬了許多文,但沒找到完整資訊,正巧看到您的大作,馬上就到博客來訂購您的書,由於本身興趣,工作相關及希望能深入了解及實作,可否冒昧的向您請求相關的程式碼及論文,謝謝!
A10,由於我不了解您的需求,是有關於數位影像處理或是視訊影像處理,還是機器人控制,所以我盡我所能把相關的程式原始碼寄給您,但由於我不了解機器人控制,在這方面我還得多多學習。若您的問題是關於數位影像或視訊處理,我相信我是可以幫得上忙,但我需要您再仔細描述需求,否則這方面的資料很多,會不知道您所需要的方向。
附件有五支程式原始碼,對於即時處理視訊影像有所幫助,但卻不知道是否是您所需要的資料。請先參考。
Q11. 關於不同位元深度的BMP檔讀寫
A11. 請參考這段Code。這段Code附有調色盤的部分,依照不同位元深度,自行修改,就可以讀任何位元深度的BMP圖檔了。
int iSaveRangeSX = 0;
int iSaveRangeEX = 0;
int iSaveRangeSY = 0;
int iSaveRangeEY = 0;
iSaveRangeSX = iRangeSX;
iSaveRangeEX = iRangeEX;
iSaveRangeSY = iRangeSY;
iSaveRangeEY = iRangeEY;
if ( ( iSaveRangeEX - iSaveRangeSX + 1 ) % 4 != 0 )
{
int iTempA = 0;
int iTempB = 0;
iTempA = ( ( iSaveRangeEX - iSaveRangeSX + 1 ) / 4 ) * 4;
iTempB = ( iSaveRangeEX - iSaveRangeSX + 1 ) - iTempA;
iSaveRangeEX -= iTempB;
iSaveRangeEX += 4;
}
int iSaveRangeWidth = 0;
int iSaveRangeHeight = 0;
iSaveRangeWidth = ( iSaveRangeEX - iSaveRangeSX + 1 );
iSaveRangeHeight = ( iSaveRangeEY - iSaveRangeSY + 1 );
BYTE* pbySaveTemp_G8 = new BYTE [ iSaveRangeWidth * iSaveRangeHeight * sizeof(BYTE) ];
memset ( pbySaveTemp_G8, 0x00, iSaveRangeWidth * iSaveRangeHeight * sizeof(BYTE) );
int iYS = 0;
int iXS = 0;
for ( int iYY = iSaveRangeSY; iYY <= iSaveRangeEY; iYY++ )
{
for ( int iXX = iSaveRangeSX; iXX <= iSaveRangeEX; iXX++ )
{
int iIndex = ( iYY * iMotionROI_Width ) + ( iXX );
int iIndexS = ( iYS * iSaveRangeWidth ) + iXS;
*( pbySaveTemp_G8 + iIndexS ) = *( pbyMarkObject_G8 + iIndex );
iXS++;
}
iXS = 0;
iYS++;
}
unsigned long ulSRangeHeight = ( iSaveRangeEY - iSaveRangeSY + 1 );
unsigned long ulSRangeWidth = ( iSaveRangeEX - iSaveRangeSX + 1 );
BYTE* pbyBitmap_G8 = new BYTE[ ulSRangeWidth * ulSRangeHeight * sizeof(BYTE) ];
memset ( pbyBitmap_G8, 0, ulSRangeWidth * ulSRangeHeight * sizeof(BYTE) );
unsigned long ulTempY = 0;
unsigned long ulTempX = 0;
for ( int iY = iSaveRangeHeight - 1; iY >= 0; iY-- )
{
for ( unsigned long ulX = 0; ulX < (unsigned long)iSaveRangeWidth; ulX++ )
{
unsigned long ulSourceIndex = ( ulX ) + ( (unsigned long)iY * iSaveRangeWidth );
unsigned long ulDestinationIndex = ( ulTempX ) + ( ulTempY * ulSRangeWidth );
*( pbyBitmap_G8 + ulDestinationIndex ) = *( pbySaveTemp_G8 + ulSourceIndex );
ulTempX++;
}
ulTempX = 0;
ulTempY++;
}
BITMAPFILEHEADER *bmpFH = new BITMAPFILEHEADER;
BITMAPINFOHEADER *bmpIH = new BITMAPINFOHEADER;
RGBQUAD *rgbQuad = new RGBQUAD[256];
bmpFH->bfType = 0x4d42;
bmpFH->bfReserved1 = 0;
bmpFH->bfReserved2 = 0;
bmpFH->bfSize = (DWORD) sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + ( sizeof(RGBQUAD) * 256 ) +
( ulSRangeWidth * ulSRangeHeight );
bmpFH->bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + ( sizeof(RGBQUAD) * 256 );
bmpIH->biSize = sizeof(BITMAPINFOHEADER);
bmpIH->biWidth = (LONG) ( ulSRangeWidth );
bmpIH->biHeight = (LONG) ( ulSRangeHeight );
bmpIH->biPlanes = 1;
bmpIH->biBitCount = 8;
bmpIH->biCompression = BI_RGB;
bmpIH->biSizeImage = (DWORD) ( ulSRangeWidth * ulSRangeHeight );
bmpIH->biXPelsPerMeter = 0;
bmpIH->biYPelsPerMeter = 0;
bmpIH->biClrUsed = 0;
bmpIH->biClrImportant = 0;
for ( int iI = 0; iI < 256; iI++ )
{
rgbQuad[iI].rgbBlue = iI;
rgbQuad[iI].rgbGreen = iI;
rgbQuad[iI].rgbRed = iI;
rgbQuad[iI].rgbReserved = 0;
}
char pcFileName[100] ;
sprintf_s( pcFileName, "C:\\LP\\LPR__%d__%d.bmp", lPixelChangeDistance, ulCountLPRIndex );
ulCountLPRIndex++;
FILE *pBMPFile = fopen( pcFileName, "wb" );
if( pBMPFile )
{
fwrite( bmpFH, sizeof(BITMAPFILEHEADER), 1, pBMPFile );
fwrite( bmpIH, sizeof(BITMAPINFOHEADER), 1, pBMPFile );
fwrite( rgbQuad, sizeof(RGBQUAD) * 256, 1, pBMPFile );
fwrite( pbyBitmap_G8, ulSRangeWidth * ulSRangeHeight, 1, pBMPFile );
fclose( pBMPFile );
}
SAFE_DELETE (bmpFH);
SAFE_DELETE (bmpIH);
SAFE_DELETE_ARRAY ( rgbQuad );
SAFE_DELETE_ARRAY ( pbyBitmap_G8 );
SAFE_DELETE_ARRAY ( pbySaveTemp_G8 );
Q12. 前您提到~有物件在上面時候~就不要更新時間背景~那應該要用哪一個物件提取的結果來參考呢?一個是影像減去時間背景(可是當時間背景還沒學完時物件資訊並不正確)另一個是快速背景法(可是這物件資訊並沒有很正確)另一個是影像減去預先儲存背景(這最好可是我沒辦法預先取得一個OK的背景)還是有另外的方法比較OK呢??
A12. 呵呵~您真的很用心喔,這就是關鍵。用教科書的背景法絕對不實用ㄉ,有沒有想過把三個背景加再一起呢?互補各背景法的不足呢?這個方向才對喔~。目前沒還有任何一種背景法可以解決所有問題。您真的很用心在做,思考一下吧,這三個要怎麼結合。
Q13. 目前在想~在時間背景完成前~到底該用什麼來當作背景?當時間背景完成後~有物件在上面不更新背景這樣就可以了~可是在時間背景完成前~若是一開始就有車停在上面~那該怎麼辦?一開始就有車在上面~時間背景就會錯誤~快速背景也沒辦法取得動量標記物件~也沒辦法儲存一個固定背景~ 請問還有其他計算背景的方法嗎??
A13. 先使用快速分割法取出動量,然後有動量的部分不要更新為背景,如此就是漸進式背景法。一開始若有車輛停在螢幕內,那變成背景是無法避免的,重點是當車輛移走時,要抓住車輛並且將原來車輛的位置更新為背景。先想想把時間背景法、快速分割法、漸進式背景法結合在一起,這樣就可以大致解決您的問題。
Q14. 請教您一下~關於背景~我有想到個方法~先用人工方式取得一張完成的時間背景~將這張完成的時間背景暫時當作程式一開始的時間背景~之後有物件在上面~就不更新時間背景~這樣的方法可以解決~如果有車停在上面很久~會變成背景的問題~而且背景依舊可以一直更新~不知道這個方法正確嗎?有沒有更好的其他方法?(因為還是一定要去現場利用人工取的一張的背景)這個方法不知道還有哪些我還沒想到的問題?或是有更棒的取得背景方式?
A14. 先用人工取得一張背景的話,那就需要清場,學術研究是可行的,但實際應用是不可行的。現在的重點不是車子變成背景,而是車子由背景離開時,能夠抓到車子,並且將車子移開的位置更新為背景。所以您的問題要換個角度思考,才會有解。嘗試漸進式背景法+時間背景法+快速分割法,您會有收穫的。
Q15. 這邊是依照您給的 VC 相似度 code 改的 Pxy 會大於1 這正確嗎 ??
Public Function HistogramSimilarity( _
ByRef Histogram1() As Integer, _
ByRef Histogram2() As Integer) As Double
Dim dbA As Double = 0, dbB As Double = 0
Dim dbC As Double = 0, dbP As Double = 0
For IntI As Integer ......
A15.
要做比較的話,第一筆資料要少於或小於第二筆資料,或者兩筆資料是相等長度。這樣才能正確比較。因此您可能第一筆資料大於或多於第二筆資料,所以才會產生大於1的情形,也就是超過100%。
不過由於您是使用長條圖,照理來說資料量是相當的,您所謂的大於1 ,有點奇怪。
試著將For IntI As Integer ......= 0 To 256 * 3
改成For IntI As Integer ......= 0 To 255 * 3
好嗎?
Q16. 請教您一下~假設相似度函式正確~利用相似度去與上一張Frame中的所有物件去比對~最像的就是上一個物件~但是最像不代表一定是耶~假設目前的 Frame 中有一個新的物體進入或是離開~那該怎麼辦呢?還有當物體離開或進入畫面一半~那這樣也沒辦法計算正確丫??將目前 Frame 中所有的物件去跟上一張所有 Frame中比對~假設從第一個物件開始比對~比對到與上一張第1個物件最像~第二個物件開始比對也發現與第1個物件最像~那怎麼辦呢?>"< 怎麼覺得越後面的問題越困難~都完全想不到一點頭緒~前面的問題還有辦法去找資源來學來處理~後面的都沒地方能學到如何解決!
A16. 先用相似度來做比較,這是比較簡單的做法。
當有新的物體進入,相似度就會很差,因此應該建立新物件資訊。
若有兩個物件相似度都很高,當然取最高相似度為主。
除了相似度之外,還有距離量測也是很簡單的做法。
當物體移動位置很接近時,也可以直接給予。......
Q17. 您好~又要請教您一下問題了~我目前遇到的問題是想要利用兩支以上同型號的webcam 同時拍攝BMP影像存入電腦的不同資料夾內~解析度也需要1600*1200由於我並沒有C語言的基礎所以這兩三個禮拜都在鬼打牆= =||~希望賴老師能給些建議與指導~謝謝
A17. 我了解您沒有C語言基礎要做影像處理的難處,因此我提供以下的程式碼,藉由OpenCV的幫助,您只需要寫以下不到20行的程式,就可以做到雙Camera的影像擷取了。嘗試看看吧。
#include <cv.h>
#include <highgui.h>
int main()
{
CvCapture *capture1,*capture2;
IplImage *frame1,*frame2;
capture1 =cvCaptureFromCAM(0);
capture2 =cvCaptureFromCAM(1);
cvNamedWindow("C1",1);
cvNamedWindow("C2",1);
while(true)
{
frame1 = cvQueryFrame(capture1);
frame2 = cvQueryFrame(capture2);
cvShowImage("C1",frame1);
cvShowImage("C2",frame2);
if(cvWaitKey(10)>=0)
break;
}
}