基于Qt实现的截图小程序
在最近做的行人检测项目中,由于需要训练分类器,而分类器的训练又需要有一个一定长宽的样本。为了方便样本的采集,因此实现了这样的一个截图程序。该程序的主要功能是加载视频到程序中,程序可以对视频进行播放、暂停的控制。程序可以在播放界面中直接拖动,画出选区,然后确认保存即可将截取的图片保存到指定路径中并且重新设置截取出来的图片的大小,以适应分类 器训练的需要。该程序的开发环境为Qt4.8.5+qtcreator2.8.0,其中还用到了opencv中的一些函数。
程序的主要界面如下:
主界面
播放视频并截图
属性设置
关于信息
以上便是程序的一些界面。
当时在需要写这个程序的时候,心中便已经有了一个大概的思路了,搭建整个框架也很简单,只用了一天的时间。但是在实际的写代码过程中,还是遇到了一些问题。其中,主要遇到的问题就是:(1)如何在QLabel中画出感兴趣区域并实时显示截取框,最后保存截取下来的图片。(2)如何在属性设置对话框和主界面之间进行参数传递。一下内容主要记录这两个问题中,我的解决方法。
一、如何在QLabel中画出感兴趣区域并实时显示截取框、最后保存截取下来的图片
该程序一开始,并不是设计成从视频中截取样本的,最初是设计成从图片中截取出需要的样本。但是后来导师希望可以从视频中截取样本,因此最后做出了修改。这也是为什么会用到opencv的原因。因为之前写的程序已经可以实现从图片中截取样本,而视频,其实是多幅图像连续播放形成的。所以我只要在原来程序的基础上,将视频的每一帧都放到控件上,就可以实现视频的截取了。而opencv是一个开源的计算机视觉库,对图像和视频有很好的处理能力(在这里用opencv其实是大材小用了,但是这样可以很大程序的复用之前写的代码)。所以可以用opencv中的函数来读取出视频中的每一帧。
因为处理视频和处理图片其实是一个样的,所以这里说说怎么实现从图片中截图的。
在Qt中,要想“显示”某些信息包括文字、图片,自然想到类QLabel了。但是QLabel只是一个基础类,它不能够完全满足我们需要的功能。所以需要继承下来,并进行功能扩充。在这里感觉到了C++中继承的魅力。我完全不知道QLabel的实现机制,仅仅知道它提供的一些函数接口,但是我可以继承下来并进行自定义以实现自己需要的功能。
MyLabel头文件定义:
//自定义图片显示区域及截图功能//MyLabel.h #ifndef MYLABEL_H #define MYLABEL_H #include <QLabel> #include <QMenu> #include <QAction> class MyLabel:public QLabel { Q_OBJECT public: MyLabel(); ~MyLabel(); void setAddress(QString addr); void setImgW(int num); void setImgH(int num); void set(int a,int b,int c,int d); private: void paintEvent(QPaintEvent *event); void mousePressEvent(QMouseEvent *ev); void mouseMoveEvent(QMouseEvent *ev); void mouseReleaseEvent(QMouseEvent *ev); void saveImage(); int x,y,wid,hei; QString address; int imgW,imgH; QPixmap grapPix; bool mouseState; QMenu *popMenu; QAction *save,*cancle; void initMenu(); void showMenu(QPoint pos); private slots: void saveSlot(); void cancleSlot(); }; #endif // MYLABEL_H
MyLabel实现文件的完整定义:
#include "mylabel.h" #include <QLabel> #include <QPainter> #include <QMouseEvent> #include <QRect> #include <QDateTime> #include <iostream> #include <math.h> MyLabel::MyLabel() { address="./images/"; imgW=; imgH=; initMenu(); mouseState=false; //图片显示面板美化 connect(save,SIGNAL(triggered()),this,SLOT(saveSlot())); connect(cancle,SIGNAL(triggered()),this,SLOT(cancleSlot())); this->setStyleSheet("border-width:2px;border-style:solid;border-color:#770576;"); } MyLabel::~MyLabel() { } void MyLabel::setAddress(QString addr) { address=addr; } void MyLabel::setImgW(int num) { imgW=num; } void MyLabel::setImgH(int num) { imgH=num; } void MyLabel::set(int a, int b, int c, int d) { x=a; y=b; wid=c; hei=d; } void MyLabel::paintEvent(QPaintEvent *event) { QLabel::paintEvent(event);//调用父类的event以显示背景 QPainter painter(this); painter.setPen(QPen(Qt::red,)); painter.drawRect(QRect(x,y,wid,hei)); } void MyLabel::mousePressEvent(QMouseEvent *ev) { x=ev->x(); y=ev->y(); } void MyLabel::mouseMoveEvent(QMouseEvent *ev) { if(!(this->pixmap())) { x=-;y=-; wid=-; hei=-; return ; } QPoint pos=ev->pos(); mouseState=true; wid=ev->x()-x; hei=ev->y()-y;this->repaint(); } void MyLabel::mouseReleaseEvent(QMouseEvent *ev) { if(!(this->pixmap())) { x=-;y=-; wid=-; hei=-; return ; } QRect rect; if(ev->x()>x&&ev->y()>y){ rect.setRect(x+,y+,abs(wid)-,abs(hei)-); } else if(ev->x()>x&&ev->y()<y){ rect.setRect(x,ev->y(),abs(wid)-,abs(hei)-); } else if(ev->x()<x&&ev->y()>y){ rect.setRect(ev->x(),y,abs(wid)-,abs(hei)-); } else{ rect.setRect(ev->x()+,ev->y()+,abs(wid)-,abs(hei)-); } //rect.setRect(ev->x(),ev->y(),abs(wid)-1,abs(hei)-1); grapPix=QPixmap::grabWidget(this,rect);//截取矩形区域内图像 QPoint temp; temp.setX(ev->x()); temp.setY(ev->y()); if(mouseState==true){ showMenu(temp); mouseState=false; } } //2015年7月23日晚上9:30 void MyLabel::saveImage() { if(!(this->pixmap())) { x=-;y=-; wid=-; hei=-; return ; } grapPix=grapPix.scaled(QSize(imgW,imgH), Qt::KeepAspectRatio);//等比例放大或缩小到指定大小 QDateTime time=QDateTime::currentDateTime(); QString str=time.toString("yyyyMMddhhmmss"); QString temp=address+str+".png"; grapPix.save(temp,"PNG"); } void MyLabel::initMenu() { popMenu=new QMenu(this); save=new QAction("save",this); cancle=new QAction("cancle",this); popMenu->addAction(save); popMenu->addAction(cancle); } void MyLabel::showMenu(QPoint pos) { popMenu->exec(QCursor::pos()); } void MyLabel::saveSlot() { saveImage(); ,-,-,-); this->repaint(); } void MyLabel::cancleSlot() { ,-,-,-); this->repaint(); }
通过上面的类继承也可以看出,我们需要重载一些函数,还需要增加一些成员函数来实现我们需要的功能。
首先需要在QLabel中显示一个矩形框,这个怎么实现呢?我们需要重载paintEvent函数来实现,其具体实现如下:
void MyLabel::paintEvent(QPaintEvent *event) { QLabel::paintEvent(event);//调用父类的event以显示背景 QPainter painter(this); painter.setPen(QPen(Qt::red,)); painter.drawRect(QRect(x,y,wid,hei)); }
其中,x,y表示矩形框左上角的坐标。wid、hei表示矩形框的宽和高。通过上面的重载,便可在Label上显示矩形框。
那么下面的问题是:如何实时显示矩形框。这里需要重载mouseMoveEvent函数。该函数实现了怎样的效果,感兴趣的伙伴可以试试,感受一下。你可以在该函数内qDebug一个数或者鼠标的坐标,然后用你继承的这个类声明一个对象,并在这个label上按下并移动你的鼠标,看看输出的是什么东西。我的重载代码如下:
void MyLabel::mouseMoveEvent(QMouseEvent *ev) { if(!(this->pixmap())) { x=-;y=-; wid=-; hei=-; return ; } QPoint pos=ev->pos(); mouseState=true; wid=ev->x()-x; hei=ev->y()-y; this->repaint(); }
其他的是一些辅助的操作,比如判断空间是否是空的,如果是则不执行该函数。如果不是,则更新矩形框的信息,并保存到成员变量中,这些信息用来绘制矩形框。上面的代码中,最主要的就是this->repaint();这个函数会立即重绘当前控件。因此,只要鼠标一移动,就会调用该函数,并重新绘制窗口,从而实现了实时显示矩形框。
以上步骤只是将感兴趣区域选择出来并在空间上显示。关于如何截取出感兴趣区域并保存到本地,Qt也提供了很好的支持。我的保存截取图片的 代码如下:
void MyLabel::mouseReleaseEvent(QMouseEvent *ev) { if(!(this->pixmap())) { x=-;y=-; wid=-; hei=-; return ; } QRect rect; if(ev->x()>x&&ev->y()>y){ rect.setRect(x+,y+,abs(wid)-,abs(hei)-); } else if(ev->x()>x&&ev->y()<y){ rect.setRect(x,ev->y(),abs(wid)-,abs(hei)-); } else if(ev->x()<x&&ev->y()>y){ rect.setRect(ev->x(),y,abs(wid)-,abs(hei)-); } else{ rect.setRect(ev->x()+,ev->y()+,abs(wid)-,abs(hei)-); } //rect.setRect(ev->x(),ev->y(),abs(wid)-1,abs(hei)-1); grapPix=QPixmap::grabWidget(this,rect);//截取矩形区域内图像 QPoint temp; temp.setX(ev->x()); temp.setY(ev->y()); if(mouseState==true){ showMenu(temp); mouseState=false; } }
我的截取图片的代码主要放在mouseReleaseEvent函数中,含义是鼠标释放时截取感兴趣的区域并保存到一个QPixmap中,至于是否保存到本地,可以在弹出菜单中决定。其中主要的函数是:grapPix=QPixmap::grabWidget(this,rect)该函数会将rect区域中的图片截取下来并保存到QPixmap中。前面的四个if-else语句只是逻辑上的一些判断,以适应从不同方向画出的选区都能够正确的截取出图片(比如,从左上角往右下角画、从右上角网左下角画等等)。
二、如何在属性设置对话框和主界面之间进行参数传递
这部分主要涉及的其实是Qt中如何通过信号传递参数。这个在网上也有较多的介绍。
在这个程序中,需要用到的信号传递参数的地方是,属性设置对话框。当属性设置对话框设置好属性并点击确定的时候,会发送一个信号,该信号在主界面窗口中和某个槽进行绑定,从而实现了参数的设置。
属性对话框头文件定义如下:
//自定义属性设置对话框,模态对话框 //result属性会返回该窗口所点击的按钮的按钮名,只能取值"ok"或"cancle" //imgWidth和imgHeight会保存输入框中输入的值 #ifndef ATTRIBUTEDIALOG_H #define ATTRIBUTEDIALOG_H #include <QWidget> #include <QLabel> #include <QPushButton> #include <QLineEdit> class AttributeDialog : public QWidget { Q_OBJECT public: AttributeDialog(); ~AttributeDialog(); int getImgWidth(); int getImgHeight(); QString getResult(); private: int imgHight,imgWidth; QString result; QString address; QLabel *heightLabel,*widthLabel,*danwei1,*danwei2,*addrLabel; QLabel *note; QPushButton *ok,*cancle,*chooseAddr; QLineEdit *widthLE,*heightLE,*addrLE; void initGui(); int qStringToInt(QString str); private slots: void okSlot(); void cancleSlot(); void chooseAddrSlot(); signals: void finishSignal(QString str,int w,int h,QString addr); }; #endif // ATTRIBUTEDIALOG_H
完整实现如下:
#include "attributedialog.h" #include <QHBoxLayout> #include <QVBoxLayout> #include <QMessageBox> #include <QFileDialog> AttributeDialog::AttributeDialog() { imgHight=; imgWidth=; result="cancle"; address="./images/"; ,); this->setWindowTitle("attribute"); initGui(); this->setWindowModality(Qt::ApplicationModal); connect(ok,SIGNAL(clicked()),this,SLOT(okSlot())); connect(cancle,SIGNAL(clicked()),this,SLOT(cancleSlot())); connect(chooseAddr,SIGNAL(clicked()),this,SLOT(chooseAddrSlot())); } AttributeDialog::~AttributeDialog() { } int AttributeDialog::getImgWidth() { return imgWidth; } int AttributeDialog::getImgHeight() { return imgHight; } QString AttributeDialog::getResult() { return result; } void AttributeDialog::initGui() { note=new QLabel; note->setText("set the size of the picture which\nyou want to save!\n______________________________________"); heightLabel=new QLabel; widthLabel=new QLabel; addrLabel=new QLabel; danwei1=new QLabel; danwei1->setText("px"); danwei1->setFixedWidth(); danwei2=new QLabel; danwei2->setText("px"); danwei2->setFixedWidth(); widthLabel->setText("width:"); widthLabel->setFixedWidth(); heightLabel->setText("height:"); heightLabel->setFixedWidth(); addrLabel->setText("path:"); addrLabel->setFixedWidth(); ok= new QPushButton; cancle=new QPushButton; chooseAddr=new QPushButton; ok->setText("ok"); cancle->setText("cancle"); chooseAddr->setText("..."); chooseAddr->setFixedWidth(); widthLE=new QLineEdit; heightLE=new QLineEdit; addrLE=new QLineEdit; widthLE->setText("); heightLE->setText("); addrLE->setText(address); //从上到下设置三个水平布局管理器 QHBoxLayout *hb1=new QHBoxLayout; QHBoxLayout *hb2=new QHBoxLayout; QHBoxLayout *hb3=new QHBoxLayout; QHBoxLayout *hb4=new QHBoxLayout; hb1->addWidget(widthLabel); hb1->addWidget(widthLE); hb1->addWidget(danwei1); hb2->addWidget(heightLabel); hb2->addWidget(heightLE); hb2->addWidget(danwei2); hb3->addWidget(addrLabel); hb3->addWidget(addrLE); hb3->addWidget(chooseAddr); hb4->addWidget(ok); hb4->addWidget(cancle); //对话框总体布局管理器 QVBoxLayout *vb=new QVBoxLayout; vb->addWidget(note); vb->addLayout(hb1); vb->addLayout(hb2); vb->addLayout(hb3); vb->addLayout(hb4); this->setLayout(vb); } int AttributeDialog::qStringToInt(QString str) { bool ok; int res; res=str.toInt(&ok,); return res; } void AttributeDialog::okSlot() { result="ok"; QString temp1,temp2; temp1=widthLE->text(); temp2=heightLE->text(); if(temp1==NULL||temp2==NULL){ QMessageBox *warning=new QMessageBox; warning->setText("NULL is not allow"); warning->setWindowTitle("warning"); return ; } imgWidth=qStringToInt(temp1); imgHight=qStringToInt(temp2); emit finishSignal("ok",imgWidth,imgHight,address); this->close(); } void AttributeDialog::cancleSlot() { result="cancle"; emit finishSignal("cancle",imgWidth,imgHight,address); this->close(); } void AttributeDialog::chooseAddrSlot() { QString temp=QFileDialog::getExistingDirectory(this, tr("choose save Directory"),"./",QFileDialog::ShowDirsOnly);// if(temp=="") return ;//如果空则直接退出 ]!='\\'){ temp=temp+'\\'; } address=temp; addrLE->setText(address); }
在该定义中可以看到,定义了一个信号叫finishSignal,并且该信号带参数QString、int、int。那么如何将这个信号发送出去呢?当你在某个时刻需要发送信号,只需要一条语句就够了,即:emit+信号名(对应参数)。在该程序中,当在属性设置对话框中点击 ok后,就会发送信号。所以我的信号发送实现如下:
void AttributeDialog::okSlot() { result="ok"; QString temp1,temp2; temp1=widthLE->text(); temp2=heightLE->text(); if(temp1==NULL||temp2==NULL){ QMessageBox *warning=new QMessageBox; warning->setText("NULL is not allow"); warning->setWindowTitle("warning"); return ; } imgWidth=qStringToInt(temp1); imgHight=qStringToInt(temp2); emit finishSignal("ok",imgWidth,imgHight,address);/*信号发送语句*/ this->close(); }
知道了如何发送信号,如何在信号中带上参数。那么如何使用信号中的参数呢?我们先定义一个简单的类(不必要的信息都省略掉了):
class Widget : public QWidget{ Q_OBJECT public: ...private: //该函数用来显示属性设置对话框 void showAttr();private slots: void testSlot(QString str,int w,int h,QString addr); }; //下面是简单的实现void Widget::showAttr(){ AttributeDialog *temp=new AttributeDialog; temp->show(); //这里就是如何使用信号中的参数了,可以发现,只要写出信号中的参数类型就可以了,不需要写出具体的参数 connect(temp,SIGNAL(finishSignal(QString,int,int)),this,SLOT(testSlot(QString,int,int)));} void testSlot(QString str,int w,int h,QString addr){ //在这里,可以直接使用形参 ...}
上面是一个简单的事例,如何使用自定义的信号,如何用connect函数连接带参数信号和槽。
三、小结
该程序最主要的功能还是从视频中截取出样本,并根据需要设置成指定的大小,因为分类器(比如Adaboost分类器)的训练需要归一化的样本。这个程序为我收集样本提供了很大的方便。同时在另一个车牌识别项目中,也需要收集车牌的样本,这个程序又再一次发挥了作用。
代码写得实在太烂,一开始还在考虑要不要贴出这么多的代码。但是想了想,还是贴出来吧。只有让别人批评自己写得不好的地方,才会有更大的进步。代码写出来就是让人看的,让人看得清楚明白了,才是优秀的代码。如果代码让人看得一头雾水,那只能说代码确实太烂。我贴了这么多代码,可能在一定程度上造成了“视觉污染”。但是我渴望的是进步。写得不好的,希望批评指教,谢谢。
原创博客,转载请注明出处:http://www.cnblogs.com/xiongmao-cpp/
基于Qt实现的截图小程序的更多相关文章
- 基于Shiro,JWT实现微信小程序登录完整例子
小程序官方流程图如下,官方地址 : https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html ...
- CRMEB系统就是集客户关系管理+营销电商系统,能够真正帮助企业基于微信公众号、小程序实现会员管理、数据分析,精准营销的电子商务管理系统。可满足企业新零售、批发、分销、等各种业务需求。
**可以快速二次开发的开源小程序商城系统源码**源码开源地址:https://github.crmeb.net/u/LXT 项目介绍: CRMEB系统就是集客户关系管理+营销电商系统,能够真正帮助企业 ...
- 基于 Autojs 的 APP、小程序自动化测试 SDK
原文:https://blog.csdn.net/laobingm/article/details/98317394 autojs sdk基于 Autojs 的 APP.小程序自动化测试 SDK,支持 ...
- 基于 Autojs 的 APP、小程序自动化测试 SDK - 2019年8月3日
原文:https://blog.csdn.net/laobingm/article/details/98317394 autojs sdk基于 Autojs 的 APP.小程序自动化测试 SDK,支持 ...
- 基于go语言结合微信小程序开发的微商城系统
最近在慕课网上录制了一门<Golang微信小程序微商城系统原型>,这门免费课程特别适合在校大学生或者刚毕业的大学生,go语言初学者以及想要从事微商城开发项目入门的小伙伴们来学习.在课程当中 ...
- Weshop基于Spring Cloud开发的小程序商城系统
WESHOP | 基于微服务的小程序商城系统 Weshop是基于Spring Cloud(Greenwich)开发的小程序商城系统,提供整套公共微服务服务模块,包含用户中心.商品中心.订单中心.营销中 ...
- 基于Tesseract的OCR识别小程序
一.背景 先说下开发背景,今年有次搬家找房子(2020了应该叫去年了),发现每天都要对着各种租房广告打很多电话.(当然网上也找了实地也找),每次基本都是对着墙面看电话号码然后拨打,次数一多就感觉非常麻 ...
- Java截图小程序源码
Java编写的全屏截图小程序 package cnom.test.testUtils; import java.awt.AWTException; import java.awt.Dimension; ...
- 基于angular实现模拟微信小程序swiper组件
这段时间的主业是完成一个家政类小程序,终于是过审核发布了.不得不说微信的这个小程序生态还是颇有想法的,抛开他现有的一些问题不说,其提供的组件系统乍一看还是蛮酷的.比如其提供的一个叫swiper的视图组 ...
随机推荐
- attr-img-src
https://dev.w3.org/html5/spec-preview/the-img-element.html#attr-img-src The src attribute must be pr ...
- ext在web工程目录导致myeclipse内存溢出问题
分类: Extjs2013-01-24 00:01 2068人阅读 评论(2) 收藏 举报 当在eclipse中的web工程中增加了extjs4,出现An internal error occurre ...
- JAVA Callable
Listing -. Calculating Euler’s Number e import java.math.BigDecimal; import java.math.MathContext; i ...
- UBUNTU 14.04 安装 OPENCV 2.4.9
1. 从OpenCV.org 下载源代码 opencv-2.4.9.zip 2. 解压到准备好的目录 unzip opencv-2.4.9.zip 3. 进入源码目录,创建release目录 cd ...
- Archiver 浅析
归档是一个过程,即用某种格式来保存一个或多个对象,以便以后还原这些对象.通常,这个过程包括将(多个)对象写入文件中,以便以后读取该对象. 两种归档数据的方法:属性列表和带键值的编码. 属性列表局限性很 ...
- 使用ASP.NET web API创建REST服务(二)
Creating a REST service using ASP.NET Web API A service that is created based upon the architecture ...
- link标签和script标签跑到body下面,网页顶部有空白
用UltraEdit的16进制编辑模式查看代码,都是EF BB BF开头的,说明都是带BOM的.我手动的将所有文件转成UTF-8 without BOM.页面终于正常了.link,script标签乖乖 ...
- Python中*args 和**kwargs的用法
当函数的参数不确定时,可以使用*args 和**kwargs,*args 没有key值,**kwargs有key值.还是直接来代码吧,废话少说[python] def fun_var_args(far ...
- c#上传文件(二)使用文件流保存文件
1.html代码: <asp:FileUpload runat="server" ID="UpLoadFile"/> <asp:Button ...
- SQLServer DMV Query
1.DMV Query to identify all active SQL Server Sessions The query below identifies all currently acti ...