هک خودرو؛ چطوری با پردازش تصویر خودرویمان را هوشمند کنیم؟
در حال حاضر بیش از ۲۰ میلیون خودرو در جاده های ایران وجود داره و این تعداد در جهان بیش از یک میلیارد هست. آماری که باعث میشه امنیت یکپارچه، بخشی جداناپذیر از وسایل نقلیه بسیاری از تولیدکنندگان خودرو باشه و بسیاری از قوانین و استانداردها برای امنیت وسایل نقلیه وجود داشته باشه. با این حال هک خودرو عمری به اندازه ۱۵ سال داره؛ تغییر مدیریت موتور و تزریق سوخت، ارسال سیگنال های مخفی برای واحدهای کنترل الکترونیکی (Electronic Control Units یا ECUs) خودرو یا ارسال ترافیک جعلی برای سیستم مسیریابی آنلاین و تغییر مسیر خودرو در این سال ها اتفاق افتاده. جالبه بدونید به صورت غیر رسمی گروه هایی ادعای دسترسی غیرمجاز به سیستم ایمنی خودروها یا ارتباط از راه دور با خودروهای متصل رو داشتند.
از طرف دیگه، در سال های اخیر هوش مصنوعی با ورود به دنیای وسایل نقلیه، نمونه های موفقی از خودروهای خودران هوشمند رو به دنیا معرفی کرده. با مستقر شدن تعداد کافی از این خودروهای خودران، جان میلیون ها انسان از خطر مرگ حفظ میشه و به شکل چشم گیری سرعت گذر جهان به انرژی های پاک بیشتر خواهد شد.
در این پست قصد داریم با رویکرد آموزشی و مرحله به مرحله، سیستمی طراحی کنیم که پس از دریافت تصاویر دوربین تعبیه شده در جلوی خودرو و پردازش این تصاویر، تغییر مسیر ناگهانی راننده بر اثر خستگی یا خطای انسانی رو تشخیص داده و از طریق علائم جلو داشبورد داخل خودرو به راننده اخطار بده.
برای پیاده سازی این سیستم، از یک طرف تصاویر دوربین رو به لپ تاپ ارسال کرده و از طرف دیگه فرمان های کنترلی برای هشدار به راننده رو به سامانه مالتی پلکس خودرو ارسال میکنیم؛ یعنی تصاویر به صورت بلادرنگ (Real-time) پردازش شده و در صورت تغییر مسیر ناگهانی، فرمان هشدار به راننده داده میشه. از اونجاییکه برای ایجاد این سیستم و دسترسی به سیستم شبکه خودرو نیازمند کمی قهرمان بازی هستیم، توصیه میشه اگر دنبال دردسر نیستید این کار رو روی خودرو خودتون امتحان نکنید? .
در این ماجراجویی، خودرو “رانا” رو انتخاب کردیم. خودرویی که بر پایه پلتفرم ۲۰۶ صندوق دار و با کمی تغییر در ظاهر و چراغ های جلو و عقب ساخته شده. البته با نگاهی فنی تر به این خودرو، متوجه میشید که از تمامی تولیدات خودروساز، وسیله و قطعه ای در این خودرو وجود داره.
در بخش اول سعی میکنیم تصاویر گرفته شده از جلوی خودرو رو پردازش کرده و مسیر حرکت اصلی خودرو رو تشخیص بدیم و در بخش دوم با هک خودرو و متصل شدن به شبکه الکترونیکی داخل خودرو، فرمان های کنترلی لازم برای هشدار به راننده رو ارسال میکنیم.
پردازش تصاویر دریافتی
زمانیکه ما رانندگی می کنیم، با کمک چشمامون خطوط جاده رو به عنوان یک مرجع ثابت برای تصمیم گیری ها و هدایت خودرو در نظر می گیریم. این اولین چیزیه که در طراحی خودروهای خودران توسط الگوریتم های مختلف پیاده سازی می شه. در اینجا ما تصویری مشابه تصویر سمت راست رو دریافت کرده و با الگوریتم های پردازش تصویر، مسیر (لاین) ی که در حال حرکت در اون هستیم – مشابه تصویر سمت چپ – رو مشخص میکنیم. در این پست از ویدیو هایی که در هنگام رانندگی از جاده ها در روز و شب از داخل ماشین ضبط کردیم، استفاده میکنیم. این تصاویر به علت سرعت بالای خودرو، مناسب نبودن مکان دوربین و نور و محیط واقعی دارای نویز بسیار بالایی هستند.
طیف خاکستری و حذف نویز
در این پست برای پردازش تصاویر از OpenCV فریم ورک بینایی کامپیوتر – پردازش تصویر و زبان ++C استفاده خواهیم کرد. در اولین گام بخش بالا و پایین تصاویر که اطلاعات مهمی از جاده رو شامل نمیشند، حذف میکنیم. چون بیشتر الگوریتم ها از تفاوت تغییر رنگ (یا همان نور یا تاثیر گذاری یک شی در بخشی از تصویر و در اینجا خط کشی جاده) استفاده می کنند، در همین مرحله تصویر رو به طیف خاکستری تبدیل می کنیم؛ یعنی مقدار هر پیکسل در سه کانال رنگی تصویر (قرمز، سبز و آبی یا RGB) ورودی رو به یک مقدار بین ۰ تا ۲۵۵ تبدیل میکنیم.
۱۲۳۴۵۶۷۸۹ Mat3b img = imread(argv[1]); //Load image Rect roi(0, 420, img.cols, img.rows - 500); //Setup a rectangle to define region of interest Mat3b img_crop = img(roi); //Crop the full image to rectangle ROI Mat img_gray; cvtColor(img_crop, img_gray, CV_BGR2GRAY); // Convert image to gray // Show results imshow("Original", img); imshow("Crop", img_crop); imshow("Gray", img_gray);
یافتن خطوط خط کشی و جداسازی
قبل از تشخیص لبه ها، به روشنی باید چیزی که در تصویر به دنبال اون هستیم رو مشخص کنیم. خط کشی جاده ها همیشه سفید یا زرد هستند. با نگاه بیشتر به تصاویر گرفته شده، یک روش ساده برای جداسازی خطوط خط کشی سفید، استخراج پیکسل هایی است که مقدار بیشتری نسبت به بقیه پیکسل های مجاور یا درون تصویر دارند. بنابراین نقاطی رو انتخاب می کنیم که مقداری کمی بیشتر از چارک بالای مقدار همه پیکسل های تصویر دارند.
در ادامه چون رنگ زرد به آسانی قابل جداسازی در یک تصویر سه کاناله RGB نیست و ممکنه این مقادیر در حالت برداشت مقدار بر حسب چارک حذف بشند، فرمت نمایش تصویر رو به Hue, Saturation, Value یا HSV تبدبل میکنیم و نقاط نظیر با رنگ های زرد تصویر HSV رو در تصویر طیف خاکستری به عنوان نقاط مورد نظر استخراج می کنیم (برای یافتن طیف های رنگی می تونید از فتوشاپ یا این ابزار آنلاین استفاده کنید). نهایتا مجموع نقاط انتخاب شده سفید و زرد با اعمال یک فیلتر حذف نویز با میانگین گیری از پیکسل های همسایه، برای تحلیل های بعدی استفاده می شند.
۱۲۳۴۵۶۷۸۹ Mat mask_hsv_yellow, mask_white, img_mask; // Make target image by apply yellow and white mask Scalar m = mean(img_gray); cvtColor(img, mask_hsv_yellow, CV_BGR2HSV); inRange(img_crop, Scalar(20, 85, 85), Scalar(30, 255, 255), mask_hsv_yellow); inRange(img_gray, Scalar(m[0] + (255 - m[0]) / 3.5), Scalar(255), mask_white); bitwise_or(mask_white, mask_hsv_yellow, img_mask); GaussianBlur(img_mask, img_mask, cv::Size(5, 5), 0); imshow("Mask", img_mask);
تشخیص لبه های خطوط
خُب، بعد از اینکه خطوط اصلی رو در تصویر حفظ کردیم و مقادیر دیگر رو حذف کردیم، باید لبه های اصلی خط کشی ها رو تشخیص بدیم. برای اینکار ابتدا باید به این سوال پاسخ بدیم:
از نظر ریاضی در یک تصویر (یک ماتریس از مقادیر داده رنگی)، چه چیزی لبه رو تعریف می کنه؟
با نگاهی دقیق تر به داده های اطراف یک لبه، می تونیم به این نتیجه برسیم که لبه ها بخش هایی هستند که مقادیر پیکسل ها به سرعت تغییر می کنند. بنابراین تشخیص لبه، یافتن پیکسل هایی ای که در مقایسه با همسایه هاشون مقادیری به شدت متفاوت دارند.
خوشبختانه در حوزه پردازش تصویر، این مسئله قبلا حل شده است. الگوریتم آشکارساز لبه Canny به همین روش و با یافتن مقادیر گرادیان در یک آستانه مشخص در طول تصویر اینکار رو انجام میده. بنابراین نیازی نیست که به مقادیر رنگ تصاویر فکر کنیم، تنها تغییرات مقدار برای ما اهمیت داره و می تونیم با این الگوریتم لبه ها رو در تصاویر مرحله قبل پیدا کنیم.
۱۲۳۴۵۶۷۸۹۱۰۱۱۱۲۱۳۱۴۱۵ Mat detected_edges, img_mask; int const max_lowThreshold = 255; int lowThreshold = 210, ratio = 3, kernel_size = 3; char* window_name = "Edge Map"; // CannyThreshold: Trackbar callback - Canny thresholds input with a ratio 1:3 void CannyThreshold(int, void*){ blur(img_mask, detected_edges, Size(3, 3)); // Reduce noise with a kernel 3x3 Canny(detected_edges, detected_edges, lowThreshold, lowThreshold * ratio, kernel_size); // Canny detector imshow(window_name, detected_edges); // Using Canny's output as a mask, and display our result } detected_edges.create(img_mask.size(), img_mask.type()); // Create a window namedWindow(window_name); createTrackbar("Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold); // Create a Trackbar for user to enter threshold CannyThreshold(0, 0); // Show the image
تشخیص خطوط مسیر
تا به اینجای کار، پیکسل هایی که لبه های خطوط رو نشون میدند، مشخص کردیم. حالا باید با اتصال این پیکسل ها به هم خطوط مشخص کننده مسیر رو تشخیص بدیم. مثل مرحله قبل این مسئله هم با یک تئوری ریاضی قابل حل هست. بیایید دوباره نگاهی به لبه های یافته شده بیاندازیم:
با نگاه نزدیک به لبه ها، مشخصه که بهترین خطی که یک لبه رو نمایش میده، خطی است که بیشترین تعداد پیکسل از لبه رو هم شامل میشه (خط ب در تصویر بالا). یافتن این خط یک مسئله دشوار هست. چرا که ممکنه تعداد این خطوط خیلی زیاد باشه و نیاز هست تا تمام خط های ممکن بررسی بشند.
برای این منظور می تونیم از تبدیل هاف استفاده کنیم. در این تبدیل، ما تمام پیکسل های لبه رو به یک فرم نمایش ریاضی دیگه تبدیل میکنیم. بعد از تبدیل کامل، هر پیکسل در “فضای تصویر” به یک خط یا منحنی در “فضای هاف” تبدیل می شه؛ یعنی در فضای هاف هر خط به شکل یک نقطه در تصویر نمایش داده میشه.
بنابراین ما نیازی به حل مسئله یافتن خطی که از همه پیکسل های همسایه عبور کنه نداریم و تنها کافی است خطی رو پیدا کنیم که در فضای هاف هست و نهایتا این خط رو به فضای تصویر نگاشت کنیم.
۱۲۳۴۵۶۷۸۹ Mat img_hlines = img_crop.clone(); // Standard Hough Line Transform vector<Vec4i> lines; // will hold the results of the detection HoughLinesP(detected_edges, lines, 1, CV_PI / 180, 50, 30, 10); // runs the actual detection // Draw the lines for (size_t i = 0; i < lines.size(); i++) { Vec4i l = lines[i]; line(img_hlines, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 255, 255), 2, CV_AA); }
گروه بندی و تصمیم گیری
در مرحله آخر پردازش تصاویر، هدف ایجاد یک معیار برای تصمیم گیری است. اگر تا اینجا همه چیز درست پیاده سازی شده باشه، خطوطی خواهیم داشت که نماینده ای از خط کشی جاده برای مسیری هستند که در حال عبور از اون هستیم. ابتدا این خطوط رو به دو دسته خط کشی سمت راست و چپ تقسیم می کنیم. یک تفاوت آشکار بین دو گروه از خط ها، جهت شیب این خطوط است. اندازه شیب هر خط زاویه رو اندازه می گیره. خطوط افقی شیب صفر و خطوط عمودی شیب بینهایت دارند. پس شیب خط های یافته شده اندازه ای بین این دو مقدار دارند، چراکه اون ها از پایین تصویر به سمت مرکز، نسبت به افق زاویه دارند.
جهت شیب یک خط نشون میده که با حرکت از چپ به راست، آیا خط به بالا متمایل است یا پایین؟ خطوط با شیب منفی به سمت پایین و خطوط با شیب مثبت به بالا حرکت می کنند. این همان چیزی است که در سیستم مختصات عادی اتفاق می افته. در سیستم مختصاتی که ما از اون استفاده می کنیم، نقطه مرکز در گوشه بالا سمت چپ تصویر هست؛ یعنی در اینجا جهت شیب ما معکوس خواهد بود.
پس خط های سمت راست شیب مثبت دارند و به پایین حرکت می کنند. مشاهدات نشون میدند که خطوطی که شیب خیلی کمی دارند احتمالا خطوط خوبی برای یافتن خط تصمیم گیری نیستند. بنابراین سعی می کنیم از این بین خطوطی که شیبی بیشتر/کمتر از ۰٫۴ (+/-) دارند رو انتخاب کنیم.
۱۲۳۴۵۶۷۸۹۱۰۱۱۱۲۱۳۱۴۱۵۱۶۱۷۱۸۱۹۲۰۲۱۲۲۲۳۲۴۲۵۲۶۲۷۲۸۲۹۳۰۳۱۳۲۳۳bool less_left(const Vec4i& lhs, const Vec4i& rhs){ return lhs[0] < rhs[0]; } bool less_right(const Vec4i& lhs, const Vec4i& rhs) { return lhs[0] > rhs[0]; } vector<Vec4i> rightls, leftls; // Calculating the slope and group lines float slope = (float)(l[3] - l[1]) / (l[2] - l[0]); if (slope > 0.40) { rightls.push_back(l); }else if (slope < -0.40) { leftls.push_back(l); } // Find regions Point left_b, left_t, right_b, right_t; if (leftls.size() > 0) { auto lmmx = minmax_element(leftls.begin(), leftls.end(), less_left); left_b = Point(get<0>(lmmx)[0][0], get<0>(lmmx)[0][1]); left_t = Point(get<0>(lmmx)[0][2], get<0>(lmmx)[0][3]); } if (rightls.size() > 0) { auto rmmx = minmax_element(rightls.begin(), rightls.end(), less_right); right_t = Point(get<0>(rmmx)[0][0], get<0>(rmmx)[0][1]); right_b = Point(get<0>(rmmx)[0][2], get<0>(rmmx)[0][3]); } Mat poly = img_hlines.clone(); vector<Point> vertices{ left_b, left_t, right_t, right_b }; vector<vector<Point>> pts{ vertices }; fillPoly(poly, pts, Scalar(58, 190, 37, 0)); addWeighted(poly, 0.50, img_hlines, 0.50, 0, img_hlines); // Find reference line points by average top and bottom middle float rx = (((right_b.x - left_b.x) / 2 + left_b.x) + ((right_t.x - left_t.x) / 2 + left_t.x)) / 2, ry = img_hlines.rows; // Draw dashed reference line Point p1(rx, 0), p2(rx, ry); LineIterator itl(img_hlines, p1, p2, 8); for (int i = 0; i < itl.count; i++, itl++) { if (i % 5 != 0) { (*itl)[1] = 80; (*itl)[2] = 75; } }
خط تصمیم گیری که نماینده ای از انحراف خودرو از مسیر در حال حرکت هست، میانگینی از دو گروه است. برای این خط، مقدار میانگین بیشترین و کمترین x از هر گروه، مشخص کننده مقدار x خواهد بود.
آخرین مرحله در اینجا دریافت تصاویر از دوربین هست. کافیه تا هر فریم (Frame) از ویدیو به صورت جداگانه دریافت شده و تمام مراحل بالا برای اون تکرار بشه. البته در مورد ویدیو میشه با دقت بیشتری اشیا مختلف رو دنبال کرد یا هر فریم رو در بازه زمان و نسبت به بقیه فریم ها در نظر گرفت که در اینجا به علت طولانی شدن مطلب به اون نمی پردازیم. اگر علاقمندید که این تصاویر رو از طریق شبکه بین موبایل و لپ تاپ دریافت کنید هم می تونید این کدها رو بررسی کنید.
۱۲۳۴۵۶۷۸ VideoCapture cap(0); // open the camera if(!cap.isOpened()) return -1; // check if we successfully connected to camera for(;;) { Mat frame; cap >> frame; // get a new frame from camera and start processing // Our codes ... if(waitKey(30) >= 0) break; }
ما موفق شدیم! الان سیستمی داریم که می تونه مسیر خودرو رو تشخیص بده. کافیه با توجه به محل قرارگیری دوربین در جلو خودرو، در صورتیکه انحراف این خط بیشتر از میزان کالیبره شده بود؛ یعنی خودرو به صورت ناگهانی تغییر مسیر داد، فرمان های هشدار به راننده داده بشه.
اما منظور از فرمان هشدار چیه؟ در ادامه می خوایم به همین سوال پاسخ بدیم.
بدون شک کاندیدای اول برای ارتباط با سامانه های الکترونیکی خودرو پورت OBD-II هست. بعد از سال ۲۰۰۴ تمام تولیدکنندگان اروپایی برای کاهش کابل های داخلی خودرو موظف به قرار دادن این پورت در تولیدات خود شدند که یک رابط استاندارد برای دریافت اطلاعات مختلف (مثل ولتاژ باتری، دمای روغن، دور موتور و …) و عیب یابی (Diagnostics) خودرو است. در خودروهای جدیدتر، این پورت به عنوان یک یونیت (Unit) در باس کن (CAN bus) با دیگر بخش های داخلی خودرو در ارتباط هست. بنابراین می شه از طریق اون اطلاعات رد و بدل شده روی شبکه داخلی خودرو رو رصد کرد یا با ارسال برخی فرامین با یونیت های داخل خودرو ارتباط داشت. تمام نود (Node) ها از طریق دو رشته سیم با نام CAN high یا CANH و CAN low یا CANL که به این شبکه متصل شده اند با هم صحبت می کنند و فریم (Frame) های ارسالی همراه با یک ID عددی برای جداسازی هر فریم و حداکثر ۸ بایت داده ارسال می شوند. CAN از دیفرانسیل سیگنالینگ استفاده می کنه؛ یعنی به ازای هر افزایش مقدار سیگنال روی یکی از خط ها به همان میزان مقدار رو روی خط دیگه کاهش میده. از این روش در محیط های پر نویز برای افزایش تحمل خطای سیستم استفاده میشه.
تا اونجاییکه می دونم، اطلاعات آنلاین زیادی از اطلاعات CAN برای رانا روی اینترنت پیدا نمیشه. محتویات هر فریم به صورت عمومی ثبت نشدند و با توجه به کارخونه سازنده منحصر بفرد هستند. پس تصمیم گرفتم برخی از جزئیات دسترسی اطلاعات خودروها رو از طریق منبع کدهای این مطلب به صورت عمومی منتشر کنم.
خودروی رانای ما با موتور TU5 مجهز به شبکه مالتی پلکس با پنج نود FCM یا Front Control Module در قسمت جلوی راننده برای کنترل چراغ ها و محدوده جلو خودرو، ICN یا Instrument Cluster Node برای کنترل بخش های مختلف جلو آمپر و BCM یا Body Control Module در داشبورد برای مدیریت قفل مرکزی، درها و شیشه بالابر که تحت پروتکل CAN Low Speed با هم در ارتباط هستند. در این سیستم BCM دستوراتی که روی سیستم اهرم های پشت فرمان به صورت آنالوگ دریافت میکنه رو به صورت دیجیتال به دستورات شبکه تبدیل میکنه تا بقیه نودها اون ها رو دریافت کنند و این نود به عنوان رابط عیب یابی مستقیما با پورت OBD در ارتباط هست. همچنین در خودرو ما ECU ی موتور و یونیت ABS در یک شبکه با استاندارد CAN High Speed هستند.
ما می خوایم برای زمانیکه راننده از مسیر اصلی منحرف میشه یک هشدار ایجاد کنیم. با ساختاری که در بالا توضیح داده شد، چراغ های هشدار مربوط به راهنما در صفحه نمایش جلو داشبورد (جلو آمپر) یک انتخاب خوب هستند. پس سعی میکنیم با هک شبکه و ارسال فریم مربوط به روشن و خاموش شدن چراغ های راهنمای سمت راست و چپ در جلو داشبورد ، به راننده زمانیکه به صورت ناگهانی از مسیر خارج شد، اخطار بدیم. بنابراین اگر خودرو از مسیر اصلی منحرف بشه، راننده بدون اینکه راهنما زده باشه، چراغ راهنمای سمت مورد نظر رو روی صفحه نمایش میبنه.
طبق نقشه ها، اطلاعات دسته راهنما به صورت آنالوگ تحلیل میشه؛ یعنی این اهرم با تغییر سطح سیگنال حالت فعلی رو به BCM اطلاع میده. از طرفی میدونیم که اطلاعات چراغ های راهنما از BCM به ICN در شبکه ارسال میشه. چون پورت OBD خودرو ما به صورت مستقیم در شبکه حضور نداره و به BCM متصل هست، برای دسترسی به اطلاعات شبکه با وصل شدن به عنوان فرد میانی (man-in-the-middle) مقادیر فریم های داخل باس رو شنود می کنیم. با کمی جستجو در مستندات و نقشه ها، در میابیم که سیگنال های داده شبکه مورد نیاز ما از BCM قابل ارسال و دریافت هستند. پایه ۱ و ۲ کانکتور ۱۸ پایه سفید روی BCM به ترتیب مربوط به CANH و CANL هستند. نحوه دسترسی به BCM و محل کانکتور ۱۸ پایه در شکل زیر مشخص شده اند.
برای دسترسی به داده ها می تونیم از کانکتورهای T استفاده کنیم یا با خراش دادن بخشی از روکش سیم با سیم دیگری داده ها رو دریافت کنیم. چون زمانبندی و مقادیر داده فریم های ارسالی در شبکه نباید تحت تاثیر قرار بگیره، پس برای دریافت داده ها از سیم ها از روش T استفاده میکنیم. سطح سیگنال CAN بین ۱٫۲۵ تا ۳٫۷۵ ولت هست، برای خواندن این مقادیر در سطح دیجیتال، از فرستنده و گیرنده TJA1050 کمک گرفته و خروجی رو با MAX232 و FT2232 از طریق کابل USB دریافت می کنیم. اگر همه چیز تا به اینجا خوب پیش رفته باشه، داده های ارسالی از پورت USB با درایور مربوطه از طریق یک پورت سریال مجازی قابل دریافت هستند.
بعد از آماده سازی سخت افزاری برای دریافت داده ها، سعی میکنیم داده ها رو دریافت کنیم. برای این منظور برنامه ای می نویسیم که داده های پورت سریال رو بخونه و تحلیل کنه. طبق توضیحات بالا، فریم های CAN بر اساس استاندارد مشخصی تولید میشند. طبق استاندارد هر فریم با مقدار ۰X7F تموم میشه و فریم ها شامل ID منحصر بفرد هستند؛ بنابراین می تونیم هر فریم رو با مقدار انتهایی و ID اون ها شناسایی کرده و دیگر بخش های فریم رو جداسازی کنیم. هر نود در شبکه CAN داده های خودش رو در یک بازه زمانی در شبکه ارسال میکنه و در هر لحظه مقادیر زیادی داده روی شبکه وجود داره. این نکته هم حائز اهمیت هست که اگر به شبکه CAN پرسرعت متصل هستیم با معماری سخت افزاری که توضیح داده شد، چون سرعت سریال کمتر از میزان داده های دریافتی هست، ممکنه بخشی از داده ها از بین بره که باید این مورد رو هم توی برنامه در نظر بگیریم.
با این برنامه، داده های شبکه بلادرنگ قابل تشخیص و جداسازی هستند. در صورتیکه فریم جدیدی در شبکه ارسال بشه، در ستون سمت چپ مشخص میشه. حالا نوبت این رسیده که فریم های مربوط به دستور روشن شدن چراغ راهنمای سمت راست و چپ در صفحه نمایش جلو آمپر رو پیدا کنیم. این فریم ها زمانی به جلو آمپر ارسال میشند که دسته راهنما در کنار فرمان به بالا و پایین حرکت داده بشه. پس کافیه در حالتیکه برنامه در حال اجرا هست، اهرم رو به طرفین حرکت داده و فریم های مربوطه رو دریافت کنیم.
سیستم هوشمند ما با آخرین مرحله کامل میشه، این فریم ها رو به صورت جداگانه زمانیکه خودرو از مسیر اصلی منحرف شد، برای جلوآمپر ارسال میکنیم تا راننده از تغییر مسیر خودرو مطلع شود.
این مطلب به صورت کامل از طریق گیت هاب و مطلب قبلی از اینجا در دسترس هست. همچنین اگر علاقمند به موضوعات هوش مصنوعی، یادگیری ماشین و بینایی کامپیوتر هستید می تونید من رو در توییتر دنبال کنید.
از تمامی عزیزانی که در چند هفته پیاده سازی و نگارش این مطلب راهنمایی هاشون راهگشای من بود، صمیمانه تشکر میکنم. خوشحال میشم اگر مطلب رو پسندید با بقیه به اشتراک بذارید و ? کنید.
این ماجراجویی تموم شد. حالا نوبت شماست که سیستم هوشمند خودتون رو طراحی کنید و تجربیاتتون رو به اشتراک بذارید. به نظر شما چالش های ایجاد یک خودروی خودران هوشمند چیه؟