MFC的相机双目标定界面设计


一、先上整体的界面图

由于是新手,所以采用的拖控件的方法,而且界面的美化基本没有。

标定界面

二、主要使用的控件

按钮控件:BUTTON (3个)分别用来实现添加图片,标定,查看标定结果。
静态文本控件:Static Text 这里主要用来做标记。
编辑框控件: Edit Control(2个) 用来显示标定结果和选择结果。
列表控件:List Control(1个) 用来显示读取的图片列表。
图片显示控件: Picture Control(2个) 用来显示每组的左右图片。
类向导中可以看到基本的控件名以及成员变量

三、具体实现代码

1.标定主要使用的Opencv,所以环境就先配置好,这里不赘述,主要就是几个按钮功能的实现。

添加图片的实现代码如下:

void CStereoCameraCalibratorDlg::OnBnClickedButton1Addimages()
{
	// TODO: 在此添加控件通知处理程序代码
	
	CFileDialog OpenFileDlg(TRUE/*FALSE为保存*/, _T("ALL(*.*)|*.*|BMP(*.bmp)|*.bmp|JPEG(*.jpg)|*.jpg|PNG(*.png)|*.png||"),
		NULL, OFN_ALLOWMULTISELECT | OFN_ENABLESIZING | OFN_HIDEREADONLY, _T("All(*.*)|*.*|BMP(*.bmp)|*.bmp|JPEG(*.jpg)|*.jpg|PNG(*..png)|*.png||"));
	TCHAR *pBuffer = new TCHAR[MAX_PATH * 50];
	OpenFileDlg.m_ofn.lpstrFile = pBuffer;
	OpenFileDlg.m_ofn.nMaxFile = MAX_PATH * 50;
	OpenFileDlg.m_ofn.lpstrFile[0] = '\0';
	std::vector<CString> BatchImgpaths;
	CString imgPath;

	if (BatchLimg.size() == 0) {
		MessageBox(TEXT("请先选择左图!"));
	}
	

	if (OpenFileDlg.DoModal() == IDOK)
	{
		CString BatchImgpath = _T("");
		CString imgName = _T("");
		POSITION pos = OpenFileDlg.GetStartPosition();
		while (pos != NULL)
		{
			BatchImgpath = OpenFileDlg.GetNextPathName(pos);
			CString temp = OpenFileDlg.GetFileTitle();
			BatchImgpaths.push_back(BatchImgpath);
			int length = BatchImgpath.GetLength();
			for (int i = length - 1; i > 0; i--)
			{
				if (BatchImgpath[i] == '\\')
				{
					//判断当前字符是否是'\'
					imgName = BatchImgpath.Right(length - i - 1);
					BatchLimg.size() == 0 ? BatchLImgNames.push_back(imgName.GetBuffer()) : BatchRImgNames.push_back(imgName.GetBuffer());
					break;   //跳出循环
				}
			}
		}
	}

	if (BatchLimg.size() == 0)
	{
		for (int i = 0; i  < BatchImgpaths.size(); i++)
		{
			cv::Mat Limg = cv::imread(BatchImgpaths[i].GetBuffer());
			BatchLimg.push_back(Limg);
		}
		return;
	}
	for (int i = 0; i < BatchImgpaths.size(); i++)
	{
		cv::Mat Rimg = cv::imread(BatchImgpaths[i].GetBuffer());
		BatchRimg.push_back(Rimg);
	}

	//在列表视图控件中插入数据   行插
	for (int i = 0; i < BatchLimg.size(); i++)
	{
		int a = 1;
		CString str;
		str.Format(_T("%d"), i);
		int nRow = m_PicList.InsertItem(i, str);
		CString LItem = "L" + str;
		m_PicList.SetItemText(i, a, LItem);
		CString RItem = "R" + str;
		m_PicList.SetItemText(i, a + 1, RItem);

	}

	//添加完图片,先在Picture Control控件中显示左右的第一张图
	cv::Mat Lmat = BatchLimg[0];
	cv::Mat Rmat = BatchRimg[0];
	CWnd* pWnd_l = GetDlgItem(IDC_STATIC_LIMAGE);
	ComputeShowImageSize(pWnd_l, Lmat.cols, Lmat.rows);
	cv::Mat resultImage;
	cv::resize(Lmat, resultImage, cv::Size(m_Control_Col, m_Control_Row), 0, 0, CV_INTER_AREA);
	imshow(NAME_LEFT_WINDOW, resultImage);
	cv::resize(Rmat, resultImage, cv::Size(m_Control_Col, m_Control_Row), 0, 0, CV_INTER_AREA);
	imshow(NAME_RIGHT_WINDOW, resultImage);
}

标定按钮的实现代码如下(头文件以及变量的命名,调用的函数实在不好上传):

void CStereoCameraCalibratorDlg::OnBnClickedButton1Calibrator()
{
	// TODO: 在此添加控件通知处理程序代码
	cal.InitChessboardCorners(BatchLimg[0], objectPoints_L, corners_seq_L, imageSize, patternSize, chessboardSize);
	for (int numCountL = 1; numCountL < BatchLimg.size(); numCountL++)
	{
		cal.InitChessboardCorners(BatchLimg[numCountL], objectPoints_L, corners_seq_L, imageSize, patternSize, chessboardSize);
	}
	cal.singleCameraCalibrate(singleCalibrate_result_L, objectPoints_L, corners_seq_L, cameraMatrix_L,
		distCoeffs_L, imageSize, patternSize);

	cout << "已完成左相机的标定!" << endl;

	cal.InitChessboardCorners(BatchRimg[0], objectPoints_R, corners_seq_R, imageSize, patternSize, chessboardSize);
	for (int numCountR = 1; numCountR < BatchRimg.size(); numCountR++)
	{
		cal.InitChessboardCorners(BatchRimg[numCountR], objectPoints_R, corners_seq_R, imageSize, patternSize, chessboardSize);
	}
	cal.singleCameraCalibrate(singleCalibrate_result_R, objectPoints_R, corners_seq_R, cameraMatrix_R,
		distCoeffs_R, imageSize, patternSize);
	cout << "已完成右相机的标定!" << endl;

	cal.stereoCalibrate1(stereoCalibrate_result, objectPoints_L, corners_seq_L, corners_seq_R, cameraMatrix_L, distCoeffs_L,
		cameraMatrix_R, distCoeffs_R, imageSize, R, T, E, F);
	
	UINT i;
	i = MessageBox(TEXT("完成双目标定!"));  //弹出小提示

}

标定结果按钮的实现代码如下:

void CStereoCameraCalibratorDlg::OnBnClickedButton1Calibratorresult()
{
	 //TODO: 在此添加控件通知处理程序代码
	CFileDialog fdlg(TRUE, NULL, _T("*.*"), OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("文本文件(*.txt)|*.txt|视频文件(*.avi)|所有文件(*.*)|*.*||"));
	if (IDCANCEL == fdlg.DoModal())
	{
		return;
	}
	//读取当前对话框选择的文件路径
	CString strPathName = fdlg.GetPathName();
	读取路径名到编辑框
	//SetDlgItemText(IDC_EDIT1, strPathName);

	//打开选择的文件
	//构造文件,同时增加异常处理
	CFile cFile;
	CFileException e;

	if (FALSE == cFile.Open(strPathName, CFile::modeRead, &e))
	{
		CString strException;
		strException.Format(_T("无法打开此文件\n\t"), e.m_cause);
		MessageBox(strException, _T("警告!"), MB_OK | MB_ICONERROR);
		return;
	}

	//读取打开的文件内容
	DWORD dwCount, dwFilelenth = (DWORD)cFile.GetLength();
	char* szBuffer = new char[dwFilelenth + 1];
	ZeroMemory(szBuffer, dwFilelenth + 1);
	CString strContent;
	while (dwCount = cFile.Read(szBuffer, dwFilelenth))
	{
		//拼接读取数据
		strContent += szBuffer;
	}
	
	//将读取的文本数据显示到编辑框中
	SetDlgItemText(IDC_EDIT1_RESULT, strContent);
	cFile.Close();
	delete[] szBuffer;
	szBuffer = nullptr;
}

用列表显示读取到的图片组并进行选择,将选择的组号显示到编辑框,将选择的图片显示到Picture Control控件的部分实现代码如下(此代码是双击List Control的响应事件下的代码):
效果如下:
在这里插入图片描述

void CStereoCameraCalibratorDlg::OnLvnItemchangedList3Showpic(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码
	*pResult = 0;

	/*int nIndex = 0;*/
	选中
	//m_PicList.SetItemState(nIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
	取消选中
	//m_PicList.SetItemState(nIndex, 0, LVIS_SELECTED | LVIS_FOCUSED);

	CString strPic;
	NMLISTVIEW *pNMListView = (NMLISTVIEW*)pNMHDR;
	if (-1 != pNMListView->iItem)   //如果iTtem不是-1,就说明有列表项被选择
	{
		//获取备选列表项第一个子项的文本
		strPic = m_PicList.GetItemText(pNMListView->iItem, 0);
		//将选择的内容显示在编辑框中
		CString tempStr = "L(" + strPic + ")+" + "R(" + strPic + ")";
		SetDlgItemText(IDC_EDIT1_SEL_PICTURE, tempStr);

		//将选中的图片显示在Picture Control控件中
		cv::Mat resultImage;
		cv::resize(BatchLimg[_ttoi(strPic)], resultImage, cv::Size(m_Control_Col, m_Control_Row), 0, 0, CV_INTER_AREA);
		imshow(NAME_LEFT_WINDOW, resultImage);
		cv::resize(BatchRimg[_ttoi(strPic)], resultImage, cv::Size(m_Control_Col, m_Control_Row), 0, 0, CV_INTER_AREA);
		imshow(NAME_RIGHT_WINDOW, resultImage);
	}
}

2.如何实现用 List Control进行选择,再用Picture Control显示选中的图片

首先,右键单击List Control控件添加成员变量,添加完后,在你的主对话框的头文件中,会自动生该成员变量的声明,同时你也可以在类向导中查看你添加的变量。
在这里插入图片描述
添加完成员变量后,我们需要在OnInitDialog下对List Control控件进行初始化,并设置该控件的属性View为Report。
初始化代码如下:
这里有一段代码是为了如期显示图片的代码。

// TODO: 在此添加额外的初始化代码
	CRect rect;

	//获取列表视图控件的位置和大小
	m_PicList.GetClientRect(&rect);
	//为列表视图控件添加全行选中和栅格风格
	m_PicList.SetExtendedStyle(m_PicList.GetExtendedStyle() | LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT);

	//为列表添加三列
	m_PicList.InsertColumn(0, _T("序号"), LVCFMT_CENTER, rect.Width() / 3, 0);
	m_PicList.InsertColumn(1, _T("左图"), LVCFMT_CENTER, rect.Width() / 3, 1);
	m_PicList.InsertColumn(2, _T("右图"), LVCFMT_CENTER, rect.Width() / 3, 2);//
	
	//将新建的显示的图片的window与Picture Control控件关联
	CWnd* pWnd_l = GetDlgItem(IDC_STATIC_LIMAGE);
	AttachWindow(pWnd_l, NAME_LEFT_WINDOW);
	CWnd* pWnd_r = GetDlgItem(IDC_STATIC_RIMAGE);
	AttachWindow(pWnd_r, NAME_RIGHT_WINDOW);

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE

另外显示图片还涉及到两个函数,具体的函数实现代码如下:

//创建两个窗口显示图片, 之后再将这两个窗口与Picture Control控件关联起来
void CCalibratorDlg::AttachWindow(CWnd *pWnd, std::string wndName)
{
	cv::namedWindow(wndName.data(), cv::WINDOW_AUTOSIZE);  //设置窗口名
	HWND hWndl = (HWND)cvGetWindowHandle(wndName.data());  //hWnd 表示窗口句柄,获得窗口句柄
	HWND hParent1 = ::GetParent(hWndl);   //GetParent函数获得一个指定子窗口的父窗口句柄
	::SetParent(hWndl, pWnd->m_hWnd);
	::ShowWindow(hParent1, SW_HIDE);   //ShowWindow指定窗口显示
}

void CCalibratorDlg::ComputeShowImageSize(CWnd *pWnd, int width, int height)
{
	CRect Show_Rect;
	pWnd->GetClientRect(&Show_Rect);
	double Show_Ratio = (double)Show_Rect.Height() / (double)Show_Rect.Width();  //便于控件显示的高宽比率

	int Roi_Row = height;
	int Roi_Col = width;
	double Roi_ratio = (double)Roi_Row / (double)Roi_Col;  //图片的高宽比率

	if (Show_Ratio > Roi_ratio)
	{
		m_Control_Col = Show_Rect.Width();
		m_Control_Row = (int)((double)m_Control_Col*Roi_ratio);
	}
	else
	{
		m_Control_Row = Show_Rect.Height();
		m_Control_Col = (int)((double)m_Control_Row / Roi_ratio);
	}
	m_ratio = (double)Roi_Row / (double)m_Control_Row;
}

当出现角点提取顺序不一致的情况时,可以手动删除选中的那组图,然后点击标定,将对剩下的图进行重新标定步骤如下:
首先在资源视图那里,点击右键添加资源,选择Menu,选择新建
在这里插入图片描述
然后按自己的要求进到该菜单的编辑界面,进行菜单编辑。
在这里插入图片描述
不要直接在右键菜单的位置,直接写成删除,你右键点击后弹出的其实是删除这个菜单,如果这里二级菜单没有,那么在下面的代码menu.GetSubMenu(0)->TrackPopupMenu(0, pt.x, pt.y, this);会报返回值为空的情况。
加载菜单部分实现代码如下,在列表控件上右键单击,选择类向导,添加消息事件处理程序,消息部分选择右键单击NM_RCLICK,再点击添加处理程序:

void CStereoCameraCalibratorDlg::OnRclickList3Showpic(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码

	CMenu menu;

	POINT pt = { 0 };   //用于储存鼠标位置pt.x和pt.y
	GetCursorPos(&pt);  //得到鼠标点击位置
	menu.LoadMenuA(IDR_MENU1_DELETE);   //加载菜单ID
	menu.GetSubMenu(0)->TrackPopupMenu(0, pt.x, pt.y, this);
	*pResult = 0;
}

删除功能的实现:

void CStereoCameraCalibratorDlg::OnDelete()
{
	// TODO: 在此添加命令处理程序代码
	int nItem = m_PicList.GetSelectionMark();   //获取点击的序号
	m_PicList.DeleteItem(nItem);    //删除
	BatchLimg.erase(BatchLimg.begin() + nItem);    //删除容器中存的对应图片,后面再进行重新标定
	BatchRimg.erase(BatchRimg.begin() + nItem);
	corners_L.erase(corners_L.begin() + nItem);
	corners_R.erase(corners_R.begin() + nItem);
}


修改后的文件读取

点击到文件夹自动获取文件夹下的内容,添加图片控件下的代码:

BeginWaitCursor();

	vector<imgGroup>().swap(imgPassive);//清空上次运行结果

	vector<imgFileName> file = cal.SelectFolder();
	if (file.size() == 0)
	{
		AfxMessageBox("选择文件夹错误!");
	}
	for (int i = 0; i < file.size(); i++)
	{
		imgGroup img_Group;
		for (int j = 0; j < file[i].LimgFileName.size(); j++)
		{
			Mat img = imread(file[i].LimgFileName[j]);
			BatchLimg.push_back(img);
			img = imread(file[i].RimgFileName[j]);
			BatchRimg.push_back(img);
		}
		imgPassive.push_back(img_Group);
	}

其中,代码的实现,以及变量的申明

//cal.h中
typedef struct _imgFileName
{
	vector<cv::String> LimgFileName;
	vector<cv::String> RimgFileName;
}imgFileName;

	std::vector<imgFileName> SelectFolder();

	void GetFilePath(vector<imgFileName>& vFilePathList, CString strDir);

//cal.cpp中
std::vector<imgFileName> Calibrator::SelectFolder()
{
	std::vector<imgFileName> vFilePathList;
	BROWSEINFO bi;
	char name[MAX_PATH];
	ZeroMemory(&bi, sizeof(BROWSEINFO));
	bi.hwndOwner = AfxGetMainWnd()->GetSafeHwnd();
	bi.pszDisplayName = (LPTSTR)name;
	bi.lpszTitle = _T("选择文件夹目录");
	bi.ulFlags = BIF_RETURNFSANCESTORS;
	LPITEMIDLIST idl = SHBrowseForFolder(&bi);
	if (idl == NULL)
		return vFilePathList;
	CString strDirectoryPath;
	SHGetPathFromIDList(idl, strDirectoryPath.GetBuffer(MAX_PATH));
	strDirectoryPath.ReleaseBuffer();
	if (strDirectoryPath.IsEmpty())
		return vFilePathList;
	if (strDirectoryPath.Right(1) != "\\")
		strDirectoryPath += "\\";
	GetFilePath(vFilePathList, strDirectoryPath);
	return vFilePathList;
}

//得到文件夹下所有文件
void Calibrator::GetFilePath(vector<imgFileName>& vFilePathList, CString strDir)
{
	CFileFind finder;
	BOOL isNotEmpty = finder.FindFile(strDir + _T("*.*"));//总文件夹,开始遍历?
	imgFileName imgFilePath;
	int flag = 0;
	while (isNotEmpty)
	{
		isNotEmpty = finder.FindNextFile();//查找文件?
		CString filename = finder.GetFilePath();
		if (finder.IsDirectory())
		{
			if (!(finder.IsDots() || finder.IsHidden() || finder.IsSystem() || finder.IsTemporary() || finder.IsReadOnly()))
			{

				CString fileTitle = finder.GetFileTitle();
				if (fileTitle == "L")
				{
					cv::glob(filename.GetBuffer(), imgFilePath.LimgFileName);
					flag++;//L文件夹数据储存
					continue;
				}
				else if (fileTitle == "R")
				{
					cv::glob(filename.GetBuffer(), imgFilePath.RimgFileName);
					flag++;//R文件夹数据储存
					continue;
				}
				GetFilePath(vFilePathList, filename + "\\");
			}
		}
	}
	if (flag == 2)
	{
		vFilePathList.push_back(imgFilePath);
		flag = 0;
		return;
	}
}

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值