原文作者:aircraft
原文鏈接:https://www.cnblogs.com/DOMLX/p/12111102.html
入門不久的人可以通過opencv實戰來鍛煉一下學習opencv的成果,百度雲鏈接:
鏈接:https://pan.baidu.com/s/1jGOD97Zx96ZDAvlkQtaPYQ
提取碼:afip
運行環境VS2017,需要配置庫為:opencv
題目:樣本采集小工具

需求:
用MFC和opencv完成樣本采集小工具。
界面功能
1、選中原圖片集的目錄。
2、選擇當前是正樣本還是負樣本?並選中其目錄。
3、通過上一張下一張更換原圖片集的圖片顯示。
鼠標點擊圖片顯示區域功能
1、左擊圖片選中,以鼠標點擊處為中心,寬W*高H的區域。
2、鼠標滾輪上滾擴大選中區域。
3、鼠標滾輪下滾縮小選中區域。
4、右擊保存選中區域的圖片在正樣本或負樣本的目錄下,取決於當前選中正樣本還是負樣本。
完成界面如圖:

第一步:把MFC界面的那些控件都拖動好並且綁定好opencv圖形框
在MFC的初始化函數中添加我們的綁定代碼:
BOOL CpicroiDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 設置此對話框的圖標。 當應用程序主窗口不是對話框時,框架將自動 // 執行此操作
SetIcon(m_hIcon, TRUE); // 設置大圖標
SetIcon(m_hIcon, FALSE); // 設置小圖標 //InitializeSkin(("Minimized.ssk"));//初始化 // TODO: 在此添加額外的初始化代碼
namedWindow("ImageShow", CV_WINDOW_KEEPRATIO); // 用OpenCV創建一個窗口
CRect cWindowRect; m_PictureControl.GetClientRect(&cWindowRect); // 獲取控件窗口大小 //int nWindowWidth = cWindowRect.Width(); //int nWindowHeight = cWindowRect.Height(); //resizeWindow("ImageShow", 200, 100);
HWND hPictureWindow = (HWND)cvGetWindowHandle("ImageShow"); // 獲取OpenCV窗口的句柄
HWND hParentWindow = ::GetParent(hPictureWindow); ::SetParent(hPictureWindow, GetDlgItem(IDC_PIC)->m_hWnd); // 關聯OpenCV窗口和MFC的控件窗口
::ShowWindow(hParentWindow, SW_HIDE); GetDlgItem(IDC_PIC)->ShowWindow(0); // 開始不顯示圖片控件
setMouseCallback("ImageShow", onMouse, 0); pcom.InsertString(0, "正樣本"); pcom.InsertString(1, "負樣本"); pcom.SetCurSel(0); return TRUE; // 除非將焦點設置到控件,否則返回 TRUE
}
第二步:選中原圖片集的目錄
這樣接下來操作的圖片都從這個目錄順序讀取,並且切換上下張
添加好對話框類之后:
雙擊這個控件,進入相應的編輯函數內部編寫事件處理代碼:

void CpicroiDlg::OnBnClickedMainFilePath() { // TODO: 在此添加控件通知處理程序代
CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("image files (*.jpeg; *.jpg; *.bmp;*.png) All Files (*.*) |*.*||")); CString m_strPath; CString m_folderpath; if (IDOK == dlg.DoModal()) { m_strPath = dlg.GetPathName(); m_folderpath = dlg.GetFolderPath(); } string strName = CT2A(m_strPath.GetString()); // CString和string之間的類型轉換
string strPathNames = CT2A(m_folderpath.GetString()); sourceImage = imread(strName); nWindowWidth = sourceImage.cols; nWindowHeight = sourceImage.rows; resizeWindow("ImageShow", nWindowWidth, nWindowHeight); if (strName == "")return; tempImage = sourceImage.clone(); imshow("ImageShow", sourceImage); GetDlgItem(IDC_PIC)->ShowWindow(1); SetDlgItemText(IDC_MAIN_FILE, m_folderpath); UpdateWindow(); getFiles1(strPathNames, files); filesLen = files.size()-1; for (auto path : files) { index++; if (path == strName)break; } }
同樣的也要選擇保存的文件路徑:

void CpicroiDlg::OnBnClickedMainFilePath2() { // TODO: 在此添加控件通知處理程序代碼
if (m_dlgMainFile.DoModal() != IDOK) return; CString cstrFile; //文件全名
string strFile; cstrFile = m_dlgMainFile.GetFolderPath(); strFile = CT2A(cstrFile.GetString()); SetDlgItemText(IDC_MAIN_FILE2, cstrFile); UpdateWindow(); getFiles1(strFile, filesPath2); picIndex = filesPath2.size(); GetDlgItem(IDC_MAIN_FILE2)->GetWindowTextA(cs_pcomValue); s_pcomComValue = CT2A(cs_pcomValue.GetString()); }

void CpicroiDlg::OnBnClickedMainFilePath3() { // TODO: 在此添加控件通知處理程序代碼
if (m_dlgMainFile.DoModal() != IDOK) return; CString strFile; //文件全名
strFile = m_dlgMainFile.GetFolderPath() + "\\"; SetDlgItemText(IDC_MAIN_FILE3, strFile); UpdateWindow(); }
這樣只要選擇好了正負樣本點擊右鍵就可以自動保存到相應的選擇好的目錄了
第三步:鼠標控制圖像的裁剪和選定
這里我們要實現的是鼠標點擊出現一個矩形框,然后用鼠標的滾輪去滾動,讓矩形框圍繞中心點改變大小,然后點擊右鍵則保存圖形
void onMouse(int event, int x, int y, int flag, void*) { //CDC *pDC = GetDC(); //CString str; str.Format(TEXT("%d,%d"), x, y); //pDC->FillSolidRect(0, 0, 100, 100, GetSysColor(COLOR_WINDOW)); //pDC->TextOut(1, 0, str);
switch (event) { case CV_EVENT_LBUTTONDOWN://左鍵按下
flag = true; mousFlag = 1; moux = x; mouy = y; if ((x - width / 2) < 0)Lu.x = 0; else Lu.x = x - width / 2; if ((y - height / 2) < 0)Lu.y = 0; else Lu.y = y - height / 2; if ((x + width / 2) > nWindowWidth - 1)Rd.x = nWindowWidth - 1; else Rd.x = x + width / 2; if ((y + height / 2) > nWindowHeight - 1 )Rd.y = nWindowHeight - 1; else Rd.y = y + height / 2; lastImage = tempImage.clone(); rectangle(tempImage, Lu, Rd, Scalar(0, 255, 0), 1, 0, 0); imshow("ImageShow", tempImage); break; case CV_EVENT_RBUTTONDOWN://右鍵按下
{ Rect rect(Lu.x, Lu.y, Rd.x-Lu.x, Rd.y-Lu.y); g_rect = rect; } if (s_pcomComValue == "") { MessageBox(AfxGetMainWnd()->m_hWnd, "存放目錄未填寫","警告", MB_OK); break; } s_save = s_pcomComValue +"\\"+ to_string(picIndex) + ".jpg"; ROI = sourceImage(g_rect); imwrite(s_save, ROI); picIndex++; break; case CV_EVENT_MOUSEWHEEL: int mousWhellFlag; int value; mousWhellFlag = 1; value = getMouseWheelDelta(flag); if (value > 0) { Lu.x = Lu.x > 0 ? Lu.x -= step : Lu.x; Lu.y = Lu.y > 0 ? Lu.y -= step : Lu.y; Rd.x = Rd.x < (nWindowWidth - 1) ? Rd.x += step : (nWindowWidth - 1); Rd.y = Rd.y < (nWindowHeight - 1) ? Rd.y += step : (nWindowHeight - 1); } else { Lu.x = Lu.x < x ? Lu.x += step : x; Lu.y = Lu.y < y ? Lu.y += step : y; Rd.x = Rd.x > x ? Rd.x -= step : x; Rd.y = Rd.y > y ? Rd.y -= step : y; } { Rect rect(Lu.x, Lu.y, Rd.x - Lu.x, Rd.y - Lu.y); g_rect = rect; } tempImage = lastImage.clone(); rectangle(tempImage, Lu, Rd, Scalar(0, 255, 0), 1, 0, 0); imshow("ImageShow", tempImage); break; default: break; } }
因為我們裁剪已經畫矩形框都不能在原圖上畫,所以我們復制一份圖像顯示,所有的操作都是在復制 的臨時圖像上操作的,然后在根據緩沖刷新,將圖像替換就行了
最后說一句,這個是直接顯示原圖的,如果原圖過大只能看到部分,這時候在代碼里面加個判斷,然后用opencv的圖像歸一化的函數去改變一下圖像大小即可。
因為不想寫的很詳細,(別問為什么,問就是因為最近很懶!!!),所以我給出了項目源碼的百度雲在文章的開頭
若有興趣交流分享技術,可關注本人公眾號,里面會不定期的分享各種編程教程,和共享源碼,諸如研究分享關於c/c++,python,前端,后端,opencv,halcon,opengl,機器學習深度學習之類有關於基礎編程,圖像處理和機器視覺開發的知識

