二维灰度图像转换成三维灰度分布图

为了便于观察图像某些特征的灰度分布,把二维灰度图像转成三维灰度分布,可以从不同的视角发现一些有意思的东西。

I = imread('test.bmp');
[rows,cols] = size(I);
[X,Y] = meshgrid(1:cols,1:rows);
mesh(X,Y,I);

【转】OpenCV使用中“内存泄露”的探索

       戏剧性阶段一:问题的出现 

最近在使用opencv的时候,发现在图像函数部分,opencv的内存管理存在一定问题。在使用IplImage的图像cvcloneImage()后,调用cvReleaseImage()时,内存并不能全部释放。在实时视频处理程序中,伴随程序运行,很容易造成系统内存消耗殆尽。

举例来说,看下面的一个最简单代码:

#include"cv.h"
#include"highgui.h"
#pragma comment(lib,"cv.lib")
#pragma comment(lib,"highgui.lib")
#pragma comment(lib,"cxcore.lib")

int _tmain(int argc, _TCHAR* argv[])
{
	CvCapture* capture = cvCreateCameraCapture(0);
	IplImage* frame;
	cvNamedWindow("ExampleShow",CV_WINDOW_AUTOSIZE);
	while(1)
	{
		frame = cvQueryFrame(capture);
		if(!frame)
			break;
		cvShowImage("ExampleShow",frame);
		char c = cvWaitKey(33);
		if(c == 27)
 			break;
	}
	cvReleaseCapture(&capture);
	cvDestroyWindow("ExampleShow");
	return 0;
}

运行程序,此时,打开资源管理器,可以看到其所占的“内存使用”一直保持稳定。而如果,简单修改下上面的程序,改变如下:

#include"cv.h"
#include"highgui.h"
#pragma comment(lib,"cv.lib")
#pragma comment(lib,"highgui.lib")
#pragma comment(lib,"cxcore.lib")

int _tmain(int argc, _TCHAR* argv[])
{
	CvCapture* capture = cvCreateCameraCapture(0);
	IplImage* frame;
	IplImage* clImage;
	cvNamedWindow("ExampleShow",CV_WINDOW_AUTOSIZE);
	cvNamedWindow("Example_Clone",CV_WINDOW_AUTOSIZE);
	while(1)
	{
		frame = cvQueryFrame(capture);
		if(!frame)
			break;
		cvShowImage("ExampleShow",frame);
		clImage = cvCreateImage(cvSize(frame->width,frame->height),frame->depth,frame->nChannels);
		clImage = cvCloneImage(frame);
		cvShowImage("Example_Clone",clImage);
		char c = cvWaitKey(33);
		if(c == 27)
			break;
		cvReleaseImage(&clImage);
	}
	cvReleaseCapture(&capture);
	cvDestroyWindow("ExampleShow");
	cvDestroyWindow("Example_Clone");
	return 0;
}

同样,运行程序,打开资源管理器,可以看到“内存使用”中,该程序的内存使用量在不断增加。虽然,程序中对拷贝的图像进行了释放,但是,事实上,却没有看到多少效果!

虽然发现了这个问题,也在网络上看到相关的这个问题的讨论,试验了几种方法,发现并不work。在此提出问题,继续探索吧。

戏剧性阶段二:“改良方法”的出现

在网络上看到,除了cvCloneImage()还有cvLoadImage()也有内存泄露问题。最终的有效解决办法是使用cvCopy()来替换代码中的cvCloneImage(),这时候,不会出现内存不断递增的情况。而cvLoadImage()可以CvvImage类的图像装载函数,然后拷贝到目标图像即可。

戏剧性三:真正原因的捕获和分析 

内存无法释放的原因分析:

今天偶然想起,在观察上面的代码时,发现在其中存在一句:clImage = cvCreateImage(cvSize(frame->width,frame->height),frame->depth,frame->nChannels);

这句是向内存申请一片空间,用于存放目的图像的空间。造成内存泄露的真正原因是这句。在使用cvCloneImage()的时候,其实是对源图像指针所指向的图像头、数据、ROI等进行了一个完全的拷贝,放在一个新的内存区域,函数结果使得目标图像指向新的内存,而原来用cvCreateImage()所分配的区域没有被正确释放,成为一片“悬挂地址区域”。在后面调用cvReleaseImage()的时候,释放的是后面其所指向的区域。

因此,要避免这种情况的出现,一种方法是:可以在cvCloneImage()前,先调用cvReleaseImage()来释放之前分配的地址区域。然后执行克隆函数cvCloneImage()操作。也可以在前面不分配空间,直接调用克隆操作。另外一种方法,如果使用cvCopy()函数操作,由于该函数并不会对图像指针分配空间,所以需要先自己用cvCreateImage()分配一段区域,然后调用拷贝函数cvCopy(),来对图像赋值。这样最后释放的是图像指针所指的地址区域。这两种方法都不会出现内存泄露的问题了。

【转】高树的故事

        很久很久以前,在拉格朗日照耀下,有几座城:分别是常微分方城和偏微分方城这两座兄弟城,还有数理方城、随机过城。从这几座城里流出了几条溪,比较著名的有:柯溪、数学分溪、泛函分溪、回归分溪、时间序列分溪等。其中某几条溪和支流汇聚在一起,形成了解析几河、微分几河、黎曼几河三条大河。

        河边有座古老的海森堡,里面生活着亥霍母子,穿着德布罗衣、卢瑟服、门捷列服,这样就不会被开尔蚊骚扰,被河里的薛定鳄咬伤。城堡门口两边摆放着牛墩和道尔墩,出去便是鲍林。鲍林里面的树非常多:有高等代树、抽象代树、线性代树、实变函树、复变函树、数值代树等,还有长满了傅立叶,开满了范德花的级树。。。。人们专门在这些树边放了许多的盖(概)桶,高桶,这是用来放尸体的,因为,挂在上面的人,太多了,太多了。。。。这些人死后就葬在微积坟,坟的后面是一片广阔的麦克劳林,林子里有一只费马,它喜欢在柯溪喝水,溪里撒着用高丝做成的ε-网,有时可以捕捉到二次剩鱼。

        后来,芬斯勒几河改道,几河不能同调,工程师李群不得不微分流形,调河分溪。几河分溪以后,水量大涨,建了个测渡也没有效果,还是挂了很多人,连非交换代树都挂满了,不得不弄到动力系桶里扔掉。

        有些人不想挂在树上,索性投入了数值逼井(近)。结果投井的人发现井下生活着线性回龟和非线性回龟两种龟:前一种最为常见的是简单线性回龟和多元线性回龟,它们都喜欢吃最小二橙。

        柯溪经过不等市,渐近县和极县,这里房子的屋顶都是用伽罗瓦盖的,人们的主食是无穷小粮。

        极县旁有一座道观叫线性无观,线性无观里有很多道士叫做多项士,道长比较二,也叫二项士。线性无观旁有一座庙叫做香寺,长老叫做满志,排出咀阵,守卫着一座塔方。一天二项士拎着马尔可夫链来踢馆,满志曰:“正定!正定!吾级数太低,愿以郑太求和,道友合同否?”二项士惊呼:“特真值啊!”立退。不料满志此人置信度太低,不以郑太求和,却要郑太回归。二项式大怒在密度函树下展开标准分布,布里包了两个钗钗,分别是标准钗和方钗。满志见状央(鞅)求饶命。二项式将其关到希尔伯特空间,命巴纳赫看守。后来,巴纳赫让其付饭钱,满志念已缴钱便贪多吃,结果在无参树下被噎死(贝叶斯)。。。。。

【转】MFC程序(SDI)一开始运行最大化

        修改MFC的窗口界面的外观,修改MFC窗口的外观要用到类风格(CS_XXXX)和窗口风格(WS_XXXX),MFC在WinMain函数的最开始都定义了WNDCLASS窗口类,该类可以改变窗口的图标,背景,光标等;若要改变窗口的大小,一般在 CMainFrame::PreCreateWindow(CREATESTRUCT& cs)中修改构造类cs的值,可以修改cs的cx,cy来改变大小。

        如果要最大化,就要修改cs.style|=WS_MAXIMIZE 使主框架类(CMainFrame)最大化,自然视图类(CView)创建时也会跟随主框架类(CMainFrame)而变化,但是此时显示时,仍然是没 有变化,因为MFC主框架类(CMainFrame)的最后显示要通过SW_XXXX参数来决定,网上有些朋友就建议在 CXXXXApp::InitInstance()中的m_pMainWnd->ShowWindow(SW_SHOW);前加入 m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);或直接改掉,其实最终效果可以实现,但运行开始时会出现先是 正常的小窗口,然后马上变成最大化,给人的视觉效果明显不好。

        那为什么会产生这种效果,是因为在 m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);调用之前会先调用 CMainFrame::ActivateFrame(int nCmdShow);此时便会根据nCmdShow(默认为SW_SHOWNORMAL)调用显示窗口一次,SW_SHOWNORMAL会根据cs中的 cx,cy,x,y来显示窗口,所以cs.style便失去作用(视图类(CView)已经创建,所以它已经是最大化了),所以再次调用 m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);会形成视觉暂留。
 

        最终解决方法是重载CMainFrame::ActivateFrame(int nCmdShow);令nCmdShow=SW_SHOWMAXIMIZED即可,然后m_pMainWnd->ShowWindow(SW_SHOW);会根据之前的设置而显示窗口   

      

原文链接:http://blog.csdn.net/fly_q/archive/2010/05/16/5598610.aspx

【转】程序的编译过程

        上次我已经说过,程序库分为静态和动态两种,今天先从程序的编译过程入手,简单介绍一下静态库和程序编译的基础知识,至于更加复杂更加深入的内容、以及动态库、运行时方面的东西,下次再说。
        静态库是什么意思呢?举例来说,你自己写的某个程序用到了opencv和highgui中的某些函数或变量(总之是需要占用内存的东西,就需要一个地址), 而opencv和highgui在发布时都是以库的形式发布,那么你的程序在最后从可重定位目标码生成可执行程序的过程中,编译器一定要知道你调用的外部 符号的地址和确切的类型,这样才能生成正确的机器指令。
这样说可能还有点抽象,再具体一点,比如你的main.c中有这样一段代码(我没看过opencv,这里瞎举例):

#include "highgui.h"
#include "opencv.h"
int main(int argc, char **argv)
{
        int gui = highgui_gethandle();
        return opencv_guihandle(gui);
}

        那么,编译器在编译这个main.c的时候,就需要知道highgui_gethandle和opencv_guihandle这两个函数的具体类型,这样 才能够判断这段程序在语法上是否正确,比如highgui_gethandle这个函数是否不需要参数、是否返回int值等。
那么,编译器靠什么 知道语法是否正确呢?显然,需要你明确的给出这两个函数的原型声明,也就是说当编译器读到main.c的这两行函数调用语句的时候,必须在之前就已经知道 了这两个函数的所有属性。所以你需要显式包含这两个头文件,这样编译器在编译阶段之前的预处理阶段会将这两个头文件(及其嵌套包含的其它头文件)的内容原 封不动的插入main.c中,这样才能保证在接下来的编译阶段能够正确理解函数原型,从而准确的判断出程序代码的语法问题。在语法分析阶段,像AST、自 动机、形式语言这些基本理论,就不是一两句话能说清楚的了。
        那么编译器又是如何知道在哪里可以找到这两个头文件的呢?一般来说,在编译编译器的时 候(这句话比较绕口,其实编译器也不过是个程序而已,因此编译器也一定是从源程序编译而来的,不可能是直接用机器码编写,就算anders本人当年也是用 汇编的),会通过配置参数或在代码中指定的方式,默认指定一些头文件和库文件的搜索路径。这样,编译器在编译其它源程序的时候,遇到包含头文件的情况,会 到这些缺省路径下去找。如果你的头文件不在缺省路径下,那么就需要程序员自己在编译指令中指定了,这就是我们通常看到的三种情况:

        1、在vs、codeblock、eclipse之类的IDE中,通过option之类的菜单配置头文件路径

        2、在Makefile中的编译指令中通过编译选项指定,比如gcc的-I参数

        3、直接在编译指令中指定,比如gcc -I/home/test/opencv/include -I /home/test/highgui/include -c main.c

        所以,经常有些人在编译程序的时候看到找不到某个包含的头文件的提示,现在应该知道怎么办了吧。
        说完了头文件,回到主题--库文件。刚才大致讲了一下基本的编译过程,真的是非常非常粗略,还有许多细节和技术我都略过了,现在就从main.c变成了 main.o(vc中是main.obj,其实都是一个意思,只是elf/pe/a.out等文件格式和内容摆放标准不同而已,其实内容都差不 多),main.o中就不再是c程序了,而已经是二进制的机器代码,不过是可重定位代码。什么叫可重定位代码,是因为这时的main.o还只是一个半成 品,是上不了台面的,无法直接运行。为什么呢?因为其中的highgui_gethandle和opencv_guihandle这两个函数都还不知道在 哪里,所以编译器在从main.c生成main.o的过程中,这里的两条函数调用指令都是半成品指令,其中只有跳转部分是真实的,而跳转的目的地址是假 的,同时会在其它地方(比如某个段中统一)存放需要重定位的指令内容,这里就是这两条跳转指令。
        随后,进入链接阶段,链接器需要完成程序 重定位和符号链接的任务,也就是说这时候就需要找到这两个函数究竟在哪里了。很显然,其实就是要找到opencv和highgui中分别包含有这两个函数 的库文件。再次很显然,如果opencv发布了多个静态库文件,链接器是不可能逐个去解析看看哪个里边还有我需要的函数的,这就需要程序员自己指定了,指 定的方式跟头文件搜索路径的指定方式类似,先给出实例如下:
        gcc main.o -L/home/test/opencv/lib -L/home/test/highgui/lib -lcv -lhighgui
        这条命令是什么意思?注意这里是main.o,而且没有-c参数,那么就意味告诉gcc我现在要做链接,gcc就会自动去调用链接器程序ld。
说 到这里,聪明人应该已经明白了八九成了,上面的-L参数就是告诉链接器到哪里去找库,而-l参数就是告诉链接器去找哪个库。这里的-L参数很明显,没什么 好说的,而-lcv是什么意思,链接器是怎么知道目标库的文件名的。这就有了一条隐含的潜规则,-l是表示要链接静态库,而在gcc工具族的实现中,规定 -lxx指定去找的库文件名就一定是libxx.a,也就是说-lcv对应libcv.a,剩下的就不用再多说了。
        vs之类的IDE中,就可能是通过project或option等菜单去指定,代替了这里的-L和-l,图形化,穿身皮而已,方便之余,对程序员隐藏了太多的技术细节,最后大家就越来越依赖于这些所谓的商业产品了。
说到这里,大家应该对自己的程序编译时,经常出现的这个函数找不到、那个变量找不到、这个库找不到、那个库版本不对之类的提示有办法了吧。
        那么,当链接器在指定的搜索路径列表中找到了指定的链接库之后,就会去看看里边有没有我需要的函数,比如这里的highgui_gethandle和 opencv_guihandle这两个函数,其中又有许多细节和技术,比如符号表、符号重载等,略过不说,说了更复杂,呵呵。如果能够找到这两个函数, 链接器就会将main.o和这两个库文件、以及libc(这里边就是大家经常看到的malloc/free/strcpy/memcpy之类的)合成一个 大文件,并将其中需要填充的跳转指令中的目的地址或外部变量地址填写完整,再指定程序的入口地址和指令,根据特定格式组织文件内容,最后就生成了一个完整 的可执行程序。
        遵循以上规则,完成了所有外部符号的定址和链接之后,这个可执行程序就可以执行了。
        最后再说一点,就是静 态库是如何生成的,libcv.a这样的文件究竟是什么东西。说白了,.a这种静态库,就是用ar命令把一堆.o文件按照特定格式组织起来,可以理解为类 似tar或jar的打包过程,这样便于整个库的发布。所以也可以用ar命令从.a中解出所有的原始.o来,这一点都不奇怪。
        关于程序的编译,其实还有更多内容可说,比如make、预处理、符号重载、符号表、词法、语法、语义、自动化、正则表达式等等等等,不过打字太累了,有机会再说吧。
         如果上面这些你都真的完全懂了,那么理解c++、java、python、perl。。。这些东西的编译或解释过程对你来说应该都不再是问题,也绝不会再局 限于各种各样的IDE中了,你应该能够一眼看穿vc、codeblock、eclipse之流的隐藏在花哨外衣之下的小花招,就能够做到返璞归真,发现 IDE其实并不像你现象中的那么好。而如果能够做到这一步,那就没什么高级语言能够对你构成障碍了,在语言层面上,你已经几乎接近“无招胜有招”的境界。 为什么是几乎呢,因为还差两点,那就是:

        1、彻彻底底真真正正的理解计算机的工作原理、左值右值、内存地址、对象、多态等重要概念,要能够完全超 脱于任何特定的语言去理解这些概念,这样才能理解python中为什么连函数都能是对象、而java的interface和c++的pure virtual class究竟是什么这类问题。

        2、能够根据需要灵活设计并部署自己的程序设计语言,这就是境界了,对你来说,软件开发已经完全不再局限于具体的语言选择了,如果有必要,完全可以自己去实现一个语言。

        当程序进入执行阶段之后,实际上是进入了一个更为广阔的外太空,这里有CPU、有操作系统、有内存管理、有动态库、有多进程多线程、有cache、有流水线、有栈、有堆、有用户态内核态、有段。。。。。。这些嘛,下次有机会再说吧,打字真是累啊。

原文链接:http://hi.baidu.com/fanjialin17/blog/item/a55819ce54600b0893457e2a.html

OpenCV创建大尺寸图像

        最近项目需要,学习学习OpenCV库。由于需要处理的图片较大,对其进行缩放时,总是出错。师弟说是由于OpenCV不能加载尺寸超过10000像素的图片。于是在OpenCV中文站查到,OpenCV在highgui的image.cpp文件中定义了下面这个max_img_size:

/*const unsigned max_img_size = 10000;*/ 注释掉
if( (bpp != 8 && bpp != 24 && bpp != 32) ||
        /*(unsigned)w >=  max_img_size || (unsigned)h >= max_img_size ||*/ 注释掉
        (origin != IPL_ORIGIN_TL && origin != IPL_ORIGIN_BL))
    {
        assert(0); // most probably, it is a programming error
        return false;
    }
...

        注释掉之后,重新编译highgui。现在能打开10000多点的了,但是还是有问题,调试定位到cxcore的cxarray.cpp中的cvCreateImage函数中:

// create IplImage header and allocate underlying data
CV_IMPL IplImage *
cvCreateImage( CvSize size, int depth, int channels )
{
    IplImage *img = 0;

    CV_FUNCNAME( "cvCreateImage" );

    __BEGIN__;

    CV_CALL( img = cvCreateImageHeader( size, depth, channels ));
    assert( img );
    CV_CALL( cvCreateData( img ));        // 报错

    __END__;

    if( cvGetErrStatus() < 0 )
        cvReleaseImage( &img );

    return img;
}

再进去:

// Allocates underlying array data
CV_IMPL void
cvCreateData( CvArr* arr )
{
    CV_FUNCNAME( "cvCreateData" );
    
    __BEGIN__;

    if( CV_IS_MAT_HDR( arr ))
    {
        size_t step, total_size;
        CvMat* mat = (CvMat*)arr;
        step = mat->step;

        if( mat->data.ptr != 0 )
            CV_ERROR( CV_StsError, "Data is already allocated" );

        if( step == 0 )
            step = CV_ELEM_SIZE(mat->type)*mat->cols;

        total_size = step*mat->rows + sizeof(int) + CV_MALLOC_ALIGN;
        CV_CALL( mat->refcount = (int*)cvAlloc( (size_t)total_size ));
        mat->data.ptr = (uchar*)cvAlignPtr( mat->refcount + 1, CV_MALLOC_ALIGN );
        *mat->refcount = 1;
    }
    else if( CV_IS_IMAGE_HDR(arr))
    {
        IplImage* img = (IplImage*)arr;

        if( img->imageData != 0 )
            CV_ERROR( CV_StsError, "Data is already allocated" );

        if( !CvIPL.allocateData )
        {
            CV_CALL( img->imageData = img->imageDataOrigin = 
                        (char*)cvAlloc( (size_t)img->imageSize ));        // 报错
        }
        else
        {
            int depth = img->depth;
            int width = img->width;

            if( img->depth == IPL_DEPTH_32F || img->nChannels == 64 )
            {
                img->width *= img->depth == IPL_DEPTH_32F ? sizeof(float) : sizeof(double);
                img->depth = IPL_DEPTH_8U;
            }

            CvIPL.allocateData( img, 0, 0 );

            img->width = width;
            img->depth = depth;
        }
    }
    else if( CV_IS_MATND_HDR( arr ))
    {
        CvMatND* mat = (CvMatND*)arr;
        int i;
        size_t total_size = CV_ELEM_SIZE(mat->type);

        if( mat->data.ptr != 0 )
            CV_ERROR( CV_StsError, "Data is already allocated" );

        if( CV_IS_MAT_CONT( mat->type ))
        {
            total_size = (size_t)mat->dim[0].size*(mat->dim[0].step != 0 ?
                         mat->dim[0].step : total_size);
        }
        else
        {
            for( i = mat->dims - 1; i >= 0; i-- )
            {
                size_t size = (size_t)mat->dim[i].step*mat->dim[i].size;

                if( total_size < size )
                    total_size = size;
            }
        }
        
        CV_CALL( mat->refcount = (int*)cvAlloc( total_size +
                                        sizeof(int) + CV_MALLOC_ALIGN ));
        mat->data.ptr = (uchar*)cvAlignPtr( mat->refcount + 1, CV_MALLOC_ALIGN );
        *mat->refcount = 1;
    }
    else
    {
        CV_ERROR( CV_StsBadArg, "unrecognized or unsupported array type" );
    }

    __END__;
}

 

下面是cxalloc.cpp中的cvAlloc:

CV_IMPL  void*  cvAlloc( size_t size )
{
    void* ptr = 0;
    
    CV_FUNCNAME( "cvAlloc" );

    __BEGIN__;

    if( (size_t)size > CV_MAX_ALLOC_SIZE )                // 限制        
        CV_ERROR( CV_StsOutOfRange,
                  "Negative or too large argument of cvAlloc function" );

    ptr = p_cvAlloc( size, p_cvAllocUserData );           // 报错
    if( !ptr )
        CV_ERROR( CV_StsNoMem, "Out of memory" );

    __END__;

    return ptr;
}

        按照zbf661 推荐的注释掉限制条件之后,重新编译,打开尺寸为28012*18251的图像,还是有问题,但是zbf661 的测试代码可行:

IPlImage * img = cvCreateImage( cvSize ( 20000 , 20000 ) , 8 , 3 );
cvZero( img ) ; 
cvShowImage( "bigimage" ,img ) ;

        chai2010 推荐大尺寸图片用GDAL扩展库,看看去!