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 #define LOG_NDEBUG 0
17
18 #define LOG_TAG "ColorCheckerTest"
19 #include <utils/Log.h>
20 #include <utils/Timers.h>
21 #include <cmath>
22 #include <string>
23
24 #include "vec2.h"
25 #include "vec3.h"
26 #include "colorcheckertest.h"
27
28 const float GAMMA_CORRECTION = 2.2f;
29 const float COLOR_ERROR_THRESHOLD = 200.f;
~ColorCheckerTest()30 ColorCheckerTest::~ColorCheckerTest() {
31 ALOGV("Deleting color checker test handler");
32
33 if (mImage != NULL) {
34 delete mImage;
35 }
36 ALOGV("Image deleted");
37
38 int numHorizontalLines = mCandidateColors.size();
39 int numVerticalLines = mCandidateColors[0].size();
40
41 for (int i = 0; i < numHorizontalLines; ++i) {
42 for (int j = 0; j < numVerticalLines; ++j) {
43 if (mCandidateColors[i][j] != NULL) {
44 delete mCandidateColors[i][j];
45 }
46 if (mCandidatePositions[i][j] != NULL) {
47 delete mCandidatePositions[i][j];
48 }
49 }
50 }
51 ALOGV("Candidates deleted!");
52
53 for (int i = 0; i < 4; ++i) {
54 for (int j = 0; j < 6; ++j) {
55 if (mMatchPositions[i][j] != NULL) {
56 delete mMatchPositions[i][j];
57 }
58 if (mReferenceColors[i][j] != NULL) {
59 delete mReferenceColors[i][j];
60 }
61 if (mMatchColors[i][j] != NULL) {
62 delete mMatchColors[i][j];
63 }
64 }
65 }
66 }
67
68 // Adds a new image to the test handler.
addTestingImage(TestingImage * inputImage)69 void ColorCheckerTest::addTestingImage(TestingImage* inputImage) {
70 if (mImage != NULL) {
71 delete mImage;
72 }
73 mImage = NULL;
74 ALOGV("Original image deleted");
75 mImage = inputImage;
76
77 if ((mImage->getHeight() == getDebugHeight()) &&
78 (mImage->getWidth() == getDebugWidth())) {
79 copyDebugImage(getDebugHeight(), getDebugWidth(), mImage->getImage());
80 }
81 }
82
processData()83 void ColorCheckerTest::processData() {
84 mSuccess = false;
85 initializeRefColor();
86 edgeDetection();
87 }
88
initializeRefColor()89 void ColorCheckerTest::initializeRefColor() {
90 mReferenceColors.resize(4, std::vector<Vec3i*>(6, NULL));
91 mMatchPositions.resize(4, std::vector<Vec2f*>(6, NULL));
92 mMatchColors.resize(4, std::vector<Vec3f*>(6, NULL));
93 mMatchRadius.resize(4, std::vector<float>(6, 0.f));
94
95 mReferenceColors[0][0]= new Vec3i(115, 82, 68);
96 mReferenceColors[0][1]= new Vec3i(194, 150, 130);
97 mReferenceColors[0][2]= new Vec3i(98, 122, 157);
98 mReferenceColors[0][3]= new Vec3i(87, 108, 67);
99 mReferenceColors[0][4]= new Vec3i(133, 128, 177);
100 mReferenceColors[0][5]= new Vec3i(103, 189, 170);
101 mReferenceColors[1][0]= new Vec3i(214, 126, 44);
102 mReferenceColors[1][1]= new Vec3i(80, 91, 166);
103 mReferenceColors[1][2]= new Vec3i(193, 90, 99);
104 mReferenceColors[1][3]= new Vec3i(94, 60, 108);
105 mReferenceColors[1][4]= new Vec3i(157, 188, 64);
106 mReferenceColors[1][5]= new Vec3i(224, 163, 46);
107 mReferenceColors[2][0]= new Vec3i(56, 61, 150);
108 mReferenceColors[2][1]= new Vec3i(70, 148, 73);
109 mReferenceColors[2][2]= new Vec3i(175, 54, 60);
110 mReferenceColors[2][3]= new Vec3i(231, 199, 31);
111 mReferenceColors[2][4]= new Vec3i(187, 86, 149);
112 mReferenceColors[2][5]= new Vec3i(8, 133, 161);
113 mReferenceColors[3][0]= new Vec3i(243, 243, 242);
114 mReferenceColors[3][1]= new Vec3i(200, 200, 200);
115 mReferenceColors[3][2]= new Vec3i(160, 160, 160);
116 mReferenceColors[3][3]= new Vec3i(122, 122, 121);
117 mReferenceColors[3][4]= new Vec3i(85, 85, 85);
118 mReferenceColors[3][5]= new Vec3i(52, 52, 52);
119 }
120
edgeDetection()121 void ColorCheckerTest::edgeDetection() {
122 int width = mImage->getWidth();
123 int height = mImage->getHeight();
124
125 bool* edgeMap = new bool[height * width];
126 unsigned char* grayImage = new unsigned char[height * width];
127
128 // If the image is a color image and can be converted to a luminance layer
129 if (mImage->rgbToGrayScale(grayImage)) {
130 float* gradientMap = new float[height * width * 2];
131
132 // Computes the gradient image on the luminance layer.
133 computeGradient(grayImage, gradientMap);
134
135 float* gradientMagnitude = new float[height * width];
136 int* gradientDirectionInt = new int[height * width];
137 float* gradientDirection = new float[height * width];
138
139 // Computes the absolute gradient of the image without padding.
140 for (int i = 1; i < height - 1; ++i) {
141 for (int j = 1; j < width - 1; ++j) {
142 gradientMagnitude[i * width + j] =
143 sqrt(gradientMap[(i * width + j) * 2] *
144 gradientMap[(i * width + j) * 2] +
145 gradientMap[(i * width + j ) * 2 + 1] *
146 gradientMap[(i * width + j ) * 2 + 1]);
147
148 // Computes the gradient direction of the image.
149 if (gradientMap[(i * width + j) * 2] == 0 ) {
150 // If the vertical gradient is 0, the edge is horizontal
151 // Mark the gradient direction as 90 degrees.
152 gradientDirectionInt[i * width + j] = 2;
153 gradientDirection[i * width + j] = 90.0f;
154 } else {
155 // Otherwise the atan operation is valid and can decide
156 // the gradient direction of the edge.
157 float gradient = atan(gradientMap[(i * width + j) * 2 + 1]
158 / gradientMap[(i * width + j) * 2])
159 / (M_PI / 4);
160
161 gradientDirection[i * width + j] = gradient * 45.0f;
162
163 // Maps the gradient direction to 4 major directions with
164 // 0 mapped to up and 2 mapped to right.
165 if (gradient - floor(gradient) > 0.5) {
166 gradientDirectionInt[i * width + j] =
167 (static_cast<int>(ceil(gradient)) + 4) % 4;
168 } else {
169 gradientDirectionInt[i * width + j] =
170 (static_cast<int>(floor(gradient)) + 4) % 4;
171 }
172 }
173 }
174 }
175
176 // Compute a boolean map to show whether a pixel is on the edge.
177 for (int i = 1; i < height - 1; ++i) {
178 for (int j = 1; j < width - 1; ++j) {
179 edgeMap[i * width + j] = false;
180
181 switch (gradientDirectionInt[i * width + j]) {
182 case 0:
183 // If the gradient points rightwards, the pixel is
184 // on an edge if it has a larger absolute gradient than
185 // pixels on its left and right sides.
186 if ((gradientMagnitude[i * width + j] >=
187 gradientMagnitude[i * width + j + 1]) &&
188 (gradientMagnitude[i * width + j] >=
189 gradientMagnitude[i * width + j - 1])) {
190 edgeMap[i * width + j] = true;
191 }
192 break;
193 case 1:
194 // If the gradient points right-downwards, the pixel is
195 // on an edge if it has a larger absolute gradient than
196 // pixels on its upper left and bottom right sides.
197 if ((gradientMagnitude[i * width + j] >=
198 gradientMagnitude[(i + 1) * width + j + 1]) &&
199 (gradientMagnitude[i * width + j] >=
200 gradientMagnitude[(i - 1) * width + j - 1])) {
201 edgeMap[i * width + j] = true;
202 }
203 break;
204 case 2:
205 // If the gradient points upwards, the pixel is
206 // on an edge if it has a larger absolute gradient than
207 // pixels on its up and down sides.
208 if ((gradientMagnitude[i * width + j] >=
209 gradientMagnitude[(i + 1) * width + j]) &&
210 (gradientMagnitude[i * width + j] >=
211 gradientMagnitude[(i - 1) * width + j])) {
212 edgeMap[i * width + j] = true;
213 }
214 break;
215 case 3:
216 // If the gradient points right-upwards, the pixel is
217 // on an edge if it has a larger absolute gradient than
218 // pixels on its bottom left and upper right sides.
219 if ((gradientMagnitude[i * width + j] >=
220 gradientMagnitude[(i - 1) * width + j + 1]) &&
221 (gradientMagnitude[i * width + j] >=
222 gradientMagnitude[(i + 1) * width + j - 1])) {
223 edgeMap[i * width + j] = true;
224 }
225 }
226
227 }
228 }
229
230 houghLineDetection(edgeMap, gradientMagnitude, gradientDirection);
231
232 // Cleans up
233 delete[] gradientMap;
234 delete[] gradientDirectionInt;
235 delete[] gradientMagnitude;
236 delete[] gradientDirection;
237
238 } else {
239 ALOGE("Not a color image!");
240 }
241
242 delete[] edgeMap;
243 delete[] grayImage;
244 }
245
246 // Runs the hough voting algorithm to find the grid of the color checker
247 // with the edge map, gradient direction and gradient magnitude as inputs.
houghLineDetection(bool * edgeMap,float * gradientMagnitude,float * gradientDirection)248 void ColorCheckerTest::houghLineDetection(bool* edgeMap,
249 float* gradientMagnitude,
250 float* gradientDirection) {
251 // Constructs a graph for Hough voting. The vertical axis counts the vote
252 // for a certain angle. The horizontal axis counts the vote for the distance
253 // of a line from the origin of the image.
254 int houghHeight = 180;
255 int houghWidth = 200;
256 int houghCounts[houghHeight][houghWidth];
257 int houghSum[houghHeight][houghWidth];
258
259 int** houghVote;
260 houghVote = new int*[180];
261 for (int i = 0; i < 180; ++i) {
262 houghVote[i] = new int[200];
263 }
264
265 for (int i = 0; i < houghHeight; ++i) {
266 for (int j = 0; j < houghWidth; ++j) {
267 houghCounts[i][j] = 0;
268 houghVote[i][j] = 0;
269 houghSum[i][j] = 0;
270 }
271 }
272
273 // Vectors to record lines in two orthogonal directions.
274 // Each line is represented by its direction and its distance to the origin.
275 std::vector<std::vector<int> > verticalLines;
276 std::vector<std::vector<int> > horizontalLines;
277 float radius;
278 int height = mImage->getHeight();
279 int width = mImage->getWidth();
280
281 // Processes the signicant edge pixels and cast vote for the corresponding
282 // edge passing this pixel.
283 for (int i = 1; i < height - 1; ++i) {
284 for (int j = 1; j < width - 1; ++j) {
285 // Sets threashold for the gradient magnitude to discount noises
286 if (edgeMap[i * width + j] &&
287 (gradientMagnitude[i * width + j] > 15)) {
288 int shiftedAngle;
289
290 // Shifts angles for 45 degrees so the vertical and horizontal
291 // direction is mapped to 45 and 135 degrees to avoid padding.
292 // This uses the assumption that the color checker is placed
293 // roughly parallel to the image boarders. So that the edges
294 // at the angle of 45 will be rare.
295 shiftedAngle = (static_cast<int>(
296 -gradientDirection[i * width + j]) + 225 % 180);
297 float shiftedAngleRad = static_cast<float>(shiftedAngle)
298 * M_PI / 180.0f;
299
300 // Computes the distance of the line from the origin.
301 float a, b;
302 a = static_cast<float>(i - j) / sqrt(2.0f);
303 b = static_cast<float>(i + j) / sqrt(2.0f);
304 radius = a * sin(shiftedAngleRad) - b * cos(shiftedAngleRad);
305
306 // Adds one vote for the line. The line's angle is shifted by
307 // 45 degrees to avoid avoid padding for the vertical lines,
308 // which is more common than diagonal lines. The line's
309 // distance is mapped to [0, 200] from [-200, 200].
310 ++houghCounts[shiftedAngle][static_cast<int>((radius / 2.0f) +
311 100.0f)];
312
313 drawPoint(i, j, Vec3i(255, 255, 255));
314 }
315 }
316 }
317
318 int houghAngleSum[houghHeight];
319 int primaryVerticalAngle, primaryHorizontalAngle;
320 int max1 = 0;
321 int max2 = 0;
322
323 // Looking for the two primary angles of the lines.
324 for (int i = 5; i < houghHeight - 5; ++i) {
325 houghAngleSum[i] = 0;
326 for (int j = 0; j < houghWidth; ++j) {
327 for (int l = -5; l <= 5; ++l) {
328 houghSum[i][j] += houghCounts[i + l][j];
329 }
330 houghAngleSum[i] += houghSum[i][j];
331 }
332
333 if ((i < houghHeight / 2) && (houghAngleSum[i] > max1)) {
334 max1 = houghAngleSum[i];
335 primaryVerticalAngle = i;
336 } else if ((i > houghHeight / 2) && (houghAngleSum[i] > max2)) {
337 max2 = houghAngleSum[i];
338 primaryHorizontalAngle = i;
339 }
340 }
341
342 ALOGV("Primary angles are %d, %d",
343 primaryVerticalAngle, primaryHorizontalAngle);
344
345 int angle;
346
347 // For each primary angle, look for the highest voted lines.
348 for (int k = 0; k < 2; ++k) {
349 if (k == 0) {
350 angle = primaryVerticalAngle;
351 } else {
352 angle = primaryHorizontalAngle;
353 }
354
355 std::vector<int> line(2);
356 for (int j = 2; j < houghWidth - 2; ++j) {
357 houghVote[angle][j] = houghSum[angle][j];
358 houghSum[angle][j] = 0;
359 }
360
361 // For each radius, average the vote with nearby ones.
362 for (int j = 2; j < houghWidth - 2; ++j) {
363 for (int m = -2; m <= 2; ++m) {
364 houghSum[angle][j] += houghVote[angle][j + m];
365 }
366 }
367
368 bool isCandidate[houghWidth];
369
370 // Find whether a lines is a candidate by rejecting the ones that have
371 // lower vote than others in the neighborhood.
372 for (int j = 2; j < houghWidth - 2; ++j) {
373 isCandidate[j] = true;
374 for (int m = -2; ((isCandidate[j]) && (m <= 2)); ++m) {
375 if ((houghSum[angle][j] < 20) ||
376 (houghSum[angle][j] < houghSum[angle][j + m])) {
377 isCandidate[j] = false;
378 }
379 }
380 }
381
382 int iter1 = 0;
383 int iter2 = 0;
384 int count = 0;
385
386 // Finds the lines that are not too close to each other and add to the
387 // detected lines.
388 while (iter2 < houghWidth) {
389 while ((!isCandidate[iter2]) && (iter2 < houghWidth)) {
390 ++iter2;
391 }
392 if ((isCandidate[iter2]) && (iter2 - iter1 < 5)) {
393 iter1 = (iter2 + iter1) / 2;
394 ++iter2;
395 } else {
396 line[0] = angle;
397 line[1] = (iter1 - 100) * 2;
398 if (iter1 != 0) {
399 if (k == 0) {
400 verticalLines.push_back(line);
401 Vec3i color(verticalLines.size() * 20, 0, 0);
402 drawLine(line[0], line[1], color);
403 } else {
404 horizontalLines.push_back(line);
405 Vec3i color(0, horizontalLines.size() * 20, 0);
406 drawLine(line[0], line[1], color);
407 }
408 }
409 iter1 = iter2;
410 ++iter2;
411 ALOGV("pushing back line %d %d", line[0], line[1]);
412 }
413 }
414 }
415
416 ALOGV("Numbers of lines in each direction is %d, %d",
417 verticalLines.size(), horizontalLines.size());
418
419 for (int i = 0; i < 180; ++i) {
420 delete[] houghVote[i];
421 }
422 delete[] houghVote;
423
424 findCheckerBoards(verticalLines, horizontalLines);
425 }
426
427 // Computes the gradient in both x and y direction of a layer
computeGradient(unsigned char * layer,float * gradientMap)428 void ColorCheckerTest::computeGradient(unsigned char* layer,
429 float* gradientMap) {
430 int width = mImage->getWidth();
431 int height = mImage->getHeight();
432
433 // Computes the gradient in the whole image except the image boarders.
434 for (int i = 1; i < height - 1; ++i) {
435 for (int j = 1; j < width - 1; ++j) {
436 gradientMap[(i * width + j) * 2] =
437 0.5f * (layer[i * width + j + 1] -
438 layer[i * width + j - 1]);
439 gradientMap[(i * width + j) * 2 + 1] =
440 0.5f * (layer[(i + 1) * width + j] -
441 layer[(i - 1) * width + j]);
442 }
443 }
444 }
445
446 // Tries to find the checker boards with the highest voted lines
findCheckerBoards(std::vector<std::vector<int>> verticalLines,std::vector<std::vector<int>> horizontalLines)447 void ColorCheckerTest::findCheckerBoards(
448 std::vector<std::vector<int> > verticalLines,
449 std::vector<std::vector<int> > horizontalLines) {
450 ALOGV("Start looking for Color checker");
451
452 int numHorizontalLines = mCandidateColors.size();
453 int numVerticalLines;
454 if (numHorizontalLines > 0) {
455 numVerticalLines = mCandidateColors[0].size();
456 for (int i = 0; i < numHorizontalLines; ++i) {
457 for (int j = 0; j < numVerticalLines; ++j) {
458 if (mCandidateColors[i][j] != NULL) {
459 delete mCandidateColors[i][j];
460 }
461 if (mCandidatePositions[i][j] != NULL) {
462 delete mCandidatePositions[i][j];
463 }
464 }
465 mCandidateColors[i].clear();
466 mCandidatePositions[i].clear();
467 }
468 }
469 mCandidateColors.clear();
470 mCandidatePositions.clear();
471
472 ALOGV("Candidates deleted!");
473
474 numVerticalLines = verticalLines.size();
475 numHorizontalLines = horizontalLines.size();
476 Vec2f pointUpperLeft;
477 Vec2f pointBottomRight;
478
479 mCandidateColors.resize(numHorizontalLines - 1);
480 mCandidatePositions.resize(numHorizontalLines - 1);
481
482 for (int i = numVerticalLines - 1; i >= 1; --i) {
483 for (int j = 0; j < numHorizontalLines - 1; ++j) {
484 // Finds the upper left and bottom right corner of each rectangle
485 // formed by two neighboring highest voted lines.
486 pointUpperLeft = findCrossing(verticalLines[i], horizontalLines[j]);
487 pointBottomRight = findCrossing(verticalLines[i - 1],
488 horizontalLines[j + 1]);
489
490 Vec3i* color = new Vec3i();
491 Vec2f* pointCenter = new Vec2f();
492 // Verifies if they are separated by a reasonable distance.
493 if (verifyPointPair(pointUpperLeft, pointBottomRight,
494 pointCenter, color)) {
495 mCandidatePositions[j].push_back(pointCenter);
496 mCandidateColors[j].push_back(color);
497 ALOGV("Color at (%d, %d) is (%d, %d, %d)", j, i,color->r(), color->g(), color->b());
498
499 } else {
500 mCandidatePositions[j].push_back(NULL);
501 mCandidateColors[j].push_back(NULL);
502 delete color;
503 delete pointCenter;
504 }
505 }
506 }
507
508 ALOGV("Candidates Number (%d, %d)", mCandidateColors.size(), mCandidateColors[0].size());
509 // Verifies whether the current line candidates form a valid color checker.
510 verifyColorGrid();
511 }
512
513 // Returns the corssing point of two lines given the lines.
findCrossing(std::vector<int> line1,std::vector<int> line2)514 Vec2f ColorCheckerTest::findCrossing(std::vector<int> line1,
515 std::vector<int> line2) {
516 Vec2f crossingPoint;
517 float r1 = static_cast<float>(line1[1]);
518 float r2 = static_cast<float>(line2[1]);
519 float ang1, ang2;
520 float y1, y2;
521
522 ang1 = static_cast<float>(line1[0]) / 180.0f * M_PI;
523 ang2 = static_cast<float>(line2[0]) / 180.0f * M_PI;
524
525 float x, y;
526 x = (r1 * cos(ang2) - r2 * cos(ang1)) / sin(ang1 - ang2);
527 y = (r1 * sin(ang2) - r2 * sin(ang1)) / sin(ang1 - ang2);
528
529 crossingPoint.set((x + y) / sqrt(2.0), (y - x) / sqrt(2.0));
530
531 //ALOGV("Crosspoint at (%f, %f)", crossingPoint.x(), crossingPoint.y());
532 return crossingPoint;
533 }
534
535 // Verifies whether two opposite corners on a quadrilateral actually can be
536 // the two corners of a color checker.
verifyPointPair(Vec2f pointUpperLeft,Vec2f pointBottomRight,Vec2f * pointCenter,Vec3i * color)537 bool ColorCheckerTest::verifyPointPair(Vec2f pointUpperLeft,
538 Vec2f pointBottomRight,
539 Vec2f* pointCenter,
540 Vec3i* color) {
541 bool success = true;
542
543 /** 5 and 30 are the threshold tuned for resolution 640*480*/
544 if ((pointUpperLeft.x() < 0) ||
545 (pointUpperLeft.x() >= mImage->getHeight()) ||
546 (pointUpperLeft.y() < 0) ||
547 (pointUpperLeft.y() >= mImage->getWidth()) ||
548 (pointBottomRight.x() < 0) ||
549 (pointBottomRight.x() >= mImage->getHeight()) ||
550 (pointBottomRight.y() < 0) ||
551 (pointBottomRight.y() >= mImage->getWidth()) ||
552 (abs(pointUpperLeft.x() - pointBottomRight.x()) <= 5) ||
553 (abs(pointUpperLeft.y() - pointBottomRight.y()) <= 5) ||
554 (abs(pointUpperLeft.x() - pointBottomRight.x()) >= 30) ||
555 (abs(pointUpperLeft.y() - pointBottomRight.y()) >= 30)) {
556
557 // If any of the quadrilateral corners are out of the image or if
558 // the distance between them are too large or too big, the quadrilateral
559 // could not be one of the checkers
560 success = false;
561 } else {
562 // Find the checker center if the corners of the rectangle meet criteria
563 pointCenter->set((pointUpperLeft.x() + pointBottomRight.x()) / 2.0f,
564 (pointUpperLeft.y() + pointBottomRight.y()) / 2.0f);
565 color->set(mImage->getPixelValue(*pointCenter).r(),
566 mImage->getPixelValue(*pointCenter).g(),
567 mImage->getPixelValue(*pointCenter).b());
568 ALOGV("Color at (%f, %f) is (%d, %d, %d)", pointCenter->x(), pointCenter->y(),color->r(), color->g(), color->b());
569 }
570 return success;
571 }
572
573 // Verifies the color checker centers and finds the match between the detected
574 // color checker and the reference MacBeth color checker
verifyColorGrid()575 void ColorCheckerTest::verifyColorGrid() {
576 ALOGV("Start looking for Color Grid");
577 int numHorizontalLines = mCandidateColors.size();
578 int numVerticalLines = mCandidateColors[0].size();
579 bool success = false;
580
581 // Computes the standard deviation of one row/column of the proposed color
582 // checker. Discards the row/column if the std is below a threshold.
583 for (int i = 0; i < numHorizontalLines; ++i) {
584 Vec3f meanColor(0.f, 0.f, 0.f);
585 int numNonZero = 0;
586
587 for (int j = 0; j < numVerticalLines; ++j) {
588 if (mCandidateColors[i][j] != NULL) {
589 ALOGV("candidate color (%d, %d) is (%d, %d, %d)", i, j, mCandidateColors[i][j]->r(), mCandidateColors[i][j]->g(), mCandidateColors[i][j]->b());
590
591 meanColor = meanColor + (*mCandidateColors[i][j]);
592 ++numNonZero;
593 }
594 }
595 if (numNonZero > 0) {
596 meanColor = meanColor / numNonZero;
597 }
598 ALOGV("Mean color for vertical direction computed!");
599
600 float std = 0;
601 for (int j = 0; j < numVerticalLines; ++j) {
602 if (mCandidateColors[i][j] != NULL) {
603 std += mCandidateColors[i][j]->squareDistance<float>(meanColor);
604 }
605 }
606 if (numNonZero > 0) {
607 std = sqrt(std / (3 * numNonZero));
608 }
609 ALOGV("st. deviation for the %d dir1 is %d", i, static_cast<int>(std));
610
611 if ((std <= 30) && (numNonZero > 1)) {
612 for (int j = 0; j < numVerticalLines; ++j) {
613 if (mCandidateColors[i][j] != NULL) {
614 delete mCandidateColors[i][j];
615 mCandidateColors[i][j] = NULL;
616 }
617 }
618 }
619 }
620
621 // Discards the column/row of the color checker if the std is below a
622 // threshold.
623 for (int j = 0; j < numVerticalLines; ++j) {
624 Vec3f meanColor(0.f, 0.f, 0.f);
625 int numNonZero = 0;
626
627 for (int i = 0; i < numHorizontalLines; ++i) {
628 if (mCandidateColors[i][j] != NULL) {
629 meanColor = meanColor + (*mCandidateColors[i][j]);
630 ++numNonZero;
631 }
632 }
633 if (numNonZero > 0) {
634 meanColor = meanColor / numNonZero;
635 }
636
637 float std = 0;
638 for (int i = 0; i < numHorizontalLines; ++i) {
639 if (mCandidateColors[i][j] != NULL) {
640 std += mCandidateColors[i][j]->squareDistance<float>(meanColor);
641 }
642 }
643 if (numNonZero > 0) {
644 std = sqrt(std / (3 * numNonZero));
645 }
646
647 ALOGV("st. deviation for the %d dir2 is %d", j, static_cast<int>(std));
648
649 if ((std <= 30) && (numNonZero > 1)) {
650 for (int i = 0; i < numHorizontalLines; ++i) {
651 if (mCandidateColors[i][j] != NULL) {
652 delete mCandidateColors[i][j];
653 mCandidateColors[i][j] = NULL;
654 }
655 }
656 }
657 }
658
659 for (int i = 0; i < numHorizontalLines; ++i) {
660 for (int j = 0; j < numVerticalLines; ++j) {
661 if (mCandidateColors[i][j] != NULL) {
662 ALOGV("position (%d, %d) is at (%f, %f) with color (%d, %d, %d)",
663 i, j,
664 mCandidatePositions[i][j]->x(),
665 mCandidatePositions[i][j]->y(),
666 mCandidateColors[i][j]->r(),
667 mCandidateColors[i][j]->g(),
668 mCandidateColors[i][j]->b());
669 } else {
670 ALOGV("position (%d, %d) is 0", i, j);
671 }
672 }
673 }
674
675 // Finds the match between the detected color checker and the reference
676 // MacBeth color checker.
677 int rowStart = 0;
678 int rowEnd = 0;
679
680 // Loops until all dectected color checker has been processed.
681 while (!success) {
682 int columnStart = 0;
683 int columnEnd = 0;
684 bool isRowStart = false;
685 bool isRowEnd = true;
686
687 // Finds the row start of the next block of detected color checkers.
688 while ((!isRowStart) && (rowStart < numHorizontalLines)) {
689 for (int j = 0; j < numVerticalLines; ++j) {
690 if (mCandidateColors[rowStart][j] != NULL) {
691 isRowStart = true;
692 }
693 }
694 ++rowStart;
695 }
696 rowStart--;
697 rowEnd = rowStart;
698 ALOGV("rowStart is %d", rowStart);
699
700 // Finds the row end of the next block of detected color checkers.
701 while ((isRowEnd) && (rowEnd < numHorizontalLines)) {
702 isRowEnd = false;
703 for (int j = 0; j < numVerticalLines; ++j) {
704 if (mCandidateColors[rowEnd][j] != NULL) {
705 isRowEnd= true;
706 }
707 }
708 if (isRowEnd) {
709 ++rowEnd;
710 }
711 }
712 if ((!isRowEnd) && isRowStart) {
713 rowEnd--;
714 }
715 if ((isRowEnd) && (rowEnd == numHorizontalLines)) {
716 rowEnd--;
717 isRowEnd = false;
718 }
719 ALOGV("rowEnd is %d", rowEnd);
720
721 // Matches color checkers between the start row and the end row.
722 bool successVertical = false;
723
724 while (!successVertical) {
725 bool isColumnEnd = true;
726 bool isColumnStart = false;
727
728 // Finds the start column of the next block of color checker
729 while ((!isColumnStart) && (columnStart < numVerticalLines)) {
730 if (mCandidateColors[rowStart][columnStart] != NULL) {
731 isColumnStart = true;
732 }
733 ++columnStart;
734 }
735 columnStart--;
736 columnEnd = columnStart;
737
738 // Finds the end column of the next block of color checker
739 while ((isColumnEnd) && (columnEnd < numVerticalLines)) {
740 isColumnEnd = false;
741 if (mCandidateColors[rowStart][columnEnd] != NULL)
742 isColumnEnd = true;
743 if (isColumnEnd) {
744 ++columnEnd;
745 }
746 }
747
748 if ((!isColumnEnd) && isColumnStart) {
749 columnEnd--;
750 }
751 if ((isColumnEnd) && (columnEnd == numVerticalLines)) {
752 columnEnd--;
753 isColumnEnd = false;
754 }
755
756 // Finds the best match on the MacBeth reference color checker for
757 // the continuous block of detected color checker
758 if (isRowStart && (!isRowEnd) &&
759 isColumnStart && (!isColumnEnd)) {
760 findBestMatch(rowStart, rowEnd, columnStart, columnEnd);
761 }
762 ALOGV("FindBestMatch for %d, %d, %d, %d", rowStart,
763 rowEnd, columnStart, columnEnd);
764
765 // If the column search finishes, go out of the loop
766 if (columnEnd >= numVerticalLines - 1) {
767 successVertical = true;
768 } else {
769 columnStart = columnEnd + 1;
770 }
771 }
772 ALOGV("Continuing to search for direction 1");
773
774 // If the row search finishes, go out of the loop
775 if (rowEnd >= numHorizontalLines - 1) {
776 success = true;
777 } else {
778 rowStart = rowEnd + 1;
779 }
780 }
781
782 for (int i = 0; i < 4; ++i) {
783 for (int j = 0; j < 6; ++j) {
784 if (mMatchPositions[i][j] != NULL) {
785 ALOGV("Reference Match position for (%d, %d) is (%f, %f)", i, j,
786 mMatchPositions[i][j]->x(), mMatchPositions[i][j]->y());
787 }
788 }
789 }
790
791 fillRefColorGrid();
792 }
793
794 // Finds the best match on the MacBeth color checker for the continuous block of
795 // detected color checkers bounded by row i1, row i2 and column j1 and column j2
796 // Assumes that the subsample is less than 4*6.
findBestMatch(int i1,int i2,int j1,int j2)797 void ColorCheckerTest::findBestMatch(int i1, int i2, int j1, int j2) {
798 int numHorizontalGrid = i2 - i1 + 1;
799 int numVerticalGrid = j2 - j1 + 1;
800
801 if (((numHorizontalGrid > 1) || (numVerticalGrid > 1)) &&
802 (numHorizontalGrid <= 4) && (numVerticalGrid <= 6)) {
803 ALOGV("i1, j2, j1, j2 is %d, %d, %d, %d", i1, i2, j1, j2);
804 float minError;
805 float error = 0.f;
806 int horizontalMatch, verticalMatch;
807
808 // Finds the match start point where the error is minimized.
809 for (int i = 0; i < numHorizontalGrid; ++i) {
810 for (int j = 0; j < numVerticalGrid; ++j) {
811 if (mCandidateColors[i1 + i][j1 + j] != NULL) {
812 error += mCandidateColors[i1 + i][j1 + j]->squareDistance<int>(
813 *mReferenceColors[i][j]);
814 }
815 }
816 }
817 ALOGV("Error is %f", error);
818 minError = error;
819 horizontalMatch = 0;
820 verticalMatch = 0;
821
822 for (int i = 0; i <= 4 - numHorizontalGrid; ++i) {
823 for (int j = 0; j <= 6 - numVerticalGrid; ++j) {
824 error = 0.f;
825
826 for (int id = 0; id < numHorizontalGrid; ++id) {
827 for (int jd = 0; jd < numVerticalGrid; ++jd) {
828 if (mCandidateColors[i1 + id][j1 + jd] != NULL) {
829 error += mCandidateColors[i1 + id][j1 + jd]->
830 squareDistance<int>(
831 *mReferenceColors[i + id][j + jd]);
832 }
833 }
834 }
835
836 if (error < minError) {
837 minError = error;
838 horizontalMatch = i;
839 verticalMatch = j;
840 }
841 ALOGV("Processed %d, %d and error is %f", i, j, error );
842 }
843 }
844
845 for (int id = 0; id < numHorizontalGrid; ++id) {
846 for (int jd = 0; jd < numVerticalGrid; ++jd) {
847 if (mCandidatePositions[i1 + id][j1 + jd] != NULL) {
848 mMatchPositions[horizontalMatch + id][verticalMatch + jd] =
849 new Vec2f(mCandidatePositions[i1 + id][j1 + jd]->x(),
850 mCandidatePositions[i1 + id][j1 + jd]->y());
851 }
852 }
853 }
854 ALOGV("Grid match starts at %d, %d", horizontalMatch, verticalMatch);
855 }
856 }
857
858 // Finds the boundary of a color checker by its color similarity to the center.
859 // Also predicts the location of unmatched checkers.
fillRefColorGrid()860 void ColorCheckerTest::fillRefColorGrid() {
861 int rowStart = 0;
862 int columnStart = 0;
863 bool foundStart = true;
864
865 for (int i = 0; (i < 4) && foundStart; ++i) {
866 for (int j = 0; (j < 6) && foundStart; ++j) {
867 if (mMatchPositions[i][j] != NULL) {
868 rowStart = i;
869 columnStart = j;
870 foundStart = false;
871 }
872 }
873 }
874 ALOGV("First match found at (%d, %d)", rowStart, columnStart);
875
876 float rowDistance, columnDistance;
877 rowDistance = 0;
878 columnDistance = 0;
879 int numRowGrids = 0;
880 int numColumnGrids = 0;
881
882 for (int i = rowStart; i < 4; ++i) {
883 for (int j = columnStart; j < 6; ++j) {
884 if (mMatchPositions[i][j] != NULL) {
885 if (i > rowStart) {
886 ++numRowGrids;
887 rowDistance += (mMatchPositions[i][j]->x() -
888 mMatchPositions[rowStart][columnStart]->x()) /
889 static_cast<float>(i - rowStart);
890 }
891 if (j > columnStart) {
892 ++numColumnGrids;
893 columnDistance += (mMatchPositions[i][j]->y() -
894 mMatchPositions[rowStart][columnStart]->y()) /
895 static_cast<float>(j - columnStart);
896 }
897 }
898 }
899 }
900
901 if ((numRowGrids > 0) && (numColumnGrids > 0)) {
902 rowDistance = rowDistance / numRowGrids;
903 columnDistance = columnDistance / numColumnGrids;
904 ALOGV("delta is %f, %f", rowDistance, columnDistance);
905
906 for (int i = 0; i < 4; ++i) {
907 for (int j = 0 ; j < 6; ++j) {
908 if (mMatchPositions[i][j] == NULL) {
909 mMatchPositions[i][j] = new Vec2f(
910 mMatchPositions[rowStart][columnStart]->x() +
911 (i - rowStart) * rowDistance,
912 mMatchPositions[rowStart][columnStart]->y() +
913 (j - columnStart) * columnDistance);
914 }
915 }
916 }
917 for (int i = 0; i < 4; ++i) {
918 for (int j = 0; j < 6; ++j) {
919 float radius = 0;
920 Vec3i color = mImage->getPixelValue(*mMatchPositions[i][j]);
921 Vec3f meanColor(0.f , 0.f, 0.f);
922
923 int numPixels = 0;
924 for (int ii = static_cast<int>(mMatchPositions[i][j]->x() -
925 rowDistance/2);
926 ii <= static_cast<int>(mMatchPositions[i][j]->x() +
927 rowDistance/2);
928 ++ii) {
929 for (int jj = static_cast<int>(mMatchPositions[i][j]->y() -
930 columnDistance/2);
931 jj <= static_cast<int>(mMatchPositions[i][j]->y() +
932 columnDistance/2);
933 ++jj) {
934 if ((ii >= 0) && (ii < mImage->getHeight()) &&
935 (jj >= 0) && (jj < mImage->getWidth())) {
936 Vec3i pixelColor = mImage->getPixelValue(ii,jj);
937 float error = color.squareDistance<int>(pixelColor);
938
939 if (error < COLOR_ERROR_THRESHOLD) {
940 drawPoint(ii, jj, *mReferenceColors[i][j]);
941 meanColor = meanColor + pixelColor;
942 numPixels++;
943 Vec2i pixelPosition(ii, jj);
944
945 if (pixelPosition.squareDistance<float>(
946 *mMatchPositions[i][j]) > radius) {
947 radius = pixelPosition.squareDistance<float>(
948 *mMatchPositions[i][j]);
949 }
950 }
951 }
952 }
953 }
954
955 /** Computes the radius of the checker.
956 * The above computed radius is the squared distance to the
957 * furthest point with a matching color. To be conservative, we
958 * only consider an area with radius half of the above computed
959 * value. Since radius is computed as a squared root, the one
960 * that will be recorded is 1/4 of the above computed value.
961 */
962 mMatchRadius[i][j] = radius / 4.f;
963 mMatchColors[i][j] = new Vec3f(meanColor / numPixels);
964
965 ALOGV("Reference color at (%d, %d) is (%d, %d, %d)", i, j,
966 mReferenceColors[i][j]->r(),
967 mReferenceColors[i][j]->g(),
968 mReferenceColors[i][j]->b());
969 ALOGV("Average color at (%d, %d) is (%f, %f, %f)", i, j,
970 mMatchColors[i][j]->r(),
971 mMatchColors[i][j]->g(),
972 mMatchColors[i][j]->b());
973 ALOGV("Radius is %f", mMatchRadius[i][j]);
974 }
975 }
976
977 mSuccess = true;
978 }
979 }
980