<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>过眼•物影天狼 &#187; 图形图像</title>
	<atom:link href="http://www.tangrui.net/category/computer-science-and-technology/programming/images-and-graphics/feed" rel="self" type="application/rss+xml" />
	<link>http://www.tangrui.net</link>
	<description>博客通天下 淡墨书豪辞</description>
	<lastBuildDate>Fri, 29 May 2009 03:25:56 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3</generator>
		<item>
		<title>Hermite 曲线的算法与实现</title>
		<link>http://www.tangrui.net/2006/01/23/algorithm-and-implementation-of-hermite-curve</link>
		<comments>http://www.tangrui.net/2006/01/23/algorithm-and-implementation-of-hermite-curve#comments</comments>
		<pubDate>Mon, 23 Jan 2006 14:26:35 +0000</pubDate>
		<dc:creator>Rui Tang</dc:creator>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[计算机科学与技术]]></category>
		<category><![CDATA[图形图像]]></category>

		<guid isPermaLink="false">http://www.tangrui.net/?p=29</guid>
		<description><![CDATA[&#160;&#160;&#160;&#160;&#160;&#160;摘要：本文主要介绍了一种自由曲线 Hermite 曲线的数学原理，并以此为依据构造了 Hermite 曲线的计算机算法，并在微软的 .net 框架下使用 GDI+ 实现。
&#160;&#160;&#160;&#160;&#160;&#160;关键字：合成曲线，Hermite曲线，C#，GDI+
一、数学原理
&#160;&#160;&#160;&#160;&#160;&#160;曲线是构建几何模型的最基本元素之一，主要分为解析曲线和合成曲线。解析曲线通常是先有曲线方程，然后才能把曲线画出来，这种方式对于曲线的构造者来说是非常复杂且不直观的，而且改变曲线的一些参数，设计者也无法立刻了解曲线形状会做怎样的变化。在作曲线设计时，设计者通常希望能先将大致形状用很直观的方式描绘出来，并能够很容易的依照所需要的形状作修改，因此合成曲线是比较合适的方式。
&#160;&#160;&#160;&#160;&#160;&#160;合成曲线通常以参数的形式来表现，是由设计者根据其设计的需求和几何信息，去“合成”出这条曲线。这样由设计者输入的几何数据，就是曲线的控制点。
&#160;&#160;&#160;&#160;&#160;&#160;一般的合成曲线，至少需要一个三次的参数式：
公式（1）
用向量表示为：
公式（2）
如何确定式中的参数，就形成了不同的合成曲线的构造方法。Hermite 曲线就是通过曲线的起点（P0）、终点（P1）、起点切向量（V0）和终点切向量（V1）来确定曲线的。改变这四个参数，就可以控制 Hermite 曲线的形状。图1就是构建 Hermite 曲线的示意图。
图1
当给定以上四个参数之后，如何来确定这条 Hermite 曲线呢？
首先将（2）式作一次微分得到：
公式（3）
然后将 u=0，u=1 代入（2）式和（3）式中，就可以得到参数式中的系数：
公式（4）
将（4）式作进一步整理，最后可以将 Hermite 曲线方程写成如下形式：
公式（5）
这就是 Hermite 曲线的参数方程。根据此方程，对于 P0=(-2,2)，P1=(3,-1)，V0=(8,10) 和 V1=(15,10) 这四个参数，可以构造如下的表格：
使用 Matlab 绘制此曲线，得到图2：
图2
&#160;&#160;&#160;&#160;&#160;&#160;但是在做曲线设计时，Hermite 曲线仍然存在不少问题，例如在建立 Hermite 曲线时设计者必须输入曲线两端切向量的大小和方向，这对设计者来讲仍然是不直观的。另外，Hermite 曲线不具有区域控制的能力，在建立 Hermite 曲线时提供的是个参数，改变任何一项输入，整条曲线的形状都会发生变化，设计者很难对其进行局部的、小范围的修改。
二、算法
&#160;&#160;&#160;&#160;&#160;&#160;有了以上的数学基础，构造算法就不是件困难的事情。

/// &#60;summary&#62;
/// 绘制Hermite曲线的核心方法
/// &#60;/summary&#62;
/// &#60;param name=&#34;pen&#34;&#62;绘制曲线用的Pen对象&#60;/param&#62;
/// &#60;param name=&#34;p0&#34;&#62;起点坐标&#60;/param&#62;
/// &#60;param name=&#34;p1&#34;&#62;终点坐标&#60;/param&#62;
/// &#60;param name=&#34;v0&#34;&#62;起点切向量&#60;/param&#62;
/// &#60;param name=&#34;v1&#34;&#62;终点切向量&#60;/param&#62;
private void DrawHermite(Pen pen,Point p0, Point p1, Point v0, [...]]]></description>
			<content:encoded><![CDATA[<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;摘要：本文主要介绍了一种自由曲线 Hermite 曲线的数学原理，并以此为依据构造了 Hermite 曲线的计算机算法，并在微软的 .net 框架下使用 GDI+ 实现。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;关键字：合成曲线，Hermite曲线，C#，GDI+</p>
<p><strong>一、数学原理</strong><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;曲线是构建几何模型的最基本元素之一，主要分为解析曲线和合成曲线。解析曲线通常是先有曲线方程，然后才能把曲线画出来，这种方式对于曲线的构造者来说是非常复杂且不直观的，而且改变曲线的一些参数，设计者也无法立刻了解曲线形状会做怎样的变化。在作曲线设计时，设计者通常希望能先将大致形状用很直观的方式描绘出来，并能够很容易的依照所需要的形状作修改，因此合成曲线是比较合适的方式。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;合成曲线通常以参数的形式来表现，是由设计者根据其设计的需求和几何信息，去“合成”出这条曲线。这样由设计者输入的几何数据，就是曲线的控制点。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;一般的合成曲线，至少需要一个三次的参数式：</p>
<p>公式（1）</p>
<p>用向量表示为：</p>
<p>公式（2）</p>
<p>如何确定式中的参数，就形成了不同的合成曲线的构造方法。Hermite 曲线就是通过曲线的起点（P0）、终点（P1）、起点切向量（V0）和终点切向量（V1）来确定曲线的。改变这四个参数，就可以控制 Hermite 曲线的形状。图1就是构建 Hermite 曲线的示意图。</p>
<p>图1</p>
<p>当给定以上四个参数之后，如何来确定这条 Hermite 曲线呢？</p>
<p>首先将（2）式作一次微分得到：</p>
<p>公式（3）</p>
<p>然后将 u=0，u=1 代入（2）式和（3）式中，就可以得到参数式中的系数：</p>
<p>公式（4）</p>
<p>将（4）式作进一步整理，最后可以将 Hermite 曲线方程写成如下形式：</p>
<p>公式（5）</p>
<p>这就是 Hermite 曲线的参数方程。根据此方程，对于 P0=(-2,2)，P1=(3,-1)，V0=(8,10) 和 V1=(15,10) 这四个参数，可以构造如下的表格：</p>
<p>使用 Matlab 绘制此曲线，得到图2：</p>
<p>图2</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;但是在做曲线设计时，Hermite 曲线仍然存在不少问题，例如在建立 Hermite 曲线时设计者必须输入曲线两端切向量的大小和方向，这对设计者来讲仍然是不直观的。另外，Hermite 曲线不具有区域控制的能力，在建立 Hermite 曲线时提供的是个参数，改变任何一项输入，整条曲线的形状都会发生变化，设计者很难对其进行局部的、小范围的修改。</p>
<p><strong>二、算法</strong><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;有了以上的数学基础，构造算法就不是件困难的事情。<br />
<pre><pre>
/// &lt;summary&gt;
/// 绘制Hermite曲线的核心方法
/// &lt;/summary&gt;
/// &lt;param name=&quot;pen&quot;&gt;绘制曲线用的Pen对象&lt;/param&gt;
/// &lt;param name=&quot;p0&quot;&gt;起点坐标&lt;/param&gt;
/// &lt;param name=&quot;p1&quot;&gt;终点坐标&lt;/param&gt;
/// &lt;param name=&quot;v0&quot;&gt;起点切向量&lt;/param&gt;
/// &lt;param name=&quot;v1&quot;&gt;终点切向量&lt;/param&gt;
private void DrawHermite(Pen pen,Point p0, Point p1, Point v0, Point v1) {
&nbsp;&nbsp;&nbsp;&nbsp;// 计算出来的当前坐标
&nbsp;&nbsp;&nbsp;&nbsp;int x = p0.X;
&nbsp;&nbsp;&nbsp;&nbsp;int y = p0.Y;

&nbsp;&nbsp;&nbsp;&nbsp;// 计算出来的前一个坐标，使用该两个做标连成一条线，来绘制曲线
&nbsp;&nbsp;&nbsp;&nbsp;int preX, preY;

&nbsp;&nbsp;&nbsp;&nbsp;// 根据参数计算每个点的坐标，参数的增量为0.01
&nbsp;&nbsp;&nbsp;&nbsp;for (double i = 0.0; i &lt;= 1.0; i = i + 0.01) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;preX = x;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;preY = y;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 保存计算中间结果，避免重复计算，提高算法效率
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;double i2 = i * i;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;double i3 = i2 * i;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;double express = 3 * i2 - 2 * i3;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 计算横坐标和纵坐标
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x = (int)((1 - express) * p0.X + express * p1.X + (i - 2 * i2 + i3) * v0.X + (i3 - i2) * v1.X);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y = (int)((1 - express) * p0.Y + express * p1.Y + (i - 2 * i2 + i3) * v0.Y + (i3 - i2) * v1.Y);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 画线
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.drawingSurface.DrawLine(pen, preX, preY, x, y);
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;this.drawingSurface.DrawLine(pen, x, y, p1.X, p1.Y);
}
</pre></pre><br />
<strong>三、使用 GDI+ 实现</strong><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在 .net 框架下，使用 GDI+ 实现这个算法是件轻松的事情。但是在编程过程中仍然出现了几个问题。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;首先，如何确定两个起点和两个切向量。本程序采用了如下的方法：先选定一个点，然后拉出一条直线，以该点为起点（或终点）并以该直线的方向和长度作为起点（或终点）切向量的方向和大小。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;其次，本程序可以实现类似 PhotoShop 中的钢笔功能。所以有一个如何产生拉动的效果的问题。这对这个问题使用了两种不同的解决方法：1、对于画曲线，使用了两个画笔，一个用于绘制，一个用于擦除。当鼠标移动的时候，就会使用绘制的画笔绘制新曲线，并用擦除画笔擦除刚才的曲线。但该方法会导致另一个问题，就是当该曲线覆盖到其他线条上之后，当曲线离开后，该线条就会有部分被擦掉。但是想解决这个问题是很困难的。（不知道 PhotoShop 是如何实现的。）2、画直线的时候，使用了 GDI+ 中自带的一个 ControlPaint.DrawReversibleLine() 方法，该方法可以自己解决以上的问题。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;第三、像这样一遍一遍的重画和擦除，会很占用系统资源，但是没有什么更好的解决方法，从网上找到的文章来看，如果复写（Override）OnMouseDown, OnMouseMove 和 OnMouseUp 事件，会比处理此三个事件的方法要来得效率高一些，因此本程序的所有事件全部采用了这种方法。下面的这段代码，是复写了 OnMouseMove 事件，用于处理当鼠标按下左键移动的时候，产生的拉动效果。<br />
<pre><pre>
protected override void OnMouseMove(MouseEventArgs e) {
&nbsp;&nbsp;&nbsp;&nbsp;// 判断当鼠标移动的时候是否有鼠标左键按下
&nbsp;&nbsp;&nbsp;&nbsp;if (e.Button == MouseButtons.Left) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// isContinuedDrawing 是个标志变量，标志所产时的动作是否为了画第二个参量
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (!isContinedDrawing) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;endPoint[0].X = e.X;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;endPoint[0].Y = e.Y;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 使用 ControlPaint 画直线
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ControlPaint.DrawReversibleLine(PointToScreen(startPoint[0])PointToScreen(previousPoint), Color.Black);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ControlPaint.DrawReversibleLine(PointToScreen(startPoint[0]), PointToScreen(endPoint[0]), Color.Black);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;previousPoint = endPoint[0];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} else {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;endPoint[1].X = e.X;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;endPoint[1].Y = e.Y; ControlPaint.DrawReversibleLine(PointToScreen(startPoint[1]), PointToScreen(previousPoint), Color.Black);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Point _v0 = new Point();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_v0.X = endPoint[0].X - startPoint[0].X;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_v0.Y = endPoint[0].Y - startPoint[0].Y;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Point _v1 = new Point();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_v1.X = previousPoint.X - startPoint[1].X;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_v1.Y = previousPoint.Y - startPoint[1].Y;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 擦除以前的曲线
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.DrawHermite(erasePen,startPoint[0], startPoint[1], _v0, _v1);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 画新的曲线
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ControlPaint.DrawReversibleLine(PointToScreen(startPoint[1]), PointToScreen(endPoint[1]), Color.Black);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_v1.X = endPoint[1].X - startPoint[1].X;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_v1.Y = endPoint[1].Y - startPoint[1].Y;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.DrawHermite(drawPen,startPoint[0], startPoint[1], _v0, _v1);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;previousPoint = endPoint[1];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;// 调用父类中的 OnMouseOver 事件三
&nbsp;&nbsp;&nbsp;&nbsp;base.OnMouseMove(e);
}
</pre></pre><br />
<strong>四、程序截图</strong><br />
<strong><br />
五、参考文献</strong></p>
<ul>
<li>台湾元智大學機械系大三機械設計課程教材</li>
<li><a href="http://www.microsoft.com/china/msdn/library/enterprisedevelopment/softwaredev/SDaskgui02172004.mspx">使用 .net 框架绘制橡皮条带</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.tangrui.net/2006/01/23/algorithm-and-implementation-of-hermite-curve/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用 C# 实现图像的边缘检测</title>
		<link>http://www.tangrui.net/2006/01/22/implementation-of-edge-detection-using-csharp</link>
		<comments>http://www.tangrui.net/2006/01/22/implementation-of-edge-detection-using-csharp#comments</comments>
		<pubDate>Sun, 22 Jan 2006 14:47:13 +0000</pubDate>
		<dc:creator>Rui Tang</dc:creator>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[计算机科学与技术]]></category>
		<category><![CDATA[图形图像]]></category>

		<guid isPermaLink="false">http://www.tangrui.net/?p=22</guid>
		<description><![CDATA[&#160;&#160;&#160;&#160;&#160;&#160;我本人对图像处理没什么兴趣，要不是这门课要交作业，我才懒得做这些东西。唉……不过程序写了，自然会有一点想法，发到 Blog 上，以备后用吧。但是，即便是写了程序，也仍然不知道作边缘检测的原理何在，只是模糊的知道大概是对图像灰度求梯度，梯度大的就是边缘了。但毕竟图像是离散化的，可以使用另外的方法求梯度，而不用像高数中那样拼命地算偏导数了。有很多学者提出了很多种不同性能的模板，只要按照模板作简单的四则运算就行了，当然这也是能用程序实现的关键。由于作为教学课程，所有的内容都是以简单的灰度图像来说明举例的，当然边缘检测也不例外，留作业写程序也是一样，所以马上就遇到的一个问题是如何将彩色图像转为灰度图像。在课程中，都是简单的认为灰度图像只有一个亮度，这自然是没错的，但是放到计算机里，灰度也是一种颜色，是颜色就要使用色彩模式（最常用的自然是 RGB 了），那么这种灰度到底应该是怎样的颜色编码呢？索性取向 Photoshop ，看一看各种灰度色调，终于有所发祥。其实也可以这么想，全黑是 #000000 ，全白是 #FFFFFF ，那么是不是只要 RGB 值都相等，这个颜色就是灰度色呢？试了一下，果然如此。这样就好办了，至少第一步知道了转换的目标是什么了。但马上就又有了一个问题，彩色图片的颜色这么多，那么如何知道哪种彩色颜色对应哪种灰度颜色呢？这一点我从 .net Framework 中找到了答案。其实我从一开始就想在 .net Framework 中寻找有没有直接将 RGB 转成灰度或者是 HIS 模式（因为 HIS 模式中的 I 就使亮度，自然就容易转成灰度了）的，是不是太奢望了，所以我也没抱太大的希望，但是在这过程中却发现 Color 中有关一个实例方法 GetBrightness() ，就是用来获得颜色亮度的，真是踏破铁鞋无觅处，得来全不费功夫。该方法返回一个 0~1 之间的浮点数，那么如果 RGB 每个各占一个字节的话，那么刚好可以用这个值去乘以 255 ，然后拼成一个 RGB ，这个颜色就是原始色彩所对应的灰色。程序代码如下：

//定义两个颜色变量，oColor为原始色彩，gColor为对应的灰度色彩
Color oColor,gColor;
//原始色彩的亮度
float brightness;
//灰度色彩用 RGB 来表示，由于 R=G=B 所以只用一个变量就可以了
int gRGB;
//遍历图像中的每个像素
for (int i = 0; i &#60; oBmp.Width; i ++) [...]]]></description>
			<content:encoded><![CDATA[<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;我本人对图像处理没什么兴趣，要不是这门课要交作业，我才懒得做这些东西。唉……不过程序写了，自然会有一点想法，发到 Blog 上，以备后用吧。但是，即便是写了程序，也仍然不知道作边缘检测的原理何在，只是模糊的知道大概是对图像灰度求梯度，梯度大的就是边缘了。但毕竟图像是离散化的，可以使用另外的方法求梯度，而不用像高数中那样拼命地算偏导数了。有很多学者提出了很多种不同性能的模板，只要按照模板作简单的四则运算就行了，当然这也是能用程序实现的关键。由于作为教学课程，所有的内容都是以简单的灰度图像来说明举例的，当然边缘检测也不例外，留作业写程序也是一样，所以马上就遇到的一个问题是如何将彩色图像转为灰度图像。在课程中，都是简单的认为灰度图像只有一个亮度，这自然是没错的，但是放到计算机里，灰度也是一种颜色，是颜色就要使用色彩模式（最常用的自然是 RGB 了），那么这种灰度到底应该是怎样的颜色编码呢？索性取向 Photoshop ，看一看各种灰度色调，终于有所发祥。其实也可以这么想，全黑是 #000000 ，全白是 #FFFFFF ，那么是不是只要 RGB 值都相等，这个颜色就是灰度色呢？试了一下，果然如此。这样就好办了，至少第一步知道了转换的目标是什么了。但马上就又有了一个问题，彩色图片的颜色这么多，那么如何知道哪种彩色颜色对应哪种灰度颜色呢？这一点我从 .net Framework 中找到了答案。其实我从一开始就想在 .net Framework 中寻找有没有直接将 RGB 转成灰度或者是 HIS 模式（因为 HIS 模式中的 I 就使亮度，自然就容易转成灰度了）的，是不是太奢望了，所以我也没抱太大的希望，但是在这过程中却发现 Color 中有关一个实例方法 GetBrightness() ，就是用来获得颜色亮度的，真是踏破铁鞋无觅处，得来全不费功夫。该方法返回一个 0~1 之间的浮点数，那么如果 RGB 每个各占一个字节的话，那么刚好可以用这个值去乘以 255 ，然后拼成一个 RGB ，这个颜色就是原始色彩所对应的灰色。程序代码如下：<br />
<pre><pre>
//定义两个颜色变量，oColor为原始色彩，gColor为对应的灰度色彩
Color oColor,gColor;
//原始色彩的亮度
float brightness;
//灰度色彩用 RGB 来表示，由于 R=G=B 所以只用一个变量就可以了
int gRGB;
//遍历图像中的每个像素
for (int i = 0; i &lt; oBmp.Width; i ++) {
&nbsp;&nbsp;&nbsp;&nbsp;for (int j = 0; j &lt; oBmp.Height; j ++) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//得到像素的原始色彩&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oColor = oBmp.GetPixel(i,j);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//得到该色彩的亮度
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;brightness = oColor.GetBrightness();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//用该亮度计算灰度
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gRGB = (int)(brightness * 255);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//组成灰度色彩
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gColor = Color.FromArgb(gRGB,gRGB,gRGB);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//最后将该灰度色彩赋予该像素
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gBmp.SetPixel(i,j,gColor); 
&nbsp;&nbsp;&nbsp;&nbsp;}
}
</pre></pre><br />
其实还是很简单的。这之后就可以按照书中所说的模板游历的方法来进行边缘检测了。程序如下：<br />
<pre><pre>
//template为模板，nThreshold是一个阈值，
//用来将模板游历的结果（也就是梯度）进行划分。
//大于阈值的和小于阈值的分别赋予两种颜色，白或黑来标志边界和背景
private void EdgeDectect(int[,] template,int nThreshold) {
&nbsp;&nbsp;&nbsp;&nbsp;//取出和模板等大的原图中的区域
&nbsp;&nbsp;&nbsp;&nbsp;int[,] gRGB = new int[3,3];
&nbsp;&nbsp;&nbsp;&nbsp;//模板值结果，梯度
&nbsp;&nbsp;&nbsp;&nbsp;int templateValue = 0;
&nbsp;&nbsp;&nbsp;&nbsp;//遍历灰度图中每个像素
&nbsp;&nbsp;&nbsp;&nbsp;for (int i = 1; i &lt; gBmp.Width - 1; i ++) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (int j = 1; j &lt; gBmp.Height - 1; j ++) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//取得模板下区域的颜色，即灰度
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gRGB[0,0] = gBmp.GetPixel(i-1,j-1).R;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gRGB[0,1] = gBmp.GetPixel(i-1,j).R;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gRGB[0,2] = gBmp.GetPixel(i-1,j+1).R;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gRGB[1,0] = gBmp.GetPixel(i,j-1).R;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gRGB[1,1] = gBmp.GetPixel(i,j).R;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gRGB[1,2] = gBmp.GetPixel(i,j+1).R;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gRGB[2,0] = gBmp.GetPixel(i+1,j-1).R;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gRGB[2,1] = gBmp.GetPixel(i+1,j).R;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gRGB[2,2] = gBmp.GetPixel(i+1,j+1).R;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//按模板计算
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (int m = 0; m &lt; 3; m ++) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (int n = 0; n &lt; 3; n ++) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;templateValue += template[m,n] * gRGB[m,n];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//将梯度之按阈值分类，并赋予不同的颜色
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (templateValue &gt; nThreshold) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;eBmp.SetPixel(i,j,Color.FromArgb(255,255,255)); //白
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} else {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;eBmp.SetPixel(i,j,Color.FromArgb(0,0,0)); //黑
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;templateValue = 0;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
}
</pre></pre></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tangrui.net/2006/01/22/implementation-of-edge-detection-using-csharp/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

