Click here to Skip to main content
15,885,146 members
Articles / Mobile Apps / Android
Tip/Trick

Android Cards Counter

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
10 Apr 2015CPOL2 min read 24.5K   291   9   2
This Android project can get cards number leveraging the principle of digital image.

Image 1 Image 2

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:

  1. 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.

  2. 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.

Image 3

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:

Image 4

Java
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];          
          
          /*----------------------just for debugging--------------------------------*//*
          long interval=System.currentTimeMillis()-btime;
          btime = System.currentTimeMillis();
          times++; 
          Log.d(TAG, "Number of attempts "+times+","+"Interval "+interval+" ms)\n");
          -----------------------------------------------------------------------*/         
          try {      
              for(int i=0;i<rowsNumber.length-1;i++){
                  rows[i]= new BitArray(width);
                  // Estimate black point for this row and load it.
                  rows[i] = image.getBlackRow(rowsNumber[i], rows[i]);
              }                        
              //Start to find the starting position after getting the binary data.
              for(int i=0;i<2;i++){
                  //select two rows,0 and 1 (0,1,2)
                  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]);
          /*-----------------------------just for debugging--------------------*//*
          hits++;
          long endTime = System.currentTimeMillis();
         
          Log.d(TAG,resultString);
          float rate=0;
          if(result[0]==20){
              correct++;          
          }
          int temp=hits-correct;
          float frequency = ((float)correct*1000/(endTime-startTime));
          rate = (float)correct/hits;
          Log.d("Correct rate","The times of success "+correct+","
                 +"The times of failed "+temp+","
                 +"The correct rate "+rate+".");
          Log.d("Correct rate","Successful frequency "+frequency+".");
         // return null;
          ------------------------------------------------------------------*/
          //Draw manual correction map
          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:

Image 5

Java
/**
       * @return return the rows number we want.
       */
      private int[] getRowsNumber(BinaryBitmap image){
          //?????5???.???????????.
          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();
            //byte[][] localLuminances = new byte[maxLines][width];
            int[] sumRows = new int[maxLines];
            for(int i=0;i<maxLines;i++){
                //There will cost some times.
                byte[] temp=source.getRow(rowsNumber[i], luminances);
                for(int j=0;j<width;j++){
                    sumRows[i]+=temp[j]&0xff;
                }
            }            
            //sort sumRows.
            int firstValue=Integer.MAX_VALUE;
            int secondValue=Integer.MAX_VALUE;
            int thirdValue=Integer.MAX_VALUE;
            int first=0;
            int second=0;
            int third=0;
            //get the first value.
            for(int i=0;i<maxLines;i++){
                if(sumRows[i]<firstValue){
                    firstValue=sumRows[i];
                    first=i;
                }
            }
            //get the second value.
           for(int i=0;i<maxLines;i++){
               if(i!=first)
               {
                    if(sumRows[i]<secondValue){
                        secondValue=sumRows[i];
                        second=i;
                    }
               }//if
            }//for
          //get the third value.
           for(int i=0;i<maxLines;i++){
               if((i!=first)&&(i!=second))
               {
                    if(sumRows[i]<thirdValue){
                      thirdValue=sumRows[i];
                        third=i;
                    }
               }//if
            }//for
           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:

Image 6

Java
/**
       * find the border.
       * @param row
       * @return 
       */
      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];
          
          /*---------modify here ,just for analysis .--------------------*//*
            int i_row[] = new int [width];
            for(int x=0;x<width;x++){
                i_row[x]=row.get(x)?1:0; //black 1; white 0
            }
            -------------------------------------------------------------*/
          
            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;
                 }//if
              }
              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;
                  }//while
                  blackWidth[loc]=blackEnd[loc]-blackStart[loc]+1;
                  i=blackEnd[loc]+1;
              }//if
              if((whiteWidth[loc]>>1)<blackWidth[loc]){
                  loc=0;
                  continue;
              }
              loc++;
              if(loc<2){
                  continue;
              }
              //................)(.........white...........)(...black...)
              //(.........white...........)(...black...)
              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:

Image 7

Java
/***
      * get the number of the white areas.
      * @param whiteStart
      * @param row
      * @return
      */
     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;
         }//while

         if(whiteStart>width-2||whiteEnd>width-2){
             //clear data.
             borderPointsX.clear();
             throw new Exception("not found");
         }
         //borderPoints0X = (ArrayList<Float>) borderPointsX.clone();
         if(index == 0){
             borderPoints0X = new  ArrayList<Float>(borderPointsX);
         }else if(index == 1 ){
             borderPoints1X = new  ArrayList<Float>(borderPointsX);
         }
         return num;
     }

History

To be continued....

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student
China China
I am interested in machine learning, computer vision and familiar with many programming languages. If you interested in this research fields, I will be glad to become a friend with you.My personal webpage is http://www.shareideas.net/.

Comments and Discussions

 
Questionhave you consider to post this as a tip? Pin
Nelek11-Apr-15 8:35
protectorNelek11-Apr-15 8:35 
AnswerRe: have you consider to post this as a tip? Pin
ShareIdeas11-Apr-15 17:54
ShareIdeas11-Apr-15 17:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.