利用边界矩形算法追踪图片轮廓

你曾经好奇过图形软件是如何追踪一个图像轮廓的吗?没有嘛?我实际上就没有好奇过,但是当我做一个复杂项目时候,我发现用边界矩形算法来追踪图形轮廓是多么的有魔力。
处理的方法是很简单的:
        1)找到一个图像边界上的像素(这跟边界矩形没关系,只是假设你找到了这个像素)。这个像素就是需要分析的。    
        2)假设有一个2x2的像素矩形,其中包括位于矩阵左上角或右下角的当前处理像素。
        3)这个时候,你有四个像素,每个都可以是透明或不透明。这样我们就有16种2x2的矩阵。 尽管当我们移到图像边界时,透明跟不透明像素都不会看到。    
        4)根据2x2矩阵中不透明像素的位置和个数,我们可以猜测轮廓的方向,将当前处理像素朝这个方向移动,从第二步起,直到你发现跟第一步中说的像素。
      现在,我们如何找到第一步中的第一个像素呢?这需要暴力算法,我们需要遍历图像像素,直到找到一个不透明像素。

      我们看下下面带注释的代码:

 

View Code
  1 package {
  2 import flash.display.Sprite;
  3 import flash.display.BitmapData;
  4 import flash.display.Bitmap;
  5 import flash.geom.Matrix;
  6 import flash.geom.Point;
  7 public class Main extends Sprite {
  8 private var bitmapData:BitmapData=new BitmapData(640,480,true,0x00000000);
  9 // tolerance 保存一个像素的Alpha值
 10 private var tolerance:Number=0x01;
 11 public function Main() {
 12 // 添加一个具有透明度的png图片
 13 bitmapData.draw(new Logo(278,429),new Matrix(1,0,0,1,100,40));
 14 var bitmap:Bitmap=new Bitmap(bitmapData);
 15 addChild(bitmap);
 16 // 函数结束后,marchingVector将包含追踪轮廓的点
 17 var marchingVector:Vector.<Point>=marchingSquares(bitmapData);
 18 }
 19 public function marchingSquares(bitmapData:BitmapData):Vector.<Point> {
 20 var contourVector:Vector.<Point> = new Vector.<Point>();
 21 //这是我们画轮廓线的canvas
 22 var canvas:Sprite=new Sprite();
 23 addChild(canvas);
 24 canvas.graphics.lineStyle(2,0x00ff00);
 25 // 获取起始像素
 26 var startPoint:Point=getStartingPixel(bitmapData);
 27 // 找到起始像素后我们就可以开始了
 28 if (startPoint!=null) {
 29 // 将画笔移到起始点
 30 canvas.graphics.moveTo(startPoint.x,startPoint.y);
 31 // pX 跟 pY是起始点的x,y坐标
 32 var pX:Number=startPoint.x;
 33 var pY:Number=startPoint.y;
 34 // stepX 和 stepY 可能是 -1, 0 或 1  代表到轮廓下一个点的查找像素步骤
 35 var stepX:Number;
 36 var stepY:Number;
 37 // 下面两个变量保存上一步步骤
 38 var prevX:Number;
 39 var prevY:Number;
 40 // 追踪整个轮廓时,closedLoop将成为true
 41 var closedLoop:Boolean=false;
 42 while (!closedLoop) {
 43 // 这段主要是获取每个像素的2x2矩阵
 44 var squareValue:Number=getSquareValue(pX,pY);
 45 switch (squareValue) {
 46 /* 往上用这些事例:
 47 +---+---+ +---+---+ +---+---+
 48 | 1 | | | 1 | | | 1 | |
 49 +---+---+ +---+---+ +---+---+
 50 | | | | 4 | | | 4 | 8 |
 51 +---+---+ +---+---+ +---+---+
 52 */
 53 case 1 :
 54 case 5 :
 55 case 13 :
 56 stepX=0;
 57 stepY=-1;
 58 break;
 59 /* 往下用这些事例
 60 +---+---+ +---+---+ +---+---+
 61 | | | | | 2 | | 1 | 2 |
 62 +---+---+ +---+---+ +---+---+
 63 | | 8 | | | 8 | | | 8 |
 64 +---+---+ +---+---+ +---+---+
 65 */
 66 case 8 :
 67 case 10 :
 68 case 11 :
 69 stepX=0;
 70 stepY=1;
 71 break;
 72 /* 往左用这些事例
 73 +---+---+ +---+---+ +---+---+
 74 | | | | | | | | 2 |
 75 +---+---+ +---+---+ +---+---+
 76 | 4 | | | 4 | 8 | | 4 | 8 |
 77 +---+---+ +---+---+ +---+---+
 78 */
 79 case 4 :
 80 case 12 :
 81 case 14 :
 82 stepX=-1;
 83 stepY=0;
 84 break;
 85 /* 往右用这些事例
 86 +---+---+ +---+---+ +---+---+
 87 | | 2 | | 1 | 2 | | 1 | 2 |
 88 +---+---+ +---+---+ +---+---+
 89 | | | | | | | 4 | |
 90 +---+---+ +---+---+ +---+---+
 91 */
 92 case 2 :
 93 case 3 :
 94 case 7 :
 95 stepX=1;
 96 stepY=0;
 97 break;
 98 case 6 :
 99 /* 特殊鞍点用case 1:
100 +---+---+ 
101 | | 2 | 
102 +---+---+
103 | 4 | |
104 +---+---+
105 如果来自上面,那就往左,否则往右
106 */
107 if (prevX==0&&prevY==-1) {
108 stepX=-1;
109 stepY=0;
110 }
111 else {
112 stepX=1;
113 stepY=0;
114 }
115 break;
116 case 9 :
117 /* 特殊鞍点 case 2:
118 +---+---+ 
119 | 1 | | 
120 +---+---+
121 | | 8 |
122 +---+---+
123 如果来自右边,就往上,否则往下
124 */
125 if (prevX==1&&prevY==0) {
126 stepX=0;
127 stepY=-1;
128 }
129 else {
130 stepX=0;
131 stepY=1;
132 }
133 break;
134 }
135 // 移到下一个点
136 pX+=stepX;
137 pY+=stepY;
138 // 保存轮廓点
139 contourVector.push(new Point(pX, pY));
140 prevX=stepX;
141 prevY=stepY;
142 // 画线
143 canvas.graphics.lineTo(pX,pY);
144 //如果返回到第一个访问的点,循环结束
145 if (pX==startPoint.x&&pY==startPoint.y) {
146 closedLoop=true;
147 }
148 }
149 }
150 return contourVector;
151 }
152 private function getStartingPixel(bitmapData:BitmapData):Point {
153 //扫描图像像素来蛮力找到非透明的像素作为起始像素
154 var zeroPoint:Point=new Point(0,0);
155 var offsetPoint:Point=new Point(0,0);
156 for (var i:Number=0; i<bitmapData.height; i++) {
157 for (var j:Number=0; j<bitmapData.width; j++) {
158 offsetPoint.x=j;
159 offsetPoint.y=i;
160 if (bitmapData.hitTest(zeroPoint,tolerance,offsetPoint)) {
161 return offsetPoint;
162 }
163 }
164 }
165 return null;
166 }
167 private function getSquareValue(pX:Number,pY:Number):Number {
168 /*
169 检测2x2像素网格,如果不是透明就给每个像素赋值
170 +---+---+
171 | 1 | 2 |
172 +---+---+
173 | 4 | 8 | <- 当前像素 (pX,pY)
174 +---+---+
175 */
176 var squareValue:Number=0;
177 // 检测左上部像素
178 if (getAlphaValue(bitmapData.getPixel32(pX-1,pY-1))>=tolerance) {
179 squareValue+=1;
180 }
181 // 检测上面像素
182 if (getAlphaValue(bitmapData.getPixel32(pX,pY-1))>tolerance) {
183 squareValue+=2;
184 }
185 // 检测左边像素
186 if (getAlphaValue(bitmapData.getPixel32(pX-1,pY))>tolerance) {
187 squareValue+=4;
188 }
189 // 检测像素本身
190 if (getAlphaValue(bitmapData.getPixel32(pX,pY))>tolerance) {
191 squareValue+=8;
192 }
193 return squareValue;
194 }
195 private function getAlphaValue(n:Number):Number {
196 // 给定ARGB值,得到alpha值
197 return n >> 24 & 0xFF;
198 }
199 }
200 }

 

这是对应的结果:

绿线就是通过算法追踪出来的图像轮廓。下次,我将展示如何用这个算法结合BoxD来创建有趣的东西。   
   下载源码:
http://www.emanueleferonato.com/wp-content/uploads/2013/03/marchingsquares.zip

原文链接:利用边界矩形算法追踪图片轮廓

英文链接:http://www.emanueleferonato.com/2013/03/01/using-marching-squares-algorithm-to-trace-the-contour-of-an-image/

 

posted @ 2013-03-06 17:47  【Winco】  阅读(826)  评论(0编辑  收藏  举报