สแกนบาร์โค้ดด้วย ML Kit บน Android

จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ

คุณใช้ ML Kit เพื่อจดจําและถอดรหัสบาร์โค้ดได้

ฟีเจอร์เลิกรวมกลุ่มแล้วรวมกลุ่ม
การใช้งานโมเดลจะดาวน์โหลดแบบไดนามิกผ่านบริการ Google Playโมเดลจะลิงก์อยู่กับแอปในเวลาที่สร้างบิลด์
ขนาดแอปมีขนาดเพิ่มขึ้นประมาณ 200 KBมีขนาดเพิ่มขึ้นประมาณ 2.4 MB
เวลาเริ่มต้นอาจต้องรอให้โมเดลดาวน์โหลดก่อนที่จะใช้งานครั้งแรกโมเดลพร้อมใช้งานทันที

ลองใช้งาน

ข้อควรทราบก่อนที่จะเริ่มต้น

  1. ในไฟล์ build.gradle ระดับโปรเจ็กต์ อย่าลืมใส่ที่เก็บ Maven ของ Google ทั้งใน buildscript และ allprojects

  2. เพิ่มทรัพยากร Dependency สําหรับไลบรารี Android ของ ML Kit ไปยังไฟล์ Gradle ระดับแอปของโมดูล ซึ่งปกติจะเป็น app/build.gradle เลือกทรัพยากร Dependency ต่อไปนี้ 1 รายการตามที่ต้องการ

    สําหรับการรวมแพ็กเกจกับแอปของคุณ ให้ทําดังนี้

    dependencies {
      // ...
      // Use this dependency to bundle the model with your app
      implementation 'com.google.mlkit:barcode-scanning:17.0.3'
    }
    

    สําหรับการใช้โมเดลในบริการ Google Play

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded model in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0'
    }
    
  3. หากเลือกใช้โมเดลในบริการ Google Play คุณสามารถกําหนดค่าแอปให้ดาวน์โหลดโมเดลลงในอุปกรณ์โดยอัตโนมัติหลังจากที่ติดตั้งแอปจาก Play Store แล้ว โดยเพิ่มการประกาศต่อไปนี้ลงในไฟล์ AndroidManifest.xml ของแอป

    <application ...>
          ...
          <meta-data
              android:name="com.google.mlkit.vision.DEPENDENCIES"
              android:value="barcode" >
          <!-- To use multiple models: android:value="barcode,model2,model3" -->
    </application>
    

    นอกจากนี้ คุณยังตรวจสอบความพร้อมใช้งานของโมเดลได้อย่างชัดเจนและส่งคําขอดาวน์โหลดผ่าน ModuleInstallClient API ของบริการ Google Play ได้ด้วย

    หากคุณไม่เปิดใช้การดาวน์โหลดโมเดลเวลาติดตั้งหรือขอการดาวน์โหลดอย่างชัดแจ้ง ระบบจะดาวน์โหลดโมเดลในครั้งแรกที่คุณเรียกใช้เครื่องมือสแกน คําขอที่คุณสร้าง ก่อนที่การดาวน์โหลดจะเสร็จสมบูรณ์ จะไม่พบผลลัพธ์

หลักเกณฑ์เกี่ยวกับรูปภาพที่ป้อน

  • เพื่อให้ ML Kit อ่านบาร์โค้ดได้อย่างแม่นยํา รูปภาพอินพุตต้องมีบาร์โค้ดที่แสดงข้อมูลพิกเซลเพียงพอ

    ข้อกําหนดของข้อมูลพิกเซลที่เจาะจงจะขึ้นอยู่กับทั้งประเภทบาร์โค้ดและจํานวนข้อมูลที่เข้ารหัสในนั้น เนื่องจากบาร์โค้ดหลายรายการจะรองรับเพย์โหลดขนาดตัวแปร โดยทั่วไป หน่วยที่มีความหมายน้อยที่สุดของบาร์โค้ดควรกว้างอย่างน้อย 2 พิกเซล และสําหรับโค้ด 2 มิติ ความสูง 2 พิกเซล

    เช่น บาร์โค้ด EAN-13 ประกอบด้วยแท่งและช่องว่างที่มีความกว้าง 1, 2, 3 หรือ 4 หน่วย ดังนั้นรูปภาพบาร์โค้ด EAN-13 จึงมีแถบและการเว้นวรรคที่กว้างอย่างน้อย 2, 4, 6 และ 8 พิกเซล เนื่องจากบาร์โค้ด EAN-13 มีความกว้างรวม 95 หน่วย บาร์โค้ดจึงควรกว้างอย่างน้อย 190 พิกเซล

    รูปแบบความหนาแน่นมากขึ้น เช่น PDF417 ต้องการขนาดพิกเซลที่มากขึ้นเพื่อให้ ML Kit อ่านได้อย่างน่าเชื่อถือ เช่น โค้ด PDF417 อาจมี "คํา" กว้าง 17 หน่วยสูงสุด 34 หน่วยในแถวเดียว ซึ่งควรกว้างอย่างน้อย 1,156 พิกเซล

  • การโฟกัสรูปภาพต่ําอาจส่งผลต่อความแม่นยําในการสแกน หากแอปไม่ได้รับผลลัพธ์ที่ยอมรับ ให้ขอให้ผู้ใช้สรุปรูปภาพ

  • สําหรับแอปพลิเคชันทั่วไป ขอแนะนําให้ใช้รูปภาพที่มีความละเอียดสูงขึ้น เช่น 1280x720 หรือ 1920x1080 ซึ่งทําให้สแกนบาร์โค้ดได้จากระยะที่ใหญ่ขึ้นจากกล้องไป

    อย่างไรก็ตาม ในแอปพลิเคชันที่มีเวลาในการตอบสนองสูง ก็สามารถปรับปรุงประสิทธิภาพได้โดยการจับภาพที่ความละเอียดต่ําลง แต่กําหนดให้บาร์โค้ดเป็นรูปภาพส่วนใหญ่ที่ป้อน ดูเคล็ดลับในการปรับปรุงประสิทธิภาพแบบเรียลไทม์

1. กําหนดค่าเครื่องสแกนบาร์โค้ด

หากคุณทราบรูปแบบบาร์โค้ดที่ต้องการอ่าน คุณสามารถปรับปรุงความเร็วของตัวตรวจจับบาร์โค้ดด้วยการกําหนดค่าให้ตรวจจับเฉพาะรูปแบบดังกล่าว

เช่น หากต้องการตรวจจับเฉพาะโค้ด Aztec และคิวอาร์โค้ด ให้สร้างออบเจ็กต์ BarcodeScannerOptions ตามตัวอย่างต่อไปนี้

Kotlin

val options = BarcodeScannerOptions.Builder()
        .setBarcodeFormats(
                Barcode.FORMAT_QR_CODE,
                Barcode.FORMAT_AZTEC)
        .build()

Java

BarcodeScannerOptions options =
        new BarcodeScannerOptions.Builder()
        .setBarcodeFormats(
                Barcode.FORMAT_QR_CODE,
                Barcode.FORMAT_AZTEC)
        .build();

ระบบรองรับรูปแบบต่อไปนี้

  • รหัส 128 (FORMAT_CODE_128)
  • รหัส 39 (FORMAT_CODE_39)
  • รหัส 93 (FORMAT_CODE_93)
  • โคดาบาร์ (FORMAT_CODABAR)
  • EAN-13 (FORMAT_EAN_13)
  • EAN-8 (FORMAT_EAN_8)
  • ITF (FORMAT_ITF)
  • UPC-A (FORMAT_UPC_A)
  • UPC-E (FORMAT_UPC_E)
  • คิวอาร์โค้ด (FORMAT_QR_CODE)
  • PDF417 (FORMAT_PDF417)
  • แอซเท็ก (FORMAT_AZTEC)
  • เมทริกซ์ข้อมูล (FORMAT_DATA_MATRIX)

2. เตรียมรูปภาพอินพุต

หากต้องการจดจําบาร์โค้ดในรูปภาพ ให้สร้างออบเจ็กต์ InputImage จาก Bitmap, media.Image, ByteBuffer, อาร์เรย์ไบต์ หรือไฟล์ในอุปกรณ์ จากนั้นส่งออบเจ็กต์ InputImage ไปยังเมธอด process ของ BarcodeScanner

คุณสามารถสร้างออบเจ็กต์ InputImage จากแหล่งที่มาต่างๆ ซึ่งอธิบายไว้ด้านล่าง

การใช้ media.Image

หากต้องการสร้างออบเจ็กต์ InputImage จากออบเจ็กต์ media.Image เช่น เมื่อคุณจับภาพจากกล้องของอุปกรณ์ ให้ส่งออบเจ็กต์ media.Image และการหมุนรูปภาพไปที่ InputImage.fromMediaImage()

หากคุณใช้ไลบรารี CameraX คลาส OnImageCapturedListener และ ImageAnalysis.Analyzer จะคํานวณค่าการหมุนเวียนสําหรับคุณ

Kotlin

private class YourImageAnalyzer : ImageAnalysis.Analyzer {

    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
}

Java

private class YourAnalyzer implements ImageAnalysis.Analyzer {

    @Override
    public void analyze(ImageProxy imageProxy) {
        Image mediaImage = imageProxy.getImage();
        if (mediaImage != null) {
          InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
          // Pass image to an ML Kit Vision API
          // ...
        }
    }
}

หากไม่ได้ใช้ไลบรารีกล้องที่ให้ระดับการหมุนของรูปภาพ คุณจะคํานวณได้จากระดับการหมุนของอุปกรณ์และการวางแนวของเซ็นเซอร์กล้องในอุปกรณ์ ดังนี้

Kotlin

private val ORIENTATIONS = SparseIntArray()

init {
    ORIENTATIONS.append(Surface.ROTATION_0, 0)
    ORIENTATIONS.append(Surface.ROTATION_90, 90)
    ORIENTATIONS.append(Surface.ROTATION_180, 180)
    ORIENTATIONS.append(Surface.ROTATION_270, 270)
}

/**
 * Get the angle by which an image must be rotated given the device's current
 * orientation.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Throws(CameraAccessException::class)
private fun getRotationCompensation(cameraId: String, activity: Activity, isFrontFacing: Boolean): Int {
    // Get the device's current rotation relative to its "native" orientation.
    // Then, from the ORIENTATIONS table, look up the angle the image must be
    // rotated to compensate for the device's rotation.
    val deviceRotation = activity.windowManager.defaultDisplay.rotation
    var rotationCompensation = ORIENTATIONS.get(deviceRotation)

    // Get the device's sensor orientation.
    val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager
    val sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360
    }
    return rotationCompensation
}

Java

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
    ORIENTATIONS.append(Surface.ROTATION_0, 0);
    ORIENTATIONS.append(Surface.ROTATION_90, 90);
    ORIENTATIONS.append(Surface.ROTATION_180, 180);
    ORIENTATIONS.append(Surface.ROTATION_270, 270);
}

/**
 * Get the angle by which an image must be rotated given the device's current
 * orientation.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private int getRotationCompensation(String cameraId, Activity activity, boolean isFrontFacing)
        throws CameraAccessException {
    // Get the device's current rotation relative to its "native" orientation.
    // Then, from the ORIENTATIONS table, look up the angle the image must be
    // rotated to compensate for the device's rotation.
    int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    int rotationCompensation = ORIENTATIONS.get(deviceRotation);

    // Get the device's sensor orientation.
    CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE);
    int sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION);

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360;
    }
    return rotationCompensation;
}

จากนั้นส่งออบเจ็กต์ media.Image และค่าองศาการหมุนเวียนไปยัง InputImage.fromMediaImage() ดังนี้

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

การใช้ URI ของไฟล์

หากต้องการสร้างออบเจ็กต์ InputImage จาก URI ของไฟล์ ให้ส่งบริบทของแอปและ URI ของไฟล์ไปยัง InputImage.fromFilePath() ซึ่งจะมีประโยชน์เมื่อคุณใช้ Intent ACTION_GET_CONTENT เพื่อแจ้งให้ผู้ใช้เลือกรูปภาพจากแอปแกลเลอรีของตน

Kotlin

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

Java

InputImage image;
try {
    image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
    e.printStackTrace();
}

การใช้ ByteBuffer หรือ ByteArray

หากต้องการสร้างออบเจ็กต์ InputImage จาก ByteBuffer หรือ ByteArray ให้คํานวณระดับการหมุนเวียนรูปภาพตามที่อธิบายไว้ก่อนหน้านี้สําหรับอินพุต media.Image จากนั้นสร้างออบเจ็กต์ InputImage ที่มีบัฟเฟอร์หรืออาร์เรย์ พร้อมด้วยความสูง ความกว้าง รูปแบบการเข้ารหัสสี และองศาการหมุนของรูปภาพ ดังนี้

Kotlin

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)
// Or:
val image = InputImage.fromByteArray(
        byteArray,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

Java

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);
// Or:
InputImage image = InputImage.fromByteArray(
        byteArray,
        /* image width */480,
        /* image height */360,
        rotation,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

การใช้ Bitmap

หากต้องการสร้างออบเจ็กต์ InputImage จากออบเจ็กต์ Bitmap โปรดประกาศต่อไปนี้

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

รูปภาพแทนออบเจ็กต์ Bitmap ร่วมกับองศาการหมุน

3. รับอินสแตนซ์ของ BarcodeScanner

Kotlin

val scanner = BarcodeScanning.getClient()
// Or, to specify the formats to recognize:
// val scanner = BarcodeScanning.getClient(options)

Java

BarcodeScanner scanner = BarcodeScanning.getClient();
// Or, to specify the formats to recognize:
// BarcodeScanner scanner = BarcodeScanning.getClient(options);

4. ประมวลผลรูปภาพ

ส่งรูปภาพไปยังเมธอด process ดังนี้

Kotlin

val result = scanner.process(image)
        .addOnSuccessListener { barcodes ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener {
            // Task failed with an exception
            // ...
        }

Java

Task<List<Barcode>> result = scanner.process(image)
        .addOnSuccessListener(new OnSuccessListener<List<Barcode>>() {
            @Override
            public void onSuccess(List<Barcode> barcodes) {
                // Task completed successfully
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });

5. รับข้อมูลจากบาร์โค้ด

หากการดําเนินการจดจําบาร์โค้ดสําเร็จ ระบบจะส่งรายการออบเจ็กต์ Barcode ไปยัง Listener ที่ประสบความสําเร็จ ออบเจ็กต์ Barcode แต่ละรายการแสดงถึงบาร์โค้ดที่ตรวจพบในรูปภาพ สําหรับบาร์โค้ดแต่ละรายการ คุณจะได้รับพิกัดที่ล้อมรอบในรูปภาพอินพุต รวมถึงข้อมูลดิบที่เข้ารหัสด้วยบาร์โค้ด นอกจากนี้ หากเครื่องสแกนบาร์โค้ดสามารถระบุประเภทข้อมูลที่เข้ารหัสด้วยบาร์โค้ด คุณก็สามารถรับออบเจ็กต์ที่มีข้อมูลที่แยกวิเคราะห์ได้

เช่น

Kotlin

for (barcode in barcodes) {
    val bounds = barcode.boundingBox
    val corners = barcode.cornerPoints

    val rawValue = barcode.rawValue

    val valueType = barcode.valueType
    // See API reference for complete list of supported types
    when (valueType) {
        Barcode.TYPE_WIFI -> {
            val ssid = barcode.wifi!!.ssid
            val password = barcode.wifi!!.password
            val type = barcode.wifi!!.encryptionType
        }
        Barcode.TYPE_URL -> {
            val title = barcode.url!!.title
            val url = barcode.url!!.url
        }
    }
}

Java

for (Barcode barcode: barcodes) {
    Rect bounds = barcode.getBoundingBox();
    Point[] corners = barcode.getCornerPoints();

    String rawValue = barcode.getRawValue();

    int valueType = barcode.getValueType();
    // See API reference for complete list of supported types
    switch (valueType) {
        case Barcode.TYPE_WIFI:
            String ssid = barcode.getWifi().getSsid();
            String password = barcode.getWifi().getPassword();
            int type = barcode.getWifi().getEncryptionType();
            break;
        case Barcode.TYPE_URL:
            String title = barcode.getUrl().getTitle();
            String url = barcode.getUrl().getUrl();
            break;
    }
}

เคล็ดลับในการปรับปรุงประสิทธิภาพแบบเรียลไทม์

หากต้องการสแกนบาร์โค้ดในแอปพลิเคชันแบบเรียลไทม์ ให้ทําตามหลักเกณฑ์ต่อไปนี้เพื่อให้อัตราเฟรมดีที่สุด

  • อย่าบันทึกอินพุตที่ความละเอียดตามค่าเริ่มต้นของกล้อง ในอุปกรณ์บางอย่าง การบันทึกอินพุตที่ความละเอียดตามค่าเริ่มต้นจะสร้างรูปภาพขนาดใหญ่มาก (10+ เมกะพิกเซล) ซึ่งส่งผลให้เวลาในการตอบสนองช้ามากและไม่ส่งผลเสียต่อความแม่นยํา ให้ขอขนาดจากกล้องที่จําเป็นสําหรับการตรวจหาบาร์โค้ดเท่านั้น ซึ่งโดยปกติแล้วต้องมีขนาดไม่เกิน 2 เมกะพิกเซล

    หากความเร็วในการสแกนเป็นสิ่งสําคัญ คุณลดความละเอียดในการจับภาพลงได้ อย่างไรก็ตาม โปรดทราบว่าข้อกําหนดขนาดบาร์โค้ดขั้นต่ําที่ระบุไว้ด้านบน

    หากคุณพยายามจดจําบาร์โค้ดจากเฟรมสตรีมวิดีโอที่ต่อเนื่องกัน ระบบจดจําอาจสร้างผลลัพธ์ที่ต่างกันจากแต่ละเฟรม คุณควรรอจนกว่าชุดค่าเดียวกันซึ่งต่อเนื่องกันจะมั่นใจได้ว่าคุณจะได้รับผลลัพธ์ที่ดี

    ระบบยังไม่รองรับหมายเลข Checksum สําหรับ ITF และ CODE-39

  • หากคุณใช้ API ของ Camera หรือ camera2 คุณจะควบคุมการใช้ตัวตรวจจับได้ หากเฟรมวิดีโอใหม่พร้อมใช้งานขณะที่ตัวตรวจจับทํางาน ให้วางเฟรมนั้น ดูตัวอย่างคลาส VisionProcessorBase ในแอปตัวอย่างคู่มือเริ่มต้นฉบับย่อ
  • หากคุณใช้ API CameraX โปรดตรวจสอบว่าได้ตั้งค่ากลยุทธ์ความกดดันเป็นค่าเริ่มต้น ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST ตัวเลือกนี้จะรับประกันว่าจะมีการส่งรูปภาพสําหรับการวิเคราะห์ข้อมูลเพียงครั้งละ 1 รูป หากมีการสร้างรูปภาพเพิ่มเติมเมื่อเครื่องมือวิเคราะห์ไม่ว่าง รูปภาพเหล่านั้นจะถูกทิ้งโดยอัตโนมัติและไม่อยู่ในคิวเพื่อนําส่ง เมื่อปิดรูปภาพที่จะวิเคราะห์แล้วโดยเรียก ImageProxy.close() ระบบจะส่งรูปภาพล่าสุดถัดไป
  • หากใช้เอาต์พุตของตัวตรวจจับเพื่อวางซ้อนกราฟิกบนรูปภาพอินพุต ก่อนอื่นให้ดูผลลัพธ์จาก ML Kit จากนั้นแสดงผลรูปภาพและวางซ้อนในขั้นตอนเดียว วิธีนี้จะแสดงผลบนแพลตฟอร์มจอแสดงผลเพียงครั้งเดียวสําหรับเฟรมอินพุตแต่ละเฟรม ดูตัวอย่างคลาส CameraSourcePreview และ GraphicOverlay ในแอปตัวอย่างเริ่มต้นอย่างรวดเร็ว
  • หากใช้ Camera2 API ให้จับภาพในรูปแบบ ImageFormat.YUV_420_888 หากคุณใช้ Camera API เวอร์ชันเก่า ให้จับภาพในรูปแบบ ImageFormat.NV21