اپلیکیشن چند پلتفرمی دسته بندی تصاویر با فلاتر و Fastai — از صفر تا صد

۱۶۱ بازدید
آخرین به‌روزرسانی: ۱۵ مهر ۱۴۰۲
زمان مطالعه: ۶ دقیقه
اپلیکیشن چند پلتفرمی دسته بندی تصاویر با فلاتر و Fastai — از صفر تا صد

در این مقاله به بررسی شیوه استفاده از یک API برای ساخت اپلیکیشن‌های موبایل چند پلتفرمی می‌پردازیم که از شبکه عصبی برای دسته‌بندی تصاویر استفاده می‌کند. در این اپلیکیشن دسته بندی تصاویر از مدلی آماده استفاده می‌کنیم، اما مطالبی که در این راهنما ارائه شده‌اند در مورد هر مدل دسته‌بندی تک برچسبی تصاویر کاربرد دارند.

این اپلیکیشن دو کامپوننت دارد که یکی API-یی است که خودمان ایجاد می‌کنیم و به عنوان یک وب اپلیکیشن عمل می‌کند و دیگری خود اپلیکیشن موبایل فلاتر است.

آماده‌سازی مراحل ساخت وب اپلیکیشن

  • ما از این ریپوی گیت‌هاب (+) استفاده خواهیم کرد. بنابراین آن را برای خودتان فورک کنید.
  • فایل pickle مدل خودتان (‎.pkl) را روی گوگل درایو آپلود کنید و لینک اشتراک فایل را کپی کنید.
  • به این وب‌سایت (+) بروید و لینک اشتراک را به یک لینک دانلود تبدیل کنید. این لینک را جایی ذخیره کنید چون در ادامه به آن نیاز پیدا خواهیم کرد.

ساخت وب اپلیکیشن

در زمان ساخت وب اپلیکیشن به طور عمده روی فایل server.py در ریپازیتوری که فورک کرده‌ایم تمرکز می‌کنیم. در واقع کد server.py قلب اپلیکیشن ما محسوب می‌شود.

فایل server.py

1import aiohttp
2import asyncio
3import uvicorn
4from fastai import *
5from fastai.vision import *
6from io import BytesIO
7from starlette.applications import Starlette
8from starlette.middleware.cors import CORSMiddleware
9from starlette.responses import HTMLResponse, JSONResponse
10from starlette.staticfiles import StaticFiles
11
12
13export_file_url = 'DOWNLOAD_URL' #This will be the download url for your export.pkl file that you have already saved.
14export_file_name = 'export.pkl'
15
16classes = ['sweep', 'coverdrive', 'straightdrive', 'helicopter', 'scoop', 'pull'] #Replace these classes with the labels that your model identifies.
17path = Path(__file__).parent
18
19app = Starlette()
20app.add_middleware(CORSMiddleware, allow_origins=['*'], allow_headers=['X-Requested-With', 'Content-Type'])
21app.mount('/static', StaticFiles(directory='app/static'))
22
23
24async def download_file(url, dest):
25    if dest.exists(): return
26    async with aiohttp.ClientSession() as session:
27        async with session.get(url) as response:
28            data = await response.read()
29            with open(dest, 'wb') as f:
30                f.write(data)
31
32
33async def setup_learner():
34    await download_file(export_file_url, path / export_file_name)
35    try:
36        learn = load_learner(path, export_file_name)
37        return learn
38    except RuntimeError as e:
39        if len(e.args) > 0 and 'CPU-only machine' in e.args[0]:
40            print(e)
41            message = "\n\nThis model was trained with an old version of fastai and will not work in a CPU environment.\n\nPlease update the fastai library in your training environment and export your model again.\n\nSee instructions for 'Returning to work' at https://course.fast.ai."
42            raise RuntimeError(message)
43        else:
44            raise
45
46
47loop = asyncio.get_event_loop()
48tasks = [asyncio.ensure_future(setup_learner())]
49learn = loop.run_until_complete(asyncio.gather(*tasks))[0]
50loop.close()
51
52
53@app.route('/')
54async def homepage(request):
55    html_file = path / 'view' / 'index.html'
56    return HTMLResponse(html_file.open().read())
57
58
59@app.route('/analyze', methods=['POST'])
60async def analyze(request):
61    img_data = await request.form()
62    img_bytes = await (img_data['file'].read())
63    img = open_image(BytesIO(img_bytes))
64    prediction = learn.predict(img)[0]
65    return JSONResponse({'result': str(prediction)})
66
67
68
69if __name__ == '__main__':
70    if 'serve' in sys.argv:
71        uvicorn.run(app=app, host='0.0.0.0', port=5000, log_level="info")

خطوطی که برای ما مهم هستند، این موارد هستند:

1export_file_url = 'DOWNLOAD_URL'#Download_url for the .pkl file
2export_file_name = 'export.pkl'
3classes = ['sweep', 'coverdrive', 'straightdrive', 'helicopter', 'scoop', 'pull']

در این خطوط در محل DOWNLOAD_URL لینک دانلود فایل export.pkl را درج کرده و کلاس‌ها را با برچسب‌های مدل خودتان عوض کنید. اینک اپلیکیشن آماده استفاده است.

با این حال همچنان برخی موارد زیبایی‌شناختی وجود دارند که باید تغییر یابند. در پوشه view فایل index.html را ویرایش کنید تا مدلتان و کاری که مدل انجام می‌دهد را نمایش دهد.

کدهای دیگر چه کاری انجام می‌دهند؟

اینک که مطمئن شدیم اپلیکیشن کار می‌کند، پیش از توضیح شیوه توزیع آن بهتر است با طرز کار کدهای دیگر نیز آشنا شویم. ما کد export_file و کلاس‌ها را قبلاً توضیح دادیم و ایمپورت‌ها نیز تا حدود زیادی گویا هستند. بنابراین توضیح خود را از بلوک کد زیر آغاز می‌کنیم:

1app = Starlette()
2app.add_middleware(CORSMiddleware, allow_origins=['*'], allow_headers=['X-Requested-With', 'Content-Type'])
3app.mount('/static', StaticFiles(directory='app/static'))
4
5
6async def download_file(url, dest):
7    if dest.exists(): return
8    async with aiohttp.ClientSession() as session:
9        async with session.get(url) as response:
10            data = await response.read()
11            with open(dest, 'wb') as f:
12                f.write(data)
13
14
15async def setup_learner():
16    await download_file(export_file_url, path / export_file_name)
17    try:
18        learn = load_learner(path, export_file_name)
19        return learn
20    except RuntimeError as e:
21        if len(e.args) > 0 and 'CPU-only machine' in e.args[0]:
22            print(e)
23            message = "\n\nThis model was trained with an old version of fastai and will not work in a CPU environment.\n\nPlease update the fastai library in your training environment and export your model again.\n\nSee instructions for 'Returning to work' at https://course.fast.ai."
24            raise RuntimeError(message)
25        else:
26            raise

با این که کد فوق کاملاً پیچیده به نظر می‌رسد، اما در واقع کاملاً ساده است. ابتدا اپلیکیشن مقداردهی می‌شود، سپس پوشه استاتیک ایجاد می‌شود تا همه کدهای CSS و جاوا اسکریپت وب اپلیکیشن دریافت شود و در نهایت دو تابع تعریف می‌شوند. هر یک از این دو تابع به طور کلی برای وب اپلیکیشن حائز اهمیت بالایی هستند. تابع نخست، download_file، برای دانلود مدل یادگیری ماشین استفاده می‌شود و تابع دوم setup_learner مدل یادگیری ماشین ایجاد شده را راه‌اندازی می‌کند.

اینک به سراغ بلوک کد بعدی می‌رویم:

1loop = asyncio.get_event_loop()
2tasks = [asyncio.ensure_future(setup_learner())]
3learn = loop.run_until_complete(asyncio.gather(*tasks))[0]
4loop.close()
5
6
7@app.route('/')
8async def homepage(request):
9    html_file = path / 'view' / 'index.html'
10    return HTMLResponse(html_file.open().read())
11
12
13@app.route('/analyze', methods=['POST'])
14async def analyze(request):
15    img_data = await request.form()
16    img_bytes = await (img_data['file'].read())
17    img = open_image(BytesIO(img_bytes))
18    prediction = learn.predict(img)[0]
19    return JSONResponse({'result': str(prediction)})

این بلوک کد دو مسیر (route) برای اپلیکیشن تعریف می‌کند. یکی از آن‌ها homepage است که فایل index.html را به صورت یک پاسخ HTML بازگشت می‌دهد. مسیر بعدی ‎/analyze بسیار مهم است چون همه کارکردهای اپلیکیشن در این مسیر اتفاق می‌افتند. کار این مسیر به دست آوردن تصویر از طریق فرم موجود در فایل index.html، به‌کارگیری مدل روی تصویر و ذخیره پیش‌بینی و در نهایت بازگشت پیش‌بینی به صورت یک پاسخ JSON است. بدین ترتیب کل کار در زمان توزیع اپلیکیشن موبایل آسان‌تر می‌شود.

توزیع وب اپلیکیشن

اکنون ما نمی‌توانیم از API ایجاد شده استفاده کنیم، مگر اینکه آن را توزیع کنیم. پس این کار را انجام می‌دهیم.

  • به وب‌سایت render.com بروید و با اطلاعات حساب گیت‌هاب خود وارد شوید.
  • یک سرویس وب جدید ایجاد کرده و از آن ریپازیتوری که در سراسر این راهنما بهره گرفتیم استفاده کنید.
  • داکر را به عنوان محیط انتخاب کرده و نامی برای سرویس خود وارد نمایید.
  • روی save web service کلیک کنید.

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

آماده‌سازی برای ساخت اپلیکیشن فلاتر

در این بخش به موارد زیر نیاز داریم:

  • اندروید استودیو
  • SDK فلاتر
  • AVD Manager (همراه با اندروید استودیو نصب می‌شود)

تنظیم وابستگی‌ها

اندروید استودیو را باز کنید و یک پروژه فلاتر جدید را آغاز نمایید:

اپلیکیشن دسته بندی تصاویر

اکنون فایل pubspec.yaml را باز کرده و بخش dependencies را به صورت زیر ویرایش کنید:

dependencies:
  flutter:
    sdk: flutter
  image_picker:
  http:

سپس ترمینال را باز کرده و دستور زیر را اجرا کنید:

flutter pub get

بدین ترتیب مطمئن می‌شویم که پکیج‌های ضروری نصب شده‌اند و این که کد با خطای ایمپورت مواجه نمی‌شود.

ساخت اپلیکیشن

برای ساخت اپلیکیشن باید فایل main.dart را ویرایش کنیم. کد موجود در main.dart را با کد زیر عوض کنید:

1import 'dart:convert';
2import 'package:image_picker/image_picker.dart';
3import 'package:flutter/material.dart';
4import 'package:http/http.dart' as http;
5import 'dart:io';
6import 'package:path/path.dart';
7import 'package:async/async.dart';
8
9String txt = "";
10String txt1 = "Upload or take an image to figure out what type of cricket shot it is";
11void main() {
12  runApp(new MaterialApp(
13    debugShowCheckedModeBanner: false,
14    title: "Cricket Shot Classifier",
15    home: new MyApp(),
16  ));
17}
18
19class MyApp extends StatefulWidget {
20  @override
21  _MyAppState createState() => _MyAppState();
22}
23
24class _MyAppState extends State<MyApp> {
25  File img;
26
27  // The fuction which will upload the image as a file
28  void upload(File imageFile) async {
29    var stream =
30    new http.ByteStream(DelegatingStream.typed(imageFile.openRead()));
31    var length = await imageFile.length();
32
33    String base =
34        "https://types-of-cricket-shots.onrender.com";
35
36    var uri = Uri.parse(base + '/analyze');
37
38    var request = new http.MultipartRequest("POST", uri);
39    var multipartFile = new http.MultipartFile('file', stream, length,
40        filename: basename(imageFile.path));
41    //contentType: new MediaType('image', 'png'));
42
43    request.files.add(multipartFile);
44    var response = await request.send();
45    print(response.statusCode);
46    response.stream.transform(utf8.decoder).listen((value) {
47      print(value);
48      int l = value.length;
49      txt = value;
50
51      setState(() {});
52    });
53  }
54
55
56  void image_picker(int a) async {
57    txt1 = "";
58    setState(() {
59
60    });
61    debugPrint("Image Picker Activated");
62    if (a == 0){
63      img = await ImagePicker.pickImage(source: ImageSource.camera);
64    }
65    else{
66      img = await ImagePicker.pickImage(source: ImageSource.gallery);
67    }
68
69    txt = "Analysing...";
70    debugPrint(img.toString());
71    upload(img);
72    setState(() {});
73  }
74
75
76
77  @override
78  Widget build(BuildContext context) {
79    return Scaffold(
80      appBar: new AppBar(
81        centerTitle: true,
82        title: new Text("Cricket Shot Classifier"),
83      ),
84      body: new Container(
85        child: Center(
86          child: Column(
87            children: <Widget>[
88              img == null
89                  ? new Text(
90                txt1,
91                style: TextStyle(
92                  fontWeight: FontWeight.bold,
93                  fontSize: 32.0,
94                ),
95              )
96                  : new Image.file(img,
97                  height: MediaQuery.of(context).size.height * 0.6,
98                  width: MediaQuery.of(context).size.width * 0.8),
99              new Text(
100                txt,
101                style: TextStyle(
102                  fontWeight: FontWeight.bold,
103                  fontSize: 32.0,
104                ),
105              )
106            ],
107          ),
108        ),
109      ),
110    floatingActionButton: new Stack(
111      children: <Widget>[
112        Align(
113            alignment: Alignment(1.0, 1.0),
114            child: new FloatingActionButton(
115            onPressed: (){
116              image_picker(0);
117            },
118            child: new Icon(Icons.camera_alt),
119          )
120        ),
121        Align(
122          alignment: Alignment(1.0, 0.8),
123          child: new FloatingActionButton(
124              onPressed: (){
125                image_picker(1);
126              },
127              child: new Icon(Icons.file_upload)
128          )
129        ),
130      ],
131    ),
132    );
133  }
134}

URL مبنا را با لینک سرویس Render که قبلاً ایجاد کردیم عوض کنید و بقیه بخش‌های اپلیکیشن را بنا به دلخواه خود سفارشی‌سازی کنید.

کد فوق را روی دستگاه مجازی اندرویدی اجرا کنید تا کارکرد آن را ببینید. کد روی دستگاه ما به صورت زیر اجرا شده است که البته بدیهی است لازم است متن ویرایش شود تا ظاهر بهتری داشته باشد:

اپلیکیشن دسته بندی تصاویر

بدین ترتیب کار ما به پایان می‌رسید. ما موفق شدیم یک اپلیکیشن موبایل چند پلتفرمی بسازیم که به دسته‌بندی تصاویر می‌پردازد.

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

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
better-programming
نظر شما چیست؟

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