基于MFC的OpenCV图像显示并标定ROI区域

源程序下载地址:http://download.csdn.net/detail/bright_geek/8157859

         改变也没有想象的那么难,如果你用心,每天进步一点点,用你下决心那一刻的心理状态严格规范自己的日常行为,我想定会遇到更好的自己。曾经看过一个故事,古时候,一位耳顺之年的老婆婆要去看望家住三十公里外的女儿,她徒步走了一天终于到了。女儿对此很感动也很惊讶,这么远的距离,你徒步是怎么来的。母亲答道:你有孕在身,无人照料,我是一定要来照顾你的。路途中,每当我想到距离太远而绝望时,我就告诉自己只要走好当下的这一小步,只要到达下一个拐角,我就成功了。从这个小故事中我们可以看到:成功=强烈的信念+坚持到底的行动!而行动往往是一项长期而艰巨的过程。故事中的老婆婆将一个大的目标,分割成一系列小目标,然后逐个击破,每完成一个小任务都更接近大目标,而且自己也更有动力,也更加坚定信念!联想到编程,我们将一个大的任务分割成若干模块(函数),相似的小任务,我们运用循环或迭代的思想来完成。而我现在要做的就是写好这篇学习总结!

        道虽弥,不行不至。事虽小,不为不成。一点小感触,共勉!

       上篇博客实现了一个简单的小程序,这篇博客我们将对上一篇内容做一个梳理与知识补充,然后加入新功能:在图像控件显示的图片上画矩形作为感兴趣区域。细节知识点还是在代码注释中呈现,在编码的过程中知其所以然。


1.准备工作

1.1界面

界面和(一)中基本一样,只是我们少了显示路径的编辑控件,如下图:


1.2外部文件

       我们要在项目中添加dirent.h和CvvImage.h和CvvImage.cpp等文件,这里我想多说句。这里的添加不是仅仅把这三个文件添加到项目路径、代码开头include即可,而是在 solution中添加Existing Item,将相应的头文件加入到Header Files,源文件加入到Source Files中,如下图。笔者犯了这样的错误,编译时出现外部链接错误,上网搜索时,发现有不少人在问这个问题,后来才发现是忘了在solution中添加。。。

2.代码实现

2.1添加新类ImageInfo用于存储每个图片信息

{...
public:
IplImage* m_pCurImage;
IplImage* m_pCurImageCopy;
char* m_pFileName;//图像名字
char* m_pDirName;//图像所在文件夹名字
CvRect* m_pROIs;
int m_ROICounter;
//到构造函数初始化
}

2.2头文件声明

在Dlg头文件中添加如下声明:
public: 
//显示选择文件夹窗口
CString m_Path;//存储浏览路径
char* m_ImageDir;//指向浏览到的路径
//读取所有图像相关变量
DIR * m_pDir;//头文件要包含#include "dirent.h"
struct dirent *m_pEnt;//dirent 存储目录中的文件信息(文件名、扩展名等等)
//获取图像控件绘制句柄:定义变量m_HDCPicCtl指向图像控件绘制句柄,m_RectPicCtl标记控件客户区域
HDC m_HDCPicCtl;
CRect m_RectPicCtl;
void getNextImage();//获取下一个文件名字
void showImage();//获取图片集的dir后负责显示一副图像

CvRect m_pCurRect;
CvvImage m_CvvImage;
bool m_LButtonDownFlag;//标记鼠标点击
bool m_MouseMoveFlag;//标记鼠标移动
char* pJpg;
char* pBmp;
char* pPng;
char* pJPG;
};

2.3相关初始化OnInitDialog()

{...
//赋予m_pImageInfo值
m_pImageInfo=new CImageInfo();
m_pImageInfo->m_pDirName=(char*)malloc(sizeof(char)*300);
m_pImageInfo->m_pFileName=(char*)malloc(sizeof(char)*200);
m_pImageInfo->m_ROICounter=0;
m_pImageInfo->m_pROIs=(CvRect*)malloc(sizeof(CvRect)*20);//最多20个ROI矩形
//接下来我们实现上一篇日志的功能
//初始化控件绘制句柄
m_HDCPicCtl=GetDlgItem(IDC_PicCtl)->GetDC()->GetSafeHdc();//获取控件窗口指针-》获取设备上下文-》获取绘制句柄;
GetDlgItem(IDC_PicCtl)->GetClientRect(&m_RectPicCtl);//获得客户区域
//开始绘制-》showImage();
m_LButtonDownFlag=0;
m_MouseMoveFlag=0;
}

2.4OpenDir按钮消息响应函数

OpenDir按钮可拆分为三个模块:点击出现选择路径对话框获取路径->读取下一副图像->显示读取的图像

2.4.1获取路径

void CMFC_OpencvTest2_ROIDlg::OnBnClickedOpendir()
{
// TODO: Add your control notification handler code here
//显示窗口,获取被搜索文件路径
CString str;//定义局部变量保存路径信息
BROWSEINFO bi;//BROWSEINFO结构体包含用户选中目录重要信息
TCHAR name[MAX_PATH];
name[0]='d';//仅用于初始化,无具体含义
ZeroMemory(&bi,sizeof(BROWSEINFO));
bi.hwndOwner=GetSafeHwnd();//当前句柄为BROWSEINFO类型对象bi的拥有者
bi.pszDisplayName=name;
bi.lpszTitle= "Select folder";//窗口标题
bi.ulFlags=0x80;
LPITEMIDLIST idl=SHBrowseForFolder(&bi);//浏览文件夹赋予项目标识符列表
if(idl==NULL)
return;
SHGetPathFromIDList(idl,str.GetBuffer(MAX_PATH));//从项目标志符列表中获取文档系统路径
str.ReleaseBuffer();
m_Path=str;//文件路径存储在m_Path中,m_Path为Dlg类CString型成员变量
//至此我们已经获取到浏览的文件夹路径
if(str.GetAt(str.GetLength()-1)!='\\')//判断str最后的字符是不是'\\',如果不是就增加‘\\’因为后面全路径要用到
m_Path+="\\";
UpdateData(FALSE);//UpdateData用于同步控件和关联的变量,FALSE:把变量中的数据输出到控件,TRUE:把控件中的数据保存到变量
m_ImageDir=m_Path.GetBuffer(m_Path.GetLength());//把路径存储在m_ImageDir中,CString转换为char*
//打开文件夹应该复制给我们之前定义的CImageInfo对象了
sprintf(m_pImageInfo->m_pDirName,"%s",m_ImageDir);
m_pDir=opendir(m_ImageDir);
for(int i=0;i<2;i++)
{
readdir(m_pDir);
}//已完成获取文件夹路径
//头文件中添加函数获取文件夹下文件名字,头文件中定义函数getNextImage();
getNextImage();
}

2.4.2读取一幅图像

void CMFC_OpencvTest2_ROIDlg::getNextImage()
{
//读取打开文件夹中所有文件名字
if(m_pDir&&(m_pEnt=readdir(m_pDir))!=NULL)//获取当前指针指向的文件名字
{
//判断名字中有没有.jpg .bmp .png,即判断是否为图片文件
pJpg=strstr(m_pEnt->d_name,".jpg");//strstr()查找指定字符串中是否含有指定的子串
pBmp=strstr(m_pEnt->d_name,".bmp");
pPng=strstr(m_pEnt->d_name,".png");
pJPG=strstr(m_pEnt->d_name,".JPG");
}
if(pJpg==NULL&&pBmp==NULL&&pPng==NULL&&pJPG==NULL)//如果不是图片文件
{
getNextImage();//获取下一个文件
}
else//如果文件名是上述之一,就显示它
{
sprintf(m_pImageInfo->m_pFileName,"%s",m_pEnt->d_name);
showImage();//把一副图像写到控件上面去
}
}

2.4.3显示图像

void CMFC_OpencvTest2_ROIDlg::showImage()
{
char fullName[400];
sprintf(fullName,"%s%s",m_pImageInfo->m_pDirName,m_pImageInfo->m_pFileName);//得到文件全路径
IplImage* src;
src=cvLoadImage(fullName);//加载一副图片
m_pImageInfo->m_pCurImageCopy=cvCreateImage(cvGetSize(src),8,3);
cvCopy(src,m_pImageInfo->m_pCurImageCopy);
m_pImageInfo->m_pCurImage=src;
m_pImageInfo->m_ROICounter=0;
//添加图像控件显示图像,获取图像控件绘制句柄
//定义变量m_HDCPicCtl指向图像控件绘制句柄,m_RectPicCtl标记控件客户区域
//初始化获取控件绘制句柄
//开始绘制
CvvImage srcCvvImage;
srcCvvImage.CopyOf(src);
srcCvvImage.DrawToHDC(m_HDCPicCtl,&m_RectPicCtl);
//cvReleaseImage(&src);现在不能释放
//增加Next按钮
}

2.5Next按钮

void CMFC_OpencvTest2_ROIDlg::OnBnClickedNext()
{
// TODO: Add your control notification handler code here
if(m_pImageInfo->m_pCurImage!=NULL)
{       //释放资源
cvReleaseImage(&m_pImageInfo->m_pCurImage);
m_pImageInfo->m_pCurImage=NULL;
}
if(m_pImageInfo->m_pCurImageCopy!=NULL)
{
cvReleaseImage(&m_pImageInfo->m_pCurImageCopy);
m_pImageInfo->m_pCurImageCopy=NULL;
}
m_pImageInfo->m_ROICounter=0;
getNextImage();
//接下来就是响应画矩形,为Dlg添加消息相应函数
}

3.绘制ROI矩形

绘制ROI矩形可拆分为三步:1.鼠标点击2.鼠标滑动3.鼠标弹起。我们分别为这个事件添加消息响应函数

3.1点击鼠标

void CMFC_OpencvTest2_ROIDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
//point点的初始原点是程序客户区左上角
m_LButtonDownFlag=1;
//获取图像控件区域
CRect PicCtlRect;
GetDlgItem(IDC_PicCtl)->GetClientRect(PicCtlRect);
GetDlgItem(IDC_PicCtl)->ClientToScreen(PicCtlRect);//把客户区坐标转换为屏幕坐标
//因为坐标不在一个参考系,所以我们都以屏幕坐标作为唯一的标准
ClientToScreen(&point);
if(PicCtlRect.PtInRect(point))//判断点击的点是不是在图像控件客户区里面
{//这时,定义一个CvRect m_pCurRect保存相对于屏幕的矩形坐标,得到点击的点相对于控件坐标系的坐标
m_pCurRect.x=(point.x-PicCtlRect.left);//点击的点(已转换为屏幕坐标)减去控件左上角相对于屏幕的坐标
m_pCurRect.y=(point.y-PicCtlRect.top);
m_pCurRect.width=0;
m_pCurRect.height=0;
m_LButtonDownFlag=1;
}
//为对话框类添加鼠标滑动的消息处理函数
CDialog::OnLButtonDown(nFlags, point);
}

3.2移动鼠标

void CMFC_OpencvTest2_ROIDlg::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_MouseMoveFlag=1;
//因为坐标不在一个参考系,所以我们都以屏幕坐标作为唯一的标准
CRect PicCtlRect;
GetDlgItem(IDC_PicCtl)->GetClientRect(PicCtlRect);
GetDlgItem(IDC_PicCtl)->ClientToScreen(PicCtlRect);//把客户区坐标转换为屏幕坐标
ClientToScreen(&point);//把鼠标坐标转换为屏幕坐标
//以上为坐标归一化
if(PicCtlRect.PtInRect(point)&&m_LButtonDownFlag==1)//定义一个变量标记鼠标是否被点击m_LButtonDownFlag
{//计算相对于图像控件客户区域的矩形
CvPoint enPoint;
enPoint.x=point.x-PicCtlRect.left;//得到移动后的点的相对于控件坐标系的坐标
enPoint.y=point.y-PicCtlRect.top;
m_pCurRect.width=enPoint.x-m_pCurRect.x;
m_pCurRect.height=enPoint.y-m_pCurRect.y;
m_pCurRect.width=m_pCurRect.width<0?1:m_pCurRect.width;
m_pCurRect.height=m_pCurRect.height<0?1:m_pCurRect.height;
//计算图像与控件区域的比例关系
float ratex,ratey;
ratex=(float)m_pImageInfo->m_pCurImage->width/(float)PicCtlRect.Width();
ratey=(float)m_pImageInfo->m_pCurImage->height/(float)PicCtlRect.Height();
//resize矩形坐标到原图像
CvRect roiRect;//保存resize后的感兴趣区域
roiRect.x=m_pCurRect.x*ratex;
roiRect.y=m_pCurRect.y*ratey;
roiRect.width=m_pCurRect.width*ratex;
roiRect.height=m_pCurRect.height*ratey;
//开始画矩形到图像上,并对控件区域重绘
IplImage* srcTemp;
srcTemp=cvCreateImage(cvGetSize(m_pImageInfo->m_pCurImage),8,3);
cvCopy(m_pImageInfo->m_pCurImage,srcTemp);
cvDrawRect(srcTemp,cvPoint(roiRect.x,roiRect.y),cvPoint(roiRect.x+roiRect.width,roiRect.y+roiRect.height),cvScalar(0,0,255,0),3);
//头文件中直接定义一个CvvImage
m_CvvImage.CopyOf(srcTemp);
m_CvvImage.DrawToHDC(m_HDCPicCtl,&m_RectPicCtl);
//注意我们显示的时候是把大图像缩放到图像控件区域
cvReleaseImage(&srcTemp);
}
CDialog::OnMouseMove(nFlags, point);
}

3.3弹起鼠标

void CMFC_OpencvTest2_ROIDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CRect PicCtlRect;
GetDlgItem(IDC_PicCtl)->GetClientRect(PicCtlRect);
GetDlgItem(IDC_PicCtl)->ClientToScreen(PicCtlRect);//把客户区坐标转换为屏幕坐标
ClientToScreen(&point);//把鼠标坐标转换为屏幕坐标
if(PicCtlRect.PtInRect(point)&&m_LButtonDownFlag==1&&m_MouseMoveFlag==1)
{
m_LButtonDownFlag=0;//恢复标记
m_MouseMoveFlag=0;
//对矩形坐标做resize
float ratex,ratey;
ratex=(float)m_pImageInfo->m_pCurImage->width/(float)PicCtlRect.Width();
ratey=(float)m_pImageInfo->m_pCurImage->height/(float)PicCtlRect.Height();
CvRect roiRect;//保存resize后的感兴趣区域
roiRect.x=m_pCurRect.x*ratex;
roiRect.y=m_pCurRect.y*ratey;
roiRect.width=m_pCurRect.width*ratex;
roiRect.height=m_pCurRect.height*ratey;
//保存ROI信息到对象
m_pImageInfo->m_pROIs[m_pImageInfo->m_ROICounter]=roiRect;
m_pImageInfo->m_ROICounter++;
//鼠标弹起再绘制一次,在ImageInfo类里面再定义一副图像(需要干净图像时使用)
cvDrawRect(m_pImageInfo->m_pCurImage,cvPoint(roiRect.x,roiRect.y),cvPoint(roiRect.x+roiRect.width,roiRect.y+roiRect.height),cvScalar(0,0,255,0),3);
m_CvvImage.CopyOf(m_pImageInfo->m_pCurImage);
m_CvvImage.DrawToHDC(m_HDCPicCtl,&m_RectPicCtl);
}
CDialog::OnLButtonUp(nFlags, point);
}

4.总结

通过以上步骤,我们完成了所述功能,如下图所示。相信程序肯定有很多可改进的地方,如果看到的朋友有更好的方法请留言交流,谢谢。







  • 1
    点赞
  • 7
    收藏
  • 打赏
    打赏
  • 5
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 5

打赏作者

Bright_Geek

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值