Tag Archives: OpenCV

用FrameBuffer实现无Highgui库的OpenCV程序的图片显示

还记得上一篇关于OpenCV移植到FPGA上的文章里,我只写了BMP图片的读写,关于如何显示,因为毕设没有需要,我也没有深入去弄。
后来几次有网友发邮件过来询问,很无奈自己也未曾尝试,更何况做毕设实验的板也早已经还给学校,这个部分就一直搁浅着。直到前一段lanying兄的来信,这个事情才有新的进展。

有个可能比较好的方案是使用GTK那些成熟的库然后整合上OpenCV来实现,这里只介绍一个初级的,也是lanying兄验证完的一个方法,就是使用FrameBuffer来显示图片

首先引用下别人的一点基础介绍

FrameBuffer 是出现在 2.2.xx 内核当中的一种驱动程序接口。
Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出 FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过 Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接 进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由 Framebuffer设备驱动来完成的。

但是呢,FrameBuffer同时也有很大的限制,比如分辨率这些在系统设置里面如果设定完了,就不能实时更改了,同理包括显示的颜色深度。相关方面的东西请自行了解,这里只给个小链接让大家看看:http://www.91linux.com/html/article/kernel/20071204/8805.html

然后就像上次提到的图片读取的编写,要读取什么格式的图片,你自己必须对那种格式的编码存储结构很了解,然后才在那基础上继续进行。因为FrameBuffer的显示图片的思路是,自己读取图片文件的像素信息,然后按照每一行每一列逐一将像素点的信息写入到FrameBuffer对应的内存空间上。

这次只说个实现思路,具体的技术细节因未亲身验证所以不妄加解说,只是与lanying兄信件往来不时了解下,下面给出一个例程给大家参考,由于没有板验证,所以诸位看代码的时候如果有小细节错误,还望见谅。

http://blog.tyreal.net/download/FrameBuffer

若你有更好的方案,请来信tyreal.han@gmail.com赐教,不胜感激

Tagged

OpenCV在基于FPGA的嵌入式系统中的移植研究

自己本科毕设的内容涉及到OpenCV
所以需要移植OpenCV到Nios平台上的嵌入式系统中
可是自己搜遍所有可能的资料
还是没有看到有人做过
即使在我毕设答辩前5天
自己依旧没有做成功

几度犹豫
曾想过腰斩题目改变毕设内容
最后忍住坚持了下来
论文末尾还都写上了移植失败分析
之后的几天
事情就像喜剧结局一样的峰回路转
原本以为不成功的移植居然在小组答辩演示的时候成功了
然后在大组答辩之前找出了不稳定的原因
于是
这篇文章就有了雏形

导师曾让我整理好自己毕设论文中的关于OpenCV的移植研究的部分
可以考虑发表出去
毕竟没人做过还是有些参考价值
自己这一段一是休息外加忙着聚会
二是不知道这样的技术含量值不值得发表
自知现在浮躁的环境发表个论文就像在刷个人数据
其实不见得文章都有什么价值
自己在查阅资料的时候也曾见过几篇文章的测试数据跟精度居然都能一模一样
只是换到自己要发表的时候
就诸多考虑
终究不想敷衍了事
只是想保持自己心中学术思想之正直
听起来多少有点自视清高的酸味
酸就酸吧
得瑟归得瑟,终究对得起自己

唠叨这么久终于开始要说正题了
感谢诸位忍到此刻的耐心
由于之前没人做过这方面的移植研究
所以本文能参考的资料不多
限于笔者的水平
若有错误之处还望见谅

移植整体步骤

  1. 配置交叉编译开发环境,移植uClinux系统
  2. 获取OpenCV源码,配置并编译,生成目标平台的静态链接库
  3. 自行编写文件读写以及图片显示函数,摒除对Highgui库的调用依赖
  4. 将改写后的程序与生成的静态链接库链接一起,下载到目标平台上测试运行

对于第一步,给个自己写的文章当参考——《uClinux在Nios II平台上的移植》,我将从第二步开始说起
值得注意的是:

  • Highgui是造成移植程序运行不稳定的一个原因,毕竟不同嵌入式硬件平台之间都有诸多区别,更何况与Pc的差异。所以要移植OpenCV程序到Nios II的嵌入式系统中,请修改源程序,摒除对Highgui库的调用依赖,自行编写文件读写以及对IplImage这种OpenCV特有数据结构的初始化。

本文使用nios2-gcc-3.4.6 + OpenCV 1.1 pre测试,OpenCV的1.0、2.0版本我也尝试过编译,均告失败,只有1.1测试通过。

配置编译OpenCV

获取OpenCV源码

你可以从http://sourceforge.net/projects/opencvlibrary/files/这里下载OpenCV的源码
点击View all files那个按钮,然后下载opencv-unix 1.1pre1,在终端输入

tar -zxvf opencv-1.1pre1.tar.gz

解压供后面使用
如果你还尝试了别的版本并且也成功了,请不吝告诉在下

配置OpenCV编译信息

进入刚才解压出来的源码文件夹,输入配置命令

$ ./configure --host=nios2-linux --without-gtk --without-carbon --without-quicktime --without-1394libs --without-ffmpeg --without-python --without-swig --enable-static --disable-shared --disable-apps CXX=nios2-linux-g++ --prefix=/home/lee/uccv

–host 指定要编译的目标平台

–witout-* 禁用各种函数库,一是uClinux本来就不支持这里的大部分库,除了gtk,其他的其实你不填也是一样不会编译进去;二是出于精简的需要,说不定日后真支持某个库了,这些我们不需要的东西还是明令去除的好

–enable-static –disable-shared 启动静态链接库,禁用动态链接,因为嵌入式系统对内核的大小要求很看重,不适合将OpenCV库编译成动态链接库,因为这样生成的库都是完整且较为庞大,所以在移植程序数量较少的情况下,通过静态链接库,只将程序用到的部分与程序编译链接到一起,是相对比较理想的选择。另外一个原因是我曾经想使用动态链接库来运行,可是编译出错,暂时无解。

–disable-apps 禁止编译范例程序,因为要编译可以在FPGA上的uClinux系统运行的OpenCV程序,需要在编译的时候额外添加参数,而且我们等会还要自行测试编译,这里一定得禁用掉,否则会导致之后make出错。

CXX 指定编译器。其实这个可以不加,只要host里面指定的平台系统能识别,最后生成的makefile里面的编译器配置信息都是一样的,之所以还保留是因为之前参考ARM下的移植文章,在此留着给大家之后编译若有错误当做一个切入点。

–prefix 可以设置OpenCV编译结果想要安装的路径,这里设置为笔者Fedora中用户所在的个人文件夹。

然后经过一堆输出信息刷新,你可以看到如下信息:

v4l那是因为我毕设要做双目测距程序,需要用到摄像头,所以我留着(不过最后还是失败了,西奈……uClinux下摄像头驱动的移植又是个头痛的问题)。

一切正常的话,我们就开始编译

$ make

如果make没有出错,就开始安装目标平台上的OpenCV函数库吧

$ make install

然后在我们设置的路径下找到lib文件夹,进入就可以看到编译好的静态链接库了

摒除Highgui的依赖

由于不能再用Highgui库了,如果你的程序有用到Highgui,我们就得自己重写文件读写以及图片显示函数,这里着重讲下如何对图片文件读取以及之后如何使用自己读取的图片数据来初始化IplImage,以此为例,其他部分按照这样的思路同样去修改。

  • 删除所有Highgui的头文件引用

为了对原来写好的程序进行最低程度的修改,我们只删除Highgui的头文件引用,然后自行添加个头文件与源文件,在其中加入的图片读取函数的名称,跟原来一样为IplImage* cvLoadImage(char* filename, int iscolor = 1)

  • 读取图片函数的编写,将图像数据存在BYTE*指针指向的内存空间

在自行编写cvLoadImage()函数中,根据要读取的图片格式不同,对应自己编写读取算法,唯一相同的步骤是,最后将图片数据读取到一个BYTE指针指向的空间

  • 使用读取的图像数据初始化OpenCV的IplImage数据结构

读取好图片,可以使用OpenCV的cxcore中的两个函数来初始化IplImage

IplImage* cvLoadImage(char* fileName, int iscolor = 1)
{
	/*...省略其他代码...*/

	//创建IplImage变量的信息头
	IplImage* img = cvCreateImageHeader(cvSize(width,height), depth, channel);
	//利用BYTE指针数据初始化IplImage
	cvSetData(img, (void *)blocks, lineByte);

	/*...省略其他代码...*/
	return img;
}

width、height是要创建IplImage图像的宽度与高度
depth是图像的位深
channel是图像的通道数
blocks是读取来的图像数据的BYTE*指针

lineByte是OpenCV中图像每一行的数据宽度,OpenCV规定图像的每一行数据宽度都必须是4的倍数,但是图像不同位数、不同宽度,不一定满足这个条件,所以这个变量之前需要额外进行一个简单的计算处理

lineByte = (width * biBitCount/8)/4*4;
//biBitCount是图像一个像素占用的位宽大小

以上width、height、biBitCount等int变量的值都可以从图片的文件头读取得知,具体方法请查阅不同图片格式的资料说明。

需要说清楚,我们只是自己简单的写了一个读取特定图片格式的函数,比不上原来的highgui原来的API那么好用,各种格式都支持,不过一般应用的时候,格式都比较固定,这点相对不是什么大问题。

  • 最后对IplImage的释放就不像原来一句cvReleaseImage()就搞定,要分2次完成
delete[] img->imageDataOrigin;
cvReleaseImageHeader(&img);

至此,我们就完成了脱离Highgui自行完成图片文件读取函数,至于文件写入乃至图片显示,限于篇幅原因就不详细讲解,基本思想掌握,剩下的就是自由发挥了。
等我工作定下之后,闲暇再找机会补写看看。

这里留下一个自己写的BMP读取的源码,供大家参考下,写的很搓很随意,而且只读取彩色图像,随意瞥下了解就好。
<—传送门—>

链接整合程序

弄到这里,有了OpenCV的静态函数库,也有脱离Highgui的源码,就可以开始编译链接目标平台的程序了

#先编译文件读取程序
$ nios2-linux-g++ -c bmpDecoder.cpp bmpDecoder.o -I/home/lee/uccv/include/opencv

#再编译测试OpenCV程序
$ nios2-linux-g++ -c test.cpp test.o -I/home/lee/uccv/include/opencv

#最后与函数库链接在一起
$ nios2-linux-g++ -o test bmpDecoder.o test.o -L/home/lee/uccv/lib -lcv -lcvaux -lcxcore -lml -lpthread -elf2flt

-I 指定源码中的头文件引用库位置

-L 指定静态链接函数库位置

-lcv -lcvaux -lcxcore -lml 是需要链接的OpenCV的几个静态链接库,顺序很重要,更改顺序很可能导致undefined reference,需要根据自己编写的程序微调这个参数列表的顺序。

-lpthread 支持多线程程序,我是根据错误提示信息加上这个参数,更深入的理由我并未深究。

-elf2flt 编译为flt格式程序,uClinux默认没有MMU,只支持绝对内存地址的二进制格式,这个记得一定要加上

假如没有什么意外的话,你就会获得最后编译出来的目标平台的OpenCV程序文件

最后唠叨

编译好了,链接成功了,按照我之前给的那篇文章的说明,再烧写下载程序到目标板上测试运行。

虽然我觉得以Cyclone II级别的FPGA器件运行OpenCV的效率,还不能到实际应用的程度,不过以现在FPGA的发展势头,多尝试新的SoPC应用开发,待硬件资源足够之时,也未必不是一大优势。

至于一些目前其他高端的FPGA的运行表现,由于自己没有条件测试,在此就不妄加评价了。

Tagged , , , ,

OpenCV学习笔记之一——图像亮度对比度变换

由于毕设的关系,开始折腾计算机视觉
双目测距的具体实现,现在刚有点眉目而已
看了下图像亮度/对比度的变换的资料
结果又发现漫山遍野的都是同一份代码
而且几乎没有什么解释
原本决定等Harris角点那些弄完再写篇文章
现在想想
还是先记下这份简单的笔记并加以讲解下

那些计算机科班出身的人就不用看这篇了
这只是给我们这些物理/电子系中没有学过”数字图像处理“这门课的人看的

术语解释

亮度(Brightness)

也叫亮度,中文翻译不同而已,你可以看Wikipedia
RGB 色彩空间中,明度可以被认为是R(红色),G(绿色)和B(蓝色)座标的算术平均 μ(尽管这三个成分中的某个要比其他看起来更明亮,但这可以被某些显示系统自动补偿):

 \mu = {R + G + B \over 3 }

对比度(Contrast)

我实在google不到满意的解释
大概上来说
灰度图像的对比度指的是图像中的最黑与最白的点,他们灰度值的比值关系
彩色图像由于有3种通道,我的理解是各个通道中的灰度值的比值关系,确切的定义还望有人指教

实现思路

知道明度跟对比度的定义,接下来
要修改亮度,就把图像的通道的灰度值一起增加或者减少就可以
要修改对比度,需要把图像的通道的灰度值以一个值为临界点,往相反两个方向变化
比如,取128为阈值,要增强对比度,低于128的值修改得比原来更小,高于128的值再修改得更大,要降低对比度就反向操作

下面给出我自己写的一份代码,演示明度以及对比度的调整
对比度的公式参考自Photoshop的公式:
nRGB = RGB + (RGB – Threshold) * Contrast / 255

源代码

#include "highgui.h"
#pragma comment(lib,"cv200d.lib")
#pragma comment(lib,"cxcore200d.lib")
#pragma comment(lib,"highgui200d.lib")

int BrightnessAdjust(const IplImage* srcImg,
					 IplImage* dstImg,
					 float brightness)
{
	assert(srcImg != NULL);
	assert(dstImg != NULL);

	int x,y,i;
	float val;
	for (i = 0; i < 3; i++)//彩色图像需要处理3个通道,灰度图像这里可以删掉
	{
		for (y = 0; y < srcImg->height; y++)
		{
			for (x = 0; x < srcImg->width; x++)
			{

				val = ((uchar*)(srcImg->imageData + srcImg->widthStep*y))[x*3+i];
				val += brightness;
				//对灰度值的可能溢出进行处理
				if(val>255)	val=255;
				if(val<0) val=0;
				((uchar*)(dstImg->imageData + dstImg->widthStep*y))[x*3+i] = (uchar)val;
			}
		}
	}

	return 0;
}

int ContrastAdjust(const IplImage* srcImg,
				   IplImage* dstImg,
				   float nPercent)
{
	assert(srcImg != NULL);
	assert(dstImg != NULL);

	int x,y,i;
	float val;
	for (i = 0; i < 3; i++)//彩色图像需要处理3个通道,灰度图像这里可以删掉
	{
		for (y = 0; y < srcImg->height; y++)
		{
			for (x = 0; x < srcImg->width; x++)
			{

				val = ((uchar*)(srcImg->imageData + srcImg->widthStep*y))[x*3+i];
				val = 128 + (val - 128) * nPercent;
				//对灰度值的可能溢出进行处理
				if(val>255) val=255;
				if(val<0) val=0;
				((uchar*)(dstImg->imageData + dstImg->widthStep*y))[x*3+i] = (uchar)val;
			}
		}
	}
	return 0;
}

int main(int argc, char** argv)
{
	IplImage* srcImg = cvLoadImage("lena.jpg");
	assert( srcImg != NULL );

	IplImage* brightnessImg = cvCloneImage(srcImg);
	//亮度变换,最后数值取值为正时变亮,负则变暗
	BrightnessAdjust(srcImg, brightnessImg, 80.0f);

	IplImage* contrastImg = cvCloneImage(srcImg);
	//对比度变换,数值小于1降低对比度,大于1增强对比度
	ContrastAdjust(srcImg, contrastImg, 1.3f);

	cvNamedWindow("Source",CV_WINDOW_AUTOSIZE);
	cvNamedWindow("BrightnessAdjust",CV_WINDOW_AUTOSIZE);
	cvNamedWindow("ContrastAdjust",CV_WINDOW_AUTOSIZE);
	cvShowImage("Source",srcImg);
	cvShowImage("BrightnessAdjust",brightnessImg);
	cvShowImage("ContrastAdjust",contrastImg);
	cvWaitKey(0);
	cvReleaseImage(&srcImg);
	cvReleaseImage(&brightnessImg);
	cvReleaseImage(&contrastImg);
	cvDestroyWindow("Source");
	cvDestroyWindow("BrightnessAdjust");
	cvDestroyWindow("ContrastAdjustrast");

	return 0;
}

对新手提示几句,运行这个代码请在工程文件的目录下放一个图片,名字是lena.jpg
另外,对彩色通道的处理,循环只有3次,这种循环最好放在最外围,因为图像的长宽一般都远大于这个值
如果你把小循环放最里面,频繁的循环切换,效率会低不少
这只是程序编写技巧上的小提示

本代码在Visual Studio 2008+OpenCV 2.0下运行通过,效果如下


并且与photoshop的调整效果对比过
亮度变换与ps旧版效果一致,貌似ps对亮度变换的公式进行过调整,新版不是这么单纯的加减灰度值
对比度就几乎都差不多了

更多资料

关于OpenCV的安装、配置以及基础学习,可以在OpenCV中文官网查得
官网的入门说明异常详细,各种平台各种IDE都有介绍,我就不多废话了

下次再说说Harris角点的事情吧

Tagged