Android : อ่าน QR code จากกล้องด้วย Zbar (อธิบาย Example Code)

สวัสดีท่านผู้อ่านนะครับช่วงนี้มีโอกาสเจอโปรเจ็กหลากหลาย เลยมีโอกาสได้เรียนรู้ใช้งาน library ที่ไม่เคยลองใช้หลายตัวครั้งนี้ก็จะมาอธิบายตัวอย่างการอ่าน QR code ด้วยกล้องครับ ซึ่ง library ที่เราจะนำมาใช้ในวันนี้คือ ZBar เอาหละไม่ฝอยนานเริ่มเลยดีกว่า

ขั้นตอนการ import และรันตัวอย่างจาก ZBar

1. ดาวน์โหลด ZBar พร้อมตัวอย่างได้จาก ที่นี่

2. ทำการ import โปรเจ็คตัวอย่างเข้าสู่ Eclipse ของเราโดย ไปที่ File > Import > Android > Existing Android Code Into Workspace

3. Browse ไปยังโฟลเดอร์ที่เราดาวน์โหลดมาครับ และเลือก examples ก็จะพบโปรเจ็คชื่อว่า CameraTest กด Finish เป็นอันเสร็จขั้นตอน

อธิบายการทำงานของตัวอย่าง Zbar เล็กน้อย

CameraPreview.java

ส่วนของ Class CameraPreview เป็นการสร้าง Camera โดยรับข้อมูลมากจาก CameraTestActivity  ผู้อ่านที่ไม่เข้าใจ SurfaceView นั้นจะอธิบายคร่าวๆว่า SurfaceView  คือการวาดภาพครับ เวลาเราเปิดแอฟฟลิเคชั่นกล้อง ก็เหมือนกับนำภาพที่ได้จากกล้องมาวาดลงบน Layout

– implements SurfaceHolder.Callback ตรงนี้ก็อธิบายคร่าวๆนะครับ มีเพื่อรอรับความเปลี่ยนแปลงของ SurfaceView ซึ่งมี 3 function หลัก

1. surfaceCreated ถูกเรียกใช้เมื่อ surface ถูกสร้าง

2. surfaceUpdate ถูกเรียกใช้เมื่อ surface ถูกเปลี่ยนแปลงเช่นการหมุนเป็นต้น

3. surfaceDestroyed ถูกเรียกเมื่อ surface ถูกทำลาย

ซึ่ง 1 และ 2 เนี่ยจะถูกเรียกใช้แต่แรก ในตัวอย่างจึงไปตั้งค่าที่ Camera ที่ surfaceUpdate โดยค่าต่างๆที่นำมาใช้ก็จำเป็นต้องใส่ใน parameter ตอนที่สร้าง Object CameraPreview (ตอนที่ CameraTestActivity เรียกใช้ CameraPreview)

 /** A basic Camera preview class */  
 public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {  
   private SurfaceHolder mHolder;  
   private Camera mCamera;  
   private PreviewCallback previewCallback;  
   private AutoFocusCallback autoFocusCallback;  
   public CameraPreview(Context context, Camera camera,  
              PreviewCallback previewCb,  
              AutoFocusCallback autoFocusCb) {  
     super(context);  
     mCamera = camera;  
     previewCallback = previewCb;  
     autoFocusCallback = autoFocusCb;  

     // Install a SurfaceHolder.Callback so we get notified when the  
     // underlying surface is created and destroyed.  
     mHolder = getHolder();  
     mHolder.addCallback(this);  
     // deprecated setting, but required on Android versions prior to 3.0  
     mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);  
   }  
   public void surfaceCreated(SurfaceHolder holder) {  
        Log.e("CameraPreview", "surfaceCreated");  
     // The Surface has been created, now tell the camera where to draw the preview.  
     try {  
       mCamera.setPreviewDisplay(holder);  
     } catch (IOException e) {  
       Log.d("DBG", "Error setting camera preview: " + e.getMessage());  
     }  
   }  
   public void surfaceDestroyed(SurfaceHolder holder) {  
     // Camera preview released in activity  
   }  
   public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {  
     /*  
      * If your preview can change or rotate, take care of those events here.  
      * Make sure to stop the preview before resizing or reformatting it.  
      */  
        Log.e("CameraPreview", "surfaceChange");  
     if (mHolder.getSurface() == null){  
      // preview surface does not exist  
      return;  
     }  
     // stop preview before making changes  
     try {  
       mCamera.stopPreview();  
     } catch (Exception e){  
      // ignore: tried to stop a non-existent preview  
     }  
     try {  
       // Hard code camera surface rotation 90 degs to match Activity view in portrait  
       mCamera.setDisplayOrientation(90);  
       mCamera.setPreviewDisplay(mHolder);  
       mCamera.setPreviewCallback(previewCallback);  
       mCamera.startPreview();  
       mCamera.autoFocus(autoFocusCallback);  
     } catch (Exception e){  
       Log.d("DBG", "Error starting camera preview: " + e.getMessage());  
     }  
   }  
 }  

CameraTestActivity.java

Class นี้ผมจะอธิบายเน้นที่ previewCb นะครับ CameraTestActivity ทำหน้าที่กำหนดค่าต่างๆให้ Camera (กำหนดคุณลักษณะต่างๆของกล้อง) เพื่อไปสร้าง CameraPreview มาสถิตบน Layout และ ทำการค้นหา QR code จากภาพครับ มีส่วนสำคัญดังนี้

– PreviewCallback previewCb ตรงนี้ทำงานตามจำนวน Frame ของ Camera เลยเมื่อเรากดเปิดกล้อง function จะค้นหา QR code ในแต่ละ Frame หากเจอก็จะหยุดการแสดงภาพไว้ (ค้างไว้ที่ Frame นั้นๆ) และ decode Qr code ที่เจอมาแสดง ซึ่งเป็นส่วนที่สำคัญมากของตัวอย่างนี้ครับ ผมทำการ comment code ที่สำคัญและอธิบายเพิ่มเติมใน sourcecode ครับ

– AutoFocusCallback autoFocusCB ทำการตั่งค่าให้ Focus เองทุก 1000 มิลลิวินาที โดยไปเรียกใช้ doAutoFocus

– getCameraInstance คือการสร้าง Object Camera

 public class CameraTestActivity extends Activity  
 {  
   private Camera mCamera;  
   private CameraPreview mPreview;  
   private Handler autoFocusHandler;  
   TextView scanText;  
   Button scanButton;  
   ImageScanner scanner;  
   private boolean barcodeScanned = false;  
   private boolean previewing = true;  
   static {  
     System.loadLibrary("iconv");  
   }   
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.main);  
     setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);  
     autoFocusHandler = new Handler();  
     mCamera = getCameraInstance(); // เปิด Camera  
     /* Instance barcode scanner */  
     scanner = new ImageScanner();  
     scanner.setConfig(0, Config.X_DENSITY, 3);  
     scanner.setConfig(0, Config.Y_DENSITY, 3);  
     mPreview = new CameraPreview(this, mCamera, previewCb, autoFocusCB);  // สร้าง CameraPreview
     FrameLayout preview = (FrameLayout)findViewById(R.id.cameraPreview);  
     preview.addView(mPreview);  // นำ Object CameraPreview มาใส่ใน Layout
     scanText = (TextView)findViewById(R.id.scanText);  
     scanButton = (Button)findViewById(R.id.ScanButton);  
     scanButton.setOnClickListener(new OnClickListener() {  
         public void onClick(View v) {  
           if (barcodeScanned) {  
             barcodeScanned = false;  
             scanText.setText("Scanning...");  
             mCamera.setPreviewCallback(previewCb);  
             mCamera.startPreview();  
             previewing = true;  
             mCamera.autoFocus(autoFocusCB);  
           }  
         }  
       });  
   }  
   public void onPause() {  
     super.onPause();  
     releaseCamera();  
   }  
   /** A safe way to get an instance of the Camera object. */  
   public static Camera getCameraInstance(){  
     Camera c = null;  
     try {  
       c = Camera.open();  
     } catch (Exception e){  
     }  
     return c;  
   }  
   private void releaseCamera() {  
     if (mCamera != null) {  
       previewing = false;  
       mCamera.setPreviewCallback(null);  
       mCamera.release();  
       mCamera = null;  
     }  
   }  
   private Runnable doAutoFocus = new Runnable() {  
       public void run() {  
         if (previewing)  
           mCamera.autoFocus(autoFocusCB);  
       }  
     };  
   PreviewCallback previewCb = new PreviewCallback() {  
       public void onPreviewFrame(byte[] data, Camera camera) {  
         Camera.Parameters parameters = camera.getParameters();  
         Size size = parameters.getPreviewSize();  
         Image barcode = new Image(size.width, size.height, "Y800");  // สร้างรูป format Y800
         barcode.setData(data);  // ใส่ข้อมูลให้ภาพ ตรงนีข้อมูลน่าจะเป็นภาพจากกล้องครับ
         int result = scanner.scanImage(barcode);  // Scan หา Qr code ในรูป
         if (result != 0) {  // หากหาเจอ result ไม่เท่ากับ 1 ก็หยุด 
           previewing = false;  
           mCamera.setPreviewCallback(null);  
           mCamera.stopPreview();  
           SymbolSet syms = scanner.getResults();   // ดึงผลลัพธ์จาก Scanner คือผลลัพธ์ที่ decode แล้ว อาจมีหลาย code
           for (Symbol sym : syms) {  
             scanText.setText("barcode result " + sym.getData());  // ดึกค่าที่ถูก decode แล้วมาแสดง
             barcodeScanned = true;  
           }  
         }  
       }  
     };  
   // Mimic continuous auto-focusing  
   AutoFocusCallback autoFocusCB = new AutoFocusCallback() {  
       public void onAutoFocus(boolean success, Camera camera) {  
         autoFocusHandler.postDelayed(doAutoFocus, 1000);  // ทำการโฟกัสใหม่ทุก 1 วินาที
       }  
     };  
 }  

จบการอธิบายแล้วครับผมหวังว่าผู้อ่านจะเข้าใจตัวอย่างมากขึ้นและสามารถนำไปใช้ต่อในงานได้นะครับ สำหรับ Document API ของ Zbar  ที่เป็น Android ผมหาไม่เจอจริงๆครับที่วางให้น่าจะเป็นของ C แต่น่าจะสามารถดูรายละเอียดของ function ได้ครับ Zbar Doc Api ในบทความต่อไปจะมาเขียนเรื่องการนำ Zbar ไปใช้ใน Project ของตนเองซึ่งนำตัวอย่างจากบทความนี้ไปใช้งานต่อครับ สุดท้ายก็ทิ้ง QR code ให้ทดลอง Scan

Screenshot from 2014-11-02 21:24:06

About octoboy


Android Developer, Study Master degree of Computer Engineering at Prince of Songkla university.