【opencv實戰】哈哈鏡

shuiyixin發表於2018-07-23

目錄

一、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);
		}
	}
}

 

相關文章