• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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