blob: ef7d2c617668d89e541a8f167104a91c17743670 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_NDEBUG 0
#define LOG_TAG "ColorCheckerTest"
#include <utils/Log.h>
#include <utils/Timers.h>
#include <cmath>
#include <string>
#include "vec2.h"
#include "vec3.h"
#include "colorcheckertest.h"
const float GAMMA_CORRECTION = 2.2f;
const float COLOR_ERROR_THRESHOLD = 200.f;
ColorCheckerTest::~ColorCheckerTest() {
ALOGV("Deleting color checker test handler");
if (mImage != NULL) {
delete mImage;
}
ALOGV("Image deleted");
int numHorizontalLines = mCandidateColors.size();
int numVerticalLines = mCandidateColors[0].size();
for (int i = 0; i < numHorizontalLines; ++i) {
for (int j = 0; j < numVerticalLines; ++j) {
if (mCandidateColors[i][j] != NULL) {
delete mCandidateColors[i][j];
}
if (mCandidatePositions[i][j] != NULL) {
delete mCandidatePositions[i][j];
}
}
}
ALOGV("Candidates deleted!");
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 6; ++j) {
if (mMatchPositions[i][j] != NULL) {
delete mMatchPositions[i][j];
}
if (mReferenceColors[i][j] != NULL) {
delete mReferenceColors[i][j];
}
if (mMatchColors[i][j] != NULL) {
delete mMatchColors[i][j];
}
}
}
}
// Adds a new image to the test handler.
void ColorCheckerTest::addTestingImage(TestingImage* inputImage) {
if (mImage != NULL) {
delete mImage;
}
mImage = NULL;
ALOGV("Original image deleted");
mImage = inputImage;
if ((mImage->getHeight() == getDebugHeight()) &&
(mImage->getWidth() == getDebugWidth())) {
copyDebugImage(getDebugHeight(), getDebugWidth(), mImage->getImage());
}
}
void ColorCheckerTest::processData() {
mSuccess = false;
initializeRefColor();
edgeDetection();
}
void ColorCheckerTest::initializeRefColor() {
mReferenceColors.resize(4, std::vector<Vec3i*>(6, NULL));
mMatchPositions.resize(4, std::vector<Vec2f*>(6, NULL));
mMatchColors.resize(4, std::vector<Vec3f*>(6, NULL));
mMatchRadius.resize(4, std::vector<float>(6, 0.f));
mReferenceColors[0][0]= new Vec3i(115, 82, 68);
mReferenceColors[0][1]= new Vec3i(194, 150, 130);
mReferenceColors[0][2]= new Vec3i(98, 122, 157);
mReferenceColors[0][3]= new Vec3i(87, 108, 67);
mReferenceColors[0][4]= new Vec3i(133, 128, 177);
mReferenceColors[0][5]= new Vec3i(103, 189, 170);
mReferenceColors[1][0]= new Vec3i(214, 126, 44);
mReferenceColors[1][1]= new Vec3i(80, 91, 166);
mReferenceColors[1][2]= new Vec3i(193, 90, 99);
mReferenceColors[1][3]= new Vec3i(94, 60, 108);
mReferenceColors[1][4]= new Vec3i(157, 188, 64);
mReferenceColors[1][5]= new Vec3i(224, 163, 46);
mReferenceColors[2][0]= new Vec3i(56, 61, 150);
mReferenceColors[2][1]= new Vec3i(70, 148, 73);
mReferenceColors[2][2]= new Vec3i(175, 54, 60);
mReferenceColors[2][3]= new Vec3i(231, 199, 31);
mReferenceColors[2][4]= new Vec3i(187, 86, 149);
mReferenceColors[2][5]= new Vec3i(8, 133, 161);
mReferenceColors[3][0]= new Vec3i(243, 243, 242);
mReferenceColors[3][1]= new Vec3i(200, 200, 200);
mReferenceColors[3][2]= new Vec3i(160, 160, 160);
mReferenceColors[3][3]= new Vec3i(122, 122, 121);
mReferenceColors[3][4]= new Vec3i(85, 85, 85);
mReferenceColors[3][5]= new Vec3i(52, 52, 52);
}
void ColorCheckerTest::edgeDetection() {
int width = mImage->getWidth();
int height = mImage->getHeight();
bool* edgeMap = new bool[height * width];
unsigned char* grayImage = new unsigned char[height * width];
// If the image is a color image and can be converted to a luminance layer
if (mImage->rgbToGrayScale(grayImage)) {
float* gradientMap = new float[height * width * 2];
// Computes the gradient image on the luminance layer.
computeGradient(grayImage, gradientMap);
float* gradientMagnitude = new float[height * width];
int* gradientDirectionInt = new int[height * width];
float* gradientDirection = new float[height * width];
// Computes the absolute gradient of the image without padding.
for (int i = 1; i < height - 1; ++i) {
for (int j = 1; j < width - 1; ++j) {
gradientMagnitude[i * width + j] =
sqrt(gradientMap[(i * width + j) * 2] *
gradientMap[(i * width + j) * 2] +
gradientMap[(i * width + j ) * 2 + 1] *
gradientMap[(i * width + j ) * 2 + 1]);
// Computes the gradient direction of the image.
if (gradientMap[(i * width + j) * 2] == 0 ) {
// If the vertical gradient is 0, the edge is horizontal
// Mark the gradient direction as 90 degrees.
gradientDirectionInt[i * width + j] = 2;
gradientDirection[i * width + j] = 90.0f;
} else {
// Otherwise the atan operation is valid and can decide
// the gradient direction of the edge.
float gradient = atan(gradientMap[(i * width + j) * 2 + 1]
/ gradientMap[(i * width + j) * 2])
/ (M_PI / 4);
gradientDirection[i * width + j] = gradient * 45.0f;
// Maps the gradient direction to 4 major directions with
// 0 mapped to up and 2 mapped to right.
if (gradient - floor(gradient) > 0.5) {
gradientDirectionInt[i * width + j] =
(static_cast<int>(ceil(gradient)) + 4) % 4;
} else {
gradientDirectionInt[i * width + j] =
(static_cast<int>(floor(gradient)) + 4) % 4;
}
}
}
}
// Compute a boolean map to show whether a pixel is on the edge.
for (int i = 1; i < height - 1; ++i) {
for (int j = 1; j < width - 1; ++j) {
edgeMap[i * width + j] = false;
switch (gradientDirectionInt[i * width + j]) {
case 0:
// If the gradient points rightwards, the pixel is
// on an edge if it has a larger absolute gradient than
// pixels on its left and right sides.
if ((gradientMagnitude[i * width + j] >=
gradientMagnitude[i * width + j + 1]) &&
(gradientMagnitude[i * width + j] >=
gradientMagnitude[i * width + j - 1])) {
edgeMap[i * width + j] = true;
}
break;
case 1:
// If the gradient points right-downwards, the pixel is
// on an edge if it has a larger absolute gradient than
// pixels on its upper left and bottom right sides.
if ((gradientMagnitude[i * width + j] >=
gradientMagnitude[(i + 1) * width + j + 1]) &&
(gradientMagnitude[i * width + j] >=
gradientMagnitude[(i - 1) * width + j - 1])) {
edgeMap[i * width + j] = true;
}
break;
case 2:
// If the gradient points upwards, the pixel is
// on an edge if it has a larger absolute gradient than
// pixels on its up and down sides.
if ((gradientMagnitude[i * width + j] >=
gradientMagnitude[(i + 1) * width + j]) &&
(gradientMagnitude[i * width + j] >=
gradientMagnitude[(i - 1) * width + j])) {
edgeMap[i * width + j] = true;
}
break;
case 3:
// If the gradient points right-upwards, the pixel is
// on an edge if it has a larger absolute gradient than
// pixels on its bottom left and upper right sides.
if ((gradientMagnitude[i * width + j] >=
gradientMagnitude[(i - 1) * width + j + 1]) &&
(gradientMagnitude[i * width + j] >=
gradientMagnitude[(i + 1) * width + j - 1])) {
edgeMap[i * width + j] = true;
}
}
}
}
houghLineDetection(edgeMap, gradientMagnitude, gradientDirection);
// Cleans up
delete[] gradientMap;
delete[] gradientDirectionInt;
delete[] gradientMagnitude;
delete[] gradientDirection;
} else {
ALOGE("Not a color image!");
}
delete[] edgeMap;
delete[] grayImage;
}
// Runs the hough voting algorithm to find the grid of the color checker
// with the edge map, gradient direction and gradient magnitude as inputs.
void ColorCheckerTest::houghLineDetection(bool* edgeMap,
float* gradientMagnitude,
float* gradientDirection) {
// Constructs a graph for Hough voting. The vertical axis counts the vote
// for a certain angle. The horizontal axis counts the vote for the distance
// of a line from the origin of the image.
int houghHeight = 180;
int houghWidth = 200;
int houghCounts[houghHeight][houghWidth];
int houghSum[houghHeight][houghWidth];
int** houghVote;
houghVote = new int*[180];
for (int i = 0; i < 180; ++i) {
houghVote[i] = new int[200];
}
for (int i = 0; i < houghHeight; ++i) {
for (int j = 0; j < houghWidth; ++j) {
houghCounts[i][j] = 0;
houghVote[i][j] = 0;
houghSum[i][j] = 0;
}
}
// Vectors to record lines in two orthogonal directions.
// Each line is represented by its direction and its distance to the origin.
std::vector<std::vector<int> > verticalLines;
std::vector<std::vector<int> > horizontalLines;
float radius;
int height = mImage->getHeight();
int width = mImage->getWidth();
// Processes the signicant edge pixels and cast vote for the corresponding
// edge passing this pixel.
for (int i = 1; i < height - 1; ++i) {
for (int j = 1; j < width - 1; ++j) {
// Sets threashold for the gradient magnitude to discount noises
if (edgeMap[i * width + j] &&
(gradientMagnitude[i * width + j] > 15)) {
int shiftedAngle;
// Shifts angles for 45 degrees so the vertical and horizontal
// direction is mapped to 45 and 135 degrees to avoid padding.
// This uses the assumption that the color checker is placed
// roughly parallel to the image boarders. So that the edges
// at the angle of 45 will be rare.
shiftedAngle = (static_cast<int>(
-gradientDirection[i * width + j]) + 225 % 180);
float shiftedAngleRad = static_cast<float>(shiftedAngle)
* M_PI / 180.0f;
// Computes the distance of the line from the origin.
float a, b;
a = static_cast<float>(i - j) / sqrt(2.0f);
b = static_cast<float>(i + j) / sqrt(2.0f);
radius = a * sin(shiftedAngleRad) - b * cos(shiftedAngleRad);
// Adds one vote for the line. The line's angle is shifted by
// 45 degrees to avoid avoid padding for the vertical lines,
// which is more common than diagonal lines. The line's
// distance is mapped to [0, 200] from [-200, 200].
++houghCounts[shiftedAngle][static_cast<int>((radius / 2.0f) +
100.0f)];
drawPoint(i, j, Vec3i(255, 255, 255));
}
}
}
int houghAngleSum[houghHeight];
int primaryVerticalAngle, primaryHorizontalAngle;
int max1 = 0;
int max2 = 0;
// Looking for the two primary angles of the lines.
for (int i = 5; i < houghHeight - 5; ++i) {
houghAngleSum[i] = 0;
for (int j = 0; j < houghWidth; ++j) {
for (int l = -5; l <= 5; ++l) {
houghSum[i][j] += houghCounts[i + l][j];
}
houghAngleSum[i] += houghSum[i][j];
}
if ((i < houghHeight / 2) && (houghAngleSum[i] > max1)) {
max1 = houghAngleSum[i];
primaryVerticalAngle = i;
} else if ((i > houghHeight / 2) && (houghAngleSum[i] > max2)) {
max2 = houghAngleSum[i];
primaryHorizontalAngle = i;
}
}
ALOGV("Primary angles are %d, %d",
primaryVerticalAngle, primaryHorizontalAngle);
int angle;
// For each primary angle, look for the highest voted lines.
for (int k = 0; k < 2; ++k) {
if (k == 0) {
angle = primaryVerticalAngle;
} else {
angle = primaryHorizontalAngle;
}
std::vector<int> line(2);
for (int j = 2; j < houghWidth - 2; ++j) {
houghVote[angle][j] = houghSum[angle][j];
houghSum[angle][j] = 0;
}
// For each radius, average the vote with nearby ones.
for (int j = 2; j < houghWidth - 2; ++j) {
for (int m = -2; m <= 2; ++m) {
houghSum[angle][j] += houghVote[angle][j + m];
}
}
bool isCandidate[houghWidth];
// Find whether a lines is a candidate by rejecting the ones that have
// lower vote than others in the neighborhood.
for (int j = 2; j < houghWidth - 2; ++j) {
isCandidate[j] = true;
for (int m = -2; ((isCandidate[j]) && (m <= 2)); ++m) {
if ((houghSum[angle][j] < 20) ||
(houghSum[angle][j] < houghSum[angle][j + m])) {
isCandidate[j] = false;
}
}
}
int iter1 = 0;
int iter2 = 0;
int count = 0;
// Finds the lines that are not too close to each other and add to the
// detected lines.
while (iter2 < houghWidth) {
while ((!isCandidate[iter2]) && (iter2 < houghWidth)) {
++iter2;
}
if ((isCandidate[iter2]) && (iter2 - iter1 < 5)) {
iter1 = (iter2 + iter1) / 2;
++iter2;
} else {
line[0] = angle;
line[1] = (iter1 - 100) * 2;
if (iter1 != 0) {
if (k == 0) {
verticalLines.push_back(line);
Vec3i color(verticalLines.size() * 20, 0, 0);
drawLine(line[0], line[1], color);
} else {
horizontalLines.push_back(line);
Vec3i color(0, horizontalLines.size() * 20, 0);
drawLine(line[0], line[1], color);
}
}
iter1 = iter2;
++iter2;
ALOGV("pushing back line %d %d", line[0], line[1]);
}
}
}
ALOGV("Numbers of lines in each direction is %d, %d",
verticalLines.size(), horizontalLines.size());
for (int i = 0; i < 180; ++i) {
delete[] houghVote[i];
}
delete[] houghVote;
findCheckerBoards(verticalLines, horizontalLines);
}
// Computes the gradient in both x and y direction of a layer
void ColorCheckerTest::computeGradient(unsigned char* layer,
float* gradientMap) {
int width = mImage->getWidth();
int height = mImage->getHeight();
// Computes the gradient in the whole image except the image boarders.
for (int i = 1; i < height - 1; ++i) {
for (int j = 1; j < width - 1; ++j) {
gradientMap[(i * width + j) * 2] =
0.5f * (layer[i * width + j + 1] -
layer[i * width + j - 1]);
gradientMap[(i * width + j) * 2 + 1] =
0.5f * (layer[(i + 1) * width + j] -
layer[(i - 1) * width + j]);
}
}
}
// Tries to find the checker boards with the highest voted lines
void ColorCheckerTest::findCheckerBoards(
std::vector<std::vector<int> > verticalLines,
std::vector<std::vector<int> > horizontalLines) {
ALOGV("Start looking for Color checker");
int numHorizontalLines = mCandidateColors.size();
int numVerticalLines;
if (numHorizontalLines > 0) {
numVerticalLines = mCandidateColors[0].size();
for (int i = 0; i < numHorizontalLines; ++i) {
for (int j = 0; j < numVerticalLines; ++j) {
if (mCandidateColors[i][j] != NULL) {
delete mCandidateColors[i][j];
}
if (mCandidatePositions[i][j] != NULL) {
delete mCandidatePositions[i][j];
}
}
mCandidateColors[i].clear();
mCandidatePositions[i].clear();
}
}
mCandidateColors.clear();
mCandidatePositions.clear();
ALOGV("Candidates deleted!");
numVerticalLines = verticalLines.size();
numHorizontalLines = horizontalLines.size();
Vec2f pointUpperLeft;
Vec2f pointBottomRight;
mCandidateColors.resize(numHorizontalLines - 1);
mCandidatePositions.resize(numHorizontalLines - 1);
for (int i = numVerticalLines - 1; i >= 1; --i) {
for (int j = 0; j < numHorizontalLines - 1; ++j) {
// Finds the upper left and bottom right corner of each rectangle
// formed by two neighboring highest voted lines.
pointUpperLeft = findCrossing(verticalLines[i], horizontalLines[j]);
pointBottomRight = findCrossing(verticalLines[i - 1],
horizontalLines[j + 1]);
Vec3i* color = new Vec3i();
Vec2f* pointCenter = new Vec2f();
// Verifies if they are separated by a reasonable distance.
if (verifyPointPair(pointUpperLeft, pointBottomRight,
pointCenter, color)) {
mCandidatePositions[j].push_back(pointCenter);
mCandidateColors[j].push_back(color);
ALOGV("Color at (%d, %d) is (%d, %d, %d)", j, i,color->r(), color->g(), color->b());
} else {
mCandidatePositions[j].push_back(NULL);
mCandidateColors[j].push_back(NULL);
delete color;
delete pointCenter;
}
}
}
ALOGV("Candidates Number (%d, %d)", mCandidateColors.size(), mCandidateColors[0].size());
// Verifies whether the current line candidates form a valid color checker.
verifyColorGrid();
}
// Returns the corssing point of two lines given the lines.
Vec2f ColorCheckerTest::findCrossing(std::vector<int> line1,
std::vector<int> line2) {
Vec2f crossingPoint;
float r1 = static_cast<float>(line1[1]);
float r2 = static_cast<float>(line2[1]);
float ang1, ang2;
float y1, y2;
ang1 = static_cast<float>(line1[0]) / 180.0f * M_PI;
ang2 = static_cast<float>(line2[0]) / 180.0f * M_PI;
float x, y;
x = (r1 * cos(ang2) - r2 * cos(ang1)) / sin(ang1 - ang2);
y = (r1 * sin(ang2) - r2 * sin(ang1)) / sin(ang1 - ang2);
crossingPoint.set((x + y) / sqrt(2.0), (y - x) / sqrt(2.0));
//ALOGV("Crosspoint at (%f, %f)", crossingPoint.x(), crossingPoint.y());
return crossingPoint;
}
// Verifies whether two opposite corners on a quadrilateral actually can be
// the two corners of a color checker.
bool ColorCheckerTest::verifyPointPair(Vec2f pointUpperLeft,
Vec2f pointBottomRight,
Vec2f* pointCenter,
Vec3i* color) {
bool success = true;
/** 5 and 30 are the threshold tuned for resolution 640*480*/
if ((pointUpperLeft.x() < 0) ||
(pointUpperLeft.x() >= mImage->getHeight()) ||
(pointUpperLeft.y() < 0) ||
(pointUpperLeft.y() >= mImage->getWidth()) ||
(pointBottomRight.x() < 0) ||
(pointBottomRight.x() >= mImage->getHeight()) ||
(pointBottomRight.y() < 0) ||
(pointBottomRight.y() >= mImage->getWidth()) ||
(std::abs(pointUpperLeft.x() - pointBottomRight.x()) <= 5) ||
(std::abs(pointUpperLeft.y() - pointBottomRight.y()) <= 5) ||
(std::abs(pointUpperLeft.x() - pointBottomRight.x()) >= 30) ||
(std::abs(pointUpperLeft.y() - pointBottomRight.y()) >= 30)) {
// If any of the quadrilateral corners are out of the image or if
// the distance between them are too large or too big, the quadrilateral
// could not be one of the checkers
success = false;
} else {
// Find the checker center if the corners of the rectangle meet criteria
pointCenter->set((pointUpperLeft.x() + pointBottomRight.x()) / 2.0f,
(pointUpperLeft.y() + pointBottomRight.y()) / 2.0f);
color->set(mImage->getPixelValue(*pointCenter).r(),
mImage->getPixelValue(*pointCenter).g(),
mImage->getPixelValue(*pointCenter).b());
ALOGV("Color at (%f, %f) is (%d, %d, %d)", pointCenter->x(), pointCenter->y(),color->r(), color->g(), color->b());
}
return success;
}
// Verifies the color checker centers and finds the match between the detected
// color checker and the reference MacBeth color checker
void ColorCheckerTest::verifyColorGrid() {
ALOGV("Start looking for Color Grid");
int numHorizontalLines = mCandidateColors.size();
int numVerticalLines = mCandidateColors[0].size();
bool success = false;
// Computes the standard deviation of one row/column of the proposed color
// checker. Discards the row/column if the std is below a threshold.
for (int i = 0; i < numHorizontalLines; ++i) {
Vec3f meanColor(0.f, 0.f, 0.f);
int numNonZero = 0;
for (int j = 0; j < numVerticalLines; ++j) {
if (mCandidateColors[i][j] != NULL) {
ALOGV("candidate color (%d, %d) is (%d, %d, %d)", i, j, mCandidateColors[i][j]->r(), mCandidateColors[i][j]->g(), mCandidateColors[i][j]->b());
meanColor = meanColor + (*mCandidateColors[i][j]);
++numNonZero;
}
}
if (numNonZero > 0) {
meanColor = meanColor / numNonZero;
}
ALOGV("Mean color for vertical direction computed!");
float std = 0;
for (int j = 0; j < numVerticalLines; ++j) {
if (mCandidateColors[i][j] != NULL) {
std += mCandidateColors[i][j]->squareDistance<float>(meanColor);
}
}
if (numNonZero > 0) {
std = sqrt(std / (3 * numNonZero));
}
ALOGV("st. deviation for the %d dir1 is %d", i, static_cast<int>(std));
if ((std <= 30) && (numNonZero > 1)) {
for (int j = 0; j < numVerticalLines; ++j) {
if (mCandidateColors[i][j] != NULL) {
delete mCandidateColors[i][j];
mCandidateColors[i][j] = NULL;
}
}
}
}
// Discards the column/row of the color checker if the std is below a
// threshold.
for (int j = 0; j < numVerticalLines; ++j) {
Vec3f meanColor(0.f, 0.f, 0.f);
int numNonZero = 0;
for (int i = 0; i < numHorizontalLines; ++i) {
if (mCandidateColors[i][j] != NULL) {
meanColor = meanColor + (*mCandidateColors[i][j]);
++numNonZero;
}
}
if (numNonZero > 0) {
meanColor = meanColor / numNonZero;
}
float std = 0;
for (int i = 0; i < numHorizontalLines; ++i) {
if (mCandidateColors[i][j] != NULL) {
std += mCandidateColors[i][j]->squareDistance<float>(meanColor);
}
}
if (numNonZero > 0) {
std = sqrt(std / (3 * numNonZero));
}
ALOGV("st. deviation for the %d dir2 is %d", j, static_cast<int>(std));
if ((std <= 30) && (numNonZero > 1)) {
for (int i = 0; i < numHorizontalLines; ++i) {
if (mCandidateColors[i][j] != NULL) {
delete mCandidateColors[i][j];
mCandidateColors[i][j] = NULL;
}
}
}
}
for (int i = 0; i < numHorizontalLines; ++i) {
for (int j = 0; j < numVerticalLines; ++j) {
if (mCandidateColors[i][j] != NULL) {
ALOGV("position (%d, %d) is at (%f, %f) with color (%d, %d, %d)",
i, j,
mCandidatePositions[i][j]->x(),
mCandidatePositions[i][j]->y(),
mCandidateColors[i][j]->r(),
mCandidateColors[i][j]->g(),
mCandidateColors[i][j]->b());
} else {
ALOGV("position (%d, %d) is 0", i, j);
}
}
}
// Finds the match between the detected color checker and the reference
// MacBeth color checker.
int rowStart = 0;
int rowEnd = 0;
// Loops until all dectected color checker has been processed.
while (!success) {
int columnStart = 0;
int columnEnd = 0;
bool isRowStart = false;
bool isRowEnd = true;
// Finds the row start of the next block of detected color checkers.
while ((!isRowStart) && (rowStart < numHorizontalLines)) {
for (int j = 0; j < numVerticalLines; ++j) {
if (mCandidateColors[rowStart][j] != NULL) {
isRowStart = true;
}
}
++rowStart;
}
rowStart--;
rowEnd = rowStart;
ALOGV("rowStart is %d", rowStart);
// Finds the row end of the next block of detected color checkers.
while ((isRowEnd) && (rowEnd < numHorizontalLines)) {
isRowEnd = false;
for (int j = 0; j < numVerticalLines; ++j) {
if (mCandidateColors[rowEnd][j] != NULL) {
isRowEnd= true;
}
}
if (isRowEnd) {
++rowEnd;
}
}
if ((!isRowEnd) && isRowStart) {
rowEnd--;
}
if ((isRowEnd) && (rowEnd == numHorizontalLines)) {
rowEnd--;
isRowEnd = false;
}
ALOGV("rowEnd is %d", rowEnd);
// Matches color checkers between the start row and the end row.
bool successVertical = false;
while (!successVertical) {
bool isColumnEnd = true;
bool isColumnStart = false;
// Finds the start column of the next block of color checker
while ((!isColumnStart) && (columnStart < numVerticalLines)) {
if (mCandidateColors[rowStart][columnStart] != NULL) {
isColumnStart = true;
}
++columnStart;
}
columnStart--;
columnEnd = columnStart;
// Finds the end column of the next block of color checker
while ((isColumnEnd) && (columnEnd < numVerticalLines)) {
isColumnEnd = false;
if (mCandidateColors[rowStart][columnEnd] != NULL)
isColumnEnd = true;
if (isColumnEnd) {
++columnEnd;
}
}
if ((!isColumnEnd) && isColumnStart) {
columnEnd--;
}
if ((isColumnEnd) && (columnEnd == numVerticalLines)) {
columnEnd--;
isColumnEnd = false;
}
// Finds the best match on the MacBeth reference color checker for
// the continuous block of detected color checker
if (isRowStart && (!isRowEnd) &&
isColumnStart && (!isColumnEnd)) {
findBestMatch(rowStart, rowEnd, columnStart, columnEnd);
}
ALOGV("FindBestMatch for %d, %d, %d, %d", rowStart,
rowEnd, columnStart, columnEnd);
// If the column search finishes, go out of the loop
if (columnEnd >= numVerticalLines - 1) {
successVertical = true;
} else {
columnStart = columnEnd + 1;
}
}
ALOGV("Continuing to search for direction 1");
// If the row search finishes, go out of the loop
if (rowEnd >= numHorizontalLines - 1) {
success = true;
} else {
rowStart = rowEnd + 1;
}
}
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 6; ++j) {
if (mMatchPositions[i][j] != NULL) {
ALOGV("Reference Match position for (%d, %d) is (%f, %f)", i, j,
mMatchPositions[i][j]->x(), mMatchPositions[i][j]->y());
}
}
}
fillRefColorGrid();
}
// Finds the best match on the MacBeth color checker for the continuous block of
// detected color checkers bounded by row i1, row i2 and column j1 and column j2
// Assumes that the subsample is less than 4*6.
void ColorCheckerTest::findBestMatch(int i1, int i2, int j1, int j2) {
int numHorizontalGrid = i2 - i1 + 1;
int numVerticalGrid = j2 - j1 + 1;
if (((numHorizontalGrid > 1) || (numVerticalGrid > 1)) &&
(numHorizontalGrid <= 4) && (numVerticalGrid <= 6)) {
ALOGV("i1, j2, j1, j2 is %d, %d, %d, %d", i1, i2, j1, j2);
float minError;
float error = 0.f;
int horizontalMatch, verticalMatch;
// Finds the match start point where the error is minimized.
for (int i = 0; i < numHorizontalGrid; ++i) {
for (int j = 0; j < numVerticalGrid; ++j) {
if (mCandidateColors[i1 + i][j1 + j] != NULL) {
error += mCandidateColors[i1 + i][j1 + j]->squareDistance<int>(
*mReferenceColors[i][j]);
}
}
}
ALOGV("Error is %f", error);
minError = error;
horizontalMatch = 0;
verticalMatch = 0;
for (int i = 0; i <= 4 - numHorizontalGrid; ++i) {
for (int j = 0; j <= 6 - numVerticalGrid; ++j) {
error = 0.f;
for (int id = 0; id < numHorizontalGrid; ++id) {
for (int jd = 0; jd < numVerticalGrid; ++jd) {
if (mCandidateColors[i1 + id][j1 + jd] != NULL) {
error += mCandidateColors[i1 + id][j1 + jd]->
squareDistance<int>(
*mReferenceColors[i + id][j + jd]);
}
}
}
if (error < minError) {
minError = error;
horizontalMatch = i;
verticalMatch = j;
}
ALOGV("Processed %d, %d and error is %f", i, j, error );
}
}
for (int id = 0; id < numHorizontalGrid; ++id) {
for (int jd = 0; jd < numVerticalGrid; ++jd) {
if (mCandidatePositions[i1 + id][j1 + jd] != NULL) {
mMatchPositions[horizontalMatch + id][verticalMatch + jd] =
new Vec2f(mCandidatePositions[i1 + id][j1 + jd]->x(),
mCandidatePositions[i1 + id][j1 + jd]->y());
}
}
}
ALOGV("Grid match starts at %d, %d", horizontalMatch, verticalMatch);
}
}
// Finds the boundary of a color checker by its color similarity to the center.
// Also predicts the location of unmatched checkers.
void ColorCheckerTest::fillRefColorGrid() {
int rowStart = 0;
int columnStart = 0;
bool foundStart = true;
for (int i = 0; (i < 4) && foundStart; ++i) {
for (int j = 0; (j < 6) && foundStart; ++j) {
if (mMatchPositions[i][j] != NULL) {
rowStart = i;
columnStart = j;
foundStart = false;
}
}
}
ALOGV("First match found at (%d, %d)", rowStart, columnStart);
float rowDistance, columnDistance;
rowDistance = 0;
columnDistance = 0;
int numRowGrids = 0;
int numColumnGrids = 0;
for (int i = rowStart; i < 4; ++i) {
for (int j = columnStart; j < 6; ++j) {
if (mMatchPositions[i][j] != NULL) {
if (i > rowStart) {
++numRowGrids;
rowDistance += (mMatchPositions[i][j]->x() -
mMatchPositions[rowStart][columnStart]->x()) /
static_cast<float>(i - rowStart);
}
if (j > columnStart) {
++numColumnGrids;
columnDistance += (mMatchPositions[i][j]->y() -
mMatchPositions[rowStart][columnStart]->y()) /
static_cast<float>(j - columnStart);
}
}
}
}
if ((numRowGrids > 0) && (numColumnGrids > 0)) {
rowDistance = rowDistance / numRowGrids;
columnDistance = columnDistance / numColumnGrids;
ALOGV("delta is %f, %f", rowDistance, columnDistance);
for (int i = 0; i < 4; ++i) {
for (int j = 0 ; j < 6; ++j) {
if (mMatchPositions[i][j] == NULL) {
mMatchPositions[i][j] = new Vec2f(
mMatchPositions[rowStart][columnStart]->x() +
(i - rowStart) * rowDistance,
mMatchPositions[rowStart][columnStart]->y() +
(j - columnStart) * columnDistance);
}
}
}
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 6; ++j) {
float radius = 0;
Vec3i color = mImage->getPixelValue(*mMatchPositions[i][j]);
Vec3f meanColor(0.f , 0.f, 0.f);
int numPixels = 0;
for (int ii = static_cast<int>(mMatchPositions[i][j]->x() -
rowDistance/2);
ii <= static_cast<int>(mMatchPositions[i][j]->x() +
rowDistance/2);
++ii) {
for (int jj = static_cast<int>(mMatchPositions[i][j]->y() -
columnDistance/2);
jj <= static_cast<int>(mMatchPositions[i][j]->y() +
columnDistance/2);
++jj) {
if ((ii >= 0) && (ii < mImage->getHeight()) &&
(jj >= 0) && (jj < mImage->getWidth())) {
Vec3i pixelColor = mImage->getPixelValue(ii,jj);
float error = color.squareDistance<int>(pixelColor);
if (error < COLOR_ERROR_THRESHOLD) {
drawPoint(ii, jj, *mReferenceColors[i][j]);
meanColor = meanColor + pixelColor;
numPixels++;
Vec2i pixelPosition(ii, jj);
if (pixelPosition.squareDistance<float>(
*mMatchPositions[i][j]) > radius) {
radius = pixelPosition.squareDistance<float>(
*mMatchPositions[i][j]);
}
}
}
}
}
/** Computes the radius of the checker.
* The above computed radius is the squared distance to the
* furthest point with a matching color. To be conservative, we
* only consider an area with radius half of the above computed
* value. Since radius is computed as a squared root, the one
* that will be recorded is 1/4 of the above computed value.
*/
mMatchRadius[i][j] = radius / 4.f;
mMatchColors[i][j] = new Vec3f(meanColor / numPixels);
ALOGV("Reference color at (%d, %d) is (%d, %d, %d)", i, j,
mReferenceColors[i][j]->r(),
mReferenceColors[i][j]->g(),
mReferenceColors[i][j]->b());
ALOGV("Average color at (%d, %d) is (%f, %f, %f)", i, j,
mMatchColors[i][j]->r(),
mMatchColors[i][j]->g(),
mMatchColors[i][j]->b());
ALOGV("Radius is %f", mMatchRadius[i][j]);
}
}
mSuccess = true;
}
}