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