本文共 8714 字,大约阅读时间需要 29 分钟。
目录
因为要做一个项目,为了实现他的趣味性,所以想应用图像处理做一些东西,在上次完成卡通化之后,又了解了哈哈镜效果,想自己实现,从网上找了好多教程,都是以前的opencv版本的代码,在opencv3.0及以上版本已经不支持使用了。
可能最新学习opencv的小伙伴不了解什么是“以前的opencv版本”。所以我先简单介绍一下。
OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
OpenCV用C++语言编写,它的主要接口也是C++语言,但是依然保留了大量的C语言接口。所有新的开发和算法都是用C++接口。一个使用CUDA的GPU接口也于2010年9月开始实现。
其他的一些介绍就在这里不多说了,大家在网上也能找到。我主要再说一下在前面我说到的以前的opencv版本和新版本的差别。这个差别不是opencv2.0,opencv2.3.4,opencv3.0.0,opencv3.1.0,opencv3.4.0等等这些版本之间的差别。大家会发现,大家现在在学习opencv时,建立图像,用的时C++语言中的Mat类,最初的opencv是用C语言编写的,C语言是没有类的,那用C语言用的自然就是结构体。所以接下来我讲一下opencv结构体的表示。
在OpenCV中IplImage是表示一个图像的结构体,也是从OpenCV1.0到目前最为重要的一个结构;在之前的图像表示用IplImage,而且之前的OpenCV是用C语言编写的,提供的接口也是C语言接口;
英文注释版结构体如下:
typedef struct _IplImage{ int nSize; /* sizeof(IplImage) */ int ID; /* version (=0)*/ int nChannels; /* Most of OpenCV functions support 1,2,3 or 4 channels */ int alphaChannel; /* Ignored by OpenCV */ int depth; /* Pixel depth in bits: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F are supported. */ char colorModel[4]; /* Ignored by OpenCV */ char channelSeq[4]; /* ditto */ int dataOrder; /* 0 - interleaved color channels, 1 - separate color channels. cvCreateImage can only create interleaved images */ int origin; /* 0 - top-left origin, 1 - bottom-left origin (Windows bitmaps style). */ int align; /* Alignment of image rows (4 or 8). OpenCV ignores it and uses widthStep instead. */ int width; /* Image width in pixels. */ int height; /* Image height in pixels. */ struct _IplROI *roi; /* Image ROI. If NULL, the whole image is selected. */ struct _IplImage *maskROI; /* Must be NULL. */ void *imageId; /* " " */ struct _IplTileInfo *tileInfo; /* " " */ int imageSize; /* Image data size in bytes (==image->height*image->widthStep in case of interleaved data)*/ char *imageData; /* Pointer to aligned image data. */ int widthStep; /* Size of aligned image row in bytes. */ int BorderMode[4]; /* Ignored by OpenCV. */ int BorderConst[4]; /* Ditto. */ char *imageDataOrigin; /* Pointer to very origin of image data (not necessarily aligned) - needed for correct deallocation */}IplImage;
中文注释版结构体如下:
typedef struct _IplImage { int nSize; /* IplImage大小 */ int ID; /* 版本 (=0)*/ int nChannels; /* 大多数OPENCV函数支持1,2,3 或 4 个通道 */ int alphaChannel; /* 被OpenCV忽略 */ int depth; /* 像素的位深度: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U, IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F 可支持 */ char colorModel[4]; /* 被OpenCV忽略 */ char channelSeq[4]; /* 同上 */ int dataOrder; /* 0 - 交叉存取颜色通道, 1 - 分开的颜色通道. cvCreateImage只能创建交叉存取图像 */ int origin; /* 0 - 顶—左结构, 1 - 底—左结构 (Windows bitmaps 风格) */ int align; /* 图像行排列 (4 or 8). OpenCV 忽略它,使用 widthStep 代替 */ int width; /* 图像宽像素数 */ int height; /* 图像高像素数*/ struct _IplROI *roi;/* 图像感兴趣区域. 当该值非空只对该区域进行处理 */ struct _IplImage *maskROI; /* 在 OpenCV中必须置NULL */ void *imageId; /* 同上*/ struct _IplTileInfo *tileInfo; /*同上*/ int imageSize; /* 图像数据大小(在交叉存取格式下imageSize=image->height*image->widthStep),单位字节*/ char *imageData; /* 指向排列的图像数据 */ int widthStep; /* 排列的图像行大小,以字节为单位 */ int BorderMode[4]; /* 边际结束模式, 被OpenCV忽略 */ int BorderConst[4]; /* 同上 */ char *imageDataOrigin; /* 指针指向一个不同的图像数据结构(不是必须排列的),是为了纠正图像内存分配准备的 */ } IplImage;
IplImage结构体是整个OpenCV函数库的基础,在定义该结构变量时需要用到函数cvCreatImage,变量定义方法如下:
//定义一个IplImage指针变量src,图像的大小是200×300,图像颜色深度8位,3通道图像。IplImage* src = "/cvCreateImage"(cvSize(200, 300), IPL_DEPTH_8U, 3);//定义一个IplImage指针变量src,图像的大小是200×300,图像颜色深度8位,单通道图像。IplImage* src = "/cvCreateImage"(cvSize(200, 300), IPL_DEPTH_8U, 1);
由于定义的src是一个指针变量,所以通过src来调用函数时,采用的是指向的方式:
//下面是两种图像数据存取方式的例子://1.直接存取 : (效率高, 但容易出错)// 对单通道字节图像 :IplImage* src = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1);((uchar *)(src->imageData + i*src->widthStep))[j] = 111;// 对多通道字节图像:IplImage* src = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 3);((uchar *)(src->imageData + i*src->widthStep))[j*src->nChannels + 0] = 111; // B((uchar *)(src->imageData + i*src->widthStep))[j*src->nChannels + 1] = 112; // G((uchar *)(src->imageData + i*src->widthStep))[j*src->nChannels + 2] = 113; // R // 对多通道浮点图像:IplImage* src = cvCreateImage(cvSize(640, 480), IPL_DEPTH_32F, 3);((float *)(src->imageData + i*src->widthStep))[j*src->nChannels + 0] = 111; // B((float *)(src->imageData + i*src->widthStep))[j*src->nChannels + 1] = 112; // G((float *)(src->imageData + i*src->widthStep))[j*src->nChannels + 2] = 113; // R //2.用指针直接存取 : (在某些情况下简单高效) // 对单通道字节图像 :IplImage* src = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1);int height = src->height;int width = src->width;int step = src->widthStep / sizeof(uchar);uchar* data = (uchar *)src->imageData;data[i*step + j] = 111;// 对多通道字节图像:IplImage* src = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 3);int height = src->height;int width = src->width;int step = src->widthStep / sizeof(uchar);int channels = src->nChannels;uchar* data = (uchar *)src->imageData;data[i*step + j*channels + k] = 111;// 对多通道浮点图像(假设用4字节调整) :IplImage* src = cvCreateImage(cvSize(640, 480), IPL_DEPTH_32F, 3);int height = src->height;int width = src->width;int step = src->widthStep / sizeof(float);int channels = src->nChannels;float * data = (float *)src->imageData;data[i*step + j*channels + k] = 111;
具体介绍内容详解我的:。里面有Mat简介,常用成员,构造函数以及三种图像类型格式的转换。
哈哈镜是表面凸凹不平的镜面,反映人像及物件的扭曲面貌,令人发笑,故名叫哈哈镜。哈哈镜的原理是曲面镜引起的不规则光线反射与聚焦,做成散乱的影像。镜面扭曲的情况不同,成像的效果也会相异。 常见的变换效果有高矮胖瘦四种效果,镜面材质有金属哈哈镜,玻璃哈哈镜等。
对应到物理中,哈哈镜其实是光的折射,可以理解为数学中的映射,不同的映射会有不同的效果,如线性映射会产生放大缩小的感觉,凸函数则会是凸透镜,凹函数就是凹透镜,原则上,不同的函数就不会产生不同的结果。
所以如果希望通过opencv来做哈哈镜,就需要找到一个对应的映射,让图像的像素扭曲,从而实现哈哈效果。在这里,实现了放大镜和缩小镜。
我希望实现的是实时将视频图像卡通化,所以需要通过opencv调用摄像头,并对其进行一系列设置。在这里,采用了最简单的调用摄像头的方式:
VideoCapture capture;capture.open(0);
获取到每一帧的图像后,需要对图像做一定的处理,因为用了两种方式做处理分别得到:放大镜,缩小镜。所以在处理之前加一个整形变量,允许用户输入,自由选择处理方式,为了防止用户非法输入,我设置循环做判断。输入合法后才允许执行下面的代码。并通过Switch语句设置两种处理方式。代码如下:
int mode = -1;//动画处理模式 cout << "请输入类型:"; cin >> mode; while (mode<0 || mode >= 2) { cout << "处理模式输入错误,请重新输入:"; cin >> mode; } switch (mode) { case 0: while (1) { capture >> hahaFrame; hahaFrame.copyTo(img); magnifyGlass(hahaFrame,img); imshow("【放大镜】", img); waitKey(30); } break; case 1: while (1) { capture >> hahaFrame; hahaFrame.copyTo(img); compressGlass(hahaFrame,img); imshow("【压缩镜】", img); waitKey(30); } break; default: break; }
接下来就是最核心的算法,即映射了。
在前面我们说到,所谓哈哈镜,就是图像像素点位置的变化,所以我们要获取到每个像素点的像素值,然后对像素点做操作。
void magnifyGlass(Mat hahaFrame,Mat img) { //【1】凸透镜 int width = hahaFrame.cols; int heigh = hahaFrame.rows; Point center(width / 2, heigh / 2); int R = sqrtf(width*width + heigh*heigh) / 2; //直接关系到放大的力度,与R成正比; for (int y = 0; y < heigh; y++) { uchar *img_p = img.ptr(y);//定义一个指针,指向第y列,从而可以访问行数据。 for (int x = 0; x < width; x++) { int dis = norm(Point(x, y) - center);//获得当前点到中心点的距离 if (dis < R)//设置变化区间 { int newX = (x - center.x)*dis / R + center.x; int newY = (y - center.y)*dis / R + center.y; img_p[3 * x] = hahaFrame.at (newY, newX * 3); img_p[3 * x + 1] = hahaFrame.at (newY, newX * 3 + 1); img_p[3 * x + 2] = hahaFrame.at (newY, newX * 3 + 2); } } }}
void compressGlass(Mat hahaFrame,Mat img) { //【2】凹透镜 int width = hahaFrame.cols; int heigh = hahaFrame.rows; Point center(width / 2, heigh / 2); for (int y = 0; y(y); for (int x = 0; x = width) newX = width - 1; if (newY<0) newY = 0; else if (newY >= heigh) newY = heigh - 1; img_p[3 * x] = hahaFrame.at (newY, newX * 3); img_p[3 * x + 1] = hahaFrame.at (newY, newX * 3 + 1); img_p[3 * x + 2] = hahaFrame.at (newY, newX * 3 + 2); } }}
转载地址:http://afyni.baihongyu.com/