
Introduction
This Android project can get the number of cards leveraging the principle of digital image using mobile phone camera. It will be convenient and faster than counting cards by hand.
Description
I encountered a problem of counting cards when I did part-time work at one of my school institutions. Few day later, I started to develop an Android application to counter cards automatically when I realized that progress of this application is similar with zxing project. Anyway, I have to say there were some bad programming styles (e.g. static
variables and methods) in my project as I wanted to simplify some operations even though my project can have good performance of recognition of cards in good lighting conditions.
This project contains two main parts:
- The image acquisition and pre processing
The code of this part comes from Zxing project as the progress of capturing images from camera is the same so that you can see my project contains many Zxing source codes.
- Decoding Images and getting the number of cards
This part contains all the functions that are used to get the number of cards. I will discuss this part in detail in the following part.
Data Processing Flow
The main progress of this project is shown in the following figure. Of course, you can get more detailed information form the source code.

Details of the Key Functions
The part of getting cards number is written by myself, which contains four main methods. I will discuss every method with description and flow chart.
Method 1
This public
method can be called from outside, which can return the final result from the original source. The logic of this method is shown in the following figure:

public String doDecode(PlanarYUVLuminanceSource source) throws Exception {
BinaryBitmap image = new BinaryBitmap(new GlobalHistogramBinarizer(source));
int[] rowsNumber=getRowsNumber(image);
int[] start=new int[rowsNumber.length];
int[] end =new int[rowsNumber.length];
int width = image.getWidth();
BitArray[] rows = new BitArray[rowsNumber.length];
try {
for(int i=0;i<rowsNumber.length-1;i++){
rows[i]= new BitArray(width);
rows[i] = image.getBlackRow(rowsNumber[i], rows[i]);
}
for(int i=0;i<2;i++){
start[i]=getStartBorder(rows[i]);
}
}catch(Exception ignored){
throw new Exception("not found");
}
if((float)Math.abs(start[0]-start[1])/(float)Math.abs
(rowsNumber[0]-rowsNumber[1])>(float)1/5){
throw new Exception("not found");
}
viewfinderView.addPossibleResultPoint(new ResultPoint((float)start[0], (float)rowsNumber[0]));
viewfinderView.addPossibleResultPoint(new ResultPoint((float)start[1], (float)rowsNumber[1]));
int[] result=new int[3];
try{
for(int i=0;i<2;i++){
result[i]=getResult(start[i],rows[i],i);
end[i]=endGlobal;
}
}catch(Exception ignored){
throw new Exception("not found");
}
viewfinderView.addPossibleResultPoint(new ResultPoint((float)end[0], (float)rowsNumber[0]));
viewfinderView.addPossibleResultPoint(new ResultPoint((float)end[1], (float)rowsNumber[1]));
if(result[0]!=result[1]){
throw new Exception("not found");
}
String resultString= String.valueOf(result[0]);
rowsNumber0 = (float)rowsNumber[0];
rowsNumber1 = (float)rowsNumber[1];
picture = source.renderCroppedGreyscaleBitmap();
return resultString;
}
Method 2
I consider that binarize all the image will be too time-consuming and thus I use a scanline-based approach. The logic of this method is shown in the following figure:

private int[] getRowsNumber(BinaryBitmap image){
int period=4;
int maxLines=5;
int width = image.getWidth();
int height = image.getHeight();
int step= height/(maxLines+1);
int microStep = step/period;
int mid = maxLines >> 1;
int[] rowsNumber=new int[maxLines];
for(int i=0;i<maxLines;i++){
if(i<mid){
rowsNumber[i]=step*(i+1)+circle*microStep;
}else if(i>mid){
rowsNumber[i]=step*(i+1)-circle*microStep;
}else{
rowsNumber[i]=step*(i+1);
}
}
circle++;
if(circle>=period){
circle=0;
}
LuminanceSource source = image.getBinarizer().getLuminanceSource();
int[] sumRows = new int[maxLines];
for(int i=0;i<maxLines;i++){
byte[] temp=source.getRow(rowsNumber[i], luminances);
for(int j=0;j<width;j++){
sumRows[i]+=temp[j]&0xff;
}
}
int firstValue=Integer.MAX_VALUE;
int secondValue=Integer.MAX_VALUE;
int thirdValue=Integer.MAX_VALUE;
int first=0;
int second=0;
int third=0;
for(int i=0;i<maxLines;i++){
if(sumRows[i]<firstValue){
firstValue=sumRows[i];
first=i;
}
}
for(int i=0;i<maxLines;i++){
if(i!=first)
{
if(sumRows[i]<secondValue){
secondValue=sumRows[i];
second=i;
}
}
}
for(int i=0;i<maxLines;i++){
if((i!=first)&&(i!=second))
{
if(sumRows[i]<thirdValue){
thirdValue=sumRows[i];
third=i;
}
}
}
int[] value= {rowsNumber[first],rowsNumber[second],rowsNumber[third]};
return value ;
}
Method 3
This method can get the first card border. The logic of it is shown in the following figure:

private int getStartBorder(BitArray row) throws Exception{
int i=0,loc=0,start=0;
int width=row.getSize();
boolean flag=true;
int[] whiteWidth=new int[2];
int[] blackWidth=new int[2];
int[] whiteStart=new int[2];
int[] whiteEnd=new int[2];
int[] blackStart=new int[2];
int[] blackEnd=new int[2];
while(i+1<width&&loc<2&&flag){
while(i+1<width&&loc<2){
whiteStart[loc]=row.getNextUnset(i);
whiteEnd[loc]=row.getNextSet(whiteStart[loc]+1)-1;
while(whiteEnd[loc]+1<width&&(!row.get(whiteEnd[loc]+2))){
whiteEnd[loc]=row.getNextSet(whiteEnd[loc]+3)-1;
}
if(whiteEnd[loc]==whiteStart[loc]){
i=whiteEnd[loc]+3;
continue;
}else{
i=whiteEnd[loc]+1;
break;
}
}
whiteWidth[loc]=whiteEnd[loc]-whiteStart[loc]+1;
if(i+1<width&&loc<2){
blackStart[loc]=i;
blackEnd[loc]=row.getNextUnset(blackStart[loc]+2)-1;
while(blackEnd[loc]+1<width&&row.get(blackEnd[loc]+2)){
blackEnd[loc]=row.getNextUnset(blackEnd[loc]+3)-1;
}
blackWidth[loc]=blackEnd[loc]-blackStart[loc]+1;
i=blackEnd[loc]+1;
}
if((whiteWidth[loc]>>1)<blackWidth[loc]){
loc=0;
continue;
}
loc++;
if(loc<2){
continue;
}
if(getGap(whiteWidth[0],whiteWidth[1])){
if(getGap(blackWidth[0],blackWidth[1])){
start=whiteStart[0];
flag=false;
}
}
i=blackEnd[0]+1;
loc=0;
}
if(start==0){
throw new Exception("not found");
}
if(start<whiteWidth[0]){
throw new Exception("not found");
}
int firstBlackStart=row.getNextSet(start-whiteWidth[0]);
int firstBlackEnd=row.getNextUnset(firstBlackStart)-1;
int firstBlackWidth=firstBlackEnd-firstBlackStart+1;
if(whiteWidth[0]-firstBlackWidth>1){
throw new Exception("not found");
}
Log.d(TAG,"Starting position "+start+".");
return start;
}
Method 4
The last method that was used to get the number of white areas (the number of cards). The logic of this method is shown in the following figure:

private int getResult(int whiteStart,BitArray row,int index) throws Exception{
int num=0;
int whiteEnd=0;
int whiteWidth=0;
int blackStart=0;
int blackEnd=0;
int blackWidth=0;
int width =row.getSize();
int preValue=width;
ArrayList<Float> borderPointsX = new ArrayList<Float>();
borderPointsX.add((float)whiteStart);
while(whiteStart+1<width){
whiteEnd=row.getNextSet(whiteStart+1)-1;
while(whiteEnd+1<width&&(!row.get(whiteEnd+2))){
whiteEnd=row.getNextSet(whiteEnd+3)-1;
}
whiteWidth=whiteEnd-whiteStart+1;
if(whiteEnd+1 < width){
blackStart=whiteEnd+1;
blackEnd=row.getNextUnset(blackStart+2)-1;
while(blackEnd+1<width&&row.get(blackEnd+2)){
blackEnd=row.getNextUnset(blackEnd+3)-1;
}
}else{
break;
}
blackWidth =blackEnd-blackStart+1;
if(whiteWidth>(2*preValue)){
if(whiteWidth<(3*preValue)){
borderPointsX.clear();
throw new Exception("not found");
}
break;
}
borderPointsX.add((float)(blackStart+1));
num++;
endGlobal=whiteEnd;
if(blackWidth>2*preValue){
break;
}
whiteStart=blackEnd+1;
preValue=whiteWidth;
}
if(whiteStart>width-2||whiteEnd>width-2){
borderPointsX.clear();
throw new Exception("not found");
}
if(index == 0){
borderPoints0X = new ArrayList<Float>(borderPointsX);
}else if(index == 1 ){
borderPoints1X = new ArrayList<Float>(borderPointsX);
}
return num;
}
History
To be continued....