上一篇實現了在MFC的窗體內顯示圖片,本篇介紹如何在MFC窗體內實時顯示攝像頭的影像。
要實現的功能是點擊一個“開始”按鈕,可以顯示影像,再點擊“停止”按鈕,可以停止顯示。
因為實時顯示影像需要在一個循環里執行,為了在顯示影像的同時還可以干別的(比如,點擊“停止”按鈕),這里需要用到多線程,即顯示影像的代碼放到子線程中,與主線程並發執行。
重點已經說清楚了,下面是開發步驟:
1、先把Halcon中實時顯示的程序搞定
2、Halcon代碼導出為C++代碼
3、建立MFC工程
4、在MFC中添加Halcon代碼
下面說細節:
1、打開Halcon,點擊助手,選擇打開新的Acquisition
從資源選項卡可以看到檢測到的接口為DirectShow,這是微軟開發的視頻設備驅動。
從連接選項卡能看到檢測到相機,是筆記本自帶的攝像頭。點擊上方的攝像機圖標,Halcon的圖形窗口就開始實時顯示攝像頭的畫面了,很方便。
下面點擊“代碼生成”選項卡,點擊“插入代碼”按鈕,就把實時顯示的代碼插入到代碼窗口中了。
注意這里的采集模式是異步采集,在循環中采集圖像的意思就是實時顯示。
生成的Halcon代碼如下:
* Image Acquisition 01: Code generated by Image Acquisition 01 open_framegrabber ('DirectShow', 1, 1, 0, 0, 0, 0, 'default', 8, 'rgb', -1, 'false', 'default', '[0] Integrated Camera', 0, -1, AcqHandle) grab_image_start (AcqHandle, -1) while (true) grab_image_async (Image, AcqHandle, -1) * Image Acquisition 01: Do something endwhile close_framegrabber (AcqHandle)
2、導出Halcon代碼
導出的C++代碼中Action函數如下:
void action() { // Local iconic variables HObject ho_Image; // Local control variables HTuple hv_AcqHandle; //Image Acquisition 01: Code generated by Image Acquisition 01 OpenFramegrabber("DirectShow", 1, 1, 0, 0, 0, 0, "default", 8, "rgb", -1, "false", "default", "[0] Integrated Camera", 0, -1, &hv_AcqHandle); GrabImageStart(hv_AcqHandle, -1); while (0 != 1) { GrabImageAsync(&ho_Image, hv_AcqHandle, -1); //Image Acquisition 01: Do something } CloseFramegrabber(hv_AcqHandle); }
這就是我們需要添加到MFC的代碼,需要注意到while循環中只是獲取了圖像,並沒有顯示圖像,所以我們還要添加顯示圖像的代碼。
3、建立MFC工程
與上一篇類似,新建基於對話框的MFC項目,添加Picture Control 和兩個按鈕。
4、添加C++代碼
首先打開對話框類的頭文件HalconCameraDlg.h,需要做下面四件事:
1、在文件開頭添加Halcon頭文件以及命名空間
#include "halconcpp.h"
using namespace HalconCpp;
2、在類外添加線程函數的聲明
//線程函數的聲明應在類CMultiThread1Dlg的外部
void ThreadFunc(LPVOID lpParam);
3、在類內添加Halcon變量為對話框類的Public成員
HObject ho_Image;
HTuple hv_AcqHandle;
HTuple m_HWindowID;
HTuple m_FGHandle,m_ImageWidth,m_ImageHeight;
4、添加線程函數的變量為對話框類的Protected成員
HANDLE hThread;
DWORD ThreadID;
然后在HalconCameraDlg.cpp中添加代碼:
1、首先添加Halcon頭文件和命名空間,並定義全局變量
volatile BOOL m_bRun;
volatile BOOL m_bShowFlag;
Volatile關鍵詞告訴編譯器不對此變量進行優化,使該值可被多個線程修改,對於多線程意義重大。
2、為開始按鈕添加單機響應函數
CWnd * pWnd = AfxGetApp()->GetMainWnd(); if(m_bShowFlag){ m_bRun=TRUE; }else{ hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ThreadFunc, // this, //傳入主窗口指針 0, &ThreadID ); }
為了能隨時停止和開始實時監控,我設置了m_bShowFlag這個變量,第一次點擊“開始”按鈕時,m_bShowFlag為FALSE,執行CreateThread函數啟動子線程,在子線程中m_bShowFlag被置為TRUE,所以下次點擊“開始”按鈕時不會再次開啟子線程,而只是修改線程中的標志位來啟動實時監控。
3、子線程函數的實現代碼
void ThreadFunc(LPVOID pParam) { CHalconCameraDlg * pMainWindow; pMainWindow=(CHalconCameraDlg * ) pParam; //強制轉化為主窗口指針 HTuple HWindowID; CRect Rect; CWnd * pWnd = pMainWindow->GetDlgItem( IDC_STATIC); HWindowID = (Hlong)pWnd->m_hWnd; pWnd->GetWindowRect(&Rect); OpenWindow(0,0,Rect.Width(),Rect.Height(),HWindowID,"visible","",&(pMainWindow->m_HWindowID) ); //顯示相機捕捉的圖像 OpenFramegrabber("DirectShow", 1, 1, 0, 0, 0, 0, "default", 8, "rgb", -1, "false", "default", "[0] Integrated Camera", 0, -1, &(pMainWindow->hv_AcqHandle) ); GrabImageStart(pMainWindow->hv_AcqHandle, -1); ClearWindow(pMainWindow->m_HWindowID); GrabImage(&(pMainWindow->ho_Image), pMainWindow->hv_AcqHandle); GetImagePointer1((pMainWindow->ho_Image),NULL,NULL,&(pMainWindow->m_ImageWidth),&(pMainWindow->m_ImageHeight) ); SetPart(pMainWindow->m_HWindowID,0,0,pMainWindow->m_ImageHeight-1,pMainWindow->m_ImageWidth-1); m_bShowFlag=TRUE;//設置運行狀態 m_bRun=TRUE; while (m_bShowFlag){ if(m_bRun){ GrabImageAsync(&(pMainWindow->ho_Image), pMainWindow->hv_AcqHandle, -1); DispObj(pMainWindow->ho_Image, pMainWindow->m_HWindowID); Sleep(50); } } ClearWindow(pMainWindow->m_HWindowID); CloseFramegrabber(pMainWindow->hv_AcqHandle); CloseWindow(pMainWindow->m_HWindowID); ExitThread(0); }
while循環之前的代碼與上一篇類似,循環中當m_bRun為TRUE時執行獲取與顯示圖像的語句,因此當全局變量m_bRun被置為FALSE時顯示會停止,實現了前述的功能(注意,此時線程並不退出)。
4、停止按鈕的響應函數
只需要一句話就夠了。因為m_bRun被聲明為volatile變量,在子線程外部可以更改它,修改為FALSE之后子線程中實時顯示的語句就無法執行,表現出來就是圖像靜止,不再更新。
m_bRun=FALSE;
遇到的問題:
在這個程序中,子線程一直沒有退出,即m_bShowFlag沒有被置為FLASE。
之前我試過在停止按鈕里把m_bShowFlag置為FALSE,即讓線程退出,然后再次點擊開始按鈕時重新啟動線程,但是在關閉窗口時會出現下面的錯誤。
觸發了一個斷點。其原因可能是堆被損壞。原因也可能是用戶在HalconCamera.exe具有焦點時按下了F12。
這個錯誤可能是退出線程時沒有把空間釋放干凈所致,在多次的開啟與關閉子線程(即多次點擊開始和停止按鈕)后,就會出現問題。
所以只能改為現在的線程不退出方案,讓子線程一直執行,通過修改其中的標志位來啟動和停止顯示。