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