QImage 和 IplImage 转换 效率更高 显示视频

和MFC比较起来,QT的信号槽机制比MFC的信号机制慢,但是因为能很好的实现跨平台,所以我在这里总结一下可能对一些人有点用。

0.搭建环境:OpenCV + QT 4.6

我的实验是基于VS2008来做的,QT官方虽然提供了VS2008-add-in的插件,我没有用。直接下载器编译好的库文件进行配置,OpenCV的在VS2008下面的配置方法Google一下到处都是,这里不再补充。首先需要做的是在VS2008里面你需要使QT下和OpenCV的程序能分别跑起来。对于QT在VS的配置其实挺简单,有头文件和相应的链接库,保证调用的时候路径正确,一般就没有问题了。常用命令行make程序的人应该会很清楚那些IDE只不过是层画皮。

1.显示图像

QWidget是QObject下的第一个子类,使用它显示图像会减少不必要的开销。首先定制一个自己需要的QWidget:

class
 myWidget : public
 QWidget

{

  Q_OBJECT

  public
:

    myWidget(const
 IplImage *img,QWidget *parent = 0);

    ~myWidget();

  protected
:

    void
 paintEvent(QPaintEvent *e);

  private
:

    IplImage* iplImg;

    QImage *qImg;

};
 

需要绘制一个图像,我重载paintEvent(QpaintEvent *e),我在这里面使用QPainter进行绘制。

void
 myWidget::paintEvent(QPaintEvent *e)

{

    QPainter painter(this
);

    painter.drawImage(QPoint(5,5),*qImg);

}
 

drawImage(QPoint(5,5),qImg);的作用是将qImg绘制在左上顶点位于QPoint(5,5)处。

这里面有可能两个问题,第一个问题是要显示的图片太小,创建的Widget太大,最后显示比较丑陋。这时可以在此函数里面获得qImg的宽高,然后resize一下就好了。另外一个问题是:绘制的时候使用的是QImage,不是IplImage类型。关于这个问题论坛上有人专门写了IplImage <-> QImage的转换代码,我在这里不重复那个做法,一是有人已经做了,另外处于效率考虑,这里提供另一种方法。

通常同学们都是用cvLoadImage来读图片,保存在IplImage里面,在这里这个图片我们保存在img里面,然后通过img传进QWidget,然后我new一个QImage

qImg = new
 QImage(QSize(img->width,img->height),QImage::Format_RGB888);
 

我这里假设iplImg是RGB格式,且每个通道大小为8。然后创建一个IplImage 的文件头

iplImg = cvCreateImageHeader(cvSize(img.width(),img.height()),8,3);
 

此iplImage和QImage的不同之处在于QImage没有直接提供创建文件头的方法,可以通过如下方式创建只有文件头数据的QImage

qImg = new
 QImage(QSize(0,0),QImage::Format_RGB888);
 

另外两者的图像矩阵像素排列有点不同,比如IplImage中的BGR到了QImage中应该是RGB,当然单通道的灰度图是一样的,值得庆幸的是两者的像素矩阵都是形状相同的多维数组。这样我们可以通过指针共享这部分数据,一种方法如下:

iplImg->imageData = (char
*)qImg.bits();
 

将iplImg的图像矩阵指到qImg那里,以后我们只需要对IplImage运用opencv里面的函数进行处理,其实就直接在处理qImg里面的数据了。但是现在的图像数据还在img里面,首先得把数据搞到手,然后放到iplImg和qImg的共享区中去,另外将颜色排列以QImage中的RGB顺序为标准。

if
 (img->origin == IPL_ORIGIN_TL)

{

	cvCopy(img,iplImg,0);

}

else


{

	cvFlip(img,iplImg,0);

}



cvCvtColor(iplImg,iplImg,CV_BGR2RGB);
 

实际上只要做到这里图片就能显示了。如下图所示

QImage 和 IplImage 转换 效率更高 显示视频 - piglet - piglet

 给出myWidget.cpp完整代码

#include "myWidget.h
"

#include <QtGui/QPainter>

#include <QtCore/QPoint>



myWidget::myWidget(const
 IplImage *img,QWidget *parent /* = 0 */
) : QWidget(parent)

{



 	qImg = new
 QImage(QSize(img->width,img->height),

 		QImage::Format_RGB888);

	iplImg = cvCreateImageHeader(cvSize(img->width,img->height),

		8,3);

	iplImg->imageData = (char
*)qImg->bits();



	if
 (img->origin == IPL_ORIGIN_TL)

	{

		cvCopy(img,iplImg,0);

	}

	else


	{

		cvFlip(img,iplImg,0);

	}



	cvCvtColor(iplImg,iplImg,CV_BGR2RGB);



	this
->resize(img->width,img->height);



}



myWidget::~myWidget()

{

	cvReleaseImage(&iplImg);

	delete
 qImg;

}



void
 myWidget::paintEvent(QPaintEvent *e)

{

	QPainter painter(this
);

	painter.drawImage(QPoint(0,0),*qImg);

}

 

调用的代码很简单:

int
 main(int
 argc,char
* argv[])

{

	QApplication app(argc,argv);



	IplImage *img = cvLoadImage("460.jpg
",1);

	if
 (img)

	{

		myWidget *mw = new
 myWidget(img);

		mw->show();

	}

	int
 re = app.exec();

	cvReleaseImage(&img);

	return
 re;

}
 
 

2.播放视频

有些时候我们需要的是处理视频文件,比如AVI,当然也不过是图像序列。在由于QT运行时的多线程机制导致sleep函数不好使,可以通过其提供的QTimer来控制视频文件的播放。在给出例子之前要罗嗦两句QT的信号/槽机制。MFC里面的消息映射固然很快,但是让一个涉其未深的人阅读起来会觉得很晦涩,QT通过signal/slot机制实现了消息交换。用signal唤起slot,比如点击了button的clicked()事件是一个signal,当这个事件发生之后可以唤起其他的操作,只要你将clicked消息和那个实际操作的槽连接起来了。

opencv里面有函数可以方便的读取视频帧,如果使用Widget播放视频,如何控制帧率是一个很巧妙的地方,我使用一个QTimer控制时间(可以理解为帧率),并周期性的唤起读取视频帧的操作,然后在这个操作里面对widget进行重绘,或者模仿前面的方法,用一个QImage当作共享区域,让用来paintEvent用来显示。

有了上面显示图像的基础,我改一下上面的代码,给出一个例子:

myWidget.h

#ifndef MYWIDGET_H

#define MYWIDGET_H



#include <QtGui/QWidget>

#include <QtGui/QPaintEvent>

#include <QtGui/QImage>

#include <QtCore/QTimer>

#include <cv.h>

#include <highgui.h>

class
 myWidget : public
 QWidget

{

	Q_OBJECT

public
:

	myWidget(const
 char
 *filename,QWidget *parent = 0);

	~myWidget();



protected
:

	void
 paintEvent(QPaintEvent *e);

private
 slots:

	void
 nextFrame();

private
:

	CvCapture *capture;

	IplImage *iplImg;

	IplImage *frame;

	QImage *qImg;

	QTimer *timer;

};

#endif
 

myWidget.cpp

#include "myWidget.h
"

#include <QtGui/QPainter>

#include <QtCore/QPoint>



myWidget::myWidget(const
 char
 *filename,QWidget *parent /* = 0 */
) : QWidget(parent)

{

	capture = cvCaptureFromFile(filename);

	if
 (capture)

	{

		frame = cvQueryFrame(capture);

		if
 (frame)

			this
->resize(frame->width,frame->height);



		qImg = new
 QImage(QSize(frame->width,frame->height),

			QImage::Format_RGB888);

		iplImg = cvCreateImageHeader(cvSize(frame->width,frame->height),

			8,3);

		iplImg->imageData = (char
*)qImg->bits();



		timer = new
 QTimer(this
);

		timer->setInterval(30);

		connect(timer,SIGNAL(timeout()),this
,SLOT(nextFrame()));

		timer->start();

	}



}



myWidget::~myWidget()

{

	cvReleaseImage(&iplImg);

	cvReleaseCapture(&capture);

	delete
 qImg;

	delete
 timer;



}



void
 myWidget::paintEvent(QPaintEvent *e)

{

	QPainter painter(this
);

	painter.drawImage(QPoint(0,0),*qImg);

}



void
 myWidget::nextFrame()

{

	frame = cvQueryFrame(capture);

	if
 (frame)

	{



		if
 (frame->origin == IPL_ORIGIN_TL)

		{

			cvCopy(frame,iplImg,0);

		}

		else


		{

			cvFlip(frame,iplImg,0);

		}



		cvCvtColor(iplImg,iplImg,CV_BGR2RGB);



		this
->update();



	}

}
 

主函数里面调用

int
 main(int
 argc,char
* argv[])

{

	QApplication app(argc,argv);



	char
 *filename = "test.avi
";

	myWidget *mw = new
 myWidget(filename);

	mw->show();



	int
 re = app.exec();

	return
 re;

}


 

由于接不截图都看不出来,所以就不截图。

后一个程序因为涉及到slots,所以添加了一个Q_OBJECT宏,编译之前需要将myWidget.h程序moc一下

比如:

moc myWidget.h –o moc_myWidget.cpp

这样会在目录下生成一个.cpp文件,make的时候加到源文件里一起make,或者编译的时候添加到源文件列表中去。

知道如何使用QT来显示图像和播放视频之后,在上述代码里面插入处理图像的代码就很简单了。显示图像的时候在构造函数里面就可以插入处理代码,当然也可以在paintEvent函数里面进行处理,播放视频的时候可以在nextFrame函数里面进行处理,有些预处理可以在构造函数里面完成。

相比于MFC的代码,QT的代码结构看上去,很明显,更加优美。

 

原文:http://bugway.appspot.com/?p=469802

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值