دو راه برای ادغام برچسبگذاری تصویر با مدلهای سفارشی وجود دارد: با ادغام خط لوله به عنوان بخشی از برنامه شما، یا با استفاده از یک خط لوله بدون دسته که به سرویسهای Google Play وابسته است. اگر خط لوله بدون دسته را انتخاب کنید، برنامه شما کوچکتر خواهد بود. برای جزئیات بیشتر به جدول زیر مراجعه کنید.
| بستهبندی شده | بدون دسته بندی | |
|---|---|---|
| نام کتابخانه | com.google.mlkit:image-labeling-custom | com.google.android.gms:play-services-mlkit-image-labeling-custom |
پیادهسازی | پایپلاین در زمان ساخت به صورت ایستا به برنامه شما متصل است. | Pipeline به صورت پویا با استفاده از سرویسهای Google Play دانلود میشود. |
| اندازه برنامه | حدود ۳.۸ مگابایت افزایش حجم. | حدود ۲۰۰ کیلوبایت افزایش حجم. |
| زمان اولیه سازی | خط لوله فوراً در دسترس است. | ممکن است لازم باشد قبل از اولین استفاده، منتظر دانلود شدن خط لوله باشید. |
| مرحله چرخه عمر API | دسترسی عمومی (GA) | بتا |
دو راه برای ادغام یک مدل سفارشی وجود دارد: مدل را با قرار دادن آن در پوشه asset برنامه خود، به صورت بستهای (bundle) درآورید، یا آن را به صورت پویا از Firebase دانلود کنید. جدول زیر این دو گزینه را با هم مقایسه میکند.
| مدل بستهای | مدل میزبانی شده |
|---|---|
| این مدل بخشی از APK برنامه شماست که باعث افزایش حجم آن میشود. | این مدل بخشی از APK شما نیست. با آپلود در فضای ابری میزبانی میشود. توصیه میکنیم از فضای ابری برای Firebase استفاده کنید. |
| این مدل بلافاصله در دسترس است، حتی زمانی که دستگاه اندروید آفلاین باشد | برنامه شما باید شامل کدی باشد که مدل را بر اساس تقاضا دانلود کند |
| نیازی به پروژه Firebase نیست | به یک پروژه Firebase نیاز دارد (در صورت استفاده از Cloud Storage برای Firebase). |
| برای بهروزرسانی مدل، باید برنامه خود را دوباره منتشر کنید | بهروزرسانیهای مدل را بدون انتشار مجدد برنامه خود، ارسال کنید |
| تست A/B داخلی ندارد | تست A/B با پیکربندی از راه دور Firebase |
امتحانش کن.
- برای مثالی از نحوهی استفاده از مدل همراه، به برنامهی شروع سریع vision و برای مثالی از نحوهی استفاده از مدل میزبانی شده ، به برنامهی شروع سریع automl مراجعه کنید.
قبل از اینکه شروع کنی
در فایل
build.gradle.ktsدر سطح پروژه، مطمئن شوید که مخزن Maven گوگل را هم در بخشهایbuildscriptو همallprojectsخود وارد کردهاید.وابستگیهای کتابخانههای اندروید ML Kit را به فایل gradle سطح برنامه ماژول خود که معمولاً
app/build.gradle.ktsاست، اضافه کنید. بر اساس نیاز خود، یکی از وابستگیهای زیر را انتخاب کنید:برای بستهبندی pipeline با برنامهتان:
dependencies { // ... // Use this dependency to bundle the pipeline with your app implementation("com.google.mlkit:image-labeling-custom:17.0.3") }برای استفاده از pipeline در سرویسهای گوگل پلی:
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") }اگر تصمیم دارید از pipeline در سرویسهای Google Play استفاده کنید ، میتوانید برنامه خود را طوری پیکربندی کنید که پس از نصب برنامه از Play Store، pipeline را به طور خودکار روی دستگاه دانلود کند. برای انجام این کار، اعلان زیر را به فایل
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>همچنین میتوانید به صراحت در دسترس بودن خط لوله را بررسی کرده و از طریق API ModuleInstallClient سرویسهای Google Play درخواست دانلود دهید.
اگر دانلودهای خط لولهای زمان نصب را فعال نکنید یا درخواست دانلود صریح ندهید، خط لوله در اولین باری که برچسبگذار را اجرا میکنید دانلود میشود. درخواستهایی که قبل از اتمام دانلود ارسال میکنید، هیچ نتیجهای ندارند.
اگر میخواهید مدلی را با استفاده از فضای ذخیرهسازی ابری برای فایربیس دانلود کنید ، اگر قبلاً فایربیس را به پروژه اندروید خود اضافه نکردهاید، حتماً آن را اضافه کنید . این کار هنگام بستهبندی مدل لازم نیست.
۱. مدل را بارگذاری کنید
میتوانید مدل را از یک منبع محلی یا یک منبع میزبانی شده از راه دور بارگذاری کنید.
پیکربندی یک منبع مدل محلی
برای اتصال مدل به برنامه خود:
فایل مدل (که معمولاً به
.tfliteیا.liteختم میشود) را در پوشهassets/برنامه خود کپی کنید. (ممکن است لازم باشد ابتدا با کلیک راست رویapp/پوشه و سپس کلیک روی New > Folder > Assets Folder ، پوشه را ایجاد کنید.)ایجاد شیء
LocalModel، با مشخص کردن مسیر فایل مدل:کاتلین
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();
پیکربندی یک منبع مدل میزبانی شده از راه دور
برای استفاده از مدل میزبانیشده از راه دور، باید فایل مدل را با استفاده از منطق برنامه خود در حافظه محلی دستگاه دانلود کنید و سپس آن را به عنوان یک مدل محلی بارگذاری کنید. توصیه میکنیم برای میزبانی یک مدل از فضای ذخیرهسازی ابری برای فایربیس استفاده کنید. برای جزئیات پیادهسازی، به راهنمای مهاجرت از فایربیس ML به فضای ذخیرهسازی ابری مراجعه کنید.
پیکربندی برچسبگذار تصویر
پس از پیکربندی منابع مدل خود، یک شیء ImageLabeler از یکی از آنها ایجاد کنید.
گزینههای زیر موجود است:
| گزینهها | |
|---|---|
confidenceThreshold | حداقل امتیاز اطمینان برچسبهای شناساییشده. در صورت عدم تنظیم، از هر آستانه طبقهبندیکنندهای که توسط فراداده مدل مشخص شده باشد، استفاده خواهد شد. اگر مدل حاوی هیچ فرادادهای نباشد یا فراداده آستانه طبقهبندیکنندهای را مشخص نکند، از آستانه پیشفرض ۰.۰ استفاده خواهد شد. |
maxResultCount | حداکثر تعداد برچسبهایی که باید برگردانده شوند. اگر تنظیم نشود، مقدار پیشفرض ۱۰ استفاده خواهد شد. |
اگر فقط یک مدلِ بستهبندیشدهی محلی دارید، کافیست یک برچسبگذار از شیء LocalModel خود ایجاد کنید:
کاتلین
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);
اگر مدلی دارید که از راه دور میزبانی میشود، باید قبل از اجرای آن، بررسی کنید که دانلود شده باشد.
اگرچه شما فقط باید قبل از اجرای برچسبگذار این موضوع را تأیید کنید، اما اگر هم یک مدل میزبانیشده از راه دور و هم یک مدل بستهبندیشده محلی دارید، انجام این بررسی هنگام نمونهسازی برچسبگذار تصویر منطقی است: اگر مدل از راه دور دانلود شده است، یک برچسبگذار از آن و در غیر این صورت از مدل محلی ایجاد کنید.
کاتلین
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);
اگر فقط یک مدل میزبانیشده از راه دور دارید، باید عملکردهای مرتبط با مدل را غیرفعال کنید - مثلاً بخشی از رابط کاربری خود را خاکستری کنید یا پنهان کنید - تا زمانی که تأیید کنید مدل دانلود شده است.
کاتلین
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 مقدار چرخش را برای شما محاسبه میکنند.
کاتلین
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 // ... } } }
اگر از کتابخانه دوربینی که درجه چرخش تصویر را به شما بدهد استفاده نمیکنید، میتوانید آن را از درجه چرخش دستگاه و جهت سنسور دوربین در دستگاه محاسبه کنید:
کاتلین
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() ارسال کنید:
کاتلین
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
استفاده از یک URI فایل
برای ایجاد یک شیء InputImage از یک URI فایل، متن برنامه و URI فایل را به InputImage.fromFilePath() ارسال کنید. این زمانی مفید است که از یک ACTION_GET_CONTENT برای وادار کردن کاربر به انتخاب یک تصویر از برنامه گالری خود استفاده میکنید.
کاتلین
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 را با بافر یا آرایه، به همراه ارتفاع، عرض، فرمت کدگذاری رنگ و درجه چرخش تصویر ایجاد کنید:
کاتلین
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 ، تعریف زیر را انجام دهید:
کاتلین
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
تصویر توسط یک شیء Bitmap به همراه درجه چرخش نمایش داده میشود.
۳. برچسبگذار تصویر را اجرا کنید
برای برچسبگذاری اشیاء در یک تصویر، شیء image را به متد process() در ImageLabeler ارسال کنید.
کاتلین
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)، امتیاز اطمینان و اندیس آن را دریافت کنید. برای مثال:کاتلین
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(); }
نکاتی برای بهبود عملکرد در زمان واقعی
اگر میخواهید تصاویر را در یک برنامهی بلادرنگ برچسبگذاری کنید، برای دستیابی به بهترین نرخ فریم، این دستورالعملها را دنبال کنید:
- اگر از API
Cameraیاcamera2استفاده میکنید، فراخوانیهای throttle به برچسبگذار تصویر را متوقف کنید. اگر در حین اجرای برچسبگذار تصویر، یک فریم ویدیویی جدید در دسترس قرار گرفت، فریم را رها کنید. برای مثال، به کلاسVisionProcessorBaseدر برنامه نمونه شروع سریع مراجعه کنید. - اگر از API
CameraXاستفاده میکنید، مطمئن شوید که استراتژی فشار معکوس (backpressure strategy) روی مقدار پیشفرض خود یعنیImageAnalysis.STRATEGY_KEEP_ONLY_LATESTتنظیم شده است. این تضمین میکند که فقط یک تصویر در هر زمان برای تجزیه و تحلیل تحویل داده میشود. اگر تصاویر بیشتری هنگام مشغول بودن تحلیلگر تولید شوند، به طور خودکار حذف میشوند و برای تحویل در صف قرار نمیگیرند. پس از بسته شدن تصویر در حال تجزیه و تحلیل با فراخوانی ImageProxy.close()، آخرین تصویر بعدی تحویل داده میشود. - اگر از خروجی برچسبگذار تصویر برای همپوشانی گرافیکها روی تصویر ورودی استفاده میکنید، ابتدا نتیجه را از ML Kit دریافت کنید، سپس تصویر و همپوشانی را در یک مرحله رندر کنید. این کار فقط یک بار برای هر فریم ورودی روی سطح نمایشگر رندر میشود. برای مثال به کلاسهای
CameraSourcePreviewوGraphicOverlayدر برنامه نمونه شروع سریع مراجعه کنید. - اگر از API دوربین ۲ استفاده میکنید، تصاویر را با فرمت
ImageFormat.YUV_420_888ضبط کنید. اگر از API دوربین قدیمیتر استفاده میکنید، تصاویر را با فرمتImageFormat.NV21ضبط کنید.