بعد از اینکه مدل خودتان را با استفاده از AutoML Vision Edge آموزش دادید ، میتوانید از آن در برنامه خود برای برچسبگذاری تصاویر استفاده کنید.
دو راه برای ادغام مدلهای آموزشدیده از AutoML Vision Edge وجود دارد: میتوانید مدل را با قرار دادن آن در پوشه asset برنامه خود، به صورت دستهای (bundle) درآورید، یا میتوانید آن را به صورت پویا از Firebase دانلود کنید.
| گزینههای بستهبندی مدل | |
|---|---|
| در برنامه شما گنجانده شده است |
|
| میزبانی شده با فایربیس |
|
قبل از اینکه شروع کنی
وابستگیهای کتابخانههای اندروید ML Kit را به فایل gradle سطح app ماژول خود که معمولاً
app/build.gradleاست، اضافه کنید:برای باندل کردن یک مدل با برنامهتان:
dependencies { // ... // Image labeling feature with bundled automl model implementation 'com.google.mlkit:image-labeling-custom:16.3.1' }برای دانلود پویای یک مدل از Firebase، وابستگی
linkFirebaseاضافه کنید:dependencies { // ... // Image labeling feature with automl model downloaded // from firebase implementation 'com.google.mlkit:image-labeling-custom:16.3.1' implementation 'com.google.mlkit:linkfirebase:16.1.0' }اگر میخواهید یک مدل را دانلود کنید ، اگر قبلاً Firebase را به پروژه اندروید خود اضافه نکردهاید، حتماً آن را اضافه کنید. این کار هنگام بستهبندی مدل لازم نیست.
۱. مدل را بارگذاری کنید
پیکربندی یک منبع مدل محلی
برای اتصال مدل به برنامه خود:
مدل و متادیتای آن را از فایل زیپی که از کنسول Firebase دانلود کردهاید، استخراج کنید. توصیه میکنیم از فایلها به همان شکلی که دانلود کردهاید، بدون تغییر (از جمله نام فایلها) استفاده کنید.
مدل و فایلهای متادیتای آن را در بسته برنامه خود قرار دهید:
- اگر پوشه assets را در پروژه خود ندارید، با کلیک راست روی
app/folder و سپس کلیک روی New > Folder > Assets Folder، یکی ایجاد کنید. - یک زیرپوشه در زیر پوشه assets ایجاد کنید تا فایلهای مدل در آن قرار گیرند.
- فایلهای
model.tflite،dict.txtوmanifest.jsonرا در زیرپوشه کپی کنید (هر سه فایل باید در یک پوشه باشند).
- اگر پوشه assets را در پروژه خود ندارید، با کلیک راست روی
برای اطمینان از اینکه Gradle هنگام ساخت برنامه، فایل مدل را فشرده نمیکند، موارد زیر را به فایل
build.gradleبرنامه خود اضافه کنید:android { // ... aaptOptions { noCompress "tflite" } }فایل مدل در بسته برنامه گنجانده شده و به عنوان یک فایل خام در اختیار ML Kit قرار خواهد گرفت.
ایجاد شیء
LocalModel، با مشخص کردن مسیر فایل مانیفست مدل:جاوا
AutoMLImageLabelerLocalModel localModel = new AutoMLImageLabelerLocalModel.Builder() .setAssetFilePath("manifest.json") // or .setAbsoluteFilePath(absolute file path to manifest file) .build();کاتلین
val localModel = LocalModel.Builder() .setAssetManifestFilePath("manifest.json") // or .setAbsoluteManifestFilePath(absolute file path to manifest file) .build()
پیکربندی یک منبع مدل میزبانیشده توسط Firebase
برای استفاده از مدل میزبانیشده از راه دور، یک شیء CustomRemoteModel ایجاد کنید و نامی را که هنگام انتشار مدل به آن اختصاص دادهاید، مشخص کنید:
جاوا
// Specify the name you assigned in the Firebase console.
FirebaseModelSource firebaseModelSource =
new FirebaseModelSource.Builder("your_model_name").build();
CustomRemoteModel remoteModel =
new CustomRemoteModel.Builder(firebaseModelSource).build();
کاتلین
// Specify the name you assigned in the Firebase console.
val firebaseModelSource = FirebaseModelSource.Builder("your_model_name")
.build()
val remoteModel = CustomRemoteModel.Builder(firebaseModelSource).build()
سپس، وظیفه دانلود مدل را آغاز کنید و شرایطی را که میخواهید تحت آن اجازه دانلود داده شود، مشخص کنید. اگر مدل روی دستگاه نباشد، یا اگر نسخه جدیدتری از مدل موجود باشد، وظیفه به صورت غیرهمزمان مدل را از Firebase دانلود میکند:
جاوا
DownloadConditions downloadConditions = new DownloadConditions.Builder()
.requireWifi()
.build();
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(@NonNull Task<Void> task) {
// Success.
}
});
کاتلین
val downloadConditions = DownloadConditions.Builder()
.requireWifi()
.build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener {
// Success.
}
بسیاری از برنامهها وظیفه دانلود را در کد مقداردهی اولیه خود شروع میکنند، اما شما میتوانید این کار را در هر زمانی قبل از نیاز به استفاده از مدل انجام دهید.
یک برچسب تصویر از مدل خود ایجاد کنید
پس از پیکربندی منابع مدل خود، یک شیء ImageLabeler از یکی از آنها ایجاد کنید.
اگر فقط یک مدل محلی دارید، کافیست یک برچسبگذار از شیء CustomImageLabelerOptions خود ایجاد کنید و آستانه امتیاز اطمینان مورد نیاز خود را پیکربندی کنید (به بخش ارزیابی مدل خود مراجعه کنید):
جاوا
CustomImageLabelerOptions customImageLabelerOptions = new CustomImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate value.
.build();
ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);
کاتلین
val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate value.
.build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)
اگر یک مدل از راه دور دارید، باید قبل از اجرای آن، بررسی کنید که آیا دانلود شده است یا خیر. میتوانید وضعیت وظیفه دانلود مدل را با استفاده از متد isModelDownloaded() در model manager بررسی کنید.
اگرچه شما فقط باید قبل از اجرای برچسبگذار این موضوع را تأیید کنید، اما اگر هم یک مدل میزبانیشده از راه دور و هم یک مدل بستهبندیشده محلی دارید، انجام این بررسی هنگام نمونهسازی برچسبگذار تصویر منطقی است: اگر مدل از راه دور دانلود شده است، یک برچسبگذار از آن و در غیر این صورت از مدل محلی ایجاد کنید.
جاوا
RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener(new OnSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean isDownloaded) {
CustomImageLabelerOptions.Builder optionsBuilder;
if (isDownloaded) {
optionsBuilder = new CustomImageLabelerOptions.Builder(remoteModel);
} else {
optionsBuilder = new CustomImageLabelerOptions.Builder(localModel);
}
CustomImageLabelerOptions options = optionsBuilder
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate threshold.
.build();
ImageLabeler labeler = ImageLabeling.getClient(options);
}
});
کاتلین
RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener { isDownloaded ->
val optionsBuilder =
if (isDownloaded) {
CustomImageLabelerOptions.Builder(remoteModel)
} else {
CustomImageLabelerOptions.Builder(localModel)
}
// Evaluate your model in the Cloud console to determine an appropriate threshold.
val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
val labeler = ImageLabeling.getClient(options)
}
اگر فقط یک مدل میزبانیشده از راه دور دارید، باید قابلیتهای مرتبط با مدل را غیرفعال کنید - برای مثال، بخشی از رابط کاربری خود را خاکستری یا پنهان کنید - تا زمانی که تأیید کنید مدل دانلود شده است. میتوانید این کار را با اتصال یک شنونده به متد download() در model manager انجام دهید:
جاوا
RemoteModelManager.getInstance().download(remoteModel, conditions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void v) {
// Download complete. Depending on your app, you could enable
// the ML feature, or switch from the local model to the remote
// model, etc.
}
});
کاتلین
RemoteModelManager.getInstance().download(remoteModel, conditions)
.addOnSuccessListener {
// Download complete. Depending on your app, you could enable the ML
// feature, or switch from the local model to the remote model, etc.
}
۲. تصویر ورودی را آماده کنید
سپس، برای هر تصویری که میخواهید برچسبگذاری کنید، یک شیء InputImage از تصویر خود ایجاد کنید. برچسبگذار تصویر زمانی که از Bitmap یا اگر از camera2 API استفاده میکنید، از YUV_420_888 media.Image استفاده میکنید، سریعتر اجرا میشود، که در صورت امکان توصیه میشوند.
شما میتوانید یک InputImage از منابع مختلفی ایجاد کنید که هر کدام در زیر توضیح داده شدهاند.
استفاده از یک media.Image
برای ایجاد یک شیء InputImage از یک شیء media.Image ، مانند زمانی که از دوربین یک دستگاه تصویر میگیرید، شیء media.Image و چرخش تصویر را به InputImage.fromMediaImage() ارسال کنید.
اگر از کتابخانه CameraX استفاده میکنید، کلاسهای OnImageCapturedListener و ImageAnalysis.Analyzer مقدار چرخش را برای شما محاسبه میکنند.
Kotlin
private class YourImageAnalyzer : ImageAnalysis.Analyzer { override fun analyze(imageProxy: ImageProxy?) { val mediaImage = imageProxy?.image if (mediaImage != null) { val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) // Pass image to an ML Kit Vision API // ... } } }
Java
private class YourAnalyzer implements ImageAnalysis.Analyzer { @Override public void analyze(ImageProxy imageProxy) { if (imageProxy == null || imageProxy.getImage() == null) { return; } Image mediaImage = imageProxy.getImage(); InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees); // Pass image to an ML Kit Vision API // ... } }
اگر از کتابخانه دوربینی که درجه چرخش تصویر را به شما بدهد استفاده نمیکنید، میتوانید آن را از درجه چرخش دستگاه و جهت سنسور دوربین در دستگاه محاسبه کنید:
Kotlin
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 90) ORIENTATIONS.append(Surface.ROTATION_90, 0) ORIENTATIONS.append(Surface.ROTATION_180, 270) ORIENTATIONS.append(Surface.ROTATION_270, 180) } /** * Get the angle by which an image must be rotated given the device's current * orientation. */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Throws(CameraAccessException::class) private fun getRotationCompensation(cameraId: String, activity: Activity, context: Context): Int { // Get the device's current rotation relative to its "native" orientation. // Then, from the ORIENTATIONS table, look up the angle the image must be // rotated to compensate for the device's rotation. val deviceRotation = activity.windowManager.defaultDisplay.rotation var rotationCompensation = ORIENTATIONS.get(deviceRotation) // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360 // Return the corresponding FirebaseVisionImageMetadata rotation value. val result: Int when (rotationCompensation) { 0 -> result = FirebaseVisionImageMetadata.ROTATION_0 90 -> result = FirebaseVisionImageMetadata.ROTATION_90 180 -> result = FirebaseVisionImageMetadata.ROTATION_180 270 -> result = FirebaseVisionImageMetadata.ROTATION_270 else -> { result = FirebaseVisionImageMetadata.ROTATION_0 Log.e(TAG, "Bad rotation value: $rotationCompensation") } } return result }
Java
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } /** * Get the angle by which an image must be rotated given the device's current * orientation. */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private int getRotationCompensation(String cameraId, Activity activity, Context context) throws CameraAccessException { // Get the device's current rotation relative to its "native" orientation. // Then, from the ORIENTATIONS table, look up the angle the image must be // rotated to compensate for the device's rotation. int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); int rotationCompensation = ORIENTATIONS.get(deviceRotation); // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360; // Return the corresponding FirebaseVisionImageMetadata rotation value. int result; switch (rotationCompensation) { case 0: result = FirebaseVisionImageMetadata.ROTATION_0; break; case 90: result = FirebaseVisionImageMetadata.ROTATION_90; break; case 180: result = FirebaseVisionImageMetadata.ROTATION_180; break; case 270: result = FirebaseVisionImageMetadata.ROTATION_270; break; default: result = FirebaseVisionImageMetadata.ROTATION_0; Log.e(TAG, "Bad rotation value: " + rotationCompensation); } return result; }
سپس، شیء media.Image و مقدار درجه چرخش را به InputImage.fromMediaImage() ارسال کنید:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
استفاده از یک URI فایل
برای ایجاد یک شیء InputImage از یک URI فایل، متن برنامه و URI فایل را به InputImage.fromFilePath() ارسال کنید. این زمانی مفید است که از یک ACTION_GET_CONTENT برای وادار کردن کاربر به انتخاب یک تصویر از برنامه گالری خود استفاده میکنید.
Kotlin
val image: InputImage try { image = InputImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
Java
InputImage image; try { image = InputImage.fromFilePath(context, uri); } catch (IOException e) { e.printStackTrace(); }
استفاده از ByteBuffer یا ByteArray
برای ایجاد یک شیء InputImage از یک ByteBuffer یا یک ByteArray ، ابتدا درجه چرخش تصویر را همانطور که قبلاً برای ورودی media.Image توضیح داده شد، محاسبه کنید. سپس، شیء InputImage را با بافر یا آرایه، به همراه ارتفاع، عرض، فرمت کدگذاری رنگ و درجه چرخش تصویر ایجاد کنید:
Kotlin
val image = InputImage.fromByteBuffer( byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 )
Java
InputImage image = InputImage.fromByteBuffer(byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 );
استفاده از Bitmap
برای ایجاد یک شیء InputImage از یک شیء Bitmap ، تعریف زیر را انجام دهید:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
تصویر توسط یک شیء Bitmap به همراه درجه چرخش نمایش داده میشود.
۳. برچسبگذار تصویر را اجرا کنید
برای برچسبگذاری اشیاء در یک تصویر، شیء image را به متد process() در ImageLabeler ارسال کنید.
جاوا
labeler.process(image)
.addOnSuccessListener(new OnSuccessListener<List<ImageLabel>>() {
@Override
public void onSuccess(List<ImageLabel> labels) {
// Task completed successfully
// ...
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Task failed with an exception
// ...
}
});
کاتلین
labeler.process(image)
.addOnSuccessListener { labels ->
// Task completed successfully
// ...
}
.addOnFailureListener { e ->
// Task failed with an exception
// ...
}
۴. اطلاعات مربوط به اشیاء برچسبگذاری شده را دریافت کنید
اگر عملیات برچسبگذاری تصویر با موفقیت انجام شود، فهرستی از اشیاء ImageLabel به شنوندهی موفقیت ارسال میشود. هر شیء ImageLabel نشاندهندهی چیزی است که در تصویر برچسبگذاری شده است. میتوانید توضیحات متنی هر برچسب، امتیاز اطمینان تطابق و اندیس تطابق را دریافت کنید. برای مثال:
جاوا
for (ImageLabel label : labels) {
String text = label.getText();
float confidence = label.getConfidence();
int index = label.getIndex();
}
کاتلین
for (label in labels) {
val text = label.text
val confidence = label.confidence
val index = label.index
}
نکاتی برای بهبود عملکرد در زمان واقعی
اگر میخواهید تصاویر را در یک برنامهی بلادرنگ برچسبگذاری کنید، برای دستیابی به بهترین نرخ فریم، این دستورالعملها را دنبال کنید:
- فراخوانی Throttle به برچسبگذار تصویر. اگر در حین اجرای برچسبگذار تصویر، فریم ویدیویی جدیدی در دسترس قرار گرفت، فریم را حذف کنید. برای مثال به کلاس
VisionProcessorBaseدر برنامه نمونه شروع سریع مراجعه کنید. - اگر از خروجی برچسبگذار تصویر برای همپوشانی گرافیکها روی تصویر ورودی استفاده میکنید، ابتدا نتیجه را دریافت کنید، سپس تصویر و همپوشانی را در یک مرحله رندر کنید. با انجام این کار، برای هر فریم ورودی فقط یک بار روی سطح نمایشگر رندر میکنید. برای مثال به کلاسهای
CameraSourcePreviewوGraphicOverlayدر برنامه نمونه شروع سریع مراجعه کنید. اگر از API دوربین ۲ استفاده میکنید، تصاویر را با فرمت
ImageFormat.YUV_420_888ثبت کنید.اگر از API دوربین قدیمیتر استفاده میکنید، تصاویر را با فرمت
ImageFormat.NV21ضبط کنید.