數學之美之分形——C++及OpenCV實現Julia集和Mandelbrot集繪製

搞了一天,終於全部搞定了,久久驚歎於分形的美而不能自拔……

先來簡單介紹下Julia集和Mandelbrot集:(來自這裏

     曼德勃羅特(Mandelbrot)集是人類有史以來做出的最奇異,最瑰麗的幾何圖形.曾被稱爲“上帝的指紋”。 

       這個點集均出自公式:Zn+1=(Zn)^2+C,

  這是一個迭代公式,式中的變量都是複數.這是一個大千世界,從他出發可以產生無窮無盡美麗圖案,他是曼德勃羅特教授在二十世紀七十年代發現的.你看上圖中,有的地方象日冕,有的地方象燃燒的火焰,只要你計算的點足夠多,不管你把圖案放大多少倍,都能顯示出更加複雜的局部.這些局部既與整體不同,又有某種相似的地方,好像着夢幻般的圖案具有無窮無盡的細節和自相似性.曼德勃羅特教授稱此爲"魔鬼的聚合物".爲此,曼德勃羅特在1988年獲得了"科學爲藝術大獎".

      更詳盡的解釋請看維基百科

      好了,讓我們邊講代碼邊體會吧:我們要做這樣一個程序,它

  1.     可以快速計算分形
  2.     可以彩色顯示
  3.     可以鼠標畫矩形放大
  4.     可以將觀察過程的圖片存起來

      1、首先,做一些準備工作:

#include <cv.h>
#include <highgui.h>
#include <cxcore.h>

#define IMG_8UB(img,x,y) ((uchar*)(img->imageData + img->widthStep * (y)))[3 * (x)] 
#define IMG_8UG(img,x,y) ((uchar*)(img->imageData + img->widthStep * (y)))[3 * (x) + 1]
#define IMG_8UR(img,x,y) ((uchar*)(img->imageData + img->widthStep * (y)))[3 * (x) + 2]
#define IMG8U(img,x,y) ((uchar*)(img->imageData + img->widthStep * (y)))[(x)] 

CvPoint pt1 = {-1,-1};//兩個用來記錄鼠標點擊和放鬆的點
CvPoint pt2 = {-1,-1};
IplImage* fractal;
IplImage* fcopy;//用來畫矩形
int width = 600;//圖像寬和高
int height = 400;
double XMax = 2.5;//複平面的最大座標
double XMin = -2.5;
double YMax = 2.5;
double YMin = -2.5;

#define MAX_COLOR 256//用來記錄配色,畢竟黑白的不是那麼好看
int B[MAX_COLOR];
int G[MAX_COLOR];
int R[MAX_COLOR];

struct Complex//複數
{
	double real;
	double img;
};

char* name[100];//即時輸出觀察時候的圖片的名字
int fileIndex = 0;//標號

2.初始化文件

char* itoa(int value)
{
	char* tmp = (char*)calloc(11,sizeof(char));
	char reverse[15];
	int index = 0;
	do
	{
		reverse[index++] = value % 10 + '0';
		value /= 10;
	}while(value);
	int size = index - 1;
	while(index--)
	{
		tmp[size - index] = reverse[index];
	}
	tmp[++size] = '.';
	tmp[++size] = 'j';
	tmp[++size] = 'p';
	tmp[++size] = 'g';
	tmp[++size] = '\0';
	return tmp;
}
void initFileName()//直接用數字命名圖片了
{
	for(int i = 0;i < 100;i++)
		name[i] = itoa(i);
}

3初始化圖片彩色像素庫

分形配色是最麻煩的了,這裏配了一個還算可以看的顏色系,別介意~如果你有好的配色方案,請分享下,謝了~^_^

void initColor()//這裏需要說明下,雖然寫的是bgr,但是這裏是當作HSV來初始化的,然後圖片繪製完成後直接轉下空間即可
{
	for(int i = 0;i < MAX_COLOR;i++)
	{
		B[i] = i * 4 % 256;
		G[i] = 0.7 * 255.0;
		R[i] = 255.0 * (1.0 - i / 255.0 * i / 255.0 / 1.2);
	}
}
4、繪製分形:

void drawPic()
{
	double deltaX = (XMax - XMin) / width;
	double deltaY = (YMax - YMin) / height;
	int max_iterations = 256;//最大迭代次數
	double max_size = 4.0;//這裏是2的平方,爲什麼?建議看看維基百科的介紹~
	for(int row = 0;row < height;row++)
	{
		for(int col = 0;col < width;col++)
		{
			int color = 0;
			Complex c,z;
			z.real = 0;//這裏是Mandelbrot集,下面被註釋掉的是Julia集
			z.img = 0;
			c.real  = XMin + col * deltaX;
			c.img = YMin + row * deltaY;
			//z.real = XMin + col * deltaX;
			//z.img = YMin + row * deltaY;
			//c.real = 0.285;
			//c.img = 0.01;

			while((color < max_iterations) && ((z.img * z.img + z.real * z.real) < max_size))
			{	
				double tmp = z.real * z.real - z.img * z.img + c.real;
				z.img = z.img * z.real + z.real * z.img + c.img;
				z.real = tmp;
				color++;
			}
			if(color >= max_iterations)
				color = 255;
			color %= MAX_COLOR;
			IMG_8UB(fractal,col,row) = B[color];
			IMG_8UG(fractal,col,row) = G[color];
			IMG_8UR(fractal,col,row) = R[color];
		}
	}
	cvCvtColor(fractal,fractal,CV_HSV2BGR);//將HSV空間轉換爲BGR,方便顯示
	cvShowImage("Fractal",fractal);
	cvSaveImage(name[fileIndex++],fractal);//記錄查看過程

	cvWaitKey(0);
}

5、鼠標事件:

void on_mouse( int event, int x, int y, int flags, void *param  =  NULL)
{
	if( !fractal)
		return;

	if( event == CV_EVENT_LBUTTONDOWN)
	{
		pt1 = cvPoint(x,y);
	}
	else if(  event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON) )
	{
		pt2 = cvPoint(x,y);
		int dx = abs(pt2.x - pt1.x);
		int dy = abs(pt2.y - pt1.y);

		if(pt1.x > 0 && pt1.y > 0 
			&& pt2.x > 0 && pt2.y > 0
			&& dx > 5 && dy > 5)
		{
			cvSaveImage(name[fileIndex++],fcopy);//記錄查看過程
			double DX = XMax - XMin;//接下來轉換放大的座標
			double DY = YMax - YMin;
			double offX = DX / width;
			double offY = DY / height;
			if(pt1.x < pt2.x)
			{		    
				XMax = offX * pt2.x + XMin;
				XMin = offX * pt1.x + XMin;
			}
			else
			{			
				XMax = offX * pt1.x + XMin;
				XMin = offX * pt2.x + XMin;
			}
			if(pt1.y < pt2.y)
			{
				YMax = offY * pt2.y + YMin;
				YMin = offY * pt1.y + YMin;
			}
			else
			{
				YMax = offY * pt1.y + YMin;
				YMin = offY * pt2.y + YMin;	
			}
			pt1 = cvPoint(-1,-1);//注意重新賦值,否則鼠標移動時還會進來,造成重複計算
			pt2 = cvPoint(-1,-1);
			printf("XMax:%.15lf XMin:%.15lf\nYMax:%.15lf YMin:%.15lf\n",XMax,XMin,YMax,YMin); 
			drawPic();//重新繪製分形
		}
	}
	else if(event == CV_EVENT_RBUTTONDOWN)//右鍵取消放大,重新選取放大區域
	{
		pt1 = cvPoint(-1,-1);
		pt2 = cvPoint(-1,-1);
	}
	else if( event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON) )//在選取過程中畫矩形
	{
		pt2 = cvPoint(x,y);
		if(pt1.x > 0 && pt1.y > 0 
			&& pt2.x > 0 && pt2.y > 0
			&& abs(pt2.x - pt1.x) > 5
			&& abs(pt2.y - pt1.y) > 5)
		{
			cvCopy(fractal,fcopy);
			cvRectangle(fcopy,pt1,pt2,cvScalar(255,255,255));
			cvShowImage( "Fractal", fcopy);
		}
	}
}
6、主函數:

int main()
{
	cvNamedWindow("Fractal",1);
	cvSetMouseCallback( "Fractal", on_mouse, 0 );//註冊鼠標事件
	fractal = cvCreateImage(cvSize(width,height),8,3);
	fcopy = cvCreateImage(cvSize(width,height),8,3);
	initFileName();//初始化
	initColor();

	drawPic();

	cvReleaseImage(&fractal);
	cvReleaseImage(&fcopy);
	cvDestroyWindow("Fractal");
	for(int i = 0;i < 100;i++)
		free(name[i]);
	return 0;
}


今天花了一天的時間在這上面,但是,很值得~

讓我來看看這些美麗的分形吧~


下面是Julia集:

c = 0.285, 0.01

c = 0.285, 0

c = -0.8, 0.156

c = -0.835, -0.2321

c = -0.70176, -0.3842

c = 0.45, -0.1428


今天的收穫還是挺大的,起碼,這些美麗無比的畫,讓我真正體會到了數學之美。


不過還是有點遺憾,畢竟沒有把顏色配好,不是非常好看,比起維基百科上的那些圖片簡直沒法比。


還有就是對這兩個集合沒有深入理解,有時間還要研究下。


STL的complex實在太操蛋了,太慢了,我用complex要用將近一分鐘才能出一幅畫!!!


據說用多線程能加快計算,看來我得學下了~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章