การจดจำหมึกดิจิทัลของ 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: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 จะได้รับการฝึกให้จัดการกับรูปแบบการเขียนหลากหลายประเภท แต่ผลลัพธ์อาจแตกต่างกันไปสำหรับผู้ใช้แต่ละคน
ต่อไปนี้คือวิธีปรับปรุงความแม่นยำของโปรแกรมจดจำข้อความ โปรดทราบว่าเทคนิคเหล่านี้ใช้ไม่ได้กับตัวแยกประเภทของการวาดภาพสำหรับอีโมจิ การวาดอัตโนมัติ และรูปร่าง
พื้นที่การเขียน
แอปพลิเคชันจำนวนมากมีการกำหนดพื้นที่เขียนไว้อย่างดีสำหรับการป้อนข้อมูลของผู้ใช้ ความหมายของสัญลักษณ์จะกำหนดโดยขนาดบางส่วนเทียบกับขนาดของพื้นที่เขียนที่มีสัญลักษณ์นั้นอยู่ เช่น ความแตกต่างระหว่างอักษรตัวพิมพ์เล็กหรือตัวพิมพ์ใหญ่ "o" หรือ "c" และคอมมากับเครื่องหมายทับ
การบอกเครื่องมือจดจำความกว้างและความสูงของพื้นที่เขียนจะช่วยเพิ่มความแม่นยำได้ แต่เครื่องมือจดจำจะถือว่าพื้นที่สำหรับเขียนข้อความมีเพียงบรรทัดเดียว หากพื้นที่เขียนเอกสารจริงมีขนาดใหญ่พอที่จะให้ผู้ใช้เขียนได้ 2 บรรทัดขึ้นไป คุณอาจได้รับผลลัพธ์ที่ดีขึ้นด้วยการส่งผ่าน WritingArea ด้วยความสูงที่ใกล้เคียงกับความสูงของข้อความบรรทัดเดียว ออบเจ็กต์ WritingArea ที่คุณส่งไปยังเครื่องมือจดจำไม่จำเป็นต้องสอดคล้องกับพื้นที่เขียนจริงบนหน้าจอ การเปลี่ยนความสูงของพื้นที่การเขียนด้วยวิธีนี้ จะดีกว่าในบางภาษา
เมื่อคุณระบุพื้นที่สำหรับเขียน ให้ระบุความกว้างและความสูงเป็นหน่วยเดียวกับพิกัดเส้นโครงร่าง อาร์กิวเมนต์พิกัด x,y ไม่มีข้อกำหนดหน่วย เพราะ API จะทำให้หน่วยทั้งหมดเป็นมาตรฐาน ดังนั้นสิ่งเดียวที่สำคัญคือขนาดและตำแหน่งของเส้นที่เกี่ยวข้อง คุณข้ามผ่านพิกัดในสเกลใดก็ได้ที่เหมาะสมสำหรับระบบของคุณ
ก่อนเริ่มบริบท
"ก่อนบริบท" คือข้อความที่อยู่หน้าเส้นใน Ink
ที่คุณพยายามจดจำทันที คุณช่วยเครื่องมือจดจำได้โดยการเล่าให้ฟังเกี่ยวกับก่อนเริ่มบริบท
ตัวอย่างเช่น ตัวอักษรคัดลายมือ "n" และ "u" มักทำให้เข้าใจผิดเป็นอักขระอื่น หากผู้ใช้ป้อนคํา "arg" ในบางส่วนของคําแล้ว ผู้ใช้อาจใช้เส้นที่จดจําได้ว่าเป็น "ument" หรือ "nment" การระบุ "arg" ก่อนบริบทจะช่วยแก้ความคลุมเครือ เนื่องจากคำว่า "argument" มักจะมากกว่า "argnment"
บริบทล่วงหน้ายังช่วยให้เครื่องมือจดจำระบุการแบ่งคำ ซึ่งก็คือการเว้นวรรคระหว่างคำได้ คุณสามารถพิมพ์เว้นวรรค แต่ไม่สามารถวาดอักขระได้ แล้วโปรแกรมจดจำจะทราบได้อย่างไรว่าคำหนึ่งสิ้นสุดเมื่อใดและคำถัดไปจะเริ่มต้นขึ้นเมื่อใด หากผู้ใช้เขียน "hello" ไว้แล้วและยังต่อด้วยคำว่า "world" โดยไม่มีบริบทก่อน เครื่องมือจดจำจะแสดงผลสตริง "world" อย่างไรก็ตาม หากคุณระบุคำว่า "hello" ก่อนบริบท โมเดลจะแสดงผลสตริง " world" พร้อมเว้นวรรคนำหน้า เนื่องจาก "helloworld" จะมีความหมายมากกว่า "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 รูปได้