【opencv实战】哈哈镜

目录

一、opencv的前身后世

1、简介

2、IplImage介绍

3、Mat介绍

二、哈哈镜介绍

1、原理

2、实现

         3、凸透镜算法

4、凹透镜算法


因为要做一个项目,为了实现他的趣味性,所以想应用图像处理做一些东西,在上次完成卡通化之后,又了解了哈哈镜效果,想自己实现,从网上找了好多教程,都是以前的opencv版本的代码,在opencv3.0及以上版本已经不支持使用了。

可能最新学习opencv的小伙伴不了解什么是“以前的opencv版本”。所以我先简单介绍一下。

一、opencv的前身后世

1、简介

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结构体的表示。

2、IplImage介绍

在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;

3、Mat介绍

具体介绍内容详解我的:【opencv学习笔记】004之Mat对象 。里面有Mat简介,常用成员,构造函数以及三种图像类型格式的转换。

二、哈哈镜介绍

1、原理

哈哈镜是表面凸凹不平的镜面,反映人像及物件的扭曲面貌,令人发笑,故名叫哈哈镜。哈哈镜的原理是曲面镜引起的不规则光线反射与聚焦,做成散乱的影像。镜面扭曲的情况不同,成像的效果也会相异。 常见的变换效果有高矮胖瘦四种效果,镜面材质有金属哈哈镜,玻璃哈哈镜等。

对应到物理中,哈哈镜其实是光的折射,可以理解为数学中的映射,不同的映射会有不同的效果,如线性映射会产生放大缩小的感觉,凸函数则会是凸透镜,凹函数就是凹透镜,原则上,不同的函数就不会产生不同的结果。

所以如果希望通过opencv来做哈哈镜,就需要找到一个对应的映射,让图像的像素扭曲,从而实现哈哈效果。在这里,实现了放大镜和缩小镜。

2、实现

我希望实现的是实时将视频图像卡通化,所以需要通过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;
	}

接下来就是最核心的算法,即映射了。

在前面我们说到,所谓哈哈镜,就是图像像素点位置的变化,所以我们要获取到每个像素点的像素值,然后对像素点做操作。

3、凸透镜算法

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<uchar>(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<uchar>(newY, newX * 3);
				img_p[3 * x + 1] = hahaFrame.at<uchar>(newY, newX * 3 + 1);
				img_p[3 * x + 2] = hahaFrame.at<uchar>(newY, newX * 3 + 2);

			}
		}
	}
}

4、凹透镜算法

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<heigh; y++)
	{
		uchar *img_p = img.ptr<uchar>(y);
		for (int x = 0; x<width; x++)
		{
			double theta = atan2((double)(y - center.y), (double)(x - center.x)); 
			int R = sqrtf(norm(Point(x, y) - center)) * 8; //直接关系到挤压的力度,与R成反比;  
			int newX = center.x + (int)(R*cos(theta));
			int newY = center.y + (int)(R*sin(theta));

			if (newX<0) 
				newX = 0;
			else if (newX >= width) 
				newX = width - 1;

			if (newY<0) 
				newY = 0;
			else if 
				(newY >= heigh) newY = heigh - 1;

			img_p[3 * x] = hahaFrame.at<uchar>(newY, newX * 3);
			img_p[3 * x + 1] = hahaFrame.at<uchar>(newY, newX * 3 + 1);
			img_p[3 * x + 2] = hahaFrame.at<uchar>(newY, newX * 3 + 2);
		}
	}
}

 

猜你喜欢

转载自blog.csdn.net/shuiyixin/article/details/81155444