最近博士师兄在做多目标跟踪,需要对图片中的行人进行标定,按照帧号以及行人的坐标记录按照格式记录在文本文件中,但是手工记录太麻烦,而且相当耗时间,于是乎自告奋勇的帮忙做了一个,花了三个小时左右的时间搞定(大神会不会觉得弱爆了),博士师兄要求的功能大致如下:
源代码地址:http://download.csdn.net/download/mao19931004/10108970
感觉不难,于是不管界面的美观性,随便写了一个能用的。即简单的图片浏览功能+坐标标定功能+数据保存功能+打开资源管理器:
Qt提供了一个QFileInfo以及QDir的类,QDir用于管你目录,而QFileInfo用于记录文件信息。QFileInfoList是一个用于存储文件信息的数据结构。
The QDir class provides access to directory structures and their contents.
A QDir is used to manipulate path names, access information regarding paths and files, and manipulate the underlying file system. It can also be used to access Qt's resource system.
Qt uses "/" as a universal directory separator in the same way that "/" is used as a path separator in URLs. If you always use "/" as a directory separator, Qt will translate your paths to conform to the underlying operating system.
A QDir can point to a file using either a relative or an absolute path. Absolute paths begin with the directory separator (optionally preceded by a drive specification under Windows). Relative file names begin with a directory name or a file name and specify a path relative to the current directory.
QFileInfo provides information about a file's name and position (path) in the file system, its access rights and whether it is a directory or symbolic link, etc. The file's size and last modified/read times are also available. QFileInfo can also be used to obtain information about a Qt resource.
那么我们将会用到的关于QDir和QFileInfo的函数为:
entryInfoList用于筛选目录下所有文件和目录,nameFilters用于指定筛选出符合一定规则的文件名或目录名,filters用于筛选文件类型,包括是否为目录、是否为文件、是否问可读文件,是否为可写文件等。这里我们要筛选的文件类型为文件,因此filters为QDir::Files
第二个就简单了,用于返回文件的绝对路径
因此实现代码为(截取的是整个工程代码的片段):
void calib_point::onOpenBtn()
{
QString folderName = QFileDialog::getExistingDirectory(this, tr("Open Folder"), QString()); //获取文件目录路径
if (!folderName.isEmpty()) {
QDir dir(folderName); //初始化文件目录
fileList=dir.entryInfoList(QStringList() << "*.jpg" << "*.png" << "*.bmp", QDir::Files, QDir::NoSort); //筛选图片文件,存入fileInfiList
if(fileList.size()>0)
{
QString infoStr=QString::fromLocal8Bit("共有");
infoStr+=QString::number(fileList.size()); //图片的数量
infoStr+=QString::fromLocal8Bit( "张图片: ");
infoStr+=fileList[fileFlag].baseName();
infoStr+=",";
infoStr+=QString::number(fileFlag+1);
ui.infoLine->setText(infoStr);
cailbMat=imread(fileList[0].absoluteFilePath().toStdString()); //转QString 为stdString,然后利用opencv imread读入图片
if(!cailbMat.empty())
{
ui.label_display->setImgBuffer(cailbMat.data,cailbMat.cols,cailbMat.rows,cailbMat.step); //显示图片,label_display是QLbale派生类,自定义的
fileFlag++;
}
}
}
}
void calib_point::onNextBtn()
{
if(!m_posVec.empty())
m_posVec.clear(); //新的图片应该没有任何标定数据,因此需要清空数据
if(fileFlagsetText(infoStr);
if(!cailbMat.empty())
{
ui.label_display->setImgBuffer(cailbMat.data,cailbMat.cols,cailbMat.rows,cailbMat.step); //显示图片
}
fileFlag++;
}
else fileFlag=0; //当所有图片读取完毕,从头开始
}
以上即为实现图片浏览的关键代码,接着是图片标定功能。
首先,读入一张图片,由于本身软件大小的或者屏幕大小的限制,可能会对图片进行一定的缩放,因此我们标定的坐标一定是基于原始图片的坐标数据。所以需要对鼠标点击的坐标和原始图片坐标的数据进行转化,才能得到真正的坐标数据,同时还要对点击的对象进行判断,只要点击到我们显示的控件,才回去读取相应的坐标。综上考虑,实现的过程如下:
QPoint calib_point::getImagePointPos(QPoint gPointPos,DisplayWgt *obj,Mat image)
{
QPoint qPos1=obj->mapFromGlobal(gPointPos);
QRect qRect = obj->contentsRect();
QPoint qPos2 = qPos1 + qRect.topLeft();
int w = obj->getW();
int h = obj->getH();
float raioW = w * 1.f / qRect.width();
float raioH = h * 1.f /qRect.height();
int x = cvCeil(raioW * qPos2.x());
int y = cvCeil(raioH * qPos2.y());
return QPoint(x,y);
}
bool calib_point::eventFilter(QObject *obj, QEvent *event)
{
if(obj==ui.label_display && event->type()==QEvent::MouseButtonPress)
{
QMouseEvent *mouseEvent=static_cast(event);
if(mouseEvent->button()==Qt::LeftButton)
{
QPoint qPos=mouseEvent->globalPos();
cailbTemp=cailbMat.clone();
m_pos=getImagePointPos(qPos,ui.label_display,cailbTemp);
if(!m_pos.isNull())
{
if(m_posVec.size()<2)
circle(cailbTemp,Point(m_pos.x(),m_pos.y()),2,CV_RGB(255,0,0)); //调用opencv画圆圈函数
//画出已经确认的标记点
for(int i=0;isetImgBuffer(cailbTemp.data,cailbTemp.cols,cailbTemp.rows,cailbTemp.step);
ui.label_pos->setText("X: "+QString::number(m_pos.x())+"\n"+"Y: "+QString::number(m_pos.y()));
}
}
}
return QWidget::eventFilter(obj,event);
}
可以在图片上显示点击点以及得到图片正确的坐标后,接下来就是实现数据确认和保存了,重点是数据确认,当有两个点数据时,画矩形框以及显示将要录入的信息:
void calib_point::onConfirmBtn()
{
if(m_posVec.size()<2)
m_posVec.push_back(Point(m_pos.x(),m_pos.y())); //如果当前确认的数据数量不足2,继续录入数据
if(m_posVec.size()==2) //已经确认的数据数量为2
{
rectangle(cailbTemp,m_posVec[0],m_posVec[1],CV_RGB(255,255,0)); //opencv画矩形框函数
ui.label_display->setImgBuffer(cailbTemp.data,cailbTemp.cols,cailbTemp.rows,cailbTemp.step); //显示画了矩形框的图片
}
if(m_posVec.size()==2)
{
dataStr=QString::number(fileFlag);
dataStr+=",";
dataStr+=QString::number(-1);
dataStr+=",";
for(int i=0;isetText(dataStr);
}
}
当然还有一个和快捷键的绑定实现,即每次不用去点击确认数据的按钮,直接空格键按下就可以确认数据,这是调用的Qt的函数实现:
ui.confirmBtn->setShortcut(Qt::Key_Space);
当数据录入了,需要查看一下保存的数据和录入的数据是否正确,可以在界面上点击转至数据,然后直接跳转到data.txt所在的文件价。
void calib_point::onTodataBtn()
{
QDir dir=QDir::current();
QString path=dir.absolutePath();
path.replace("/","\\");//将地址中的"/"替换为"\",因为在Windows下使用的是"\"。
QProcess::startDetached("explorer "+path);//打开上面获取的目录
}