您的位置:首页 > 运维架构

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

2014-11-14 10:40 471 查看
源程序下载地址: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.总结

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: