标签归档:OpenCV

opencv world版编译说明

1、命令模式生成工程

先用命令模式生成工程:

cmake . -Bbuild -G"Visual Studio 16 2019" -Ax64

指令要求生成VS2019的msvc16版本工程。
基本毫无悬念,都能正确生成(最多会有几个需要从网站上现下的库下载失败,不需要,无所谓)
工程文件生成到了build目录下,名为OpenCV.sln。

  • 关于CMake动态下载失败问题

在执行cmake生成指令时,CMake会根据配置从网站下载一些第三方库,opencv用到的几个为IPPICV、FFMPEG等,但均提示下载失败,原因是”无法解析主机名”,这里用的是主机’raw.githubusercontent.com’。CMake要求用IP,所以失败,这也许是CMake的bug。
解决办法:在hosts文件中增加github的IP映射:

  • hosts文件: C:\Windows\System32\drivers\etc\hosts (记得要用管理员打开,否则无法保存)
  • 映射到IP: 199.232.68.133 raw.githubusercontent.com
    内容大概是如下样子:
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

# localhost name resolution is handled within DNS itself.
#    127.0.0.1       localhost
#    ::1             localhost
199.232.68.133      raw.githubusercontent.com

因为其他第三方库都是这个IP,所以增加此行后,其他几个库也能正常下载。

2、编译opencv_world版本

opencv被分成了大量的动态库,使用起来非常不方便,所以有人用CMake配置了一个opencv_world版,干净利落,所以更倾向于使用world版,但不知道怎么在命令模式增加此选项,只能打开cmake GUI程序,在里面找到BUILD_opencv_world选项,勾上,然后点configure按钮,然后点Generate按钮,这时候项目只会生成opencv_ts和opencv_world两个模块。

3、编译、安装

理想情况下opencv的sdk就是一个包含了头文件、lib文件dll文件、相关文档等的目录,但是它的include目录并没有整理得很好。所幸它的install做的极其完美(这也是他没有按照预想的方式整理include、lib、dll目录的原因吧),利用它的install就能把编译完的include目录、lib文件等完美放到build目录下,方法:
到build目录双击OpenCV.sln,在vs2019中打开工程。在解决方案中,找到CMakeTarges,展开后,右键编译INSTALL。它会编译各个模块并在build目录下生成一个install目录,然后把所有所需文件“安装”到这个目录,成为一个完整的sdk包,直接拿去用。
记得debug和release两个版本都install build一下,因为如果用的是静态库,debug和release模式得用相应版本。

4、关于opencv contrib库

很多东西从3.0就被拆分到contrib中了,而且单独维护,就是说,默认下载的很多东西没了,如果要用contrib里的东西,比如tracking等,你需要下载contrib库:

https://github.com/opencv/opencv_contrib

下完后解压,放到某目录,在cmake-gui中搜索extra,搜到后指定你的contrib中的modules目录,再次配置、生成,就有了tracking等功能;

  • 组件下载失败导致contrib编译不通过

contrib中有一个xfeatures2d库,这个库用到的一些文件需要动态从github下载,而如果下载配置有问题,大概率就会编译失败:

C1083    Cannot open include file: 'vgg_generated_120.i': No such file or directory
C1083    Cannot open include file: 'boostdesc_bgm.i': No such file or directory

配置成功后,执行cmake生成工程时会看到如下信息:

-- xfeatures2d/boostdesc: Download: boostdesc_bgm.i
-- xfeatures2d/boostdesc: Download: boostdesc_bgm_bi.i
-- xfeatures2d/boostdesc: Download: boostdesc_bgm_hd.i
-- xfeatures2d/boostdesc: Download: boostdesc_binboost_064.i
-- xfeatures2d/boostdesc: Download: boostdesc_binboost_128.i
-- xfeatures2d/boostdesc: Download: boostdesc_binboost_256.i
-- xfeatures2d/boostdesc: Download: boostdesc_lbgm.i
-- xfeatures2d/vgg: Download: vgg_generated_48.i
-- xfeatures2d/vgg: Download: vgg_generated_64.i
-- xfeatures2d/vgg: Download: vgg_generated_80.i
-- xfeatures2d/vgg: Download: vgg_generated_120.i
  • opencv_contrib_4.5无法编译的问题

上面提到的contrib库,我是从contrib的master上下载的,与4.5配合发布的contrib 4.5无法编译,master里已经解决了这个问题。

5、第三方引用的cmake find文件

假设名为FindOpencvWorld.cmake,其内容为:

########################################################################
# CMake module for finding OpencvWorld
#
# The following variables will be defined:
#
#  OPENCVWORLD_FOUND
#  OPENCVWORLD_INCLUDE_DIR
#  OPENCVWORLD_LIBRARY
#

unset(OpenCV_INCLUDE_DIR CACHE)
unset(OpenCV_FOUND CACHE)
unset(OpenCV_LIBRARY CACHE)

if (${arch} STREQUAL "i386")
    set(build_arch "x86")
endif (${arch} STREQUAL "i386")
if (${arch} STREQUAL "x86_64")
    set(build_arch "x64")
endif (${arch} STREQUAL "x86_64")

find_path(OPENCVWORLD_INCLUDE_DIR
  NAMES opencv2/opencv.hpp
  PATHS ${PROJECT_SOURCE_DIR}/thirdparty/opencv-4.5.0/include
  NO_DEFAULT_PATH)
find_library(OPENCVWORLD_LIBRARY_debug
  NAMES opencv_world450d
  PATHS ${PROJECT_SOURCE_DIR}/thirdparty/opencv-4.5.0/${build_arch}/vc16/lib/
  NO_DEFAULT_PATH)

find_library(OPENCVWORLD_LIBRARY_release
   NAMES opencv_world450
   PATHS ${PROJECT_SOURCE_DIR}/thirdparty/opencv-4.5.0/${build_arch}/vc16/lib/
   NO_DEFAULT_PATH)

set(OPENCVWORLD_LIBRARY optimized ${OPENCVWORLD_LIBRARY_release} debug ${OPENCVWORLD_LIBRARY_debug})
#set(OPENCVWORLD_LIBRARY ${OPENCVWORLD_LIBRARY_release})

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(OpencvWorld DEFAULT_MSG OPENCVWORLD_LIBRARY OPENCVWORLD_INCLUDE_DIR)

【转】QT框架中快速应用OpenCV

  和MFC比较起来,QT的信号槽机制比MFC的信号机制慢,但是因为能很好的实现跨平台,所以我在这里总结一下可能对一些人有点用。OpenCV.China论坛上有一个帖子叫做《在MFC框架中快速应用OpenCV》看了后就想结合QT写一下。

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);
 

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

给出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