اپلیکیشن چند پلتفرمی دسته بندی تصاویر با فلاتر و 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 که قبلاً ایجاد کردیم عوض کنید و بقیه بخشهای اپلیکیشن را بنا به دلخواه خود سفارشیسازی کنید.
کد فوق را روی دستگاه مجازی اندرویدی اجرا کنید تا کارکرد آن را ببینید. کد روی دستگاه ما به صورت زیر اجرا شده است که البته بدیهی است لازم است متن ویرایش شود تا ظاهر بهتری داشته باشد:
بدین ترتیب کار ما به پایان میرسید. ما موفق شدیم یک اپلیکیشن موبایل چند پلتفرمی بسازیم که به دستهبندی تصاویر میپردازد.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی اندروید
- مجموعه آموزشهای برنامهنویسی
- قابلیت چند زبانی در فلاتر — به زبان ساده
- پیادهسازی تکمیل خودکار فهرست جستجو در فلاتر — از صفر تا صد
- آموزش گوگل فلاتر (Flutter ): ساخت اپلیکیشن دستورهای آشپزی — (بخش اول)
==