GLSL/C++ 實現濾鏡效果

入門效果之浮雕

"浮雕"圖象效果是指圖像的前景前向凸出背景。常見于一些紀念碑的雕刻上。要實現浮雕事實上很easy。我們把圖象的一個象素和左上方的象素進行求差運算。并加上一個灰度。這個灰度就是表示背景顏色。這里我們設置這個插值為128 (圖象RGB的值是0-255)。同一時候,我們還應該把這兩個顏色的差值轉換為亮度信息.否則浮雕圖像會出現彩色。

    "precision mediump float;      \n""varying vec2 v_texCoord;      \n""uniform sampler2D s_baseMap;     \n""uniform vec2 TexSize;            \n""void main()                 \n""{                             \n""   vec2 tex =v_texCoord;   \n""	vec2 upLeftUV = vec2(tex.x-1.0/TexSize.x,tex.y-1.0/TexSize.y);           \n""	vec4 curColor = texture2D(s_baseMap,v_texCoord);                           \n""	vec4 upLeftColor = texture2D(s_baseMap,upLeftUV);                  \n""	vec4 delColor = curColor - upLeftColor;                           \n""	float h = 0.3*delColor.x + 0.59*delColor.y + 0.11*delColor.z;                  \n""   vec4 bkColor = vec4(0.5, 0.5, 0.5, 1.0);                   \n""	gl_FragColor = vec4(h,h,h,0.0) +bkColor;                             \n""}                           \n";

C++版

void relief(int x,int y,BYTE *pre,BYTE *now,int Stride,int index){int upperleft;upperleft=(y-1)*Stride+4*(x-1);Vec4 Pixel,NowPixel;NowPixel.SetPixel(pre,index);Pixel.SetPixel(pre,upperleft);Pixel=NowPixel-Pixel;BeGray(Pixel);Pixel.GetPixelToNow(now,index);
}

???


入門效果之馬賽克

接下來我們完畢一個更加常見的效果—馬賽克.圖片的馬賽克就是把圖片的一個相當大小的區域用同一個點的顏色來表示.能夠覺得是大規模的減少圖像的分辨率,而讓圖像的一些細節隱藏起來, 比方電視中要秀一下某個罪犯的身材,卻又不能展示他的臉,這個時候我們就能夠給他的臉加一個馬賽克.

用HLSL代碼實現馬賽克是很easy的,可是相同的,我們須要一些額外的步驟,第一步就是先把紋理坐標轉換成圖像實際大小的整數坐標.接下來,我們要把圖像這個坐標量化---比方馬賽克塊的大小是8x8象素。那么我們能夠用下列方法來得到馬賽克后的圖像採樣值,如果[x.y]為圖像的整數坐標:

[x,y]mosaic = [ int(x/8)*8 , int(y/8)*8].

    "precision mediump float;    \n""varying vec2 v_texCoord;    \n""uniform sampler2D s_baseMap;\n""uniform vec2 TexSize;       \n""vec2 mosaicSize = vec2(8,8);\n""void main()                 \n""{                           \n""	vec2 intXY = vec2(v_texCoord.x*TexSize.x, v_texCoord.y*TexSize.y);   \n""	vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x,floor(intXY.y/mosaicSize.y)*mosaicSize.y);  \n""	vec2 UVMosaic = vec2(XYMosaic.x/TexSize.x,XYMosaic.y/TexSize.y);     \n""	vec4 baseMap = texture2D(s_baseMap,UVMosaic);                        \n""	gl_FragColor = baseMap;                                              \n""}                                                                       \n";

C++?版

void Mosaic(int x,int y,BYTE *pre,BYTE *now,int Stride,int index){Vec4 Pixel,Pixel_UpperLeft;Pixel.SetPixel(pre,index);if (x%8==0&&y%8==0){Pixel.GetPixelToNow(now,index);} else{int tmpX,tmpY;tmpX=x/8*8;tmpY=y/8*8;int index_UpperLeft;index_UpperLeft=tmpY*Stride+4*(tmpX);Pixel_UpperLeft.SetPixel(pre,index_UpperLeft);Pixel_UpperLeft.GetPixelToNow(now,index);}
}



讀者可能會發現這個馬賽克太普通了,確實它不夠新穎,以下我們來改良一下。我們希望達到這樣一個效果:馬賽克區域不是方的,而是圓的,圓形區域以外,我們用圖像原來的顏色覆蓋。這樣我們須要改變一下代碼。

首先求出原來馬賽克區域的正中心(原來是左上角):然后計算圖像採樣點到這個中心的距離,假設在馬賽克圓內。就用區域的中心顏色,否則就用原來的顏色。

改良后的代碼例如以下。這里我們把馬賽克區域大小調節成16x16。這樣效果更明顯。

    "precision highp float;            \n""varying vec2 v_texCoord;            \n""uniform sampler2D s_baseMap;        \n""uniform vec2 TexSize;               \n""vec2 mosaicSize = vec2(8,8);      \n""void main()                         \n""{                                   \n""	vec2 intXY = vec2(v_texCoord.x*TexSize.x, v_texCoord.y*TexSize.y);    \n""	vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x,floor(intXY.y/mosaicSize.y)*mosaicSize.y) + 0.5*mosaicSize; \n""	vec2 delXY = XYMosaic - intXY;   \n""	float delL = length(delXY);      \n""	vec2 UVMosaic = vec2(XYMosaic.x/TexSize.x,XYMosaic.y/TexSize.y); \n""	vec4 _finalColor;                \n""	if(delL< 0.5*mosaicSize.x)       \n""		_finalColor = texture2D(s_baseMap,UVMosaic);  \n""	else                             \n""		_finalColor = texture2D(s_baseMap,v_texCoord);  \n""	gl_FragColor = _finalColor;      \n""}                                   \n"  ;

C++版

void Mosaic_Point(int x,int y,BYTE *pre,BYTE *now,int Stride,int index){Vec4 Pixel,Pixel_UpperLeft;Pixel.SetPixel(pre,index);int i,j;i=x%8;j=y%8;double dist=sqrt(double((4-i)*(4-i)+(4-j)*(4-j)));if (dist>4){Pixel.GetPixelToNow(now,index);} else{int tmpX,tmpY;tmpX=x/8*8;tmpY=y/8*8;int index_UpperLeft;index_UpperLeft=tmpY*Stride+4*(tmpX);Pixel_UpperLeft.SetPixel(pre,index_UpperLeft);Pixel_UpperLeft.GetPixelToNow(now,index);}
}


圖:? 改良后的馬賽克效果

l? 進階效果之銳化模糊

以上兩個效果相對照較簡單,姑且稱之為入門效果。 它并沒實用到太多數字圖像處理或者信號處理方面的知識。

接下來我們要介紹略微復雜一點的效果。第一個就是圖像的模糊和銳化。

圖像的模糊又成為圖像的平滑(smoothing),我們知道人眼對高頻成分是非常敏感的。假設在一個亮度連續變化的圖像中,突然出現一個亮點,那么我們非常easy察覺出來,類似的,假設圖像有個突然的跳躍—明顯的邊緣,我們也是非常easy察覺出來的。

這些突然變化的分量就是圖像的高頻成分。

人眼一般是通過低頻成分來辨別輪廓,通過高頻成分來感知細節的(這也是為什么照片分辨率低的時候,人們僅僅能辨認出照片的大概輪廓,而看不到細節)。可是這些高頻成分通常也包括了噪聲成分。圖像的平滑處理就是濾除圖像的高頻成分。

那么怎樣才干濾除圖像的高頻成分呢?我們先來介紹一下圖像數字濾波器的概念。

簡單通俗的來說,圖像的數字濾波器事實上就是一個n x n的數組(數組中的元素成為濾波器的系數或者濾波器的權重。n稱為濾波器的階)。

對圖像做濾波的時候。把某個像素為中心的nxn個像素的值和這個濾波器做卷積運算(也就是相應位置上的像素和相應位置上的權重的乘積累加起來),公式例如以下

?當中x , y 為當前正在處理的像素坐標。

通常情況下,我們濾波器的階數為3已經足夠了,用于模糊處理的3x3濾波器例如以下

?????? ?????????????????????????

經過這種濾波器。事實上就是等效于把一個像素和周圍8個像素一起求平均值,這是很合理的---等于把一個像素和周圍幾個像素攪拌在一起—自然就模糊了。


"precision mediump float; 							   \n""vec4 dip_filter(mat3 _filter, sampler2D _image, vec2 _xy, vec2 texSize)               \n""{                                                									  \n""	mat3 _filter_pos_delta_x=mat3(vec3(-1.0, 0.0, 1.0), vec3(0.0, 0.0 ,1.0) ,vec3(1.0,0.0,1.0));            \n""   mat3 _filter_pos_delta_y=mat3(vec3(-1.0,-1.0,-1.0),vec3(-1.0,0.0,0.0),vec3(-1.0,1.0,1.0));              \n""	vec4 final_color = vec4(0.0, 0.0, 0.0, 0.0);                                      \n""	for(int i = 0; i<3; i++)                                                          \n""	{                                                                                 \n""		for(int j = 0; j<3; j++)                                                      \n""		{                                                                             \n""			vec2 _xy_new = vec2(_xy.x + _filter_pos_delta_x[i][j], _xy.y + _filter_pos_delta_y[i][j]); \n""			vec2 _uv_new = vec2(_xy_new.x/texSize.x, _xy_new.y/texSize.y);            \n""			final_color += texture2D(_image,_uv_new) * _filter[i][j];                 \n""		}																			  \n""	}																				  \n""	return final_color;																  \n""}																					  \n""varying vec2 v_texCoord;															  \n""uniform vec2 TexSize;    															  \n""uniform sampler2D s_baseMap;														  \n""void main()																		  \n""{																					  \n""	vec2 intXY = vec2(v_texCoord.x * TexSize.x, v_texCoord.y * TexSize.y);   		  \n""	mat3 _smooth_fil = mat3(1.0/9.0,1.0/9.0,1.0/9.0,										  \n""							1.0/9.0,1.0/9.0,1.0/9.0,										  \n""							1.0/9.0,1.0/9.0,1.0/9.0);										  \n""   vec4 tmp = dip_filter(_smooth_fil, s_baseMap, intXY, TexSize);""	gl_FragColor = tmp;                                                  			  \n""}																					  \n";

C++版

void dip_filter(int x,int y,BYTE *pre,BYTE *now,int Stride,int index)
{int dir_x[3][3]={-1,0,1,-1,0,1,-1,0,1};int dir_y[3][3]={1,1,1,0,0,0,-1,-1,-1};int tmpX,tmpY;Vec4 Pixel,PrePixel;double num=sqrt(2.0);double filter[3][3]={-1,0,1,-num,0,num,-1,0,1};Pixel.Clear();for (int i=0;i<3;i++){for (int j=0;j<3;j++){tmpX=x+dir_x[i][j];tmpY=y+dir_y[i][j];int tmp=tmpY*Stride+4*(tmpX);PrePixel.SetPixel(pre,tmp);Pixel+=PrePixel*filter[i][j];}}BeGray(Pixel);Pixel.GetPixelToNow(now,index);
}


以上的模糊濾波器稱為BOX濾波器,是最簡單的濾波器。假設考慮到離開中心像素的距離對濾波器系數的影響,我們通常採用更加合理的濾波器---高斯濾波器—一種通過2維高斯採樣得到的濾波器。它的模板例如以下:


非常easy看出來。離開中心越遠的像素。權重系數越小。

對于銳化操作,經常使用的銳化模板是拉普拉斯(Laplacian)模板,這個模板定義例如以下:


easy看出拉普拉斯模板的作法:先將自身與周圍的8個象素相減,表示自身與周圍象素的區別。再將這個區別加上自身作為新象素的灰度。

可見,假設一片暗區出現了一個亮點,那么銳化處理的結果是這個亮點變得更亮,這就增強了圖像的細節。

以下三副圖分別表示了經過BOX濾波。高斯濾波和拉普拉斯濾波后的圖像

??

BOX 模糊????????????????? 高斯模糊??????????????????? 拉普拉斯銳化

高斯模糊和拉普拉斯銳化效果的GLSL和BOX的代碼基本一致,就是filter的系數不同。這里不在列出。

通過這個兩個效果。我們介紹了圖像的濾波操作,這種操作,也成為模板操作。它實現了一種鄰域運算(Neighborhood Operation),即某個象素點的結果灰度不僅和該象素灰度有關。并且和其鄰域點的值有關。模板運算在圖象處理中常常要用到。能夠看出。它是一項很耗時的運算。有一種優化的方法稱為可分離式濾波,就是使用兩個pass來進行x/y方向分別濾波,能讓運算次數大大降低。

并且濾波器階數越高,優勢越明顯。

數字圖像濾波的時候,相同還須要注意邊界像素的問題,只是幸好,GLSL能讓邊界處理更加的透明和簡單。


l? 進階效果之描邊效果

相對浮雕效果來說。描邊(邊緣檢測)的代碼并不復雜多少,僅僅是在理論上相對來說略微復雜一點,并且效果看上去更加的討人喜歡一些。

我們知道 ,假設在圖像的邊緣處。灰度值肯定經過一個跳躍。我們能夠計算出這個跳躍,并對這個值進行一些處理,來得到邊緣濃黑的描邊效果。

首先我們能夠考慮對這個象素的左右兩個象素進行差值。得到一個差量。這個差量越大,表示圖像越處于邊緣,并且這個邊緣應該左右方向的,相同我們能得到上下方向和兩個對角線上的圖像邊緣。這樣我們構造一個濾波器


經過這個濾波器后。我們得到的是圖像在這個象素處的變化差值,我們把它轉化成灰度值,并求絕對值(差值可能為負),然后我們定義差值的絕對值越大的地方越黑(邊緣顯然是黑的)。否則越白,我們便得到例如以下的效果:

圖:鉛筆描邊效果

該效果的代碼例如以下(當中dip_filter函數代碼同上):

	"precision mediump float; 							   \n""vec4 dip_filter(mat3 _filter, sampler2D _image, vec2 _xy, vec2 texSize)               \n""{                                                									  \n""	mat3 _filter_pos_delta_x=mat3(vec3(-1.0, 0.0, 1.0), vec3(0.0, 0.0 ,1.0) ,vec3(1.0,0.0,1.0));            \n""   mat3 _filter_pos_delta_y=mat3(vec3(-1.0,-1.0,-1.0),vec3(-1.0,0.0,0.0),vec3(-1.0,1.0,1.0));              \n""	vec4 final_color = vec4(0.0, 0.0, 0.0, 0.0);                                      \n""	for(int i = 0; i<3; i++)                                                          \n""	{                                                                                 \n""		for(int j = 0; j<3; j++)                                                      \n""		{                                                                             \n""			vec2 _xy_new = vec2(_xy.x + _filter_pos_delta_x[i][j], _xy.y + _filter_pos_delta_y[i][j]); \n""			vec2 _uv_new = vec2(_xy_new.x/texSize.x, _xy_new.y/texSize.y);            \n""			final_color += texture2D(_image,_uv_new) * _filter[i][j];                 \n""		}																			  \n""	}																				  \n""	return final_color;																  \n""}																					  \n""varying vec2 v_texCoord;															  \n""uniform vec2 TexSize;    															  \n""uniform sampler2D s_baseMap;														  \n""void main()																		  \n""{																					  \n""	vec2 intXY = vec2(v_texCoord.x * TexSize.x, v_texCoord.y * TexSize.y);   		  \n""	mat3 _smooth_fil = mat3(-0.5,-1.0,0.0,										  \n""							-1.0,0.0,1.0,										  \n""							 0.0,1.0,0.5);										  \n""   vec4 delColor = dip_filter(_smooth_fil, s_baseMap, intXY, TexSize);           \n""   float deltaGray = 0.3*delColor.x + 0.59*delColor.y + 0.11*delColor.z;          \n""   if(deltaGray < 0.0) deltaGray = -1.0 * deltaGray;                             \n""   deltaGray = 1.0 - deltaGray;                                                  \n""	gl_FragColor = vec4(deltaGray,deltaGray,deltaGray,1.0);                        \n""}																					  \n";

C++版

void Gaussian_filter(int x,int y,BYTE *pre,BYTE *now,int Stride,int index)
{int dir_x[3][3]={-1,0,1,-1,0,1,-1,0,1};int dir_y[3][3]={1,1,1,0,0,0,-1,-1,-1};int tmpX,tmpY;Vec4 Pixel,PrePixel;double filter[3][3]={1.0/16,2.0/16,1.0/16,2.0/16,4.0/16,2.0/16,1.0/16,2.0/16,1.0/16};Pixel.Clear();for (int i=0;i<3;i++){for (int j=0;j<3;j++){tmpX=x+dir_x[i][j];tmpY=y+dir_y[i][j];int tmp=tmpY*Stride+4*(tmpX);PrePixel.SetPixel(pre,tmp);Pixel+=PrePixel*filter[i][j];}}//BeGray(Pixel);Pixel.GetPixelToNow(now,index);
}




上面演示的效果種用到的模板就是一種邊緣檢測器,在信號處理上是一種基于梯度的濾波器。又稱邊緣算子。梯度是有方向的,和邊沿的方向總是正交(垂直)的,在上面的代碼中,我們採用的就是一個梯度為45度方向模板。它能夠檢測出135度方向的邊沿。

以上是簡單的邊緣檢測算子。更加嚴格的。我們能夠採樣Sobel算子,Sobel 算子有兩個,一個是檢測水平邊沿的,還有一個是檢測垂直平邊沿的。相同,Sobel算子還有一種形式是各向同性Sobel算子。也有兩個,一個是檢測水平邊沿的,還有一個是檢測垂直邊沿的。各向同性Sobel算子和普通Sobel算子相比,它的位置加權系數更為準確,在檢測不同方向的邊沿時梯度的幅度一致。讀者能夠自行嘗試Sobel算子的效果,僅僅要改動pencil_filter的值就能夠了。

l? 高級效果之偽 HDR/Blow

HDR和Blow在如今主流游戲中是很時髦的效果。

所謂HDR就是高動態范圍的意思,我們知道。在普通的顯示器和位圖里。每通道都是8-bit,也就是說RGB分量的范圍都是0-255,這用來表示現實中的顏色顯然是遠遠不夠的,現實中的圖像的動態范圍遠遠大的多,那么怎樣在現有的顯示設備里盡可能的保持更大的動態范圍,并且讓它能更符合人眼的習慣就成了圖形學研究的一個熱點。通常真正的HDR的做法都是採用浮點紋理。把渲染運算的過程中,我們使用16bit的動態范圍來保存運算結果,然后我們對運算結果進行分析。求出這個圖像的中間灰度值,然后對圖像進行調整映射到LDR的設備中。可是這種算法有兩個很耗資源的過程,當中一個是浮點紋理,另外一個就是求圖像中間灰度(通常情況是把圖像不停的渲染到RenderTarget。每渲染一次,圖像大小縮小一半。直到縮小到1x1大,一個1024 x1024的圖像須要渲染10次!)。因此盡管HDR的效果很美麗。可是眼下還是僅僅有為數不多的游戲採用了這種算法,大部分都是採用的偽HDR+blow效果。

偽HDR效果一般是又一次調整圖像的亮度曲線。讓亮的更亮,暗的更暗一些,而Blow效果則是圖像的亮度擴散開來。產生非常柔的效果。

在這里我們採用一個二次曲線來又一次調整圖像的亮度,這個曲線的方程是

   x [ (2-4k) x + 4k-1 ).

K的取值范圍為0.5 – 2.0

經過這個公式調整以后,圖像上亮的區域將更加的亮。而且總體亮度會提高。那么接下來,我們怎樣使圖像的亮度擴散開來呢?一種可行的方法就是對場景圖像做一次downsample。

把它變成原來的1/4次大小,那樣就等于亮度往外擴散了4x4個象素的區域。

	"precision mediump float;     \n""varying vec2 v_texCoord;	  \n""uniform sampler2D s_baseMap; \n""uniform float k;					  \n""vec4 xposure(vec4 _color, float gray, float ex)  \n""{							  \n""	float b = (4.0*ex - 1.0);     \n""	float a = 1.0 - b;          \n""	float f = gray*(a*gray + b); \n""	return f*_color;		  \n""}							  \n""void main()				  \n""{							  \n""	vec4 _dsColor = texture2D(s_baseMap, v_texCoord); \n""	float _lum = 0.3*_dsColor.x + 0.59*_dsColor.y;    \n""	vec4 _fColor = texture2D(s_baseMap, v_texCoord);  \n""	gl_FragColor = xposure(_fColor, _lum, k);         \n""}                                                    \n";

C++版

void HDR(int x,int y,BYTE *pre,BYTE *now,int index,double k){Vec4 Pixel;double GrayPixel;Pixel.SetPixel(pre,index);GrayPixel=GetGray(Pixel)/255.0;double b=(4*k-1.0);double a=1-b;double f=GrayPixel*(a*GrayPixel+b);Pixel*=f;OverFlow(Pixel);Pixel.GetPixelToNow(now,index);
}


以下是原圖像和經過處理后圖像的對照:

??

原圖????????????????????? k = 1.1????????????????????? k = 1.6

圖:經過偽HDR+Blow處理過的圖像和原圖的對照

l? 高級效果之水彩化

真正的水彩效果在shader中是比較難實現的。它須要進行中值濾波后累加等一些操作,還須要處理NPR中的筆觸一類的概念。本文繞開這些概念,僅僅從視覺效果上能盡量模擬出水彩的畫的那種感覺來。

我們知道,水彩畫一個最大的特點是水彩在紙上流動擴散后會和周圍的顏色攪拌在一起,另外一個特點就是水彩一般會形成一個個的色塊,過渡不像照片那樣的平滑。

針對這兩個特點。

我們能夠設計這種一個算法來模擬水彩畫的效果。

我們能夠採用噪聲紋理的方式。既事先計算好一個nxn的隨機數數組,作為紋理傳遞給Pixel shader,這樣在Pixel Shader里我們就能獲得隨機數了。得到隨機數后,我們將隨機數映射成紋理坐標的偏移值,就能模擬出色彩的擴散了。典型的噪聲紋理是這個樣子的:

圖:噪聲紋理

接下來我們須要處理色塊,我們對顏色的RGB值分別進行量化,把RGB分量由原來的8bit量化成比特數更低的值。這樣顏色的過渡就會顯得不那么的平滑,而是會呈現出一定的色塊效果。

通過以上兩步處理后,我們得到的圖像依舊有非常多的細節。尤其是第一步處理中產生的非常多細節噪點,非常自然的我們就想到通過平滑模糊的方式來過濾掉這些高頻噪聲成分。

算法設計好了,接下來看看我們怎樣在RenderMonkey里實現這個算法。

類似上一個效果。我們須要兩個pass來完畢這個算法,第一個pass叫flow pass,模擬顏色的流動和處理顏色的量化。

第二個pass叫Gauss pass。也就是前面提到的高斯模糊算法。

我們的重點在第一個pass。

在模擬擴散的pass中,我們相同須要一個RenderTarget。以把結果保存在當中以便興許處理,然后還須要一個噪聲紋理來產生隨機數。詳細代碼例如以下:

	"precision mediump float;    \n""varying vec2 v_texCoord;    \n""uniform sampler2D s_baseMap;  \n""uniform vec2 TexSize;       \n""float _waterPower = 40.0;     \n""float _quatLevel = 5.0;       \n""vec4 quant(vec4 _cl, float n)  \n""{                            \n""	_cl.x = floor(_cl.x*255.0/n)*n/255.0;  \n""	_cl.y = floor(_cl.y*255.0/n)*n/255.0;  \n""	_cl.z = floor(_cl.z*255.0/n)*n/255.0;  \n""	return _cl;                            \n""}                                         \n""void main()                               \n""{                                         \n""	vec4 noiseColor = _waterPower*texture2D(s_baseMap,v_texCoord);           \n""	vec2 newUV =vec2 (v_texCoord.x + noiseColor.x/TexSize.x,v_texCoord.y + noiseColor.y/TexSize.y);  \n""	vec4 _fColor = texture2D(s_baseMap,newUV);                 \n""	gl_FragColor = quant(_fColor, 255.0/pow(2,_quatLevel));   \n""}                                                \n";

C++版

void WaterFilter(int x,int y,BYTE *pre,BYTE *now,int Stride,int index,double _quatLevel,double _waterPower,int width,int height){Vec4 nowPixel,RoundPixel;int indexRound;double Level;nowPixel.SetPixel(pre,index);int rx=rand()%2,ry=rand()%2;indexRound=(y+rx)*Stride+(x+ry)*4;RoundPixel.SetPixel(pre,indexRound);Level=255/pow(2,_quatLevel);RoundPixel.r=floor(RoundPixel.r/Level)*Level;RoundPixel.g=floor(RoundPixel.g/Level)*Level;RoundPixel.b=floor(RoundPixel.b/Level)*Level;OverFlow(RoundPixel);RoundPixel.GetPixelToNow(now,index);
}


代碼中的_quatLevel用來表示對圖像的量化比特數。值越小。色塊越明顯。比較合理的取值范圍是2-6。_waterPower則表示圖像顏色擴散范圍,取值范圍在8-64之間的效果比較好。

以下是經過水彩畫處理后的圖像:

?

?圖:水彩畫效果。左圖量化比特數為6比特,擴散范圍為20象素。

???????????????? 右圖量化比特數為5比特,擴散范圍為40象素


最后貼個C++版用到的函數

struct Vec4{double r,g,b,a;Vec4 (){this->r=0;this->g=0;this->b=0;this->a=0;}Vec4(double r,double g,double b){this->r=r;this->g=g;this->b=b;}Vec4 operator+(const double one) const{return Vec4(r+one,g+one,b+one);}Vec4 operator+(const Vec4& rhs)const{return Vec4(r+rhs.r,g+rhs.g,b+rhs.b);}Vec4& operator+=(const double one){r+=one;g+=one;b+=one;return *this;}Vec4& operator+=(const Vec4& rhs){r+=rhs.r;g+=rhs.g;b+=rhs.b;return *this;}Vec4 operator-(const double one) const{return Vec4(r-one,g-one,b-one);}Vec4 operator-(const Vec4& rhs)const{return Vec4(r-rhs.r,g-rhs.g,b-rhs.b);}Vec4& operator-=(const double one){r-=one;g-=one;b-=one;return *this;}Vec4 operator*(const double one) const{return Vec4(r*one,g*one,b*one);}Vec4& operator*=(const double one){r*=one;g*=one;b*=one;return *this;}Vec4 operator/(const double one) const{return Vec4(r/one,g/one,b/one);}Vec4& operator/=(const double one){r/=one;g/=one;b/=one;return *this;}void Clear(){r=g=b=0;}void SetPixel(BYTE *pre,int index){this->r=pre[index];this->g=pre[index+1];this->b=pre[index+2];this->a=pre[index+3];}void SetPixel(double RGB){this->r=RGB;this->g=RGB;this->b=RGB;}void SetPixel(double R,double G,double B){this->r=R;this->g=G;this->b=B;}void GetPixelToNow(BYTE *now,int index){now[index]=this->r;now[index+1]=this->g;now[index+2]=this->b;}
};
void OverFlow(Vec4 &Pixel){if (Pixel.r>255.0){Pixel.r=255;}else if (Pixel.r<0.0){Pixel.r=-Pixel.r;}if (Pixel.g>255.0){Pixel.g=255;}else if (Pixel.g<0.0){Pixel.g=-Pixel.g;}if (Pixel.b>255.0){Pixel.b=255;}else if (Pixel.b<0.0){Pixel.b=-Pixel.b;}
}
void OverFlow(double &Pixel){if (Pixel>255.0){Pixel=255;}else if (Pixel<0.0){Pixel=-Pixel;}
}
void ToGray(Vec4 &Pixel){double detaGray;detaGray=Pixel.r*0.3+Pixel.g*0.59+Pixel.b*0.11;Pixel.SetPixel(detaGray);OverFlow(Pixel);
}
double GetGray(Vec4 Pixel){double detaGray;detaGray=Pixel.r*0.3+Pixel.g*0.59+Pixel.b*0.11;OverFlow(detaGray);return detaGray;
}
void BeGray(Vec4 &Pixel){double detaGray;detaGray=Pixel.r*0.3+Pixel.g*0.59+Pixel.b*0.11;if (detaGray<0.0){detaGray=-detaGray;}else if (detaGray>255.0){detaGray=255;}detaGray=255-detaGray;Pixel.SetPixel(detaGray);
}
void Solve(){BYTE *pre = (BYTE*)m_srcImg.Scan0;BYTE *now = (BYTE*)m_copySrcImg.Scan0;//函數在這邊調用就可以。

?? ?int index=0,Stride=m_copySrcImg.Stride; ?? ?int width=m_pCopyImg->GetWidth(); ?? ?int height=m_pCopyImg->GetHeight(); ?? ?index=Stride+4; ?? ?for (int j=1;j<m_copySrcImg.Height-1;j++) ?? ?{ ?? ??? ?for (int i=1;i<m_copySrcImg.Width-1;i++) ?? ??? ?{ ?? ??? ??? //Gaussian_filter(i,j,pre,now,Stride,index); ?? ??? ??? ?//dip_filter(i,j,pre,now,Stride,index); ?? ??? ??? ?//relief(i,j,pre,now,Stride,index); ?? ??? ??? ?//Mosaic(i,j,pre,now,Stride,index); ?? ??? ??? ?//Mosaic_Point(i,j,pre,now,Stride,index); ?? ??? ??? ?//HDR(i,j,pre,now,index,1.1); ?? ??? ??? ?//WaterFilter(i,j,pre,now,Stride,index,10,40,width,height);?? ? ?? ??? ??? ?index+=4; ?? ??? ?} ?? ??? ?index+=8; ?? ?} ?? ?//調用函數。

}



轉載于:https://www.cnblogs.com/lxjshuju/p/7343873.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/454963.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454963.shtml
英文地址,請注明出處:http://en.pswp.cn/news/454963.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

cv mat的shape_pybind11—opencv圖像處理(numpy數據交換)

前言C opencv中圖像和矩陣的表示采用Mat類&#xff0c;比如imread()讀取的結果就是返回一個Mat對象。對于python而言&#xff0c;numpy 通常用于矩陣運算&#xff0c; 矩陣&#xff0c;圖像表示為numpy.ndarray類。因此&#xff0c;想要將python numpy.ndarray的數據傳遞到C op…

H.264算法的優化策略

文章來源&#xff1a; http://www.tichinese.com/Article/Video/200909/2150.html 編輯&#xff1a;小乙哥 1 代碼優化的主要方法 通過代碼移植能夠獲得在DSP上初步運行的代碼&#xff0c;但是它由于沒有考慮到DSP自身的硬件特點&#xff0c;不適合DSP強大的并行處理能力&#…

吃飯、睡覺、打星星之“打星星”!

大家見過這樣的星星么&#xff1f; 你想要多少就可以多少的星星&#xff01;&#xff01;&#xff01; 下面我們就來用奇妙的JavaScript來實現 首先我們要引入一個輸入包 let readline require("readline-sync");然后再讓客戶輸入數字&#xff0c;并將其存放起來con…

mysql 自動分表_Mysql Event 自動分表

create table TempComments Like dycomments;上述 SQL語句創建的新表帶有原表的所有屬性&#xff0c;主鍵&#xff0c;索引等。自動分表怎么做呢&#xff1f;使用上述語句自動創建分表。那么ID怎么設置呢&#xff1f;更改表格自增主鍵的起始值 例如 表格為 xxx_201604 那么將起…

《大道至簡》周愛民讀后感

作為一個準大二的軟件工程系的學生&#xff0c;初讀此書&#xff0c;很多部分是不太容易理解的&#xff0c;自己又沒有經歷過&#xff0c;感覺差了一個高度似的。自己讀的挺蒙&#xff0c;于是就去百度了一下這本書的讀后感&#xff0c;看看別人讀懂了什么&#xff0c;許多的評…

使用iconv-lite解決node當中不支持GBK編碼的問題

1、Node環境當中不支持GBK編碼 node.js當中的Buffer對象支持的編碼格式的種類有限&#xff0c;大概有ascii、utf8、utf16le、ucs2、base64、binary、hex。不支持GBK的編碼形式。對于windows系統來說&#xff0c;由于歷史原因&#xff0c;許多文件默認的編碼格式均為GBK。 比如我…

c1

dmg和package是安裝文件&#xff0c;dmg直接拖進應用程序中&#xff0c;pkg要進行安裝。 playfround是swift項目。--ios -----oc&#xff08;面向對象的C&#xff09; -----swift(oc的封裝)1963年劍橋大學退出cpl,1967年對cpl簡化推出bcpl&#xff0c;1970貝爾實驗室對bcpl簡化…

mysql必_MySQL必知必會(一)

摘自《MySQL必知必會》1.1.1 什么是數據庫數據庫&#xff1a;保存有組織的數據的容器(通常是一個文件或一組文件)人們通常用數據庫這個術語來代表他們使用的數據庫軟件。這是不正確的&#xff0c;它是引起混淆的根源。確切地說&#xff0c;數據庫軟件應稱為DBMS(數據庫管理系統…

python之工作舉例:通過復制NC文件來造數據

1 # 通過對NC文件復制來造數據2 import os, shutil3 4 # 遍歷的根目錄5 root_dir "D:\\test_data\\DISASTER\\"6 # 獲取NC文件的時間7 time_source 201612280800008 # 生成NC文件的時間9 time_new 2018122808000010 11 12 def get_dir_path(dir_name, time_str):1…

Python 3.5.2 TypeError: a bytes-like object is required, not 'str’問題解決方案

運行環境Mac Python 3.5.2 Q: http_response """\ HTTP/1.1 200 OK Hello, World! """ client_connection.sendall(http_response) TypeError: a bytes-like object is required, not str 類型錯誤&#xff0c;需要的是一個byte類型&#xff0…

mysql 集群架構_mysql企業常用集群架構

轉自 https://blog.csdn.net/kingice1014/article/details/760200611、mysql企業常用集群架構在中小型互聯網的企業中。mysql的集群一般就是上圖的架構。WEB節點讀取數據庫的時候讀取dbproxy服務器。dbproxy服務器通過對SQL語句的判斷來進行數據庫的讀寫分離。讀請求負載到從庫…

h.264視頻文件封裝

所謂封裝格式就是將已經編碼壓縮好的視頻軌和音頻軌按照一定的格式放到一個文件中&#xff0c;也就是說僅僅是一個外殼&#xff0c;或者大家把它當成一個放視頻軌和音頻軌的文件夾也可以。說得通俗點&#xff0c;視頻軌相當于飯&#xff0c;而音頻軌相當于菜&#xff0c;封裝格…

python cookbook 筆記三

分組&#xff1a; rows [{address: 5412 N CLARK, date: 07/01/2012},{address: 5148 N CLARK, date: 07/04/2012},{address: 5800 E 58TH, date: 07/02/2012},{address: 2122 N CLARK, date: 07/03/2012},{address: 5645 N RAVENSWOOD, date: 07/02/2012},{address: 1060 W A…

關于Vue2.0,Express實現的簡單跨域

npm install express -g 通過npm全局安裝express&#xff0c;之后可以通過 express --version 來查看express版本 express server 通過express server生成server項目文件 npm install 安裝server的項目依賴 可以通過執行server下的bin\www文件可以開啟服務 在www文件我們可以默…

mysql datetime類型按天查詢_mysql 時間相關sql , 按天、月、季度、年等條件進行查詢...

-- mysql查詢本季度-- 今天select * from ticket_order_detail where to_days(use_time) to_days(now());-- 7天SELECT *FROM ticket_order_detail where DATE_SUB(CURDATE(), INTERVAL 7 DAY) < date( use_time)-- 近30天SELECT *FROM ticket_order_detail where DATE_SUB…

ffmpeg分析系列

hello&#xff0c;各位好&#xff0c;本人是一名嵌入式軟件工程師&#xff0c;目前正使用ffmpeg開發一款嵌入式多媒體播放器&#xff0c;《ffmpeg分析》系列博文是本人在閱讀ffmpeg源代碼時所做的筆記&#xff0c;希望對各位有點幫助。分析過程結合下面的例程&#xff1a;http:…

Linux kernel的中斷子系統之(二):IRQ Domain介紹

返回目錄&#xff1a;《ARM-Linux中斷系統》。 總結&#xff1a;一、二概述了軟硬件不同角度的IRQ Number和HW Interrupt ID&#xff0c;這就需要他們之間架個橋梁。 三介紹了架設這種橋梁的幾種方式&#xff1a;Linear、Radix Tree和no map。 四介紹了兩種基礎數據結構描述中斷…

mysql返回yyyy mm dd_怎么把取出mysql數據庫中的yyyy-MM-dd日期轉成yyyy年MM月dd日格式...

您好&#xff0c;通過兩個個步驟可以完成轉換&#xff1a;第一步&#xff1a;日期處理可以在模板數據集中通過sql語句轉換&#xff0c;轉換方式方式如下&#xff1a;SELECT DATE_FORMAT(NOW(),%Y) YEAR輸出結果&#xff1a;2018SELECT DATE_F…

關于JS的時間控制

關于JS的時間控制實現動態效果及實例操作 <script>BOM //Bowers Object Model 瀏覽器對象模型setTimeout() // 延遲執行一次setInterval() // 間隔執行var a 300;window.setTimeout(abc(a),3000); // 自定義函數賦值function abc(i){alert(i);}//setInterv…

感動一生的幾句話

為什么80%的碼農都做不了架構師&#xff1f;>>> 很多東西就掌握在我們手中&#xff1a; 比如快樂&#xff0c;你不快樂&#xff0c;誰會同情你的悲傷&#xff1b; 比如堅強&#xff0c;你不堅強&#xff0c;誰會憐憫你的懦弱&#xff1b; 比如努力&#xff0c;你不…