การจดจำหมึกดิจิทัลของ ML Kit ช่วยให้คุณจดจำข้อความที่เขียนด้วยลายมือบนพื้นผิวดิจิทัลได้หลายร้อยภาษา รวมถึงจัดประเภทภาพสเก็ตช์ได้ด้วย
ลองเลย
- ลองใช้แอปตัวอย่างเพื่อดูตัวอย่างการใช้งาน API นี้
ก่อนเริ่มต้น
- ในไฟล์
build.gradleระดับโปรเจ็กต์ ให้ตรวจสอบว่าได้รวมที่เก็บ Maven ของ Google ไว้ในทั้งส่วนbuildscriptและallprojects - เพิ่มทรัพยากร Dependency สำหรับไลบรารี ML Kit สำหรับ Android ลงในไฟล์ Gradle ระดับแอปของโมดูล ซึ่งโดยปกติจะเป็น
app/build.gradle
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}
ตอนนี้คุณพร้อมที่จะเริ่มจดจำข้อความในออบเจ็กต์ Ink รายการแล้ว
สร้างออบเจ็กต์ Ink
วิธีหลักในการสร้างวัตถุ Ink คือการวาดบนหน้าจอสัมผัส ใน Android คุณสามารถใช้ Canvas เพื่อจุดประสงค์นี้ได้ เครื่องจัดการการโต้ตอบแบบสัมผัสควรเรียกใช้เมธอด addNewTouchEvent() ที่แสดงข้อมูลโค้ดต่อไปนี้เพื่อจัดเก็บจุดในเส้นที่ผู้ใช้วาดลงในออบเจ็กต์ Ink
รูปแบบทั่วไปนี้แสดงอยู่ในข้อมูลโค้ดต่อไปนี้ ดูตัวอย่างที่เสร็จสมบูรณ์ยิ่งขึ้นได้ในตัวอย่างการเริ่มต้นใช้งาน ML Kit อย่างรวดเร็ว
Kotlin
var inkBuilder = Ink.builder() lateinit var strokeBuilder: Ink.Stroke.Builder // Call this each time there is a new event. fun addNewTouchEvent(event: MotionEvent) { val action = event.actionMasked val x = event.x val y = event.y var t = System.currentTimeMillis() // If your setup does not provide timing information, you can omit the // third paramater (t) in the calls to Ink.Point.create when (action) { MotionEvent.ACTION_DOWN -> { strokeBuilder = Ink.Stroke.builder() strokeBuilder.addPoint(Ink.Point.create(x, y, t)) } MotionEvent.ACTION_MOVE -> strokeBuilder!!.addPoint(Ink.Point.create(x, y, t)) MotionEvent.ACTION_UP -> { strokeBuilder.addPoint(Ink.Point.create(x, y, t)) inkBuilder.addStroke(strokeBuilder.build()) } else -> { // Action not relevant for ink construction } } } ... // This is what to send to the recognizer. val ink = inkBuilder.build()
Java
Ink.Builder inkBuilder = Ink.builder(); Ink.Stroke.Builder strokeBuilder; // Call this each time there is a new event. public void addNewTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); long t = System.currentTimeMillis(); // If your setup does not provide timing information, you can omit the // third paramater (t) in the calls to Ink.Point.create int action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: strokeBuilder = Ink.Stroke.builder(); strokeBuilder.addPoint(Ink.Point.create(x, y, t)); break; case MotionEvent.ACTION_MOVE: strokeBuilder.addPoint(Ink.Point.create(x, y, t)); break; case MotionEvent.ACTION_UP: strokeBuilder.addPoint(Ink.Point.create(x, y, t)); inkBuilder.addStroke(strokeBuilder.build()); strokeBuilder = null; break; } } ... // This is what to send to the recognizer. Ink ink = inkBuilder.build();
รับอินสแตนซ์ของ DigitalInkRecognizer
หากต้องการจดจำ ให้ส่งอินสแตนซ์ Ink ไปยังออบเจ็กต์ DigitalInkRecognizer โค้ดด้านล่างแสดงวิธีสร้างอินสแตนซ์ของเครื่องมือจดจำดังกล่าวจากแท็ก BCP-47
Kotlin
// Specify the recognition model for a language var modelIdentifier: DigitalInkRecognitionModelIdentifier try { modelIdentifier = DigitalInkRecognitionModelIdentifier.fromLanguageTag("en-US") } catch (e: MlKitException) { // language tag failed to parse, handle error. } if (modelIdentifier == null) { // no model was found, handle error. } var model: DigitalInkRecognitionModel = DigitalInkRecognitionModel.builder(modelIdentifier).build() // Get a recognizer for the language var recognizer: DigitalInkRecognizer = DigitalInkRecognition.getClient( DigitalInkRecognizerOptions.builder(model).build())
Java
// Specify the recognition model for a language DigitalInkRecognitionModelIdentifier modelIdentifier; try { modelIdentifier = DigitalInkRecognitionModelIdentifier.fromLanguageTag("en-US"); } catch (MlKitException e) { // language tag failed to parse, handle error. } if (modelIdentifier == null) { // no model was found, handle error. } DigitalInkRecognitionModel model = DigitalInkRecognitionModel.builder(modelIdentifier).build(); // Get a recognizer for the language DigitalInkRecognizer recognizer = DigitalInkRecognition.getClient( DigitalInkRecognizerOptions.builder(model).build());
ประมวลผลออบเจ็กต์ Ink
Kotlin
recognizer.recognize(ink) .addOnSuccessListener { result: RecognitionResult -> // `result` contains the recognizer's answers as a RecognitionResult. // Logs the text from the top candidate. Log.i(TAG, result.candidates[0].text) } .addOnFailureListener { e: Exception -> Log.e(TAG, "Error during recognition: $e") }
Java
recognizer.recognize(ink) .addOnSuccessListener( // `result` contains the recognizer's answers as a RecognitionResult. // Logs the text from the top candidate. result -> Log.i(TAG, result.getCandidates().get(0).getText())) .addOnFailureListener( e -> Log.e(TAG, "Error during recognition: " + e));
โค้ดตัวอย่างด้านบนถือว่าโมเดลการจดจำได้รับการดาวน์โหลดแล้ว ตามที่อธิบายไว้ในส่วนถัดไป
การจัดการการดาวน์โหลดโมเดล
แม้ว่า API การจดจำหมึกดิจิทัลจะรองรับหลายร้อยภาษา แต่แต่ละภาษาก็ต้องดาวน์โหลดข้อมูลบางอย่างก่อนที่จะจดจำได้ ต้องใช้พื้นที่เก็บข้อมูลประมาณ 20 MB ต่อภาษา ซึ่งจะจัดการโดยออบเจ็กต์ RemoteModelManager
ดาวน์โหลดโมเดลใหม่
Kotlin
import com.google.mlkit.common.model.DownloadConditions import com.google.mlkit.common.model.RemoteModelManager var model: DigitalInkRecognitionModel = ... val remoteModelManager = RemoteModelManager.getInstance() remoteModelManager.download(model, DownloadConditions.Builder().build()) .addOnSuccessListener { Log.i(TAG, "Model downloaded") } .addOnFailureListener { e: Exception -> Log.e(TAG, "Error while downloading a model: $e") }
Java
import com.google.mlkit.common.model.DownloadConditions; import com.google.mlkit.common.model.RemoteModelManager; DigitalInkRecognitionModel model = ...; RemoteModelManager remoteModelManager = RemoteModelManager.getInstance(); remoteModelManager .download(model, new DownloadConditions.Builder().build()) .addOnSuccessListener(aVoid -> Log.i(TAG, "Model downloaded")) .addOnFailureListener( e -> Log.e(TAG, "Error while downloading a model: " + e));
ตรวจสอบว่ามีการดาวน์โหลดโมเดลแล้วหรือยัง
Kotlin
var model: DigitalInkRecognitionModel = ... remoteModelManager.isModelDownloaded(model)
Java
DigitalInkRecognitionModel model = ...; remoteModelManager.isModelDownloaded(model);
ลบโมเดลที่ดาวน์โหลด
การนำโมเดลออกจากพื้นที่เก็บข้อมูลของอุปกรณ์จะช่วยเพิ่มพื้นที่ว่าง
Kotlin
var model: DigitalInkRecognitionModel = ... remoteModelManager.deleteDownloadedModel(model) .addOnSuccessListener { Log.i(TAG, "Model successfully deleted") } .addOnFailureListener { e: Exception -> Log.e(TAG, "Error while deleting a model: $e") }
Java
DigitalInkRecognitionModel model = ...; remoteModelManager.deleteDownloadedModel(model) .addOnSuccessListener( aVoid -> Log.i(TAG, "Model successfully deleted")) .addOnFailureListener( e -> Log.e(TAG, "Error while deleting a model: " + e));
เคล็ดลับในการปรับปรุงความแม่นยำของการจดจำข้อความ
ความแม่นยำของการจดจำข้อความอาจแตกต่างกันไปในแต่ละภาษา ความแม่นยำยังขึ้นอยู่กับรูปแบบการเขียนด้วย แม้ว่า Digital Ink Recognition จะได้รับการฝึกให้จัดการกับรูปแบบการเขียนหลายประเภท แต่ผลลัพธ์อาจแตกต่างกันไปในผู้ใช้แต่ละราย
ต่อไปนี้เป็นวิธีปรับปรุงความแม่นยำของเครื่องมือจดจำข้อความ โปรดทราบว่าเทคนิคเหล่านี้ใช้ไม่ได้กับตัวแยกประเภทการวาดสำหรับอีโมจิ, AutoDraw และรูปร่าง
พื้นที่เขียน
แอปพลิเคชันจำนวนมากมีพื้นที่การเขียนที่กำหนดไว้อย่างชัดเจนสำหรับการป้อนข้อมูลของผู้ใช้ ความหมายของสัญลักษณ์จะขึ้นอยู่กับขนาดของสัญลักษณ์นั้นเมื่อเทียบกับขนาดของพื้นที่เขียนที่มีสัญลักษณ์นั้นอยู่ เช่น ความแตกต่างระหว่างตัวอักษร "o" หรือ "c" ตัวพิมพ์เล็กหรือตัวพิมพ์ใหญ่ และเครื่องหมายคอมมากับเครื่องหมายทับ
การบอกความกว้างและความสูงของพื้นที่เขียนให้โปรแกรมจดจำทราบจะช่วยเพิ่มความแม่นยำได้ อย่างไรก็ตาม เครื่องมือจดจำจะถือว่าพื้นที่เขียนมีข้อความเพียงบรรทัดเดียว หากพื้นที่การเขียนจริงมีขนาดใหญ่พอที่จะให้ผู้ใช้เขียนได้ 2 บรรทัดขึ้นไป คุณอาจได้ผลลัพธ์ที่ดีกว่าโดยการส่ง WritingArea ที่มีความสูงซึ่งเป็นค่าประมาณที่ดีที่สุดของความสูงของข้อความบรรทัดเดียว ออบเจ็กต์ WritingArea ที่คุณส่งไปยังเครื่องมือจดจำไม่จำเป็นต้องตรงกับพื้นที่การเขียนจริงบนหน้าจอทุกประการ การเปลี่ยนความสูงของ WritingArea ด้วยวิธีนี้ใช้ได้ดีในบางภาษามากกว่าภาษาอื่นๆ
เมื่อระบุพื้นที่เขียน ให้ระบุความกว้างและความสูงในหน่วยเดียวกับพิกัดเส้นขีด อาร์กิวเมนต์พิกัด x,y ไม่จำเป็นต้องมีหน่วย เนื่องจาก API จะปรับหน่วยทั้งหมดให้เป็นมาตรฐาน ดังนั้นสิ่งสำคัญเพียงอย่างเดียวคือขนาดและตำแหน่งสัมพัทธ์ของเส้นวาด คุณสามารถส่งพิกัดในสเกลใดก็ได้ที่เหมาะสมกับระบบของคุณ
บริบทก่อนหน้า
บริบทก่อนหน้าคือข้อความที่อยู่ก่อนหน้าเส้นขีดใน Ink ที่คุณพยายามจดจำโดยตรง คุณสามารถช่วยเครื่องมือจดจำได้โดยบอกเกี่ยวกับบริบทก่อนหน้า
ตัวอย่างเช่น ตัวอักษรเขียน "n" และ "u" มักจะถูกเข้าใจผิดว่าเป็นตัวเดียวกัน หากผู้ใช้ป้อนคำบางส่วนว่า "arg" แล้ว ผู้ใช้อาจเขียนต่อด้วยเส้นที่ระบบจดจำได้ว่าเป็น "ument" หรือ "nment" การระบุบริบทก่อนหน้า "arg" จะช่วยแก้ความกำกวมได้ เนื่องจากคำว่า "argument" มีแนวโน้มที่จะถูกต้องมากกว่า "argnment"
บริบทก่อนหน้ายังช่วยให้โปรแกรมจดจำระบุการเว้นวรรคระหว่างคำได้ด้วย คุณสามารถพิมพ์อักขระเว้นวรรคได้ แต่จะวาดไม่ได้ แล้วเครื่องมือจดจำจะระบุได้อย่างไรว่าคำหนึ่งสิ้นสุดลงและคำถัดไปเริ่มต้นขึ้นเมื่อใด หากผู้ใช้เขียนคำว่า "hello" ไปแล้วและเขียนคำว่า "world" ต่อ โดยไม่มีบริบทก่อนหน้า เครื่องมือจดจำจะแสดงผลสตริง "world" อย่างไรก็ตาม หากคุณระบุบริบทก่อนหน้าเป็น "hello" โมเดลจะแสดงผลสตริง " world" โดยมีเว้นวรรคนำหน้า เนื่องจาก "hello world" มีความหมายมากกว่า "helloword"
คุณควรระบุสตริงบริบทก่อนหน้าที่ยาวที่สุดเท่าที่จะเป็นไปได้ โดยมีความยาวไม่เกิน 20 อักขระ (รวมการเว้นวรรค) หากสตริงยาวกว่านั้น เครื่องมือจดจำจะใช้เฉพาะอักขระ 20 ตัวสุดท้ายเท่านั้น
ตัวอย่างโค้ดด้านล่างแสดงวิธีกำหนดพื้นที่เขียนและใช้ออบเจ็กต์ RecognitionContext เพื่อระบุบริบทก่อนหน้า
Kotlin
var preContext : String = ...; var width : Float = ...; var height : Float = ...; val recognitionContext : RecognitionContext = RecognitionContext.builder() .setPreContext(preContext) .setWritingArea(WritingArea(width, height)) .build() recognizer.recognize(ink, recognitionContext)
Java
String preContext = ...; float width = ...; float height = ...; RecognitionContext recognitionContext = RecognitionContext.builder() .setPreContext(preContext) .setWritingArea(new WritingArea(width, height)) .build(); recognizer.recognize(ink, recognitionContext);
ลำดับการลากเส้น
ความแม่นยำในการจดจำจะขึ้นอยู่กับลำดับของเส้นขีด เครื่องมือจดจำคาดหวังว่าเส้นจะเกิดขึ้นตามลำดับที่ผู้คนเขียนตามธรรมชาติ เช่น จากซ้ายไปขวาสำหรับภาษาอังกฤษ กรณีใดก็ตามที่แตกต่างจากรูปแบบนี้ เช่น การเขียนประโยคภาษาอังกฤษที่ขึ้นต้นด้วยคำสุดท้าย จะให้ผลลัพธ์ที่แม่นยำน้อยกว่า
อีกตัวอย่างหนึ่งคือเมื่อมีการนำคำที่อยู่ตรงกลางของInkออกและแทนที่ด้วยคำอื่น การแก้ไขอาจอยู่ตรงกลางประโยค แต่เส้นขีดสำหรับการแก้ไขจะอยู่ที่ส่วนท้ายของลำดับเส้นขีด
ในกรณีนี้ เราขอแนะนำให้ส่งคำที่เขียนใหม่ไปยัง API แยกต่างหาก และรวมผลลัพธ์กับการจดจำก่อนหน้าโดยใช้ตรรกะของคุณเอง
การจัดการกับรูปร่างที่กำกวม
มีบางกรณีที่ความหมายของรูปร่างที่ระบุให้กับเครื่องมือจดจำมีความกำกวม เช่น สี่เหลี่ยมผืนผ้าที่มีขอบมนมากอาจมองเป็นสี่เหลี่ยมผืนผ้าหรือวงรีก็ได้
กรณีที่ไม่ชัดเจนเหล่านี้สามารถจัดการได้โดยใช้คะแนนการจดจำเมื่อมีให้บริการ เฉพาะตัวแยกประเภทรูปร่างเท่านั้นที่ให้คะแนน หากโมเดลมีความมั่นใจมาก คะแนนของผลลัพธ์อันดับแรกจะดีกว่าผลลัพธ์อันดับ 2 มาก หากมีความไม่แน่นอน คะแนนของผลลัพธ์ 2 อันดับแรกจะใกล้เคียงกัน นอกจากนี้ โปรดทราบว่าตัวแยกประเภทรูปร่างจะตีความ Ink ทั้งหมดเป็นรูปร่างเดียว ตัวอย่างเช่น หาก Ink มีสี่เหลี่ยมผืนผ้าและวงรีอยู่ติดกัน เครื่องมือจดจำอาจแสดงผลเป็นรูปใดรูปหนึ่ง (หรือรูปอื่นที่แตกต่างไปโดยสิ้นเชิง) เนื่องจากตัวเลือกการจดจำรายการเดียวไม่สามารถแสดงรูปร่าง 2 รูปได้