void CutMask::fillMask(int x, int y, MatrixXf & m) { if(x >= 0 && x < BlockSize && y >= 0 && y < BlockSize && m(y,x) == 1) { m(y,x) = 0; fillMask(x + 1, y, m); fillMask(x - 1, y, m); fillMask(x, y + 1, m); fillMask(x, y - 1, m); } }
void CutMask::fillMask(MatrixXf & m, BlockType type) { switch(type) { case VERTICAL: for(int i = 0; i < BlockSize; i++) fillMask(0, i, m); break; case V_BOTHSIDES: for(int i = 0; i < BlockSize; i++) { fillMask(0, i, m); fillMask(BlockSize - 1, i, m); } break; case L_SHAPED: case N_SHAPED: for(int i = 0; i < BlockSize; i++) { fillMask(i, 0, m); fillMask(0, i, m); if(type == L_SHAPED && i < BandSize) fillMask(BlockSize - 1, i, m); else if(type == N_SHAPED) fillMask(BlockSize - 1, i, m); } break; case NONE_BLOCK: case HORIZONTAL: break; } }
void CharacterSegmenter::filterEdgeBoxes(vector<Mat> thresholds, const vector<Rect> charRegions, float avgCharWidth, float avgCharHeight) { const float MIN_ANGLE_FOR_ROTATION = 0.4; int MIN_CONNECTED_EDGE_PIXELS = (avgCharHeight * 1.5); // Sometimes the rectangle won't be very tall, making it impossible to detect an edge // Adjust for this here. int alternate = thresholds[0].rows * 0.92; if (alternate < MIN_CONNECTED_EDGE_PIXELS && alternate > avgCharHeight) MIN_CONNECTED_EDGE_PIXELS = alternate; // // Pay special attention to the edge boxes. If it's a skinny box, and the vertical height extends above our bounds... remove it. //while (charBoxes.size() > 0 && charBoxes[charBoxes.size() - 1].width < MIN_SEGMENT_WIDTH_EDGES) // charBoxes.erase(charBoxes.begin() + charBoxes.size() - 1); // Now filter the "edge" boxes. We don't want to include skinny boxes on the edges, since these could be plate boundaries //while (charBoxes.size() > 0 && charBoxes[0].width < MIN_SEGMENT_WIDTH_EDGES) // charBoxes.erase(charBoxes.begin() + 0); // TECHNIQUE #1 // Check for long vertical lines. Once the line is too long, mask the whole region if (charRegions.size() <= 1) return; // Check both sides to see where the edges are // The first starts at the right edge of the leftmost char region and works its way left // The second starts at the left edge of the rightmost char region and works its way right. // We start by rotating the threshold image to the correct angle // then check each column 1 by 1. vector<int> leftEdges; vector<int> rightEdges; for (int i = 0; i < thresholds.size(); i++) { Mat rotated; if (top.angle > MIN_ANGLE_FOR_ROTATION) { // Rotate image: rotated = Mat(thresholds[i].size(), thresholds[i].type()); Mat rot_mat( 2, 3, CV_32FC1 ); Point center = Point( thresholds[i].cols/2, thresholds[i].rows/2 ); rot_mat = getRotationMatrix2D( center, top.angle, 1.0 ); warpAffine( thresholds[i], rotated, rot_mat, thresholds[i].size() ); } else { rotated = thresholds[i]; } int leftEdgeX = 0; int rightEdgeX = rotated.cols; // Do the left side int col = charRegions[0].x + charRegions[0].width; while (col >= 0) { int rowLength = getLongestBlobLengthBetweenLines(rotated, col); if (rowLength > MIN_CONNECTED_EDGE_PIXELS) { leftEdgeX = col; break; } col--; } col = charRegions[charRegions.size() - 1].x; while (col < rotated.cols) { int rowLength = getLongestBlobLengthBetweenLines(rotated, col); if (rowLength > MIN_CONNECTED_EDGE_PIXELS) { rightEdgeX = col; break; } col++; } if (leftEdgeX != 0) leftEdges.push_back(leftEdgeX); if (rightEdgeX != thresholds[i].cols) rightEdges.push_back(rightEdgeX); } int leftEdge = 0; int rightEdge = thresholds[0].cols; // Assign the edge values to the SECOND closest value if (leftEdges.size() > 1) { sort (leftEdges.begin(), leftEdges.begin()+leftEdges.size()); leftEdge = leftEdges[leftEdges.size() - 2] + 1; } if (rightEdges.size() > 1) { sort (rightEdges.begin(), rightEdges.begin()+rightEdges.size()); rightEdge = rightEdges[1] - 1; } if (leftEdge != 0 || rightEdge != thresholds[0].cols) { Mat mask = Mat::zeros(thresholds[0].size(), CV_8U); rectangle(mask, Point(leftEdge, 0), Point(rightEdge, thresholds[0].rows), Scalar(255,255,255), -1); if (top.angle > MIN_ANGLE_FOR_ROTATION) { // Rotate mask: Mat rot_mat( 2, 3, CV_32FC1 ); Point center = Point( mask.cols/2, mask.rows/2 ); rot_mat = getRotationMatrix2D( center, top.angle * -1, 1.0 ); warpAffine( mask, mask, rot_mat, mask.size() ); } // If our edge mask covers more than x% of the char region, mask the whole thing... const float MAX_COVERAGE_PERCENT = 0.6; int leftCoveragePx = leftEdge - charRegions[0].x; float leftCoveragePercent = ((float) leftCoveragePx) / ((float) charRegions[0].width); float rightCoveragePx = (charRegions[charRegions.size() -1].x + charRegions[charRegions.size() -1].width) - rightEdge; float rightCoveragePercent = ((float) rightCoveragePx) / ((float) charRegions[charRegions.size() -1].width); if ((leftCoveragePercent > MAX_COVERAGE_PERCENT) || (charRegions[0].width - leftCoveragePx < config->segmentationMinBoxWidthPx)) { rectangle(mask, charRegions[0], Scalar(0,0,0), -1); // Mask the whole region if (this->config->debugCharSegmenter) cout << "Edge Filter: Entire left region is erased" << endl; } if ((rightCoveragePercent > MAX_COVERAGE_PERCENT) || (charRegions[charRegions.size() -1].width - rightCoveragePx < config->segmentationMinBoxWidthPx)) { rectangle(mask, charRegions[charRegions.size() -1], Scalar(0,0,0), -1); if (this->config->debugCharSegmenter) cout << "Edge Filter: Entire right region is erased" << endl; } for (int i = 0; i < thresholds.size(); i++) { bitwise_and(thresholds[i], mask, thresholds[i]); } if (this->config->debugCharSegmenter) { cout << "Edge Filter: left=" << leftEdge << " right=" << rightEdge << endl; Mat bordered = addLabel(mask, "Edge Filter #1"); imgDbgGeneral.push_back(bordered); Mat invertedMask(mask.size(), mask.type()); bitwise_not(mask, invertedMask); for (int z = 0; z < imgDbgCleanStages.size(); z++) fillMask(imgDbgCleanStages[z], invertedMask, Scalar(0,0,255)); } } // TECHNIQUE #2 // Check for tall skinny blobs on the edge boxes. If they're too long and skinny, maks the whole char region /* * float MIN_EDGE_CONTOUR_HEIGHT = avgCharHeight * 0.7; float MIN_EDGE_CONTOUR_AREA_PCT = avgCharHeight * 0.1; for (int i = 0; i < thresholds.size(); i++) { // Just check the first and last char box. If the contour extends too far above/below the line. Drop it. for (int boxidx = 0; boxidx < charRegions.size(); boxidx++) { if (boxidx != 0 || boxidx != charRegions.size() -1) { // This is a middle box. we never want to filter these here. continue; } vector<vector<Point> > contours; Mat mask = Mat::zeros(thresholds[i].size(),CV_8U); rectangle(mask, charRegions[boxidx], Scalar(255,255,255), CV_FILLED); bitwise_and(thresholds[i], mask, mask); findContours(mask, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); //int tallContourIndex = isSkinnyLineInsideBox(thresholds[i], charRegions[boxidx], allContours[i], hierarchy[i], avgCharWidth, avgCharHeight); float tallestContourHeight = 0; float fattestContourWidth = 0; float biggestContourArea = 0; for (int c = 0; c < contours.size(); c++) { Rect r = boundingRect(contours[c]); if (r.height > tallestContourHeight) tallestContourHeight = r.height; if (r.width > fattestContourWidth) fattestContourWidth = r.width; float a = r.area(); if (a > biggestContourArea) biggestContourArea = a; } float minArea = charRegions[boxidx].area() * MIN_EDGE_CONTOUR_AREA_PCT; if ((fattestContourWidth < MIN_BOX_WIDTH_PX) || (tallestContourHeight < MIN_EDGE_CONTOUR_HEIGHT) || (biggestContourArea < minArea) ) { // Find a good place to MASK this contour. // for now, just mask the whole thing if (this->debug) { rectangle(imgDbgCleanStages[i], charRegions[boxidx], COLOR_DEBUG_EDGE, 2); cout << "Edge Filter: threshold " << i << " box " << boxidx << endl; } rectangle(thresholds[i], charRegions[boxidx], Scalar(0,0,0), -1); } else { filteredCharRegions.push_back(charRegions[boxidx]); } } } */ }
CutMask::CutMask(MatrixXf & overlap, BlockType type) { for(int y = 0; y < BlockSize; y++) { for(int x = 0; x < BlockSize; x++) { points[nodes.size()] = Point(x,y); nodes[Point(x,y)] = nodes.size(); } } protectCore(overlap, type); costMatrix = overlap; for(int y = 0; y < BlockSize; y++) { for(int x = 0; x < BlockSize; x++) { Point p1 = Point(x , y ); Point p2 = Point(x + 1, y ); Point p3 = Point(x , y + 1); if(point(p2)) g.AddEdge(nodes[p1], nodes[p2], cost(p1,p2)); if(point(p3)) g.AddEdge(nodes[p1], nodes[p3], cost(p1,p3)); } } // Add start and end nodes int start = nodes.size(); int end = start + 1; BlockType originalType = type; if(originalType == V_BOTHSIDES) { // convert it to N_SHAPED for(int i = BandSize - 1; i < BlockSize - BandSize; i++) { g.SetEdgeWeight(nodes[Point(i,0)], nodes[Point(i+1,0)], 0); g.SetEdgeWeight(nodes[Point(i+1,0)], nodes[Point(i,0)], 0); } type = N_SHAPED; } // connect start and end to graph, first is start node case switch(type) { case VERTICAL: for(int i = 0; i < BandSize; i++) g.AddEdge(start, nodes[Point(i, 0)], 0); break; case L_SHAPED: //for(int i = 0; i < BandSize; i++) // g.AddEdge(start, nodes[Point(BlockSize - 1, i)], 0); g.AddEdge(start, nodes[Point(BlockSize - 1, BandSize - 1)], 0); break; case N_SHAPED: for(int i = 0; i < BandSize; i++) g.AddEdge(start, nodes[Point((BlockSize - 1) - i, BlockSize - 1)], 0); break; case NONE_BLOCK: case V_BOTHSIDES: case HORIZONTAL: break; } // end node connections for(int i = 0; i < BandSize; i++) g.AddEdge(end, nodes[Point(i, BlockSize - 1)], 0); // Find the shortest path std::list<int> path = g.DijkstraShortestPath(start, end); // Remove start and end points path.pop_front(); path.pop_back(); // turn into boolean mask mask = MatrixXf::Ones(BlockSize, BlockSize); for(std::list<int>::iterator i = path.begin(); i != path.end(); i++) { Point p = points[*i]; mask(p.y, p.x) = 0; } fillMask(mask, type); // Special case: fix V_BOTHSIDES mask if(originalType == V_BOTHSIDES) { for(int i = BandSize; i < BlockSize - BandSize; i++) { mask(0, i) = 1.0f; } } }