1 /*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //#define LOG_NDEBUG 0
18 #include <utils/Log.h>
19 #include <utils/Timers.h>
20
21 #include "colorchecker.h"
22 #include "grouping.h"
23 #include <string>
24 #include <vector>
25 #include <set>
26 #include <algorithm>
27 #include <cmath>
28
29 const int totalChannels = 4; // Input image channel count
30 const int colorChannels = 3; // Input image color channel count
31 const float gammaCorrection = 2.2; // Assumed gamma curve on input
32 const int thresholdSq = 675; // Threshold on pixel difference to be considered
33 // part of the same patch
34
35 class PixelId {
36 public:
37 int id;
38 const unsigned char *p;
39
PixelId()40 PixelId(): id(0), p(NULL) {}
41
operator !=(const PixelId & other) const42 bool operator!=(const PixelId &other) const {
43 int dR = ((int)p[0] - other.p[0]) * ((int)p[0] - other.p[0]);
44 int dG = ((int)p[1] - other.p[1]) * ((int)p[1] - other.p[1]);
45 int dB = ((int)p[2] - other.p[2]) * ((int)p[2] - other.p[2]);
46 int distSq = dR + dG + dB;
47 if (distSq > thresholdSq) return true;
48 else return false;
49 }
50 };
51
52 class ImageField: public Field<PixelId> {
53 public:
ImageField(int width,int height,const unsigned char * data)54 ImageField(int width, int height, const unsigned char *data):
55 mWidth(width), mHeight(height), pData(data) {
56 }
57
operator ()(int y,int x) const58 PixelId operator() (int y, int x) const {
59 PixelId pId;
60 pId.p = pData + (y * mWidth + x ) * totalChannels;
61 if (mask.size() != 0) {
62 pId.id = mask[y][x];
63 }
64 return pId;
65 }
66
getWidth() const67 int getWidth() const { return mWidth; }
getHeight() const68 int getHeight() const { return mHeight; }
69 private:
70 int mWidth;
71 int mHeight;
72 const unsigned char *pData;
73 };
74
75 class PixelGroup {
76 public:
PixelGroup(int id,const ImageField * src)77 PixelGroup(int id, const ImageField *src):
78 mId(id),
79 mMinX(1e7),
80 mMinY(1e7),
81 mMaxX(0),
82 mMaxY(0),
83 mArea(0),
84 mSrc(src),
85 mRNeighbor(NULL),
86 mDNeighbor(NULL),
87 mLNeighbor(NULL),
88 mUNeighbor(NULL) {
89 mSum[0] = 0;
90 mSum[1] = 0;
91 mSum[2] = 0;
92 }
93
94 struct IdCompare {
operator ()PixelGroup::IdCompare95 bool operator() (const PixelGroup* l, const PixelGroup* r) const {
96 return l->getId() < r->getId();
97 }
98 };
99
growGroup(int x,int y)100 void growGroup(int x, int y) {
101 if (x < mMinX) mMinX = x;
102 if (x > mMaxX) mMaxX = x;
103 if (y < mMinY) mMinY = y;
104 if (y > mMaxY) mMaxY = y;
105 mArea++;
106 const unsigned char *p = (*mSrc)(y,x).p;
107 mSum[0] += p[0];
108 mSum[1] += p[1];
109 mSum[2] += p[2];
110 }
111
getId() const112 int getId() const {
113 return mId;
114 }
115
getArea() const116 int getArea() const {
117 return mArea;
118 }
119
getBoundArea() const120 int getBoundArea() const {
121 return (mMaxX - mMinX) * (mMaxY - mMinY);
122 }
123
getApproxAspectRatio() const124 float getApproxAspectRatio() const {
125 return ((float)(mMaxY - mMinY)) / (mMaxX - mMinX);
126 }
127
getApproxCenter(int * x,int * y) const128 void getApproxCenter(int *x, int *y) const {
129 *x = (mMaxX + mMinX)/2;
130 *y = (mMaxY + mMinY)/2;
131 }
132
getBoundingBox(int * x1,int * y1,int * x2,int * y2) const133 void getBoundingBox(int *x1, int *y1, int *x2, int *y2) const {
134 *x1 = mMinX;
135 *x2 = mMaxX;
136 *y1 = mMinY;
137 *y2 = mMaxY;
138 }
139
getAvgValue(unsigned char * p) const140 void getAvgValue(unsigned char *p) const {
141 p[0] = mSum[0] / mArea;
142 p[1] = mSum[1] / mArea;
143 p[2] = mSum[2] / mArea;
144 }
145
operator <(const PixelGroup & other) const146 bool operator<(const PixelGroup &other) const {
147 return mArea < other.getArea();
148 }
149
150 typedef std::set<PixelGroup*, PixelGroup::IdCompare> IdSet;
151 typedef std::set<PixelGroup*, PixelGroup::IdCompare>::iterator IdSetIter;
152
findNeighbors(IdSet & candidates)153 void findNeighbors(IdSet &candidates) {
154 int cX, cY;
155 getApproxCenter(&cX, &cY);
156 int rDistSq = 1e9; // Larger than any reasonable image distance
157 int dDistSq = rDistSq;
158
159 for (IdSetIter neighbor = candidates.begin();
160 neighbor != candidates.end();
161 neighbor++) {
162 if (*neighbor == this) continue;
163 int nX, nY;
164 (*neighbor)->getApproxCenter(&nX, &nY);
165 // 'right' means slope between (-1/3, 1/3), positive X change
166 if ( (nX - cX) > 0 ) {
167 float slope = ((float)(nY - cY)) / (nX - cX);
168 if (slope > -0.33 && slope < 0.33) {
169 int distSq = (nX - cX) * (nX - cX) + (nY - cY) * (nY - cY);
170 if (distSq < rDistSq) {
171 setRNeighbor(*neighbor);
172 rDistSq = distSq;
173 }
174 }
175 }
176 // 'down' means inv slope between (-1/3, 1/3), positive Y change
177 if ( (nY - cY) > 0) {
178 float invSlope = ((float)(nX - cX)) / (nY - cY);
179 if (invSlope > -0.33 && invSlope < 0.33) {
180 int distSq = (nX - cX) * (nX - cX) + (nY - cY) * (nY - cY);
181 if (distSq < dDistSq) {
182 setDNeighbor(*neighbor);
183 dDistSq = distSq;
184 }
185 }
186 }
187 }
188 // Do reverse links if possible
189 if (getRNeighbor() != NULL) {
190 getRNeighbor()->setLNeighbor(this);
191 }
192 if (getDNeighbor() != NULL) {
193 getDNeighbor()->setUNeighbor(this);
194 }
195
196 }
197
setRNeighbor(PixelGroup * rNeighbor)198 void setRNeighbor(PixelGroup *rNeighbor) {
199 mRNeighbor = rNeighbor;
200 }
201
getRNeighbor(int distance=1)202 PixelGroup* getRNeighbor(int distance = 1) {
203 PixelGroup *current = this;
204 for (int i=0; i < distance; i++) {
205 if (current != NULL) {
206 current = current->mRNeighbor;
207 } else break;
208 }
209 return current;
210 }
211
setDNeighbor(PixelGroup * dNeighbor)212 void setDNeighbor(PixelGroup *dNeighbor) {
213 mDNeighbor = dNeighbor;
214 }
215
getDNeighbor(int distance=1)216 PixelGroup* getDNeighbor(int distance = 1) {
217 PixelGroup *current = this;
218 for (int i=0; i < distance; i++) {
219 if (current != NULL) {
220 current = current->mDNeighbor;
221 } else break;
222 }
223 return current;
224 }
225
setLNeighbor(PixelGroup * lNeighbor)226 void setLNeighbor(PixelGroup *lNeighbor) {
227 mLNeighbor = lNeighbor;
228 }
229
getLNeighbor(int distance=1)230 PixelGroup* getLNeighbor(int distance = 1) {
231 PixelGroup *current = this;
232 for (int i=0; i < distance; i++) {
233 if (current != NULL) {
234 current = current->mLNeighbor;
235 } else break;
236 }
237 return current;
238 }
239
setUNeighbor(PixelGroup * uNeighbor)240 void setUNeighbor(PixelGroup *uNeighbor) {
241 mUNeighbor = uNeighbor;
242 }
243
getUNeighbor(int distance=1)244 PixelGroup* getUNeighbor(int distance = 1) {
245 PixelGroup *current = this;
246 for (int i=0; i < distance; i++) {
247 if (current != NULL) {
248 current = current->mUNeighbor;
249 } else break;
250 }
251 return current;
252 }
253
distanceSqTo(const PixelGroup * other)254 float distanceSqTo(const PixelGroup* other) {
255 int mX, mY;
256 getApproxCenter(&mX, &mY);
257 int oX, oY;
258 other->getApproxCenter(&oX, &oY);
259 int dx = (oX - mX);
260 int dy = (oY - mY);
261 return dx * dx + dy * dy;
262 }
263
distanceTo(const PixelGroup * other)264 float distanceTo(const PixelGroup* other) {
265 return sqrt( distanceSqTo(other) );
266 }
267
268 private:
269 int mId;
270 int mMinX, mMinY;
271 int mMaxX, mMaxY;
272 int mArea;
273 int mSum[3];
274 const ImageField *mSrc;
275
276 PixelGroup *mRNeighbor;
277 PixelGroup *mDNeighbor;
278 PixelGroup *mLNeighbor;
279 PixelGroup *mUNeighbor;
280 };
281
282 /* Scales input down by factor of outScale to output. Assumes input size is
283 * exactly output size times scale */
downsample(const unsigned char * input,unsigned char * output,int rowSpan,int outWidth,int outHeight,int outScale)284 void downsample(const unsigned char *input,
285 unsigned char *output,
286 int rowSpan,
287 int outWidth,
288 int outHeight,
289 int outScale) {
290 for (int oY = 0, iY = 0; oY < outHeight; oY++, iY += outScale) {
291 for (int oX = 0, iX = 0; oX < outWidth; oX++, iX += outScale) {
292 short out[3] = {0,0,0};
293 const unsigned char *in = input + iY * rowSpan + iX * totalChannels;
294 for (int j = 0; j < outScale; j++) {
295 for (int k = 0; k < outScale; k++) {
296 for (int i = 0; i < colorChannels; i++) {
297 out[i] += in[i];
298 }
299 in += totalChannels;
300 }
301 in += rowSpan - outScale * totalChannels;
302 }
303 output[0] = out[0] / (outScale * outScale);
304 output[1] = out[1] / (outScale * outScale);
305 output[2] = out[2] / (outScale * outScale);
306 output += totalChannels;
307 }
308 }
309 }
310
drawLine(unsigned char * image,int rowSpan,int x0,int y0,int x1,int y1,int r,int g,int b)311 void drawLine(unsigned char *image,
312 int rowSpan,
313 int x0, int y0,
314 int x1, int y1,
315 int r, int g, int b) {
316 if ((x0 == x1) && (y0 == y1)) {
317 unsigned char *p = &image[(y0 * rowSpan + x0) * totalChannels];
318 if (r != -1) p[0] = r;
319 if (g != -1) p[1] = g;
320 if (b != -1) p[2] = b;
321 return;
322 }
323 if ( std::abs(x1-x0) > std::abs(y1-y0) ) {
324 if (x0 > x1) {
325 std::swap(x0, x1);
326 std::swap(y0, y1);
327 }
328 float slope = (float)(y1 - y0) / (x1 - x0);
329 for (int x = x0; x <= x1; x++) {
330 int y = y0 + slope * (x - x0);
331 unsigned char *p = &image[(y * rowSpan + x) * totalChannels];
332 if (r != -1) p[0] = r;
333 if (g != -1) p[1] = g;
334 if (b != -1) p[2] = b;
335 }
336 } else {
337 if (y0 > y1) {
338 std::swap(x0, x1);
339 std::swap(y0, y1);
340 }
341 float invSlope = (float)(x1 - x0) / (y1 - y0);
342 for (int y = y0; y <= y1; y++) {
343 int x = x0 + invSlope * (y - y0);
344 unsigned char *p = &image[(y*rowSpan + x) * totalChannels];
345 if (r != -1) p[0] = r;
346 if (g != -1) p[1] = g;
347 if (b != -1) p[2] = b;
348 }
349 }
350
351 }
findColorChecker(const unsigned char * image,int width,int rowSpan,int height,float * patchColors,unsigned char ** outputImage,int * outputWidth,int * outputHeight)352 bool findColorChecker(const unsigned char *image,
353 int width,
354 int rowSpan,
355 int height,
356 float *patchColors,
357 unsigned char **outputImage,
358 int *outputWidth,
359 int *outputHeight) {
360 int64_t startTime = systemTime();
361
362 const int outTargetWidth = 160;
363 const int outScale = width / outTargetWidth;
364 const int outWidth = width / outScale;
365 const int outHeight = height / outScale;
366 LOGV("Debug image dimensions: %d, %d", outWidth, outHeight);
367
368 unsigned char *output = new unsigned char[outWidth * outHeight * totalChannels];
369
370 unsigned char *outP;
371 unsigned char *inP;
372
373 // First step, downsample for speed/noise reduction
374 downsample(image, output, rowSpan, outWidth, outHeight, outScale);
375
376 // Find connected components (groups)
377 ImageField outField(outWidth, outHeight, output);
378 Grouping(&outField);
379
380 // Calculate component bounds and areas
381 std::vector<PixelGroup> groups;
382 groups.reserve(outField.id_no);
383 for (int i = 0; i < outField.id_no; i++) {
384 groups.push_back(PixelGroup(i + 1, &outField));
385 }
386
387 inP = output;
388 for (int y = 0; y < outHeight; y++) {
389 for (int x = 0; x < outWidth; x++) {
390 groups[ outField(y, x).id - 1].growGroup(x, y);
391 }
392 }
393
394 // Filter out groups that are too small, too large, or have too
395 // non-square aspect ratio
396 PixelGroup::IdSet candidateGroups;
397
398 // Maximum/minimum width assuming pattern is fully visible and >
399 // 1/3 the image in width
400 const int maxPatchWidth = outWidth / 6;
401 const int minPatchWidth = outWidth / 3 / 7;
402 const int maxPatchArea = maxPatchWidth * maxPatchWidth;
403 const int minPatchArea = minPatchWidth * minPatchWidth;
404 // Assuming nearly front-on view of target, so aspect ratio should
405 // be quite close to square
406 const float maxAspectRatio = 5.f / 4.f;
407 const float minAspectRatio = 4.f / 5.f;
408 for (int i = 0; i < (int)groups.size(); i++) {
409 float aspect = groups[i].getApproxAspectRatio();
410 if (aspect < minAspectRatio || aspect > maxAspectRatio) continue;
411 // Check both boundary box area, and actual pixel count - they
412 // should both be within bounds for a roughly square patch.
413 int boundArea = groups[i].getBoundArea();
414 if (boundArea < minPatchArea || boundArea > maxPatchArea) continue;
415 int area = groups[i].getArea();
416 if (area < minPatchArea || area > maxPatchArea) continue;
417 candidateGroups.insert(&groups[i]);
418 }
419
420 // Find neighbors for candidate groups. O(n^2), but not many
421 // candidates to go through
422 for (PixelGroup::IdSetIter group = candidateGroups.begin();
423 group != candidateGroups.end();
424 group++) {
425 (*group)->findNeighbors(candidateGroups);
426 }
427
428 // Try to find a plausible 6x4 grid by taking each pixel group as
429 // the candidate top-left corner and try to build a grid from
430 // it. Assumes no missing patches.
431 float bestError = -1;
432 std::vector<int> bestGrid(6 * 4,0);
433 for (PixelGroup::IdSetIter group = candidateGroups.begin();
434 group != candidateGroups.end();
435 group++) {
436 int dex, dey; (*group)->getApproxCenter(&dex, &dey);
437 std::vector<int> grid(6 * 4, 0);
438 PixelGroup *tl = *group;
439 PixelGroup *bl, *tr, *br;
440
441 // Find the bottom-left and top-right corners
442 if ( (bl = tl->getDNeighbor(3)) == NULL ||
443 (tr = tl->getRNeighbor(5)) == NULL ) continue;
444 LOGV("Candidate at %d, %d", dex, dey);
445 LOGV(" Got BL and TR");
446
447 // Find the bottom-right corner
448 if ( tr->getDNeighbor(3) == NULL ) {
449 LOGV(" No BR from TR");
450 continue;
451 }
452 br = tr->getDNeighbor(3);
453 if ( br != bl->getRNeighbor(5) ) {
454 LOGV(" BR from TR and from BL don't agree");
455 continue;
456 }
457 br->getApproxCenter(&dex, &dey);
458 LOGV(" Got BR corner at %d, %d", dex, dey);
459
460 // Check that matching grid edge lengths are about the same
461 float gridTopWidth = tl->distanceTo(tr);
462 float gridBotWidth = bl->distanceTo(br);
463
464 if (gridTopWidth / gridBotWidth < minAspectRatio ||
465 gridTopWidth / gridBotWidth > maxAspectRatio) continue;
466 LOGV(" Got reasonable widths: %f %f", gridTopWidth, gridBotWidth);
467
468 float gridLeftWidth = tl->distanceTo(bl);
469 float gridRightWidth = tr->distanceTo(br);
470
471 if (gridLeftWidth / gridRightWidth < minAspectRatio ||
472 gridLeftWidth / gridRightWidth > maxAspectRatio) continue;
473 LOGV(" Got reasonable heights: %f %f", gridLeftWidth, gridRightWidth);
474
475 // Calculate average grid spacing
476 float gridAvgXGap = (gridTopWidth + gridBotWidth) / 2 / 5;
477 float gridAvgYGap = (gridLeftWidth + gridRightWidth) / 2 / 3;
478
479 // Calculate total error between average grid spacing and
480 // actual spacing Uses difference in expected squared distance
481 // and actual squared distance
482 float error = 0;
483 for (int x = 0; x < 6; x++) {
484 for (int y = 0; y < 4; y++) {
485 PixelGroup *node;
486 node = tl->getRNeighbor(x)->getDNeighbor(y);
487 if (node == NULL) {
488 error += outWidth * outWidth;
489 grid[y * 6 + x] = 0;
490 } else {
491 grid[y * 6 + x] = node->getId();
492 if (node == tl) continue;
493 float dist = tl->distanceSqTo(node);
494 float expXDist = (gridAvgXGap * x);
495 float expYDist = (gridAvgYGap * y);
496 float expDist = expXDist * expXDist + expYDist * expYDist;
497 error += fabs(dist - expDist);
498 }
499 }
500 }
501 if (bestError == -1 ||
502 bestError > error) {
503 bestGrid = grid;
504 bestError = error;
505 LOGV(" Best candidate, error %f", error);
506 }
507 }
508
509 // Check if a grid wasn't found
510 if (bestError == -1) {
511 LOGV("No color checker found!");
512 }
513
514 // Make sure black square is in bottom-right corner
515 if (bestError != -1) {
516 unsigned char tlValues[3];
517 unsigned char brValues[3];
518 int tlId = bestGrid[0];
519 int brId = bestGrid[23];
520
521 groups[tlId - 1].getAvgValue(tlValues);
522 groups[brId - 1].getAvgValue(brValues);
523
524 int tlSum = tlValues[0] + tlValues[1] + tlValues[2];
525 int brSum = brValues[0] + brValues[1] + brValues[2];
526 if (brSum > tlSum) {
527 // Grid is upside down, need to flip!
528 LOGV("Flipping grid to put grayscale ramp at bottom");
529 bestGrid = std::vector<int>(bestGrid.rbegin(), bestGrid.rend());
530 }
531 }
532
533 // Output average patch colors if requested
534 if (bestError != -1 && patchColors != NULL) {
535 for (int n = 0; n < 6 * 4 * colorChannels; n++) patchColors[n] = -1.f;
536
537 // Scan over original input image for grid regions, degamma, average
538 for (int px = 0; px < 6; px++) {
539 for (int py = 0; py < 4; py++) {
540 int id = bestGrid[py * 6 + px];
541 if (id == 0) continue;
542
543 PixelGroup &patch = groups[id - 1];
544 int startX, startY;
545 int endX, endY;
546 patch.getBoundingBox(&startX, &startY, &endX, &endY);
547
548 float sum[colorChannels] = {0.f};
549 int count = 0;
550 for (int y = startY; y <= endY; y++) {
551 for (int x = startX; x < endX; x++) {
552 if (outField(y,x).id != id) continue;
553 for (int iY = y * outScale;
554 iY < (y + 1) * outScale;
555 iY++) {
556 const unsigned char *inP = image +
557 (iY * rowSpan)
558 + (x * outScale * totalChannels);
559 for (int iX = 0; iX < outScale; iX++) {
560 for (int c = 0; c < colorChannels; c++) {
561 // Convert to float and normalize
562 float v = inP[c] / 255.f;
563 // Gamma correct to get back to
564 // roughly linear data
565 v = pow(v, gammaCorrection);
566 // Sum it up
567 sum[c] += v;
568 }
569 count++;
570 inP += totalChannels;
571 }
572 }
573 }
574 }
575 for (int c = 0 ; c < colorChannels; c++) {
576 patchColors[ (py * 6 + px) * colorChannels + c ] =
577 sum[c] / count;
578 }
579 }
580 }
581 // Print out patch colors
582 IF_LOGV() {
583 for (int y = 0; y < 4; y++) {
584 char tmpMsg[256];
585 int cnt = 0;
586 cnt = snprintf(tmpMsg, 256, "%02d:", y + 1);
587 for (int x = 0; x < 6; x++) {
588 int id = bestGrid[y * 6 + x];
589 if (id != 0) {
590 float *p = &patchColors[ (y * 6 + x) * colorChannels];
591 cnt += snprintf(tmpMsg + cnt, 256 - cnt,
592 "\t(%.3f,%.3f,%.3f)", p[0], p[1], p[2]);
593 } else {
594 cnt += snprintf(tmpMsg + cnt, 256 - cnt,
595 "\t(xxx,xxx,xxx)");
596 }
597 }
598 LOGV("%s", tmpMsg);
599 }
600 }
601 }
602
603 // Draw output if requested
604 if (outputImage != NULL) {
605 *outputImage = output;
606 *outputWidth = outWidth;
607 *outputHeight = outHeight;
608
609 // Draw all candidate group bounds
610 for (PixelGroup::IdSetIter group = candidateGroups.begin();
611 group != candidateGroups.end();
612 group++) {
613
614 int x,y;
615 (*group)->getApproxCenter(&x, &y);
616
617 // Draw candidate bounding box
618 int x0, y0, x1, y1;
619 (*group)->getBoundingBox(&x0, &y0, &x1, &y1);
620 drawLine(output, outWidth,
621 x0, y0, x1, y0,
622 255, 0, 0);
623 drawLine(output, outWidth,
624 x1, y0, x1, y1,
625 255, 0, 0);
626 drawLine(output, outWidth,
627 x1, y1, x0, y1,
628 255, 0, 0);
629 drawLine(output, outWidth,
630 x0, y1, x0, y0,
631 255, 0, 0);
632
633 // Draw lines between neighbors
634 // Red for to-right and to-below of me connections
635 const PixelGroup *neighbor;
636 if ( (neighbor = (*group)->getRNeighbor()) != NULL) {
637 int nX, nY;
638 neighbor->getApproxCenter(&nX, &nY);
639 drawLine(output,
640 outWidth,
641 x, y, nX, nY,
642 255, -1, -1);
643 }
644 if ( (neighbor = (*group)->getDNeighbor()) != NULL) {
645 int nX, nY;
646 neighbor->getApproxCenter(&nX, &nY);
647 drawLine(output,
648 outWidth,
649 x, y, nX, nY,
650 255, -1, -1);
651 }
652 // Blue for to-left or to-above of me connections
653 if ( (neighbor = (*group)->getLNeighbor()) != NULL) {
654 int nX, nY;
655 neighbor->getApproxCenter(&nX, &nY);
656 drawLine(output,
657 outWidth,
658 x, y, nX, nY,
659 -1, -1, 255);
660 }
661 if ( (neighbor = (*group)->getUNeighbor()) != NULL) {
662 int nX, nY;
663 neighbor->getApproxCenter(&nX, &nY);
664 drawLine(output,
665 outWidth,
666 x, y, nX, nY,
667 -1, -1, 255);
668 }
669 }
670
671 // Mark found grid patch pixels
672 if (bestError != -1) {
673 for (int x=0; x < 6; x++) {
674 for (int y =0; y < 4; y++) {
675 int id = bestGrid[y * 6 + x];
676 if (id != 0) {
677 int x0, y0, x1, y1;
678 groups[id - 1].getBoundingBox(&x0, &y0, &x1, &y1);
679 // Fill patch pixels with blue
680 for (int px = x0; px < x1; px++) {
681 for (int py = y0; py < y1; py++) {
682 if (outField(py,px).id != id) continue;
683 unsigned char *p =
684 &output[(py * outWidth + px)
685 * totalChannels];
686 p[0] = 0;
687 p[1] = 0;
688 p[2] = 255;
689
690 }
691 }
692 drawLine(output, outWidth,
693 x0, y0, x1, y1,
694 0, 255, 0);
695 drawLine(output, outWidth,
696 x0, y1, x0, y1,
697 0, 255, 0);
698 }
699 }
700 }
701 }
702
703 } else {
704 delete output;
705 }
706
707 int64_t endTime = systemTime();
708 LOGV("Process time: %f ms",
709 (endTime - startTime) / 1000000.);
710
711 if (bestError == -1) return false;
712
713 return true;
714 }
715