| /* |
| * 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; |
| } |
| } |