OSDN Git Service

[更新]NyARToolkit for Java
[nyartoolkit-and/nyartoolkit-and.git] / trunk / sample / sandbox / jp / nyatla / nyartoolkit / sandbox / qrcode / NyARQrCodeDetector.java
1 package jp.nyatla.nyartoolkit.sandbox.qrcode;\r
2 \r
3 import jp.nyatla.nyartoolkit.NyARException;\r
4 import jp.nyatla.nyartoolkit.core.INyARSquareDetector;\r
5 import jp.nyatla.nyartoolkit.core.NyARSquare;\r
6 import jp.nyatla.nyartoolkit.core.NyARSquareStack;\r
7 import jp.nyatla.nyartoolkit.core.NyARVertexCounter;\r
8 import jp.nyatla.nyartoolkit.core.labeling.INyARLabeling;\r
9 import jp.nyatla.nyartoolkit.core.labeling.NyARLabelingImage;\r
10 import jp.nyatla.nyartoolkit.core.labeling.NyARLabelingLabel;\r
11 import jp.nyatla.nyartoolkit.core.labeling.NyARLabelingLabelStack;\r
12 import jp.nyatla.nyartoolkit.core.labeling.NyARLabeling_ARToolKit;\r
13 import jp.nyatla.nyartoolkit.core.param.NyARCameraDistortionFactor;\r
14 import jp.nyatla.nyartoolkit.core.pca2d.INyARPca2d;\r
15 import jp.nyatla.nyartoolkit.core.pca2d.*;\r
16 import jp.nyatla.nyartoolkit.core.raster.NyARBinRaster;\r
17 import jp.nyatla.nyartoolkit.core.types.NyARDoublePoint2d;\r
18 import jp.nyatla.nyartoolkit.core.types.NyARIntPoint;\r
19 import jp.nyatla.nyartoolkit.core.types.NyARIntSize;\r
20 import jp.nyatla.nyartoolkit.core.types.NyARLinear;\r
21 import jp.nyatla.nyartoolkit.core.types.matrix.NyARDoubleMatrix22;\r
22 \r
23 public class NyARQrCodeDetector implements INyARSquareDetector\r
24 {\r
25         private NyARQrCodeSymbolBinder _binder;\r
26         private static final double VERTEX_FACTOR = 2.0;// 線検出のファクタ\r
27 \r
28         private static final int AR_AREA_MAX = 10000;\r
29 \r
30         private static final int AR_AREA_MIN = 50;\r
31 \r
32         private final int _width;\r
33 \r
34         private final int _height;\r
35 \r
36         private final NyARLabeling_ARToolKit _labeling;\r
37 \r
38         private final NyARLabelingImage _limage;\r
39 \r
40         private final NyARCameraDistortionFactor _dist_factor_ref;\r
41         private final double[] _xpos;\r
42         private final double[] _ypos;\r
43         /**\r
44          * 最大i_squre_max個のマーカーを検出するクラスを作成する。\r
45          * \r
46          * @param i_param\r
47          */\r
48         public NyARQrCodeDetector(NyARCameraDistortionFactor i_dist_factor_ref, NyARIntSize i_size) throws NyARException\r
49         {\r
50                 this._width = i_size.w;\r
51                 this._height = i_size.h;\r
52                 this._dist_factor_ref = i_dist_factor_ref;\r
53                 this._labeling = new NyARLabeling_ARToolKit();\r
54                 this._limage = new NyARLabelingImage(this._width, this._height);\r
55                 this._labeling.attachDestination(this._limage);\r
56                 this._binder=new NyARQrCodeSymbolBinder(i_dist_factor_ref);\r
57 \r
58                 // 輪郭の最大長はMAX_COORD_NUMの2倍に制限\r
59                 int number_of_coord = MAX_COORD_NUM* 2;\r
60 \r
61                 // 輪郭バッファはnumber_of_coordの2倍\r
62                 this._max_coord = number_of_coord;\r
63                 this._xcoord = new int[number_of_coord * 2];\r
64                 this._ycoord = new int[number_of_coord * 2];\r
65                 this._xpos=new double[this._width+this._height];//最大辺長はthis._width+this._height\r
66                 this._ypos=new double[this._width+this._height];//最大辺長はthis._width+this._height\r
67                 \r
68         }\r
69 \r
70         private final int _max_coord;\r
71 \r
72         private final int[] _xcoord;\r
73 \r
74         private final int[] _ycoord;\r
75 \r
76         private void normalizeCoord(int[] i_coord_x, int[] i_coord_y, int i_index, int i_coord_num)\r
77         {\r
78                 // vertex1を境界にして、後方に配列を連結\r
79                 System.arraycopy(i_coord_x, 1, i_coord_x, i_coord_num, i_index);\r
80                 System.arraycopy(i_coord_y, 1, i_coord_y, i_coord_num, i_index);\r
81         }\r
82 \r
83         private final int[] __detectMarker_mkvertex = new int[5];\r
84 \r
85         /**\r
86          * ARMarkerInfo2 *arDetectMarker2( ARInt16 *limage, int label_num, int *label_ref,int *warea, double *wpos, int *wclip,int area_max, int area_min, double\r
87          * factor, int *marker_num ) 関数の代替品 ラベリング情報からマーカー一覧を作成してo_marker_listを更新します。 関数はo_marker_listに重なりを除外したマーカーリストを作成します。\r
88          * \r
89          * @param i_raster\r
90          * 解析する2値ラスタイメージを指定します。\r
91          * @param o_square_stack\r
92          * 抽出した正方形候補を格納するリスト\r
93          * @throws NyARException\r
94          */\r
95         public final void detectMarker(NyARBinRaster i_raster, NyARSquareStack o_square_stack) throws NyARException\r
96         {\r
97                 final INyARLabeling labeling_proc = this._labeling;\r
98                 final NyARLabelingImage limage = this._limage;\r
99 \r
100                 // 初期化\r
101 \r
102                 // マーカーホルダをリセット\r
103                 o_square_stack.clear();\r
104 \r
105                 // ラベリング\r
106                 labeling_proc.labeling(i_raster);\r
107 \r
108                 // ラベル数が0ならここまで\r
109                 final int label_num = limage.getLabelStack().getLength();\r
110                 if (label_num < 1) {\r
111                         return;\r
112                 }\r
113 \r
114                 final NyARLabelingLabelStack stack = limage.getLabelStack();\r
115                 final NyARLabelingLabel[] labels = (NyARLabelingLabel[]) stack.getArray();\r
116 \r
117                 // ラベルを大きい順に整列\r
118                 stack.sortByArea();\r
119 \r
120                 // デカいラベルを読み飛ばし\r
121                 int i;\r
122                 for (i = 0; i < label_num; i++) {\r
123                         // 検査対象内のラベルサイズになるまで無視\r
124                         if (labels[i].area <= AR_AREA_MAX) {\r
125                                 break;\r
126                         }\r
127                 }\r
128                 \r
129                 final int xsize = this._width;\r
130                 final int ysize = this._height;\r
131                 final int[] xcoord = this._xcoord;\r
132                 final int[] ycoord = this._ycoord;\r
133                 final int coord_max = this._max_coord;\r
134                 final int[] mkvertex = this.__detectMarker_mkvertex;\r
135                 final int[] buf = (int[]) limage.getBufferReader().getBuffer();\r
136                 final int[] indextable = limage.getIndexArray();\r
137                 int coord_num;\r
138                 int label_area;\r
139                 NyARLabelingLabel label_pt;\r
140                 NyARSquareStack wk_stack=new NyARSquareStack(10);\r
141                 wk_stack.clear();\r
142 \r
143                 for (; i < label_num; i++) {\r
144                         label_pt = labels[i];\r
145                         label_area = label_pt.area;\r
146                         // 検査対象サイズよりも小さくなったら終了\r
147                         if (label_area < AR_AREA_MIN) {\r
148                                 break;\r
149                         }\r
150                         // クリップ領域が画面の枠に接していれば除外\r
151                         if (label_pt.clip_l == 1 || label_pt.clip_r == xsize - 2) {// if(wclip[i*4+0] == 1 || wclip[i*4+1] ==xsize-2){\r
152                                 continue;\r
153                         }\r
154                         if (label_pt.clip_t == 1 || label_pt.clip_b == ysize - 2) {// if( wclip[i*4+2] == 1 || wclip[i*4+3] ==ysize-2){\r
155                                 continue;\r
156                         }\r
157                         // 特徴点候補であるかを確認する。\r
158                         if (!hasQrEdgeFeature(buf, indextable, label_pt)) {\r
159                                 continue;\r
160                         }\r
161 \r
162                         // 輪郭を取得\r
163                         coord_num = limage.getContour(i, coord_max, xcoord, ycoord);\r
164                         if (coord_num == coord_max) {\r
165                                 // 輪郭が大きすぎる。\r
166                                 continue;\r
167                         }\r
168                         // 頂点候補のインデクスを取得\r
169                         final int vertex1 = scanVertex(xcoord, ycoord, coord_num);\r
170 \r
171                         // 頂点候補(vertex1)を先頭に並べなおした配列を作成する。\r
172                         normalizeCoord(xcoord, ycoord, vertex1, coord_num);\r
173 \r
174                         // 頂点情報を取得\r
175                         if (!getSquareVertex(xcoord, ycoord, vertex1, coord_num, label_area, mkvertex)) {\r
176                                 continue;\r
177                         }\r
178                         NyARSquare square=(NyARSquare)wk_stack.prePush();\r
179                         //矩形からラインと観察座標を取得\r
180                         if(!getSquareLine(mkvertex,xcoord,ycoord,square.line,square.imvertex)){\r
181                                 wk_stack.pop();\r
182                                 continue;\r
183                         }\r
184                 }\r
185                 //シンボルの関連付け\r
186                 bindQrcodeEdge(wk_stack,o_square_stack);\r
187                 //エッジ同士の相関関係をしらべる。\r
188 \r
189                 return;\r
190         }\r
191 \r
192         /**\r
193          * QRコードのエッジグループを作る\r
194          * @param i_square_stack\r
195          */\r
196         public void bindQrcodeEdge(NyARSquareStack i_square_stack,NyARSquareStack o_square_stack) throws NyARException\r
197         {\r
198                 NyARSquare[] group=new NyARSquare[3];\r
199                 int number_of_edge=i_square_stack.getLength();\r
200                 if(number_of_edge<3){\r
201                         return;\r
202                 }\r
203                 NyARSquare[] sa=(NyARSquare[])i_square_stack.getArray();\r
204                 for(int i=0;i<number_of_edge-2;i++)\r
205                 {       \r
206                         group[0]=sa[i];\r
207                         for(int i2=i+1;i2<number_of_edge-1;i2++)\r
208                         {\r
209                                 group[1]=sa[i2];\r
210                                 for(int i3=i2+1;i3<number_of_edge;i3++){\r
211                                         group[2]=sa[i3];\r
212                                         //3個のエッジの関連性を確認する。\r
213                                         NyARSquare new_square=(NyARSquare)o_square_stack.prePush();\r
214                                         if(!this._binder.composeSquare(group,new_square)){\r
215                                                 o_square_stack.pop();\r
216                                         }\r
217                                 }\r
218                         }\r
219                 }\r
220                 return;\r
221         }\r
222         private static int MAX_COORD_NUM=(320+240)*2;//サイズの1/2の長方形の編程度が目安(VGAなら(320+240)*2)\r
223         private final INyARPca2d _pca=new NyARPca2d_MatrixPCA_O2();\r
224         private final NyARDoubleMatrix22 __getSquareLine_evec=new NyARDoubleMatrix22();\r
225         private final NyARDoublePoint2d __getSquareLine_mean=new NyARDoublePoint2d();\r
226         private final NyARDoublePoint2d __getSquareLine_ev=new NyARDoublePoint2d();\r
227         /**\r
228          * 頂点インデクスと輪郭配列から、Ideal座標系とLineを作成して変数に返す\r
229          * @param i_cparam\r
230          * @return\r
231          * @throws NyARException\r
232          */\r
233         private boolean getSquareLine(int[] i_mkvertex, int[] i_xcoord, int[] i_ycoord, NyARLinear[] o_line,NyARIntPoint[] o_imvertex) throws NyARException\r
234         {\r
235                 final NyARDoubleMatrix22 evec=this.__getSquareLine_evec;\r
236                 final NyARDoublePoint2d mean=this.__getSquareLine_mean;\r
237                 final NyARDoublePoint2d ev=this.__getSquareLine_ev;\r
238         \r
239                 \r
240                 for (int i = 0; i < 4; i++) {\r
241                         final double w1 = (double) (i_mkvertex[i + 1] - i_mkvertex[i] + 1) * 0.05 + 0.5;\r
242                         final int st = (int) (i_mkvertex[i] + w1);\r
243                         final int ed = (int) (i_mkvertex[i + 1] - w1);\r
244                         final int n = ed - st + 1;\r
245                         if (n < 2 || n>MAX_COORD_NUM) {\r
246                                 // nが2以下、又はMAX_COORD_NUM以上なら主成分分析をしない。\r
247                                 return false;\r
248                         }\r
249                         //配列作成\r
250                         this._dist_factor_ref.observ2IdealBatch(i_xcoord, i_ycoord, st, n,this._xpos,this._ypos);\r
251                         \r
252                         //主成分分析する。\r
253                         this._pca.pca(this._xpos,this._ypos,n,evec, ev,mean);\r
254                         final NyARLinear l_line_i = o_line[i];\r
255                         l_line_i.run = evec.m01;// line[i][0] = evec->m[1];\r
256                         l_line_i.rise = -evec.m00;// line[i][1] = -evec->m[0];\r
257                         l_line_i.intercept = -(l_line_i.run * mean.x + l_line_i.rise * mean.y);// line[i][2] = -(line[i][0]*mean->v[0] + line[i][1]*mean->v[1]);\r
258                 }\r
259                 for (int i = 0; i < 4; i++) {\r
260                         final NyARLinear l_line_i = o_line[i];\r
261                         final NyARLinear l_line_2 = o_line[(i + 3) % 4];\r
262                         final double w1 = l_line_2.run * l_line_i.rise - l_line_i.run * l_line_2.rise;\r
263                         if (w1 == 0.0) {\r
264                                 return false;\r
265                         }\r
266                         // 頂点インデクスから頂点座標を得て保存\r
267                         o_imvertex[i].x = i_xcoord[i_mkvertex[i]];\r
268                         o_imvertex[i].y = i_ycoord[i_mkvertex[i]];\r
269                 }\r
270                 return true;\r
271         }\r
272         /**\r
273          * 辺からの対角線が最長になる点を対角線候補として返す。\r
274          * \r
275          * @param i_xcoord\r
276          * @param i_ycoord\r
277          * @param i_coord_num\r
278          * @return\r
279          */\r
280         private int scanVertex(int[] i_xcoord, int[] i_ycoord, int i_coord_num)\r
281         {\r
282                 final int sx = i_xcoord[0];\r
283                 final int sy = i_ycoord[0];\r
284                 int d = 0;\r
285                 int w, x, y;\r
286                 int ret = 0;\r
287                 for (int i = 1; i < i_coord_num; i++) {\r
288                         x = i_xcoord[i] - sx;\r
289                         y = i_ycoord[i] - sy;\r
290                         w = x * x + y * y;\r
291                         if (w > d) {\r
292                                 d = w;\r
293                                 ret = i;\r
294                         }\r
295                         // ここでうまく終了条件入れられないかな。\r
296                 }\r
297                 return ret;\r
298         }\r
299 \r
300         private final NyARVertexCounter __getSquareVertex_wv1 = new NyARVertexCounter();\r
301         private final NyARVertexCounter __getSquareVertex_wv2 = new NyARVertexCounter();\r
302 \r
303         /**\r
304          * static int arDetectMarker2_check_square( int area, ARMarkerInfo2 *marker_info2, double factor ) 関数の代替関数 OPTIMIZED STEP [450->415] o_squareに頂点情報をセットします。\r
305          * \r
306          * @param i_x_coord\r
307          * @param i_y_coord\r
308          * @param i_vertex1_index\r
309          * @param i_coord_num\r
310          * @param i_area\r
311          * @param o_vertex\r
312          * 要素数はint[4]である事\r
313          * @return\r
314          */\r
315         private boolean getSquareVertex(int[] i_x_coord, int[] i_y_coord, int i_vertex1_index, int i_coord_num, int i_area, int[] o_vertex)\r
316         {\r
317                 final NyARVertexCounter wv1 = this.__getSquareVertex_wv1;\r
318                 final NyARVertexCounter wv2 = this.__getSquareVertex_wv2;\r
319                 final int end_of_coord = i_vertex1_index + i_coord_num - 1;\r
320                 final int sx = i_x_coord[i_vertex1_index];// sx = marker_info2->x_coord[0];\r
321                 final int sy = i_y_coord[i_vertex1_index];// sy = marker_info2->y_coord[0];\r
322                 int dmax = 0;\r
323                 int v1 = i_vertex1_index;\r
324                 for (int i = 1 + i_vertex1_index; i < end_of_coord; i++) {// for(i=1;i<marker_info2->coord_num-1;i++)\r
325                         // {\r
326                         final int d = (i_x_coord[i] - sx) * (i_x_coord[i] - sx) + (i_y_coord[i] - sy) * (i_y_coord[i] - sy);\r
327                         if (d > dmax) {\r
328                                 dmax = d;\r
329                                 v1 = i;\r
330                         }\r
331                 }\r
332                 final double thresh = (i_area / 0.75) * 0.01 * VERTEX_FACTOR;\r
333 \r
334                 o_vertex[0] = i_vertex1_index;\r
335 \r
336                 if (!wv1.getVertex(i_x_coord, i_y_coord, i_vertex1_index, v1, thresh)) { // if(get_vertex(marker_info2->x_coord,marker_info2->y_coord,0,v1,thresh,wv1,&wvnum1)<\r
337                         // 0 ) {\r
338                         return false;\r
339                 }\r
340                 if (!wv2.getVertex(i_x_coord, i_y_coord, v1, end_of_coord, thresh)) {// if(get_vertex(marker_info2->x_coord,marker_info2->y_coord,v1,marker_info2->coord_num-1,thresh,wv2,&wvnum2)\r
341                         // < 0) {\r
342                         return false;\r
343                 }\r
344 \r
345                 int v2;\r
346                 if (wv1.number_of_vertex == 1 && wv2.number_of_vertex == 1) {// if(wvnum1 == 1 && wvnum2== 1) {\r
347                         o_vertex[1] = wv1.vertex[0];\r
348                         o_vertex[2] = v1;\r
349                         o_vertex[3] = wv2.vertex[0];\r
350                 } else if (wv1.number_of_vertex > 1 && wv2.number_of_vertex == 0) {// }else if( wvnum1 > 1 && wvnum2== 0) {\r
351                         // 頂点位置を、起点から対角点の間の1/2にあると予想して、検索する。\r
352                         v2 = (v1 - i_vertex1_index) / 2 + i_vertex1_index;\r
353                         if (!wv1.getVertex(i_x_coord, i_y_coord, i_vertex1_index, v2, thresh)) {\r
354                                 return false;\r
355                         }\r
356                         if (!wv2.getVertex(i_x_coord, i_y_coord, v2, v1, thresh)) {\r
357                                 return false;\r
358                         }\r
359                         if (wv1.number_of_vertex == 1 && wv2.number_of_vertex == 1) {\r
360                                 o_vertex[1] = wv1.vertex[0];\r
361                                 o_vertex[2] = wv2.vertex[0];\r
362                                 o_vertex[3] = v1;\r
363                         } else {\r
364                                 return false;\r
365                         }\r
366                 } else if (wv1.number_of_vertex == 0 && wv2.number_of_vertex > 1) {\r
367                         // v2 = (v1-i_vertex1_index+ end_of_coord-i_vertex1_index) / 2+i_vertex1_index;\r
368                         v2 = (v1 + end_of_coord) / 2;\r
369 \r
370                         if (!wv1.getVertex(i_x_coord, i_y_coord, v1, v2, thresh)) {\r
371                                 return false;\r
372                         }\r
373                         if (!wv2.getVertex(i_x_coord, i_y_coord, v2, end_of_coord, thresh)) {\r
374                                 return false;\r
375                         }\r
376                         if (wv1.number_of_vertex == 1 && wv2.number_of_vertex == 1) {\r
377                                 o_vertex[1] = v1;\r
378                                 o_vertex[2] = wv1.vertex[0];\r
379                                 o_vertex[3] = wv2.vertex[0];\r
380                         } else {\r
381                                 return false;\r
382                         }\r
383                 } else {\r
384                         return false;\r
385                 }\r
386                 o_vertex[4] = end_of_coord;\r
387                 return true;\r
388         }\r
389         /**\r
390          * QRコードのシンボル特徴を持つラベルであるかを調べる\r
391          * @param buf\r
392          * @param index_table\r
393          * @param i_label\r
394          * @return\r
395          */\r
396         private boolean hasQrEdgeFeature(int[] buf, int[] index_table, NyARLabelingLabel i_label)\r
397         {\r
398                 int tx, bx;\r
399                 int w;\r
400                 int i_label_id = i_label.id;\r
401                 int limage_j_ptr;\r
402                 final int clip_l = i_label.clip_l;\r
403                 final int clip_b = i_label.clip_b;\r
404                 final int clip_r = i_label.clip_r;\r
405                 final int clip_t = i_label.clip_t;\r
406 \r
407                 tx = bx = 0;\r
408                 // 上接点(→)\r
409                 limage_j_ptr = clip_t*this._width;\r
410                 for (int i = clip_l; i <= clip_r; i++) {// for( i = clip[0]; i <=clip[1]; i++, p1++ ) {\r
411                         w = buf[limage_j_ptr+i];\r
412                         if (w > 0 && index_table[w - 1] == i_label_id) {\r
413                                 tx = i;\r
414                                 break;\r
415                         }\r
416                 }\r
417                 // 下接点(←)\r
418                 limage_j_ptr = clip_b*this._width;\r
419                 for (int i = clip_r; i >= clip_l; i--) {// for( i = clip[0]; i <=clip[1]; i++, p1++ ) {\r
420                         w = buf[limage_j_ptr+i];\r
421                         if (w > 0 && index_table[w - 1] == i_label_id) {\r
422                                 bx = i;\r
423                                 break;\r
424                         }\r
425                 }\r
426                 final int cx = (clip_l + clip_r) / 2;\r
427                 final int cy = (clip_t + clip_b) / 2;\r
428                 // 横断チェック(中心から線を引いて、010になるかしらべる)\r
429                 if (!checkDiagonalLine(buf, cx, cy, bx, clip_b)) {\r
430                         return false;\r
431                 }\r
432                 if (!checkDiagonalLine(buf, tx, clip_t, cx, cy)) {\r
433                         return false;\r
434                 }\r
435                 return true;\r
436         }\r
437 \r
438         /**\r
439          * シンボルのパターン特徴を調べる関数\r
440          * 対角線の一部が010になってるか調べる。\r
441          * \r
442          * @param buf\r
443          * @param i_px1\r
444          * @param i_py1\r
445          * @param i_px2\r
446          * @param i_py2\r
447          * @return\r
448          */\r
449         private boolean checkDiagonalLine(int[] buf, int i_px1, int i_py1, int i_px2, int i_py2)\r
450         {\r
451                 int sub_y = i_py2 - i_py1;\r
452                 int sub_x = i_px2 - i_px1;\r
453                 // 黒\r
454                 int i = 0;\r
455                 for (; i < sub_y; i++) {\r
456                         int yp = i_py1 + i;\r
457                         int xp = i_px1 + i * sub_x / sub_y;\r
458                         if (buf[yp*this._width+xp] == 0 && buf[yp*this._width+(xp-1)] == 0 && buf[yp*this._width+(xp+1)] == 0) {\r
459                                 break;\r
460                         }\r
461 \r
462                 }\r
463                 if (i == sub_y) {\r
464                         return false;\r
465                 }\r
466                 // 白\r
467                 for (; i < sub_y; i++) {\r
468                         int yp = i_py1 + i;\r
469                         int xp = i_px1 + i * sub_x / sub_y;\r
470                         if (buf[yp*this._width+xp] != 0 && buf[yp*this._width+(xp-1)] != 0 && buf[yp*this._width+(xp+1)] != 0) {\r
471                                 break;\r
472                         }\r
473 \r
474                 }\r
475                 if (i == sub_y) {\r
476                         return false;\r
477                 }\r
478                 // 黒\r
479                 for (; i < sub_y; i++) {\r
480                         int yp = i_py1 + i;\r
481                         int xp = i_px1 + i * sub_x / sub_y;\r
482                         if (buf[yp*this._width+xp] == 0 && buf[yp*this._width+(xp-1)] == 0 && buf[yp*this._width+(xp+1)] == 0) {\r
483                                 break;\r
484                         }\r
485 \r
486                 }\r
487                 if (i != sub_y) {\r
488                         return false;\r
489                 }\r
490                 // 端まで到達したらOK\r
491                 return true;\r
492         }\r
493 \r
494 }\r