学习opencv中文版教程——第二章
所有案例,跑起来~~~然而并没有都跑起来。。。我只把我能跑的都尽量跑了,毕竟看书还是很生硬,能运行能出结果,才比较好。
越着急,心越慌,越是着急,越要慢,越是陌生,越不能盲进。否则更容易走错路。
看了一些东西发现都挺坑的,然后看了看书,发现书上写的也。。。所以就把看书笔记,和跑动例程都来做一个整理。
关于如何配置,是重中之重
所以配置写在了这里:http://www.cnblogs.com/letben/p/5278595.html
然后是看书又看回到了这本学习opencv的白色装帧书上。
初试牛刀——显示图像
例2-1
#include "highgui.h"//1、 int main(int argc,char **argv){ //2、 IplImage* img = cvLoadImage(argv[1]);//3、 cvNamedWindow("Example1", CV_WINDOW_AUTOSIZE);//4、 cvShowImage("Example1", img);//5、 cvWaitKey(0);//6、 cvReleaseImage(&img);//7、 cvDestroyWindow("Example1");//8、 }
1、高级GUI图像用户界面,包含媒体的输入输出,视频捕捉,图像视频的编解码,图形交互界面的接口。GUI:Graphical User Interface 图形用户界面(接口)
2、这个程序还是要求有一定的C++基础的,比如最起码要知道主函数是干什么的?怎么用我记得之前在一片博文里面提到过,第一个参数是,在命令行里面,到底键入了几个字符组,第二个参数用来获取每个字符串的内容。关于详细可以看下这篇文章 http://www.cnblogs.com/letben/p/5229933.html 里面简单说了一下参数的情况
3、如果能明白主函数的参数情况,那么在命令行里面键入 :opencv教程——显示图像 1.png 。其中第一个参数要想展示的话是:argv[0]如果cout<<argv[0]将会是“opencv教程——显示图像”,所以argv[1] 是 “1.png”所以下面一同也给了一个目录结构,为了能在当前目录里面找到1.png这张图片。如果找到的话,就加载这张图片,并且把图像信息赋给img。
4、创建一个窗口,命名为“Example1”,关于大小是自适应内容大小的,也就是图像内容有多大,我们的窗口就建立多大,以刚好存放我们要展示的内容此时已窗口不可再变化。可选参数有0.当设置为0的时候,窗口大小可以拖动,比较符合我们一般使用照片查看器的习惯,这里需要读者尝试这两个参数
5、展示图片函数,把我们的img图片放到刚刚创建好的“Example1”里面
6、等到用户操作,键入字母后,程序结束。此参数大于0时,比如5000,指的是在5000毫秒后,程序结束。小于等于0时表示等待用户操作。
7、释放图片占用内存
8、释放窗口占用相关资源。
命令行:
例2-2播放avi视频
#include "highgui.h" int main(int argc,char **argv){ cvNamedWindow("Example2", CV_WINDOW_AUTOSIZE); CvCapture* capture = cvCreateFileCapture(argv[1]);//1、 IplImage* frame;//2、 while (1){//3、 frame = cvQueryFrame(capture);//单帧图像赋值, if (!frame) break;//如果单帧图像没有值,为真,就退出 cvShowImage("Example2", frame); char c = cvWaitKey(33);//在33毫秒内等待用户输入 字符。 if (c == 27) break;//如果该字符是 ESC-> 27 的话,就退出 } //8、 cvReleaseCapture(&capture); cvDestroyWindow("Example2"); }
1、同例1的“3、”一样,这里面是从用户输入的地址得到一个视频的对象,因为是视频所以接受方式不能再是IplImage了,这次改成 指向CvCapture类型的 变量 capture,这是一个封装好的对象,可以利用cvQueryFrame()得到单帧图像。
2、声明一个图片类型的变量
3、从视频对象里面获得的单帧图像传递给当前一个frame
4、如果frame为空就退出
5、能执行到这里说明frame不为空,也就是可以显示把当前图像予以显示,
6、每张图片等待33毫秒,他能返回用户键入的字符,
7、如果字符是键盘上的ESC的话,就退出。
8、释放 视频窗口资源。
想一想:cvWaitKey()里面的参数有什么用处?设置成1、50、100来验证你的想法。
例2-3视频播放控制
其实开始觉得人家书写的挺好的。但是从这里就开始觉得坑了,不知道是写的不咋地,还是翻译的不细致,总之开始遇到了一些讲不明白的点了。然后就自己试,一直到代码能跑。。。
然后这里希望把能跑的代码分享出来,也算是对自己学习过程的一个记录,以前都看过好多东西只是都忘了。。。现在开始养成记录的习惯,一旦自己忘了,可以过来看看,如果还能对别人有所帮助,那就再好不过。
#include "cv.h"//1、 #include "highgui.h" int g_slider_position = 0;//2、 CvCapture* g_capture = NULL;//3、这句话能通过需要 目录在 include下面。 void onTrackbarSlide(int pos){//4、 cvSetCaptureProperty(g_capture,CV_CAP_PROP_POS_FRAMES,pos);//5、 } //如何实现反向的显示,就是 视频播放中,改变滚动条的位置。 int main(int argc,char** argv){ cvNamedWindow("Example3", CV_WINDOW_AUTOSIZE); g_capture = cvCreateFileCapture(argv[1]); int frames = (int)cvGetCaptureProperty(g_capture, CV_CAP_PROP_FRAME_COUNT);//6、 if (frames != 0){//7、 cvCreateTrackbar("Position", "Example3", &g_slider_position, frames, onTrackbarSlide); } IplImage* frame; while (1){ frame = cvQueryFrame(g_capture); if (!frame) break; cvShowImage("Example3", frame); char c = cvWaitKey(33); if (c == 27)break; //8、反向显示 //g_slider_position = cvGetCaptureProperty(g_capture, CV_CAP_PROP_POS_FRAMES); //cvSetTrackbarPos("Position", "Example3", g_slider_position); } cvReleaseCapture(&g_capture); cvDestroyWindow("Example3"); }
1、这次包含的头多了一个,这个是opencv 第一个版本留下的精华,与他一同留下来的还有其他10个文件,这个是里面最大的一个有4KB。其他的都是3KB还有一个是1KB【http://i.cnblogs.com/Files.aspx 可以到这个里面下载MyLittleFindy,然后键入目录 以及cv.h 来看看这些信息。】
2、这个是滑动位置,如果不进行反向显示,这个参数没有用。这也是我开始不喜欢这套教程的原因。
3、把它提出来是为了非主函数内可用,以为我们还有参数要使用这个 视频捕捉 对象。
4、这是一个函数,当我们双击这个地方的时候,发现7里面,用了这个函数,但是使用方式跟我们以前的方式不大一样。函数的使用应该被传递参数,但是这里面就只有一个函数名,也就是当前函数的指针,这种方式叫做 回调函数,即,把一个函数的地址作为另外一个程序的参数传进来的方式。其中有两个角色一个是主调函数,另外一个是回调函数。主调函数中有回调函数的函数名,回调函数就是我们通常意义上的一个函数,只不过当它的使用是以函数地址形式出现的时候,就被称为回调函数。参考
http://www.cnblogs.com/letben/p/5225521.html
这个里面 onTrackbarSlide 就是回调函数了。
5、设置视频参数,第一个是代表当前视频,CV_CAP_PROP_POS_FRAMES:有太多的不理解,就是这个不理解,指的是:计算机视觉_视频_参数_位置_帧数,位置,位置是传递进来的参数
6、这一行比较浅显:总帧数 = 计算机视觉得到视频参数(当前视频,视频总帧数)
7、cvCreateTrackbar("Position", "Example3", &g_slider_position, frames, onTrackbarSlide);主调函数,由他调用回调函数 Position是控制条的名称,example3 是窗口的名称,控制条的起始位置,这里使用引用还是比较有用的,frames是总帧数,函数地址。
8、由读者自己考虑,这怎么考虑这些参数、函数都没有介绍啊。。。
例2-4 平滑处理
图像平滑:突出图像的宽大区域、低频成分、主干部分或抑制图像噪声和干扰高频成分的图像处理方法,用以实现亮度平缓渐变,减小突变梯度,改善图像质量。常见的图像平滑法有1、差值平滑方法;2、线性平滑方法;3、卷积法等 方法的选取取决于噪声种类 比如遇到椒盐噪声 可采用线性平滑方法。
然后是这个调用不了啊。。。
于是 就决定把它调用起来:
#include "highgui.h" #include "cv.h" using namespace std; void example2_4(IplImage *img){ cvNamedWindow("Example4-in"); cvNamedWindow("Example4-out"); cvShowImage("Example4-in",img); IplImage * out = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3);//1、 cvSmooth(img, out, CV_GAUSSIAN,1,31);//2、 cvShowImage("Example4-out", out); cvReleaseImage(&out); cvWaitKey(0); cvDestroyAllWindows(); } int main(int argc,char **argv){ IplImage* img = cvLoadImage(argv[1]); example2_4(img); }
前面的好好读了,默写几遍,后面的就用已知凑就可以了,然后再有一点儿英语的底子,最起码简单的单词像这种 release destroy smooth,然后后面的程序理解起来就很容易了。
所以到了这里需要解释的应该就1跟2了。
1、创建图片,首先要求创建图片的图片大小,然后是图像的深度信息,这个是8位的。通常都是8位的,8位刚好用来表示从0-255,刚好是用来表示颜色的大小。rgb三色都是从0-255,0-255,0-255。接下来是3,3表示通道,rgb三个通道,如果是灰度图的话是1.只有黑白。rgb是三个颜色。所以是三个通道。这样这个out就是跟原图片一样大,深度为8位,三通道的图片底子。
2、cvSmooth(img, out, CV_GAUSSIAN,1,31);计算机视觉平滑(输入的图片,输出的图片,采用计算机视觉_高斯方式,横向模糊,纵向模糊);
经检测这些数值都只能是奇数。猜想如果模糊当前点的话,偶数点数不行,奇数的话,就可以满足当前点左右,上下。并不知道1个点是怎么处理的。这里书上给的是3,3
例2-5使用cvPryDown()创建一副宽度和高度为输入图像一般尺寸的图像
#include "highgui.h" #include "cv.h" IplImage* dopyrDown(IplImage* in,int filter = IPL_GAUSSIAN_5x5){ assert(in->width % 2 == 0 && in->height % 2 == 0); IplImage* out = cvCreateImage(cvSize(in->width / 2, in->height / 2), in->depth, in->nChannels); cvPyrDown(in, out); return (out); } int main(int argc, char **argv){ IplImage* img = cvLoadImage(argv[1]); cvNamedWindow("Example5-in"); cvShowImage("Example5-in", img); IplImage* img1 = dopyrDown(img); cvNamedWindow("Example5"); cvShowImage("Example5", img1); cvReleaseImage(&img1); cvWaitKey(0); cvDestroyAllWindows(); }
//多多使用 F12按键 和‘ctrl’+‘-’读作contrl 和减号。F12用来向下查询函数的声明或者定义,后面的组合键用以返回到上一层。
/*
Smoothes the input image with gaussian kernel and then down-samples it.
dst_width = floor(src_width/2)[+1],
dst_height = floor(src_height/2)[+1]
*/
然后就发现这是向下模糊的方式,它用来将输入的图片使用高斯内核平滑,然后向下采样。
看到他的孪生姐姐:
/*
Up-samples image and smoothes the result with gaussian kernel.
dst_width = src_width*2,
dst_height = src_height*2
*/
CVAPI(void) cvPyrUp( const CvArr* src, CvArr* dst,
int filter CV_DEFAULT(CV_GAUSSIAN_5x5) );
尝试使用这个函数,让函数能跑起来。我们使用的doPryDown() 把原图缩减为一半,然后采用5*5的方式进行高斯模糊,现在使用 doPryUp() 把原图扩大扩大1倍,然后进行模糊。
哦,这里assert忘了说了,就是一种硬设定,要求他是个什么什么样子,这个在junit里面见到过,用来进行单元测试。在这里面强硬令输入图片的宽和高均能被2整除,然后缩放才有意义
练习:新建一个doPryUp()函数实现上述效果。
练习答案:
IplImage* doPyrUp(IplImage* in, int filter = IPL_GAUSSIAN_5x5){ IplImage* out = cvCreateImage(cvSize(in->width * 2, in->height * 2), in->depth, in->nChannels); cvPyrUp(in, out); return (out); }
练习2-6
canny边缘检测将输出写入一个单通道(灰度级)图像
#include "highgui.h" #include "cv.h" using namespace std; IplImage* doCanny(IplImage* in,double lowThresh,double highThresh,double aperture){ assert(in->nChannels = 1); if (in->nChannels != 1){ cout << in->nChannels << endl; cout << "不能输出" << endl; return 0; } IplImage* out = cvCreateImage(cvGetSize(in), IPL_DEPTH_8U, 1);//原书这里绝对有问题。 cvCanny(in, out, lowThresh, highThresh, aperture); return out; } int main(int argc,char **argv){ IplImage* img = cvLoadImage(argv[1]); cvNamedWindow("Example5"); cvShowImage("Example5", img); IplImage* img1 = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1); cvConvertImage(img, img1, CV_BGR2GRAY);//这个在 cv.h 包里面 IplImage* out = doCanny(img1, 10, 100, 3); cvNamedWindow("Example5-out"); cvShowImage("Example5-out", out); cvReleaseImage(&out); cvWaitKey(0); cvDestroyAllWindows(); }
我们已经基本熟悉了这些内容,所以现在要从题目名称入手,来解决这些问题,就是告诉你有一个canny可以做边缘检测。
然后把这个canny敲出来,坐等它补全参数,或者先按照书上的内容把参数补全,然后最重要的是按F12进去。
然后你发现了:
/****************************************************************************************
* Feature detection *
****************************************************************************************/
/* Runs canny edge detector */
CVAPI(void) cvCanny( const CvArr* image, CvArr* edges, double threshold1,
double threshold2, int aperture_size CV_DEFAULT(3) );
所以他们是用来做特征点检测的,下面还有茫茫多的其他函数,那应该也是做特征点检测的。先结合我们的例子知道这些东西是干嘛的比较重要:
cvCanny(in, out, lowThresh, highThresh, aperture);
我们为了把这个函数跑起来,做了一个doCanny,以下函数声明:
IplImage* doCanny(IplImage* in,double lowThresh,double highThresh,double aperture)
out是用来输出的图片指针。
/*
输入了一个图片参数 和3个double类型的参数,具体的可能要看看canny参数的实现过程。然后这个就比较往下抠了:F:opencv249opencvsourcesmodulesimgprocsrc去这个里面找到canny.cpp用个什么的打开之后就可以看人家怎么实现的了,然后就需要借助一些文献,以搞清每个地方都是干什么用的。这个其实就是算法,算法的目的就是检测边缘,算法的实现就是这些东西,要想看懂得看文献。然后就是所谓的算法优化了,我就真特么操蛋,一进去某公司问你会算法么?是水仙花数么?~还是梅森素数?随便搞一个小学生的数学题都需要很强的算法才能做出来。解决的问题不一样,问题的领域不一样,想问题的方法不一样,要怎么才能把“会”字说出口?就跟问你会做饭一样么?好比你很会做饭了,但是现在要你去中国做饭,当然你会的是美国的各种几分熟牛排的做法,去了中国,生的不好吃,要不没味儿,要不吃不惯要不太硬了,要不咬不动,你觉得你还有被利用的价值么?没有啊!你只会美国的那套做饭方式,中国没人吃,你这算法没人用。或者同样是边缘检测,算法优化的一种是从算法本身上优化,还有一种就是直接摒弃这种所谓的canny方式,直接换了方法。所以关于面试题你会不会算法,你可以算法优化么简直特么的。对于这种问题只能说需要时间,并且我以前做过什么样的算法实现了怎么样的优化。再牛逼的算法就需要灵感了。好了 我就是突然想起来吐个槽。特么的。
既然来了这个canny里面那么一定还有很多其他的检测方式。可以采用其他的特征点检测方式看看结果:我看到了 cvCornerHarris 试试它是如何使用的。哈哈 并没试出来,不过肯定是有办法弄的,可能是某些点不太符合规范,总之还是要快一些了解整个框架,所以不用扣这么太细。
*/
canny要求输入图片时单通道的,并且如果不是就强行转换成单通道的assert(in->nChannels = 1);所以合理的方式是在输入的时候就给一个单通道的图片。
通过
IplImage* img1 = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
创建图片,大小,深度信息8位,单通道。
cvConvertImage(img, img1, CV_BGR2GRAY);
把彩图转换成灰图。
然后传入。
创建输出图片信息。
IplImage* out = cvCreateImage(cvGetSize(in), IPL_DEPTH_8U, 1);
大小,深度,单通道
做canny传递参数,现在还不知道这些参数是干什么用的,总之传对了先跑起来再说。
然后看看效果,然后调参数看看每个参数具体对应什么结果。
后面这几个比较坑,感觉根本都没说明白。但是关于函数调用也不该是在这里学的。优化,也不在这里展开了,往下看是从摄像机读入数据:
例2-9:
#include "highgui.h" int main(int argc,char ** argv){ CvCapture* capture; if (argc == 1){ capture = cvCreateCameraCapture(0); } else{ capture = cvCreateFileCapture(argv[1]); } assert(capture != NULL); cvNamedWindow("Example6", CV_WINDOW_AUTOSIZE); IplImage* frame; while (1){ frame = cvQueryFrame(capture);//单帧图像赋值, if (!frame) break;//如果单帧图像没有值,为真,就退出 cvShowImage("Example6", frame); char c = cvWaitKey(33);//在33毫秒内等待用户输入 字符。 if (c == 27) break;//如果该字符是 ESC-> 27 的话,就退出 //按住ESC是正确终止,按 ctrl+c的效果反而不大好。 } cvReleaseCapture(&capture); cvDestroyWindow("Example6"); }
通过参数可以发现cvCreateCameraCaptrue(0)是打开摄像头
cvCreateFileCapture(路径) 是打开视频文件。
然后是这个饱受诟病的
例2-10一个完整的程序用来实现读入一个彩色视频文件并以灰度格式输出这个视频文件
我输出了多少次都是一个很坑爹的。。。效果,开始对logpolar无感,越读越感觉polar是极点的意思,然后抄了3遍之后,得到了一个结果,瞬间觉得这个怎么特么的是极坐标啊。
所以假设题目是正确的:也就是读入一个彩色视频灰度输出的话:
#include "highgui.h" #include "cv.h" int main(int argc,char **argv){ //得到视频 CvCapture *capture = 0; capture = cvCreateFileCapture("F:\opencv练习代码\opencv教程7——写入avi文件\Debug\n.mp4"); //得到路径显示图像 if (!capture) return -1; //从capture里面得到每一帧的图像 IplImage *bgr_frame = cvQueryFrame(capture);//通过视频得到图像 double fps = cvGetCaptureProperty(capture, CV_CAP_PROP_FPS);//帧率,就是一个函数用以得到视频的实行,这里面得到的是fps /*什么是fps http://baike.baidu.com/link?url=Z1KZNXGP-UB05rJOsom0bv-VDOYG2v6-dylVHBZ8Eo22Vy_klv6GdSo0H3SYfSD5hmCkM2hNQB0gdFmBlTvKM_ */ CvSize size = cvSize( (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH), (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT)); CvVideoWriter *writer = cvCreateVideoWriter("D:\b.avi", CV_FOURCC('M', 'J', 'P', 'G'), fps, size,0);//这里0是灰图,1是彩图。所以不写时候就按默认的1进行处理,然后再写彩图就写不出来。路径,编码格式,帧率,单张frame大小,按灰图输出。 IplImage* logpolar_frame = cvCreateImage(size, IPL_DEPTH_8U, 1); while ((bgr_frame = cvQueryFrame(capture)) != NULL) {//这个写法还是比较经典的,在字符流的部分讲述比较多。 cvCvtColor(bgr_frame,logpolar_frame,CV_BGR2GRAY);//转换成灰图 cvWriteFrame(writer, logpolar_frame);//写入到输出文件 } //释放资源 cvReleaseVideoWriter(&writer); cvReleaseImage(&logpolar_frame); cvReleaseCapture(&capture); return 0; }
书上的代码应该是无误的。写出来是按照极坐标弄出来的扭曲的视频文件。
习题:
可能这个书的出现真的是第一版。因为没有习题1的opencv.sln...
所以从第二题开始做吧:
然后找了半晌,也没找到这个 所谓的lkdemo.c,所以肯定了这本书的出现时机应该是第一版本,并且没有继续优化,F:opencv249opencvsourcessamplescpp在这个路径下面找到
lkdemo.cpp,然后打开复制全文,到vs2013里面新建工程,项目,粘贴,然后按照我们这篇博文开篇地方的配制方法,配置包含目录, 库目录,链接器里面的链接加载项,然后就好了。然后编译,出现结果。
3、4基本上是一个题了,按照我们前面默写的感觉,最起码要能自己把这些注释写出来,自己写一个总结,然后三四题就能比较容易的写出来了。
这个并不能缩放,应该是还有什么点,没有分析透彻。或者是有什么函数不太清楚。
第五题并没有做出来。
#include "highgui.h" #include "cv.h" int main(int argc ,char **argv){ CvCapture *capture = cvCreateCameraCapture(0); IplImage* frame; CvSize size = cvSize( (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH) / 2, (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT) / 2); CvVideoWriter* writer = cvCreateAVIWriter(argv[1], 6, 24, size);//后面还有一个参数,1是彩图,0是灰图。默认彩图 cvNamedWindow("Example6", CV_WINDOW_AUTOSIZE); while (1){ frame = cvQueryFrame(capture); cvWriteFrame(writer, frame); cvShowImage("Example6", frame); char c = cvWaitKey(33);//在33毫秒内等待用户输入 字符。事实上小于41.67都是流程的, if (c == 27) break; } cvReleaseVideoWriter(&writer); cvReleaseImage(&frame); cvDestroyAllWindows(); //cvReleaseCapture(&g_capture); //感觉程序没有正常结束啊。 //还好 总之视频是关掉了的。 return 0; }