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