/** digitFeature.java Project 'Digit Recognizer' for CISC 859: Pattern Recognition * @author Hongzhi Li, Department of Computing and Information Science Queen's University. Sept. 2000 * Extract the features of a connected region */ import java.util.*; import java.io.*; public class digitFeature { public int maxX = 0; //bound info public int maxY = 0; public int minX = Integer.MAX_VALUE; public int minY = Integer.MAX_VALUE; public int height = 0; //height of region public int width = 0; //width of region public int hlongY = 0; //number of vertical lines longer than vlineRatio*height of box public int hlongX = 0; //number of horizental lines longer than hlineRatio*width of box public double hlineRatio = 0.9; //Threshold to check hlongX (long horizental line) number public double vlineRatio = 0.9; //Threshold to check hlongY (long verticall line) number public int holes = 0; //number of holes. 4-neighborhood to check hole, coz using 8-neighbor to check region! public int upholes = 0; //number of holes on top part of region public int downholes = 0; //number of holes at the bottom part of region public int centerholes = 0; //number of holes in center //upholes+downholes+centerholes = holes public double topPercent = 0.4; //Must be less than 0.5 //top part of region for a hole is defined as the center of the hole's height is higher // than (ceil)topPercent*height. Similiar value to downholes. The other holes is center hole private int holet=100000; private int holeb = -10000; //mark bottom/top of the hole private boolean uph=false; private boolean dnh=false; //check different holes public int notchs = 0; //number of notchs public int area = 0; //aera of region public int boxArea = 0; //area of bounding box public int holeArea = 0; //area of the holes public int notchArea = 0; //area of notch public double abRatio = 0.0; // area/boxArea public double bbRatio = 0.0; // Width/Height of box public double hbRatio = 0.0; // holeArea/boxArea public double nbRatio = 0.0; //notchArea/boxArea public Vector regionx; // x-values of region public Vector regiony; // y-values of region public int[][] pixels; //store the pixel value of the box, used when finding the holes private boolean ahole = true; //check hole private int wArea = 0; //temp store the hole/notch area public int isHor = -1; //mark pixel as -1 when is horizental line. Must be a negative integer! public int isVer = -2; //mark the vertical line as -2. Be ready to compare with isHor, like isHor+isVer for some pixels public int isBoublePixels = 1; //How many minimum overlapped pixels to determine a parallel double-line? Must be >0 integer! /**Construct a digitFeature Object with the 2 Vector which stored the region X/Y info as input */ public digitFeature(Vector regionx, Vector regiony) { this.regionx = regionx; this.regiony = regiony; evaluate(); //Evaluate the features of this object } /*Evaluate the features of the connected region */ private void evaluate() { getBound(); getHole(); getLongLine(); } /**Calculate region/bound's info, like area, maxX, a(b)bRatio, etc. */ public void getBound() { area = regionx.size(); //cal area for (int i = 0; i < regionx.size(); i++) { //cal bound box parameters int tempx = ((Integer)regionx.get(i)).intValue(); //extract integer value from Vector int tempy = ((Integer)regiony.get(i)).intValue(); if (tempx > maxX) maxX = tempx; if (tempx < minX) minX = tempx; if (tempy > maxY) maxY = tempy; if (tempy < minY) minY = tempy; } width = (maxX - minX + 1); //image width/height height = (maxY - minY + 1); boxArea = width * height; //bounding-box area if (boxArea <=0) System.out.println("Error! Box area is "+boxArea); abRatio = (double)area/boxArea; bbRatio = (double)width/height; } /**Cal number of holes/notchs in region * Warning! After check of the hole, pixels[][] value are all reset to 0! */ public void getHole() { getPixels(); //Recover the pixel values of the region for (int rows = 0; rows < height; rows++) { for (int cols = 0; cols < width; cols++) { if (pixels[rows][cols] == 1) { //white pixel? ahole = true; wArea=0; uph=false; dnh=false; holet=100000; holeb=-100000; checkHole(rows, cols); if (ahole) { holes++; //hole there? holeArea += wArea; //cal hole area holeType(); //check the type of the hole if (uph) upholes ++; else if (dnh) downholes ++; else centerholes++; } else { notchs++; //must be a notch! notchArea += wArea; } } } } hbRatio = (double)holeArea/boxArea; nbRatio = (double)notchArea/boxArea; } /**Check what's the type (upper/bottom/center) of the hole */ public void holeType() { int centerW = (holet+holeb)/2; if (centerW > height -1 || centerW < 1) System.out.println("Error in hole finding! holeType()!") ; if (centerW <= height*topPercent) uph=true; //top hole found else if (centerW >=(height-1-height*topPercent)) dnh=true; //bottom hole } /**Calculate the number of lines longer than hlineRatio*width or vlineRatio*height of the bounding box */ public void getLongLine() { getPixels(); //Recover the pixel values of the region hlongX=0; hlongY=0; for (int rows = 0; rows < height; rows++) { for (int cols = 0; cols < width; cols++) { if (pixels[rows][cols] < 1) { //black pixel? checkHLine(rows, cols); checkVLine(rows, cols); } } } } /**Recover the pixel values of the region as a box */ public void getPixels() { pixels = new int [height][width]; for (int rows = 0; rows < height; rows++) { for (int cols = 0; cols < width; cols++) { pixels[rows][cols] = 1; for (int i = 0; i < regionx.size(); i++) { int tempx = ((Integer)regionx.get(i)).intValue(); int tempy = ((Integer)regiony.get(i)).intValue(); if ((tempx - minX) == cols && (tempy - minY) == rows) { pixels[rows][cols] = 0; //System.out.println(""+cols+","+rows); } } } } } /**Check whether there is a horizental line longer than hlineRatio*width */ public void checkHLine(int rows, int cols) { if (countLine(rows, cols, isHor) >= Math.floor(hlineRatio*width)) { if (markLine(rows, cols, isHor)) hlongX++; } } /**Check whether there is a horizental line longer than hlineRatio*width */ public void checkVLine(int rows, int cols) { if (countLine(rows, cols, isVer) >= Math.floor(vlineRatio*height)) { if (markLine(rows, cols, isVer)) hlongY++; } } /**Count how many pixels are in a line. *@param int lineDir The horizental (= isHor) or vertical (= isVer) direction? *@return int The number of pixels in this direction */ public int countLine (int rows, int cols, int lineDir) { int lineLength = 0; if (lineDir == isHor && pixels[rows][cols] != isHor && pixels[rows][cols] !=(isHor+isVer)) { //not picked out yet for (int i = cols; i >= 0; i--) { if (pixels[rows][i] == 0 || pixels[rows][i] == isVer) { lineLength++; } else { break; } } for (int i = cols; i < width; i++) { if (pixels[rows][i] == 0 || pixels[rows][i] == isVer) { lineLength++; } else { break; } } lineLength--; //remove the double count for [rows][cols] } else if (lineDir == isVer && pixels[rows][cols] != isVer && pixels[rows][cols] !=(isHor+isVer)) { for (int i = rows; i >= 0; i--) { if (pixels[i][cols] != 1) { lineLength++; } else { break; } } for (int i = rows; i < height; i++) { if (pixels[i][cols] != 1) { lineLength++; } else { break; } } lineLength--; //remove the double count for [rows][cols] } else { //System.out.println("Warning! Invalid line's direction! No lines can be picked out!"); } return lineLength; } /**Mark the pixels of a line. = isHor for Horizental; = isVer for vertical; = isHor+isVer for both *@param int lineDir The horizental (= isHor) or vertical (= isVer) direction? *@return boolean True for a real line (no lines at neighbor). False for lines with a picked neighbor. *The false lines are also marked as lineDir. */ public boolean markLine(int rows, int cols, int lineDir) { int countDouble = 0; //count the double points if (lineDir == isHor) { for (int i = cols; i >= 0; i--) { if (pixels[rows][i] == 0 || pixels[rows][i] == isVer) { pixels[rows][i] += isHor; //mark as a horizental line if (rows>0 && (pixels[rows-1][i] == isHor || pixels[rows-1][i] == (isHor+isVer))) countDouble++; if (rows<(height-1) && (pixels[rows+1][i] == isHor || pixels[rows+1][i] == (isHor+isVer))) countDouble++; //check double lines } else { break; } } for (int i = cols; i < width; i++) { if (pixels[rows][i] == 0 || pixels[rows][i] == isVer) { if (i>cols) { pixels[rows][i] += isHor; if (rows>0 && (pixels[rows-1][i] == isHor || pixels[rows-1][i] == (isHor+isVer))) countDouble++; if (rows<(height-1) && (pixels[rows+1][i] == isHor || pixels[rows+1][i] == (isHor+isVer))) countDouble++; } } else if (i > cols) { break; } } } else if (lineDir == isVer) { for (int i = rows; i >= 0; i--) { if (pixels[i][cols] == 0 || pixels[i][cols] == isHor) { pixels[i][cols] += isVer; //mark as a vertical line if (cols>0 && (pixels[i][cols-1] == isVer || pixels[i][cols-1] == (isHor+isVer))) countDouble++; if (cols<(width-1) && (pixels[i][cols+1] == isVer || pixels[i][cols+1] == (isHor+isVer))) countDouble++; } else { break; } } for (int i = rows; i < height; i++) { if (pixels[i][cols] == 0 || pixels[i][cols] == isHor) { if (i > rows) { pixels[i][cols] += isVer; if (cols>0 && (pixels[i][cols-1] == isVer || pixels[i][cols-1] == (isHor+isVer))) countDouble++; if (cols<(width-1) && (pixels[i][cols+1] == isVer || pixels[i][cols+1] == (isHor+isVer))) countDouble++; } } else if (i > rows) { break; } } } return (countDouble < isBoublePixels); } /**Check whether there is a hole in region when spreading from pixel (rows, cols) * There will be a hole if none of the 4-white neighbor hit the edge of the bounding box * If there is a hole, param ahole will be true. * Only 4-neighbor are checked for holes, to be asymmetry for 8-neighbor region checking! */ public void checkHole(int rows, int cols) { if (pixels[rows][cols] == 1) { wArea++; //Increment hole/notch area if (rows < holet) holet=rows; if (rows > holeb) holeb=rows; //check top/bottom of the hole } if (!edge(rows, cols)) { pixels[rows][cols]=0; //erase this white pixel goNeighbor(rows, cols); //spread checking to neighbor } else { pixels[rows][cols]=2; //marked edge notch pixels as 2 ahole = false; //white region hits the box, not a hole! goNotchEdge(rows, cols); } return; } /**Checking four neighborhood pixels for possible holes * Only 4-neighbor can be used here to keep asymmetry with the 8-neighbor region finder! */ public void goNeighbor(int rows, int cols) { if (!edge(rows, cols)) { if (pixels[rows-1][cols] != 0) checkHole(rows-1, cols); //0: black pixel. 1:white. 2:white edge if (pixels[rows+1][cols] != 0) checkHole(rows+1, cols); //Can't only check ==1! Edge = 2!! if (pixels[rows][cols-1] != 0) checkHole(rows, cols-1); if (pixels[rows][cols+1] != 0) checkHole(rows, cols+1); //if (pixels[rows+1][cols+1] != 0) checkHole(rows+1, cols+1); //if (pixels[rows-1][cols-1] != 0) checkHole(rows-1, cols-1); //if (pixels[rows-1][cols+1] != 0) checkHole(rows-1, cols+1); //if (pixels[rows+1][cols-1] != 0) checkHole(rows+1, cols-1); } return; } /**Check the neighbor of a notch for the edge pixels * Black it if it's a white pixel */ public void goNotchEdge(int rows, int cols){ if (rows == 0 || rows == (height-1)) { //top/bottom if (cols > 0 && pixels[rows][cols-1] == 1) checkHole(rows, cols-1); //search edge if (cols < (width-1) && pixels[rows][cols+1] ==1) checkHole(rows, cols+1); if ((cols == 0 || cols == (width-1)) && rows == 0 && pixels[rows+1][cols] == 1) checkHole(rows+1, cols); //top corner? if ((cols == 0 || cols == (width-1)) && rows == (height-1) && pixels[rows-1][cols] == 1) checkHole(rows-1,cols); //bottom corner? if (rows == 0 && pixels[rows+1][cols] ==1) checkHole(rows+1, cols); //search inside if (rows == (height-1) && pixels[rows-1][cols] ==1) checkHole(rows-1, cols); } else { //left/right and no corner if (rows > 0 && pixels[rows-1][cols] == 1) checkHole(rows-1, cols); //serach edge if (rows < (height-1) && pixels[rows+1][cols] == 1) checkHole(rows+1, cols); if (cols == 0 && pixels[rows][cols+1] == 1) checkHole(rows, cols+1); //serach inside if (cols == (width-1) && pixels[rows][cols-1] == 1) checkHole(rows, cols-1); } } /**Check whether a pixel is at edge of the bounding box */ public boolean edge(int rows, int cols) { return (rows == 0 || rows == (height -1) || cols == 0 || cols == (width - 1)); } /**Override to show features' info */ public String toString() { String info = "\nBounding width = "+width+". height = "+height; info += "\n Area/boxArea Ratio = "+abRatio; info +="\n width/height (box) Ratio = "+bbRatio; info +="\n holesArea/boxArea Ratio = "+hbRatio; info +="\n notchArea/boxArea = "+nbRatio; info +="\n (Area+hole+notch)/box [test, must =1] = "+(abRatio+hbRatio+nbRatio); info +="\n Holes = "+holes+". Up holes = "+upholes+". Down holes = "+downholes+". Center holes = "+centerholes; info +="\n Notchs = "+notchs; info +="\n Horizental lines = "+hlongX+" (>="+(int)Math.floor(hlineRatio*width)+" p)."; info +="\n Vertical lines = "+hlongY+" (>="+(int)Math.floor(vlineRatio*height)+" p)."; getPixels(); for (int rows = 0; rows < height; rows++) { info +="\n"; for (int cols = 0; cols < width; cols++) { info +=""+pixels[rows][cols]; } } info +="\n"; return info; } /**Return an double array for Extract features' value */ public double[] toFeature() { double[] info = new double[13]; info[0] = width; info[1] = height; info[2] = abRatio; // Aera/boxAera info[3] = bbRatio; //width/height info[4] = hbRatio; //holeA/boxA info[5] = nbRatio; //notchA/boxA info[6] = holes; //holes info[7] = upholes; //top holes info[8] = downholes; //downholes info[9] = centerholes; //center holes info[10] = notchs; info[11] = hlongX; //long H lines info[12] = hlongY; //long V lines return info; } } // end of class digitFeature