Home | History | Annotate | Download | only in colorchecker
      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 
     40     PixelId(): id(0), p(NULL) {}
     41 
     42     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:
     54     ImageField(int width, int height, const unsigned char *data):
     55             mWidth(width), mHeight(height), pData(data) {
     56     }
     57 
     58     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 
     67     int getWidth() const { return mWidth; }
     68     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:
     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 {
     95         bool operator() (const PixelGroup* l, const PixelGroup* r) const {
     96             return l->getId() < r->getId();
     97         }
     98     };
     99 
    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 
    112     int getId() const {
    113         return mId;
    114     }
    115 
    116     int getArea() const {
    117         return mArea;
    118     }
    119 
    120     int getBoundArea() const {
    121         return (mMaxX - mMinX) * (mMaxY - mMinY);
    122     }
    123 
    124     float getApproxAspectRatio() const {
    125         return ((float)(mMaxY - mMinY)) / (mMaxX - mMinX);
    126     }
    127 
    128     void getApproxCenter(int *x, int *y) const {
    129         *x = (mMaxX + mMinX)/2;
    130         *y = (mMaxY + mMinY)/2;
    131     }
    132 
    133     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 
    140     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 
    146     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 
    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 
    198     void setRNeighbor(PixelGroup *rNeighbor) {
    199         mRNeighbor = rNeighbor;
    200     }
    201 
    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 
    212     void setDNeighbor(PixelGroup *dNeighbor) {
    213         mDNeighbor = dNeighbor;
    214     }
    215 
    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 
    226     void setLNeighbor(PixelGroup *lNeighbor) {
    227         mLNeighbor = lNeighbor;
    228     }
    229 
    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 
    240     void setUNeighbor(PixelGroup *uNeighbor) {
    241         mUNeighbor = uNeighbor;
    242     }
    243 
    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 
    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 
    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 */
    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 
    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 }
    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