การจดจำลายมือดิจิทัลของ ML Kit ช่วยให้คุณจดจำข้อความที่เขียนด้วยลายมือบน พื้นผิวแบบดิจิทัลได้ในหลายร้อยภาษา รวมถึงจัดประเภทภาพร่างได้ด้วย
ลองเลย
- ลองใช้แอปตัวอย่างเพื่อดูตัวอย่างการใช้งาน API นี้
ก่อนเริ่มต้น
- ในไฟล์
build.gradle
ระดับโปรเจ็กต์ ให้ตรวจสอบว่าได้รวมที่เก็บ Maven ของ Google ไว้ในทั้งส่วนbuildscript
และallprojects
- เพิ่มทรัพยากร Dependency สำหรับไลบรารี Android ของ ML Kit ลงในไฟล์ Gradle ระดับแอปของโมดูล ซึ่งโดยปกติคือ
app/build.gradle
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}
ตอนนี้คุณพร้อมที่จะเริ่มจดจำข้อความในออบเจ็กต์ Ink
แล้ว
สร้างออบเจ็กต์ Ink
วิธีหลักในการสร้างออบเจ็กต์ Ink
คือการวาดบนหน้าจอสัมผัส ใน Android คุณสามารถใช้ผืนผ้าใบเพื่อวัตถุประสงค์นี้ได้ แฮนเดิลเหตุการณ์การแตะ
ควรเรียกใช้เมธอด 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));
เคล็ดลับในการปรับปรุงความแม่นยำในการจดจำข้อความ
ความแม่นยำของการจดจำข้อความอาจแตกต่างกันไปในแต่ละภาษา ความแม่นยำยังขึ้นอยู่กับสไตล์การเขียนด้วย แม้ว่าระบบจดจำลายมือดิจิทัลจะได้รับการฝึกให้รองรับรูปแบบการเขียนหลายประเภท แต่ผลลัพธ์อาจแตกต่างกันไปในแต่ละผู้ใช้
วิธีปรับปรุงความแม่นยำของตัวจดจำข้อความมีดังนี้ โปรดทราบว่าเทคนิคเหล่านี้ใช้ไม่ได้กับตัวแยกประเภทการวาดสำหรับอีโมจิ, AutoDraw และรูปร่าง
พื้นที่เขียน
แอปพลิเคชันจำนวนมากมีพื้นที่เขียนที่กำหนดไว้อย่างดีสำหรับข้อมูลจากผู้ใช้ ความหมายของสัญลักษณ์จะ กำหนดบางส่วนโดยขนาดของสัญลักษณ์นั้นเมื่อเทียบกับขนาดของพื้นที่เขียนที่มีสัญลักษณ์นั้น เช่น ความแตกต่างระหว่างตัวอักษร "o" หรือ "c" ที่เป็นตัวพิมพ์เล็กหรือตัวพิมพ์ใหญ่ และเครื่องหมายคอมมากับ เครื่องหมายทับ
การบอกความกว้างและความสูงของพื้นที่เขียนให้ตัวจดจำทราบจะช่วยเพิ่มความแม่นยำได้ อย่างไรก็ตาม ตัวจดจำจะถือว่าพื้นที่เขียนมีข้อความเพียงบรรทัดเดียว หากพื้นที่เขียนจริงมีขนาดใหญ่พอที่จะให้ผู้ใช้เขียนได้ 2 บรรทัดขึ้นไป คุณอาจได้รับผลลัพธ์ที่ดีขึ้นโดยการส่ง WritingArea ที่มีความสูงซึ่งเป็นการประมาณความสูงของข้อความ 1 บรรทัดที่ดีที่สุด ออบเจ็กต์ 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 อันดับแรกจะ
ใกล้เคียงกัน นอกจากนี้ โปรดทราบว่าตัวแยกประเภทรูปร่างจะตีความ Ink
ทั้งหมดเป็นรูปร่างเดียว ตัวอย่างเช่น หาก Ink
มีสี่เหลี่ยมผืนผ้าและวงรีอยู่ติดกัน ตัวจดจำอาจแสดงผลเป็นสี่เหลี่ยมผืนผ้าหรือวงรี (หรือรูปร่างอื่นที่แตกต่างออกไปโดยสิ้นเชิง) เนื่องจากผู้สมัครรับการจดจำรายการเดียวไม่สามารถแสดงรูปร่าง 2 แบบได้