دو روش برای ادغام برچسبگذاری تصویر با مدلهای سفارشی وجود دارد: ازطریق دستهبندی خط لوله بهعنوان بخشی از برنامه، یا ازطریق استفاده از خط لوله دستهبندینشده که به خدمات Google Play وابسته است. اگر خط لوله غیریکپارچه را انتخاب کنید، برنامه شما کوچکتر خواهد بود. برای جزئیات، جدول زیر را ببینید.
| موجود در بستهبندی | تفکیکشده | |
|---|---|---|
| نام کتابخانه | com.google.mlkit:image-labeling-custom | com.google.android.gms:play-services-mlkit-image-labeling-custom |
پیادهسازی | خط لوله در زمان ساخت بهصورت ایستا به برنامه شما پیوند داده میشود. | خط لوله بهصورت پویا بااستفاده از «خدمات Google Play» بارگیری میشود. |
| اندازه برنامه | حدود ۳٫۸ مگابایت افزایش اندازه. | حدود ۲۰۰ کیلوبایت افزایش اندازه. |
| زمان مقداردهی اولیه | خط لوله بلافاصله دردسترس است. | ممکن است لازم باشد قبلاز اولین استفاده، منتظر بمانید تا خط لوله بارگیری شود. |
| مرحله چرخه حیات میانای برنامهسازی کاربردی | دردسترس عموم (GA) | بتا |
دو روش برای یکپارچه کردن مدل سفارشی وجود دارد: مدل را با قرار دادن آن در پوشه دارایی برنامه دستهبندی کنید، یا آن را بهصورت پویا از Firebase بارگیری کنید. جدول زیر این دو گزینه را مقایسه میکند.
| مدل بستهای | مدل میزبانیشده |
|---|---|
| مدل بخشی از APK برنامهتان است که اندازه آن را افزایش میدهد. | این مدل بخشی از APK شما نیست. با بارگذاری در Cloud Storage میزبانی میشود. توصیه میکنیم از Cloud Storage برای Firebase استفاده کنید. |
| مدل فوراً دردسترس قرار میگیرد، حتی وقتی دستگاه Android آفلاین باشد | برنامهتان باید حاوی کدی برای بارگیری مدل درصورت درخواست باشد |
| نیازی به پروژه Firebase نیست | به پروژه Firebase نیاز دارد (درصورت استفاده از Cloud Storage for Firebase). |
| برای بهروز کردن مدل باید برنامهتان را دوباره منتشر کنید | بهروزرسانیهای مدل را بدون بازانتشار برنامه خود ارسال کنید |
| نداشتن آزمایش آ/ب داخلی | آزمایش آ/ب با پیکربندی از راه دور Firebase |
امتحان کنید
- برای نمونهای از استفاده از مدل دستهای و برنامه شروع سریع automl برای نمونهای از استفاده از مدل میزبانیشده، برنامه شروع سریع دیداری را ببینید.
قبلاز شروع
در فایل
build.gradle.ktsسطح پروژه، حتماً مخزن Maven متعلق به Google را در هر دو بخشbuildscriptوallprojectsاضافه کنید.وابستگیهای کتابخانههای ML Kit Android را به فایل gradle سطح برنامه واحدتان اضافه کنید، که معمولاً
app/build.gradle.ktsاست. براساس نیازهایتان، یکی از وابستگیهای زیر را انتخاب کنید:برای دستهبندی خط لوله با برنامه:
dependencies { // ... // Use this dependency to bundle the pipeline with your app implementation("com.google.mlkit:image-labeling-custom:17.0.3") }برای استفاده از خط لوله در «خدمات Google Play»:
dependencies { // ... // Use this dependency to use the dynamically downloaded pipeline in Google Play services implementation("com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta5") }اگر انتخاب کنید که از خط لوله در «خدمات Google Play» استفاده کنید، میتوانید برنامهتان را پیکربندی کنید تا خط لوله را بهطور خودکار در دستگاه پساز نصب برنامه از «فروشگاه Play» بارگیری کند. برای انجام این کار، اظهارنامه زیر را به فایل
AndroidManifest.xmlبرنامهتان اضافه کنید:<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="custom_ica" /> <!-- To use multiple downloads: android:value="custom_ica,download2,download3" --> </application>همچنین میتوانید دردسترس بودن خط لوله را بهطور صریح بررسی کنید و ازطریق خدمات Google Play ModuleInstallClient API درخواست بارگیری کنید.
اگر بارگیریهای خط لوله زمان نصب را فعال نکنید یا درخواست بارگیری صریح نکنید، خط لوله اولین باری که برچسبزن را اجرا میکنید بارگیری میشود. درخواستهایی که قبلاز تکمیل بارگیری انجام میدهید نتیجهای نخواهند داشت.
اگر میخواهید مدلی را بااستفاده از «فضای ذخیرهسازی ابری ویژه Firebase» بارگیری کنید، مطمئن شوید که Firebase را به پروژه Android خود اضافه کردهاید، اگر قبلاً این کار را نکردهاید. این کار هنگام دستهبندی مدل الزامی نیست.
۱. بار کردن مدل
میتوانید مدل را از منبع بستهبندیشده محلی یا منبع میزبانیشده از دور بار کنید.
پیکربندی منبع مدل محلی
برای دستهبندی مدل با برنامه:
فایل مدل (معمولاً با
.tfliteیا.liteتمام میشود) را در پوشهassets/برنامه کپی کنید. (ممکن است ابتدا لازم باشد پوشه را با کلیک راست روی پوشهapp/و سپس کلیک روی جدید > پوشه > پوشه داراییها ایجاد کنید.)شیء
LocalModelرا ایجاد کنید و مسیر فایل مدل را مشخص کنید:Kotlin
val localModel = LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute path to model file) // or .setUri(URI to model file) .build()
جاوا
LocalModel localModel = new LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute path to model file) // or .setUri(URI to model file) .build();
پیکربندی منبع مدل میزبانیشده از راه دور
برای استفاده از مدل میزبانیشده از دور، باید فایل مدل را بااستفاده از منطق برنامه خودتان در فضای ذخیرهسازی محلی دستگاه بارگیری کنید و سپس آن را بهعنوان مدل محلی بار کنید. توصیه میکنیم از Cloud Storage for Firebase برای میزبانی مدل استفاده کنید. برای جزئیات پیادهسازی، راهنمای انتقال Firebase ML به Cloud Storage را ببینید.
پیکربندی برچسبزن تصویر
پساز پیکربندی منابع مدل، یک شیء ImageLabeler از یکی از آنها ایجاد کنید.
گزینههای زیر دردسترس است:
| گزینهها | |
|---|---|
confidenceThreshold
|
حداقل امتیاز اطمینان برچسبهای شناساییشده. اگر تنظیم نشده باشد، از هر آستانه طبقهبندیکنندهای که در فراداده مدل مشخص شده است استفاده خواهد شد. اگر مدل هیچ فرادادهای نداشته باشد یا فراداده آستانه طبقهبندی را مشخص نکند، از آستانه پیشفرض ۰٫۰ استفاده خواهد شد. |
maxResultCount
|
حداکثر تعداد برچسبهایی که برگردانده میشود. اگر تنظیم نشده باشد، از مقدار پیشفرض ۱۰ استفاده خواهد شد. |
اگر فقط مدل دستهبندی محلی دارید، کافی است از
LocalModel شیء خود یک برچسبزن ایجاد کنید:
Kotlin
val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build() val labeler = ImageLabeling.getClient(customImageLabelerOptions)
جاوا
CustomImageLabelerOptions customImageLabelerOptions = new CustomImageLabelerOptions.Builder(localModel) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build(); ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);
اگر مدل از دور میزبانی میشود، باید بررسی کنید که قبلاز اجرا شدن بارگیری شده باشد.
اگرچه فقط باید این مورد را قبلاز اجرای برچسبزن تأیید کنید، اما اگر هم مدل از دور میزبانیشده و هم مدل بستهبندیشده محلی دارید، ممکن است منطقی باشد که این بررسی را هنگام نمونهسازی برچسبزن تصویر انجام دهید: اگر مدل از دور بارگیری شده است، برچسبزنی از مدل از دور ایجاد کنید، و درغیراینصورت از مدل محلی ایجاد کنید.
Kotlin
val modelFile = File(context.cacheDir, "my_downloaded_model.tflite") val model = if (modelFile.exists()) { // Use the downloaded model if available LocalModel.Builder().setAbsoluteFilePath(modelFile.absolutePath).build() } else { // Fall back to the bundled model LocalModel.Builder().setAssetFilePath("model.tflite").build() } val options = CustomImageLabelerOptions.Builder(model) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build() val labeler = ImageLabeling.getClient(options)
جاوا
File modelFile = new File(context.getCacheDir(), "my_downloaded_model.tflite"); LocalModel model; if (modelFile.exists()) { // Use the downloaded model if available model = new LocalModel.Builder().setAbsoluteFilePath(modelFile.getAbsolutePath()).build(); } else { // Fall back to the bundled model model = new LocalModel.Builder().setAssetFilePath("model.tflite").build(); } CustomImageLabelerOptions options = new CustomImageLabelerOptions.Builder(model) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build(); ImageLabeler labeler = ImageLabeling.getClient(options);
اگر فقط مدل میزبانی از دور دارید، باید عملکرد مربوط به مدل را غیرفعال کنید—برای مثال، بخشی از رابط کاربریتان را خاکستری کنید یا پنهان کنید—تا زمانی که تأیید کنید مدل بارگیری شده است.
Kotlin
val localFile = File(context.cacheDir, "my_remote_model.tflite") if (localFile.exists()) { initializeLabeler(localFile) } else { showLoadingUI() val storage = Firebase.storage val modelRef = storage.getReferenceFromUrl("gs://YOUR_BUCKET/path/to/model.tflite") modelRef.getFile(localFile) .addOnSuccessListener { hideLoadingUI() initializeLabeler(localFile) } .addOnFailureListener { showErrorUI() } } private fun initializeLabeler(modelFile: File) { val localModel = LocalModel.Builder().setAbsoluteFilePath(modelFile.absolutePath).build() val options = CustomImageLabelerOptions.Builder(localModel).build() val labeler = ImageLabeling.getClient(options) enableMLFeatures(labeler) }
جاوا
File localFile = new File(context.getCacheDir(), "my_remote_model.tflite"); if (localFile.exists()) { initializeLabeler(localFile); } else { showLoadingUI(); FirebaseStorage storage = FirebaseStorage.getInstance(); StorageReference modelRef = storage.getReferenceFromUrl("gs://YOUR_BUCKET/path/to/model.tflite"); modelRef.getFile(localFile) .addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() { @Override public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) { hideLoadingUI(); initializeLabeler(localFile); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception exception) { showErrorUI(); } }); } private void initializeLabeler(File modelFile) { LocalModel localModel = new LocalModel.Builder().setAbsoluteFilePath(modelFile.getAbsolutePath()).build(); CustomImageLabelerOptions options = new CustomImageLabelerOptions.Builder(localModel).build(); ImageLabeler labeler = ImageLabeling.getClient(options); enableMLFeatures(labeler); }
۲. آماده کردن تصویر ورودی
سپس، برای هر تصویری که میخواهید برچسبگذاری کنید، یک شیء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 // ... } } }
جاوا
private class YourAnalyzer implements ImageAnalysis.Analyzer { @Override public void analyze(ImageProxy imageProxy) { Image mediaImage = imageProxy.getImage(); if (mediaImage != null) { InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees()); // Pass image to an ML Kit Vision API // ... } } }
اگر از کتابخانه دوربینی استفاده نمیکنید که درجه چرخش تصویر را به شما ارائه دهد، میتوانید آن را از درجه چرخش دستگاه و جهت حسگر دوربین در دستگاه محاسبه کنید:
Kotlin
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 0) ORIENTATIONS.append(Surface.ROTATION_90, 90) ORIENTATIONS.append(Surface.ROTATION_180, 180) ORIENTATIONS.append(Surface.ROTATION_270, 270) } /** * 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, isFrontFacing: Boolean): 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) // Get the device's sensor orientation. val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! if (isFrontFacing) { rotationCompensation = (sensorOrientation + rotationCompensation) % 360 } else { // back-facing rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360 } return rotationCompensation }
جاوا
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 0); ORIENTATIONS.append(Surface.ROTATION_90, 90); ORIENTATIONS.append(Surface.ROTATION_180, 180); ORIENTATIONS.append(Surface.ROTATION_270, 270); } /** * 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, boolean isFrontFacing) 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); // Get the device's sensor orientation. CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); if (isFrontFacing) { rotationCompensation = (sensorOrientation + rotationCompensation) % 360; } else { // back-facing rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360; } return rotationCompensation; }
سپس، شیء media.Image و مقدار درجه چرخش را به InputImage.fromMediaImage() منتقل کنید:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
استفاده از نشانی وب فایل
برای ایجاد شیء InputImage از نشانی وب فایل، بافت برنامه و نشانی وب فایل را به 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 ) // Or: val image = InputImage.fromByteArray( byteArray, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 )
جاوا
InputImage image = InputImage.fromByteBuffer(byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 ); // Or: InputImage image = InputImage.fromByteArray( byteArray, /* image width */480, /* image height */360, rotation, 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 را به روش ImageLabeler's
process() منتقل کنید.
Kotlin
labeler.process(image) .addOnSuccessListener { labels -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
جاوا
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 // ... } });
۴. دریافت اطلاعات درباره نهادهای برچسبگذاریشده
اگر عملیات برچسبگذاری تصویر موفقیتآمیز باشد، فهرستی از اشیاءImageLabel
به شنونده موفقیت ارسال میشود. هر شیء ImageLabel نشاندهنده
چیزی است که در تصویر برچسبگذاری شده است. میتوانید شرح نوشتاری هر برچسب (درصورت دردسترس بودن در فراداده فایل مدل LiteRT)، امتیاز اطمینان، و نمایه را دریافت کنید. برای مثال:
Kotlin
for (label in labels) { val text = label.text val confidence = label.confidence val index = label.index }
جاوا
for (ImageLabel label : labels) { String text = label.getText(); float confidence = label.getConfidence(); int index = label.getIndex(); }
نکتههایی برای بهبود عملکرد همزمان
اگر میخواهید تصاویر را در یک برنامه همزمان برچسبگذاری کنید، این دستورالعملها را دنبال کنید تا به بهترین نرخ فریم دست یابید:
- اگر از
Cameraیاcamera2API استفاده میکنید، فراخوانیهای برچسبگذار تصویر را محدود کنید. اگر قاب ویدیو جدیدی درحین اجرای برچسبزن تصویر دردسترس قرار گرفت، قاب را رها کنید. برای نمونه، کلاسVisionProcessorBaseرا در برنامه نمونه شروع سریع ببینید. - اگر از
CameraXAPI استفاده میکنید، مطمئن شوید که استراتژی فشار برگشتی روی مقدار پیشفرض آنImageAnalysis.STRATEGY_KEEP_ONLY_LATESTتنظیم شده باشد. این کار تضمین میکند که در هر بار فقط یک تصویر برای تجزیهوتحلیل ارسال شود. اگر وقتی تجزیهگر مشغول است تصاویر بیشتری تولید شود، این تصاویر بهطور خودکار حذف میشوند و در صف تحویل قرار نمیگیرند. وقتی تصویر درحال تجزیهوتحلیل با فراخوانی ImageProxy.close() بسته شود، جدیدترین تصویر بعدی ارائه خواهد شد. - اگر از برونداد برچسبزن تصویر برای رونهادن گرافیک روی تصویر ورودی استفاده میکنید، ابتدا نتیجه را از ML Kit دریافت کنید، سپس تصویر و رونهاد را در یک مرحله پردازش کنید. این کار فقط یکبار برای هر قاب ورودی در سطح نمایش انجام میشود. برای نمونه، کلاسهای
CameraSourcePreviewوGraphicOverlayرا در برنامه نمونه راهاندازی سریع ببینید. - اگر از Camera2 API استفاده میکنید، تصاویر را در قالب
ImageFormat.YUV_420_888ضبط کنید. اگر از Camera API قدیمیتر استفاده میکنید، تصاویر را در قالبImageFormat.NV21ضبط کنید.