การจดจำด้วยหมึกดิจิทัลของ 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:18.1.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" การระบุก่อนบริบทเป็น "อาร์กิวเมนต์" จะช่วยแก้ไขความกำกวม เนื่องจากคำว่า "อาร์กิวเมนต์" มีแนวโน้มที่จะเป็น "อาร์กิวเมนต์"
นอกจากนี้ บริบทเบื้องต้นยังช่วยให้ระบบจดจำระบุตัวแบ่งคำหรือการเว้นวรรคระหว่างคำได้ด้วย คุณพิมพ์อักขระเว้นวรรคได้ แต่วาดอักขระหนึ่งไม่ได้ แล้วโปรแกรมรู้จำจะบอกได้อย่างไรว่าคำหนึ่งสิ้นสุดและคำถัดไปเริ่มต้นเมื่อใด หากผู้ใช้เขียนคำว่า "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 รูปได้