目录
一、opencv的前身后世
1、简介
2、IplImage介绍
3、Mat介绍
二、哈哈镜介绍
1、原理
2、实现
3、凸透镜算法
4、凹透镜算法
因为要做一个项目,为了实现他的趣味性,所以想应用图像处理做一些东西,在上次完成卡通化之后,又了解了哈哈镜效果,想自己实现,从网上找了好多教程,都是以前的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;
具体介绍内容详解我的:【opencv学习笔记】004之Mat对象 。里面有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);
}
}
}