یادگیری ماشین در اندروید با ML Kit برای فایربیس — از صفر تا صد

۲۶ بازدید
آخرین به‌روزرسانی: ۱۷ دی ۱۳۹۹
زمان مطالعه: ۸ دقیقه
ML Kit

آن روزها که استفاده از قابلیت‌های یادگیری ماشین تنها روی کلود امکان داشت و این مسئله نیازمند توان محاسباتی بالا، سخت‌افزار پیشرفته و مواردی از این دست بود، گذشته است. امروزه دستگاه‌های موبایل بسیار قدرتمندتر شده‌اند و الگوریتم‌های ما نیز کارایی بالاتری یافته‌اند. همه این موارد منجر به این نتیجه شده است که بهره‌گیری از یادگیری ماشین روی دستگاه‌های همراه امکان یافته است و دیگر صرفاً یک نظریه عملی-تخیلی محسوب نمی‌شود. در این نوشته به بررسی عملی یک پروژه یادگیری ماشین در اندروید با استفاده از  ML Kit‌ برای «فایربیس» (Firebase) می‌پردازیم.

مقدمه

یادگیری ماشین امروزه روی دستگاه‌های گوناگون در همه جا استفاده می‌شود. نمونه‌هایی از این مسئله به شرح زیر هستند:

  • دستیار هوشمند: کورتانا، Siri و دستیار گوگل نمونه‌هایی از این دستیارها هستند. دستیار گوگل در کنفرانس IO امسال یک به‌روزرسانی عمده دریافت کرده است و عمده توجه بر روی افزایش ظرفیت‌های یادگیری ماشین آن روی دستگاه‌های همراه بوده است.
  • فیلترهای اسنپ‌چت: اسنپ‌چت از یادگیری ماشین برای تشخیص چهره‌های انسان و قرار دادن فیلترهای 3 بعدی مانند عینک، کلاه، و.. روی آن‌ها استفاده می‌کند.
  • پاسخ هوشمند: پاسخ هوشمند (Smart Replay) یک از قابلیت‌های بسیاری از دستگاه‌های امروزی است و در اپلیکیشن‌های چت مانند واتس اپ و غیره استفاده می‌شود؛ هدف آن ارائه پیغام‌های از پیش تعریف شده‌ای است که می‌توانید از آن‌ها را در پاسخ به پیام‌های دریافتی گوناگون خود بهره بگیرید.
  • گوگل لنز: با این که این سرویس همچنان در مراحل ابتدایی خود است، اما از مدل‌های یادگیری ماشین برای شناسایی و برچسب‌گذاری اشیا استفاده می‌کند.

این فهرست را می‌توان بسیار ادامه داد، بنابراین به این مقدار اکتفا می‌کنیم. در این مقاله به بررسی شیوه‌های استفاده از یادگیری ماشین در اپلیکیشن‌های اندروید و هوشمندتر ساختن آن‌ها می‌پردازیم.

ML Kit برای فایربیس چیست؟

ML Kit‌ برای فایربیس یک SDK موبایل است که گنجاندن ظرفیت‌های یادگیری ماشین در اپلیکیشن‌ها را برای توسعه‌دهندگان موبایل آسان‌تر ساخته است. این کیت شامل API های پیش‌ساخته زیر است:

  • بازشناسی متن: این API برای بازشناسی و استخراج متن از تصاویر استفاده می‌شود.
  • تشخیص چهره: برای تشخیص چهره و نشانه‌گذاری بخش‌های مختلف چهره همراه با مرزهای آن استفاده می‌شود.
  • تشخیص و ردگیری شیء: برای تشخیص، ردگیری و طبقه‌بندی اشیا در تصاویر دوربین و عکس‌های از پیش ثبت شده استفاده می‌شود.
  • برچسب‌گذاری تصویر: شناسایی اشیا، مکان‌ها، فعالیت‌ها، گونه‌های حیوانات و مواردی از این دست.
  • اسکن بارکد: برای اسکن و پردازش بارکدها استفاده می‌شود.
  • تشخیص مکان: برای شناسایی امکان معروف و شناخته شده در تصاویر به کار می‌رود.
  • شناسه زبان: برای تشخیص زبان یک متن استفاده می‌شود.
  • ترجمه روی دستگاه: متن نمایش داده شده روی یک دستگاه را از یک زبان به زبانی دیگر ترجمه می‌کند.
  • پاسخ هوشمند: پاسخ‌های متنی هوشمندانه‌ای بر مبنای پیام‌های قبلی تولید می‌کند.

ML Kit‌ در اصل و به زبان ساده، راهکاری است که با آن می‌توانید از پیچیدگی‌ها بهره‌گیری از یادگیری ماشین در اپلیکیشن‌های خود برای تلفن‌های هوشمند بکاهید.

چه چیزی خواهیم ساخت؟

ما در این مقاله از ظرفیت تشخیص چهره ML Kit‌ برای شناسایی چهره‌ها در یک تصویر استفاده می‌کنیم. بدین منظور تصاویر را از طریق دوربین می‌گیریم و اینترفیس را روی آن اجرا می‌کنیم.

برای استفاده از ML Kit‌ لازم است که یک حساب «فایربیس» (Firebase) داشته باشید. اگر چنین حسابی ندارید به این صفحه (+) مراجعه کنید و یک حساب باز کنید. توجه داشته باشید در زمان نگارش مقاله، این حساب برای دسترسی ایرانیان مسدود بوده است و برای دسترسی به آن نیاز به استفاده از پراکسی وجود دارد.

ایجاد یک پروژه روی فایربیس

زمانی که یک حساب کاربری روی فایربیس باز کردید، به این صفحه (+) بروید و روی add project کلیک کنید.

کیت ML

یک نام به پروژه خود بدهید و روی Create کلیک کنید. ممکن است چند ثانیه طول بکشد تا پروژه شما آماده شود. سپس در ادامه یک اپلیکیشن اندروید به پروژه خود اضافه می‌کنیم.

افزودن اپلیکیشن

اندروید استودیو را باز کنید و یک پروژه جدید با یک اکتیویتی خالی اضافه کنید. سپس روی Tools -> Firebase کلیک کنید. بدین ترتیب دستیار فایربیس در پنل سمت راست باز می‌شود. در فهرست گزینه‌ها، ML Kit را انتخاب کرده و روی گزینه زیر کلیک کنید:

Use ML Kit to detect faces in images and video

کیت ML

احتمالاً از شما درخواست می‌شود که اندروید استودیو خود را به یک اپلیکیشن در فایربیس وصل کنید. روی connect کلیک کرده و در پروژه فایربیس خود sign in کنید. زمانی که اندروید استودیو اجازه دسترسی به پروژه فایربیس را داد، از شما خواسته می‌شود که یک پروژه انتخاب کنید. پروژه‌ای را که در گام قبلی ایجاد کردید انتخاب کنید. پس از آن باید ML Kit را به اپلیکیشن خود اضافه کنید که یک فرایند تک کلیکی محسوب می‌شود. زمانی که کار اضافه کردن وابستگی‌ها به پایان رسید، آماده هستید که اپلیکیشن خود را ایجاد کنید.

پیکربندی AndroidManifest.xml

شما باید فایل AndroidManifest.xml خود را طوری ویرایش کنید که امکان یادگیری ماشین آفلاین در اپلیکیشن اندروید خود را داشته باشید. متا-تگ زیر را درست زیر تگ اپلیکیشن خود وارد کنید.

<meta-data
        android:name="com.google.firebase.ml.vision.DEPENDENCIES"
        android:value="face"/>

ضمناً قصد داریم یک قابلیت دوربین به اپلیکیشن خود اضافه کنیم و از این رو یک تگ permission در مانیفست اضافه می‌کنیم.

<uses-permission android:name="android.permission.CAMERA"/>

افزودن یک دوربین به اپلیکیشن

برای افزودن دوربین به اپلیکیشن باید CameraKit مربوط به WonderKiln را اضافه کنیم. برای استفاده از CameraKit وابستگی‌های زیر را در فایل build.gradle سطح اپلیکیشن اضافه می‌کنیم.

implementation 'com.camerakit:camerakit:1.0.0-beta3.11'
implementation 'com.camerakit:jpegkit:0.1.0'
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.31'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'

نکته: اگر می‌خواهید از کاتلین در اپلیکیشن اندروید خود استفاده کنید، باید وابستگی‌های کاتلین نیز در آن گنجانده شوند.

پروژه را Sync کنید تا وابستگی‌ها دانلود شوند. سپس یک CameraKitView در فایل لی‌آوت activity_main.xml اضافه می‌کنیم. به این منظور کد زیر را در فایل لی‌‌آوت اضافه کنید.

<com.camerakit.CameraKitView
        android:layout_above="@id/btn_detect"
        android:layout_width="match_parent"
        android:id="@+id/camera_view"
        android:layout_height="match_parent"
        android:adjustViewBounds="true"
        android:keepScreenOn="true"
        app:camera_flash="auto"
        app:camera_facing="back"
        app:camera_focus="continuous"
        app:camera_permissions="camera">
</com.camerakit.CameraKitView>

همچنین یک دکمه به زیر صفحه اضافه می‌کنیم تا به وسیله آن عکس بگیریم.

<Button android:layout_width="match_parent" android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/btn_detect"
        android:text="Detect"/>

پیکربندی دوربین

ما دوربین را در فایل MainActivity.kt مقداردهی می‌کنیم. به این منظور برخی متدهای چرخه عمر را override خواهیم کرد و درون این متدها، متدهای چرخه عمر را برای دوربین فراخوانی می‌کنیم:

override fun onResume() {
    super.onResume()
    camera_view.onResume()
}
override fun onPause() {
    super.onPause()
    camera_view.onPause()
}
override fun onStart() {
    super.onStart()
    camera_view.onStart()
}
override fun onStop() {
    super.onStop()
    camera_view.onStop()
}

از اندروید نسخه M به بعد باید مجوزهای زمان اجرا را نیز تقاضا کنیم. این وضعیت از سوی خود کتابخانه CameraKit مربوط به WonderKiln مدیریت می‌شود. به این منظور کافی است متد زیر را override کنیم:

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    camera_view.onRequestPermissionsResult(requestCode, permissions, grantResults)
}

عکس گرفتن

ما با فشردن دکمه یک عکس می‌گیریم و آن را به دتکتور ارسال می‌کنیم. سپس یک مدل یادگیری ماشین روی تصویر اعمال می‌کنیم تا چهره را شناسایی کنیم. یک onclicklistener روی دکمه و درون تابع onClick تنظیم می‌کنیم و درون آن تابع captureImage مربوط به ویوی Camera را فراخوانی می‌کنیم.

btn_detect.setOnClickListener {
            camera_view.captureImage { cameraKitView, byteArray ->
            }
        }

در ادامه مقدار byteArray دریافتی را در یک bitmap دیکود می‌کنیم و سپس bitmap را به یک اندازه معقول مقیاس‌بندی می‌نماییم. این اندازه اساساً باید به اندازه camera_view ما باشد.

btn_detect.setOnClickListener {
            camera_view.captureImage { cameraKitView, byteArray ->
                camera_view.onStop()
                alertDialog.show()
                var bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray?.size ?: 0)
                bitmap = Bitmap.createScaledBitmap(bitmap, camera_view?.width ?: 0, camera_view?.height ?: 0, false)
                runDetector(bitmap)
            }
            graphic_overlay.clear()
        }

اکنون زمان آن فرا رسیده است که دتکتور ما روی bitmap اجرا شود. به این منظور تابع جداگانه runDetector() اجرا می‌کنیم.

شروع تشخیص

این بخش اصلی اپلیکیشن ما است. ما یک دتکتور روی تصویر ورودی اجرا می‌کنیم. ابتدا یک FirebaseVisionImage از bitmap دریافتی تهیه می‌کنیم.

val image = FirebaseVisionImage.fromBitmap(bitmap)

سپس نوبت آن می‌رسد که برخی گزینه‌ها مانند performanceMode ،countourMode ،landmarkMode و غیره را اضافه کنیم.

val options = FirebaseVisionFaceDetectorOptions.Builder()

.build()

اکنون دتکتور خود را با استفاده از گزینه‌ها می‌سازیم.

val detector = FirebaseVision.getInstance()
    .getVisionFaceDetector(options)

در نهایت، نوبت آن می‌رسد که فرایند شناسایی را شروع کنیم. ما با استفاده از این دتکتور تابع detectInImage را فراخوانی کرده و تصویر را به آن ارسال می‌کنیم. در ادامه callback-هایی برای موفقیت یا شکست دریافت می‌کنیم.

طرز کار تابع runDetector به صورت زیر است:

private fun runDetector(bitmap: Bitmap) {
    val image = FirebaseVisionImage.fromBitmap(bitmap)
    val options = FirebaseVisionFaceDetectorOptions.Builder()
        .build()
    val detector = FirebaseVision.getInstance()
        .getVisionFaceDetector(options)
    detector.detectInImage(image)
        .addOnSuccessListener { faces ->
            processFaceResult(faces)
        }.addOnFailureListener {
            it.printStackTrace()
        }
}

ایجاد کادر پیرامونی

برای ایجاد یک کادر پیرامونی که چهره‌های شناسایی‌شده را احاطه کند، باید دو view ایجاد کنیم. ویوی اول یک ویوی رویی شفاف است که روی آن کادر را رسم می‌کنیم. سپس کادر واقعی را روی تصویر قرار می‌دهیم. برای این کار از عنصر Canvas استفاده شده است.

فایل GraphicOverlay.java به صورت زیر است:

public class GraphicOverlay extends View {
    private final Object mLock = new Object();
    private int mPreviewWidth;
    private float mWidthScaleFactor = 1.0f;
    private int mPreviewHeight;
    private float mHeightScaleFactor = 1.0f;
    private int mFacing = CameraSource.CAMERA_FACING_BACK;
    private Set<Graphic> mGraphics = new HashSet<>();
    /**
     * Base class for a custom graphics object to be rendered within the graphic overlay.  Subclass
     * this and implement the {@link Graphic#draw(Canvas)} method to define the
     * graphics element.  Add instances to the overlay using {@link GraphicOverlay#add(Graphic)}.
     */
    public static abstract class Graphic {
        private GraphicOverlay mOverlay;
        public Graphic(GraphicOverlay overlay) {
            mOverlay = overlay;
        }
        /**
         * Draw the graphic on the supplied canvas.  Drawing should use the following methods to
         * convert to view coordinates for the graphics that are drawn:
         * <ol>
         * <li>{@link Graphic#scaleX(float)} and {@link Graphic#scaleY(float)} adjust the size of
         * the supplied value from the preview scale to the view scale.</li>
         * <li>{@link Graphic#translateX(float)} and {@link Graphic#translateY(float)} adjust the
         * coordinate from the preview's coordinate system to the view coordinate system.</li>
         * </ol>
         *
         * @param canvas drawing canvas
         */
        public abstract void draw(Canvas canvas);
        /**
         * Adjusts a horizontal value of the supplied value from the preview scale to the view
         * scale.
         */
        public float scaleX(float horizontal) {
            return horizontal * mOverlay.mWidthScaleFactor;
        }
        /**
         * Adjusts a vertical value of the supplied value from the preview scale to the view scale.
         */
        public float scaleY(float vertical) {
            return vertical * mOverlay.mHeightScaleFactor;
        }
        /**
         * Adjusts the x coordinate from the preview's coordinate system to the view coordinate
         * system.
         */
        public float translateX(float x) {
            if (mOverlay.mFacing == CameraSource.CAMERA_FACING_FRONT) {
                return mOverlay.getWidth() - scaleX(x);
            } else {
                return scaleX(x);
            }
        }
        /**
         * Adjusts the y coordinate from the preview's coordinate system to the view coordinate
         * system.
         */
        public float translateY(float y) {
            return scaleY(y);
        }
        public void postInvalidate() {
            mOverlay.postInvalidate();
        }
    }
    public GraphicOverlay(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    /**
     * Removes all graphics from the overlay.
     */
    public void clear() {
        synchronized (mLock) {
            mGraphics.clear();
        }
        postInvalidate();
    }
    /**
     * Adds a graphic to the overlay.
     */
    public void add(Graphic graphic) {
        synchronized (mLock) {
            mGraphics.add(graphic);
        }
        postInvalidate();
    }
    /**
     * Removes a graphic from the overlay.
     */
    public void remove(Graphic graphic) {
        synchronized (mLock) {
            mGraphics.remove(graphic);
        }
        postInvalidate();
    }
    /**
     * Sets the camera attributes for size and facing direction, which informs how to transform
     * image coordinates later.
     */
    public void setCameraInfo(int previewWidth, int previewHeight, int facing) {
        synchronized (mLock) {
            mPreviewWidth = previewWidth;
            mPreviewHeight = previewHeight;
            mFacing = facing;
        }
        postInvalidate();
    }
    /**
     * Draws the overlay with its associated graphic objects.
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        synchronized (mLock) {
            if ((mPreviewWidth != 0) && (mPreviewHeight != 0)) {
                mWidthScaleFactor = (float) canvas.getWidth() / (float) mPreviewWidth;
                mHeightScaleFactor = (float) canvas.getHeight() / (float) mPreviewHeight;
            }
            for (Graphic graphic : mGraphics) {
                graphic.draw(canvas);
            }
        }
    }
}

کادر مستطیلی واقعی به صورت زیر است:

public class RectOverlay extends GraphicOverlay.Graphic {
    private int RECT_COLOR = Color.RED;
    private float strokeWidth = 4.0f;
    private Paint rectPaint;
    private Rect rect;
    private GraphicOverlay graphicOverlay;
    public RectOverlay(GraphicOverlay graphicOverlay, Rect rect) {
        super(graphicOverlay);
        this.graphicOverlay = graphicOverlay;
        this.rect = rect;
        rectPaint = new Paint();
        rectPaint.setColor(RECT_COLOR);
        rectPaint.setStyle(Paint.Style.STROKE);
        rectPaint.setStrokeWidth(strokeWidth);
        postInvalidate();
    }


    @Override
    public void draw(Canvas canvas) {
        RectF rectF = new RectF(rect);
        rectF.left = translateX(rectF.left);
        rectF.right = translateX(rectF.right);
        rectF.top = translateY(rectF.top);
        rectF.bottom = translateY(rectF.bottom);
        canvas.drawRect(rectF, rectPaint);
    }
}

اینک زمان آن رسیده که این ویوی graphicOverlay را در فایل لی‌آوت activity_main.xml خود اضافه کنیم.

<com.example.imagelove.GraphicOverlay
        android:id="@+id/graphic_overlay"
        android:layout_width="match_parent" android:layout_height="match_parent"/>

در نهایت فایل activity_main.xml به صورت زیر درمی‌آید:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent" android:layout_height="match_parent"
        tools:context=".MainActivity">
    <Button android:layout_width="match_parent" android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:id="@+id/btn_detect"
            android:text="Detect"/>
    <com.camerakit.CameraKitView
            android:layout_above="@id/btn_detect"
            android:layout_width="match_parent"
            android:id="@+id/camera_view"
            android:layout_height="match_parent"
            android:adjustViewBounds="true"
            android:keepScreenOn="true"
            app:camera_flash="auto"
            app:camera_facing="back"
            app:camera_focus="continuous"
            app:camera_permissions="camera">
    </com.camerakit.CameraKitView>
    <com.example.imagelove.GraphicOverlay
            android:id="@+id/graphic_overlay"
            android:layout_width="match_parent" android:layout_height="match_parent"/>
</RelativeLayout>

اینک زمان آن است که کادر پیرامونی چهره‌های شناسایی‌ شده را رسم کنیم. ما با استفاده از دتکتور چهره‌ها را در تابع ()processFaceResult دریافت کرده‌ایم.

در ادامه حلقه‌ای روی همه چهره‌ها تعریف کرده و یک کادر روی هر چهره به صورت زیر اضافه می‌کنیم:

private fun processFaceResult(faces: MutableList<FirebaseVisionFace>) {
    faces.forEach {
        val bounds = it.boundingBox
        val rectOverLay = RectOverlay(graphic_overlay, bounds)
        graphic_overlay.add(rectOverLay)
    }
    alertDialog.dismiss()
}

سخن پایانی

در نهایت اپلیکیشن ما چیزی مانند تصویر زیر خواهد بود:

شما می‌توانید سورس کد این اپلیکیشن را از این صفحه گیت‌هاب (+) دانلود کنید. روی این کد کار کنید و ایده‌های مختلفی که برای پیاده‌سازی یادگیری ماشین در اپلیکیشن‌های اندرویدی دارید امتحان نمایید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۵ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
proandroiddev

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *