你曾经好奇过图形软件是如何追踪一个图像轮廓的吗?没有嘛?我实际上就没有好奇过,但是当我做一个复杂项目时候,我发现用边界矩形算法来追踪图形轮廓是多么的有魔力。
处理的方法是很简单的:
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
原文链接:利用边界矩形算法追踪图片轮廓