接触数字图像处理最早是在高中,那时候photoshop还是4.0,可能是因为先入为主的关系,到现在都没有学3dmax之类的兴趣,2d到3d的飞跃估计是没我什么事了,舍不得那平方到立方的高薪....呵呵。
在上大学的时候,就和同学一起写过一些图像处理的程序,那个时候编程还很随意,考虑的只是如何实现,现在看来真正的技术是把握全局的能力,而不是灵光一现的神奇。前些日子接触了一些国外的图像处理程序,在这里算是作个总结,估计以后不会再针对性的研究图像处理方面的东西了。
以前的一个同学曾经跟我说过.net没有指针,现在很多培训课好像也是这么讲的,其实这是一个谬误。只是framework不推荐使用指针,尤其是在webservise,remoting等跨进程操作中,指针都是不安全的。但用过tc的各位都应该对指针的执行效率又深刻的印象,在批量运算大规模数据的需求下,指针是不二的选择。因而.net聪明的保留的保留了指针,并将其列入不安全方法集中。合理的使用指针将大幅度提高执行效率,我曾做过试验,对640*480的图像进行逐点运算,非指针运算要执行数分钟,而指针运算几乎是瞬间完成的。所以不要害怕使用指针。
其次就是数学,奉劝大家一定要弄明白了再写程序,数学课不是闹着玩的......想不明白就要躺在床上反复的想,我总觉得数学能预防老年痴呆。
言归正传,说说程序结构吧 :
imaging项目(滤镜,纹理,图像模式)
math项目(算法,边界,定制。及常用计算方法)
主程序项目
各举个例子来说明,我也来一回面向接口编程 ,
public interface ifilter
{
bitmap apply( bitmap img );
}
举例来说明,我也来一回面向接口编程 ,各滤镜都要实现这个接口,接口定义还包括一个不生成实际图像,只生成二进制对象的借口定义,在这里暂不作考虑。以取反色滤镜为例
public bitmap apply( bitmap srcimg )
{
// get source image size
int width = srcimg.width;
int height = srcimg.height;
pixelformat fmt = ( srcimg.pixelformat == pixelformat.format8bppindexed ) ?
pixelformat.format8bppindexed : pixelformat.format24bpprgb;
// lock source bitmap data
bitmapdata srcdata = srcimg.lockbits(
new rectangle( 0, 0, width, height ),
imagelockmode.readonly, fmt );
// create new image
bitmap dstimg = ( fmt == pixelformat.format8bppindexed ) ?
aforge.imaging.image.creategrayscaleimage( width, height ) :
new bitmap( width, height, fmt );
// lock destination bitmap data
bitmapdata dstdata = dstimg.lockbits(
new rectangle( 0, 0, width, height ),
imagelockmode.readwrite, fmt );
// copy image
win32.memcpy( dstdata.scan0, srcdata.scan0, srcdata.stride * height );
// process the filter
processfilter( dstdata, fmt );
// unlock both images
dstimg.unlockbits( dstdata );
srcimg.unlockbits( srcdata );
return dstimg;
}
是该滤镜方法的入口,完成了处理前的准备工作,processfilter同时调用每个滤镜类中共有的processfilter方法,而这个processfilter就是实现功能的关键所在了逐点运算或模版运算。
// process the filter
private unsafe void processfilter( bitmapdata data, pixelformat fmt )
{
int width = data.width;
int height = data.height;
int linesize = width * ( ( fmt == pixelformat.format8bppindexed ) ? 1 : 3 );
int offset = data.stride - linesize;
// do the job
byte * ptr = (byte *) data.scan0.topointer( );
// invert
for ( int y = 0; y < height; y++ )
{
for ( int x = 0; x < linesize; x++, ptr ++ )
{
// ivert each pixel
*ptr = (byte)( 255 - *ptr );
}
ptr += offset;
}
}
其中format8bppindexed是不必太关心的,个人认为设计初期可以不用考虑兼容它的问题。
下面来说说纹理,这个以前考虑得还不太多,但发现老外很喜欢玩这个,因为纹理在数学方面发挥的空间更大,我也不知道他们是怎么想出来的,凭空想可能还真是有难度,可能是他们谁在玩数学建模软件的时候发现这个玩法的,于是高数老师谁也不服谁,把算法玩的火火的。反正我觉得是这么回事。。。
public interface itexturegenerator
{
/**//// <summary>
/// generate texture
/// </summary>
float[,] generate( int width, int height );
/**//// <summary>
/// reset - regenerate internal random numbers
/// </summary>
void reset( );
}
这是纹理生成器的实现接口,为了保证每次的纹理不同,还要更新随机数以作为计算参数
private math.perlinnoise noise = new math.perlinnoise( 1.0 / 32, 0.05, 0.5, 8 );
实现纹理细节还需要靠noise实现,因而需要实现许多种noise。
// constructors
public woodtexture( ) : this( 12.0 ) { }
public woodtexture( double rings )
{
this.rings = rings;
reset( );
}
构造函数提供了默认值的设置,也就是对单位纹理大小的限定。
// generate texture
public float[,] generate( int width, int height )
{
float[,] texture = new float[height, width];
int w2 = width / 2;
int h2 = height / 2;
for ( int y = 0; y < height; y++ )
{
for ( int x = 0; x < width; x++ )
{
double xv = (double) ( x - w2 ) / width;
double yv = (double) ( y - h2 ) / height;
texture[y, x] =
math.max( 0.0f, math.min( 1.0f, (float)
math.abs( math.sin(
( math.sqrt( xv * xv + yv * yv ) + noise.function2d( x + r, y + r ) )
* math.pi * 2 * rings
))
));
}
}
return texture;
}
这就是。。。我数学不好的下场。都不知道她在说什么呢,最小值中选出最大值。算法不难找,关键是要看结构如何将他们整合起来。
public void reset( )
{
r = rand.next( 5000 );
}别忘了这个随机数,数字的图像也需要自然的美。
math工程中面向对象的观念不它容易得到贯彻,看一看那个perlinnoise吧,抛砖引玉。
public perlinnoise( double initfrequency, double initamplitude, double persistance, int octaves )
{
this.initfrequency = initfrequency;
this.initamplitude = initamplitude;
this.persistance = persistance;
this.octaves = octaves;
}
首先要收集数据,因为图像处理要涉及到一维和二维两种情况,因而像noise这种底层方法要分别对应着两种情况给出对应的方法。
/**//// <summary>
/// 1-d perlin noise function
/// </summary>
public double function( double x )
{
double frequency = initfrequency;
double amplitude = initamplitude;
double sum = 0;
// octaves
for ( int i = 0; i < octaves; i++ )
{
sum += smoothednoise( x * frequency ) * amplitude;
frequency *= 2;
amplitude *= persistance;
}
return sum;
}
/**//// <summary>
/// 2-d perlin noise function
/// </summary>
public double function2d( double x, double y )
{
double frequency = initfrequency;
double amplitude = initamplitude;
double sum = 0;
// octaves
for ( int i = 0; i < octaves; i++ )
{
sum += smoothednoise( x * frequency, y * frequency ) * amplitude;
frequency *= 2;
amplitude *= persistance;
}
return sum;
}
一维跟二维的区别是什么,上中学的时候知道了线的运动生成了面,上大学又知道了循环着变化着的线能代表面,但如果做过了边缘识别和锐化以后话,又发现以前小看线了,其实它只是比面少一个参数而已。
/**//// <summary>
/// ordinary noise function
/// </summary>
protected double noise( int x )
{
int n = ( x << 13 ) ^ x;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
}
protected double noise( int x, int y )
{
int n = x + y * 57;
n = ( n << 13 ) ^ n ;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
}又一次证明了前面那段话,个人感觉这个x+y*57有点投影的意思。获取相应的噪点值。但噪点不是直接就能拿来用的
/**//// <summary>
/// smoothed noise
/// </summary>
protected double smoothednoise( double x )
{
int xint = (int) x;
double xfrac = x - xint;
return cosineinterpolate( noise( xint ) , noise( xint + 1 ), xfrac );
}
protected double smoothednoise( double x, double y )
{
int xint = (int) x;
int yint = (int) y;
double xfrac = x - xint;
double yfrac = y - yint;
// get four noise values
double x0y0 = noise( xint , yint );
double x1y0 = noise( xint + 1, yint );
double x0y1 = noise( xint , yint + 1 );
double x1y1 = noise( xint + 1, yint + 1) ;
// x interpolation
double v1 = cosineinterpolate( x0y0, x1y0, xfrac );
double v2 = cosineinterpolate( x0y1, x1y1, xfrac );
// y interpolation
return cosineinterpolate( v1, v2, yfrac );
}平滑的噪点,这个称呼似乎有点不协调,通过余弦插值,而不是离散余弦来运算。什么是余弦插值呢? /**//// <summary>
/// cosine interpolation
/// </summary>
protected double cosineinterpolate( double x1, double x2, double a )
{
double f = ( 1 - math.cos( a * math.pi ) ) * 0.5;
return x1 * ( 1 - f ) + x2 * f;
}就是这个,有些事情,大师知道就够了,你就照着去做就行了,为什么?因为你可能一辈子也不明白,自然有人会去弄明白的,知识还在传承。就像你不必知道自己的胃酸比例,也可以放心的吃香喝辣一样,也不必担心子孙后代消化不良。有些事情不必强求,有点消极了,呵呵。
画面并不难,只要把握好调用关系就可以了,另外像photoshop那样的悬浮窗体是最佳的选择我认为, // invert image
private void invertcolorfiltersitem_click(object sender, system.eventargs e)
{
applyfilter(new invert());
}
// apply filter on the image
private void applyfilter(ifilter filter)
{
try
{
// set wait cursor
this.cursor = cursors.waitcursor;
// apply filter to the image
bitmap newimage = filter.apply(image);
if (host.createnewdocumentonchange)
{
// open new image in new document
host.newdocument(newimage);
}
else
{
if (host.rememberonchange)
{
// backup current image
if (backup != null)
backup.dispose();
backup = image;
}
else
{
// release current image
image.dispose();
}
image = newimage;
// update
updatenewimage();
}
}
catch (argumentexception)
{
messagebox.show("selected filter can not be applied to the image", "error", messageboxbuttons.ok, messageboxicon.error);
}
finally
{
// restore cursor
this.cursor = cursors.default;
}
}调用顺畅的话,多少代码都不会觉得乱,对于初学者来说,要善用region。
这里还有个documentshost的概念,用它来承载图像文件,并将图像和窗体连接起来,很方便 /**//// <summary>
/// idocumentshost interface
/// provides connectione between documents and the main widnow
/// </summary>
public interface idocumentshost
{
bool createnewdocumentonchange{get;}
bool rememberonchange{get;}
bool newdocument(bitmap image);
bool newdocument(compleximage image);
bitmap getimage(object sender, string text, size size, pixelformat format);
}
欢迎大家跟我讨论
新闻热点
疑难解答
图片精选