NB این جدید است و هنوز در حال آزمایش است.
این طرح نمونهای از پردازش دادههای سنسور برد اولتراسونیک با استفاده از ترکیبی از فیلترهای سیگنال و درخت طبقهبندی را نشان میدهد. کد طبقهبندیکننده با استفاده از اسکریپت Python classify_gen.py روی دادههای آموزشی ثبتشده و برچسبگذاریشده ایجاد شد. درخت طبقه بندی زیربنایی به طور خودکار با استفاده از کتابخانه scikit-learn پایتون تولید شد. برای جزئیات بیشتر در مورد فیلتر کردن، لطفاً به FilterDemos Arduino Sketch مراجعه کنید.
هدف طبقهبندیکننده طبقهبندی یک نقطه داده چند بعدی به یک عدد صحیح است که عضویت را در یک مجموعه مجزا از طبقهبندیها نشان میدهد. در مدل نمونه، داده ها برای وضوح نمودار نتیجه دو بعدی هستند. نقاط داده را می توان با گنجاندن چندین نمونه در طول زمان یا سایر کانال های حسگر به ابعاد بالاتر گسترش داد.
چند مرحله برای استفاده از این رویکرد در سیستم شما وجود دارد.
- تصمیم بگیرید که چگونه شرایط فیزیکی متفاوتی را ایجاد کنید که دستهبندی معنیداری از دادهها را تولید کند.
- تصمیم بگیرید که چه ترکیبی از ورودی های حسگر و سیگنال های پردازش شده ممکن است دسته ها را ابهام بخشد. این تعریف هر نقطه داده را تشکیل می دهد.
- طرحی را با بخش نمونهبرداری و فیلتر کردن دادههای سیستم خود به عنوان ابزاری برای ضبط دادههای دنیای واقعی تنظیم کنید. مثال از واحدهای عدد صحیح برای کارایی استفاده می کند. ممکن است بخواهید داده های خود را برای افزایش دقت اعداد صحیح پیش مقیاس کنید، یا ممکن است تصمیم بگیرید که مقادیر شناور را فعال کنید.
- اگر سیستم شما بتواند چند ورودی اضافی کاربر را پشتیبانی کند، فرآیند جمعآوری دادهها آسانتر خواهد بود اگر دادهها در حین جمعآوری برچسبگذاری شوند. به عنوان مثال، افزودن یک دکمه «Record» و برخی دکمههای دستهبندی میتواند از انتشار دادههای برچسبدار مستقیماً از آردوینو پشتیبانی کند. (این کار در کد نمونه زیر انجام نشده است).
- داده ها را از سیستم واقعی تحت شرایط مختلف ثبت کنید.
- داده ها را در صورت نیاز برش دهید تا گذراهای راه اندازی جعلی یا سایر ورودی های مخدوش کننده حذف شوند.
- در صورت نیاز، هر نمونه داده را برچسب گذاری کنید و در یک فایل آموزشی ادغام کنید. برای مثال، label_and_merge.py را ببینید.
- برای پردازش فایل داده آموزشی به کد، classify_gen.py را اجرا کنید.
- برای داده های دوبعدی، خروجی نمودار را به عنوان یک بررسی سالم بررسی کنید. ممکن است بخواهید پارامترهای مدلسازی را تنظیم کنید یا مجموعه دادههای خود را تنظیم کرده و مدل را بازسازی کنید.
- کد طبقه بندی نهایی را در طرح خود بگنجانید.
- تصمیم بگیرید که آیا خروجی طبقهبندیکننده به پردازش اضافی نیاز دارد، مثلاً برای حذف گذرای جعلی.
فایلهای طرح ممکن است در یک فایل بایگانی بهعنوان ClassifierDemo.zip دانلود شوند یا به صورت خام در پوشه منبع مرور شوند. فایل های فردی در زیر مستند شده است.
نمونه مدل
مدل نمونه با ثبت داده های فیلتر شده تولید شده توسط این طرح تحت چهار شرایط فیزیکی مختلف ساخته شد. فایلهای جداگانه به صورت دستی بریده شدند، سپس با استفاده از label_and_merge.py برچسبگذاری و ترکیب شدند.
این مثال خاص تا حدودی ساختگی است، زیرا میتوان پس از بررسی دادهها، یک طبقهبندیکننده دو بعدی معقول ساخت. اما این می تواند در ابعاد بالاتر بسیار سخت تر باشد، به عنوان مثال، اگر هر نقطه داده برای شامل چند نمونه از تاریخ گسترش یابد.
درخت طبقه بندی کننده باینری که روی داده های آموزشی ترسیم شده است. هر نقطه نشان دهنده یک نمونه از موقعیت و سرعت است که توسط فیلترهای صاف و برازش محاسبه شده است. هر رنگ نشان دهنده یک کلاس برچسب گذاری شده است: آبی برای "نزدیک"، قرمز برای "دور"، نارنجی برای "پسرفت"، سبز برای "نزدیک شدن". خطوط سیاه نشان دهنده خطوط تقسیم باینری هستند که مناطق نمونه را تقسیم می کنند. هر خط تقسیم مربوط به بلوک if/else در کد طبقه بندی کننده است.
تست زنده طبقه بندی کننده. خط آبی تخمین موقعیت فیلتر شده را نشان میدهد، خط قرمز سرعت، و خط سبز خروجی برگشتخورده طبقهبندیکننده را نشان میدهد. توالی حالت زیر دو بار مشاهده می شود: نزدیک، عقب نشینی، دور، نزدیک شدن. طرح از پلاتر سریال Arduino IDE گرفته شده است.
فایل های مرتبط:
- label_and_merge.py
- training.csv
- classify_gen.py
طرح اصلی
فایل طرح اصلی ClassifierDemo.ino است. این شامل یک حلقه رویداد برای نمونه برداری از سنسور فاصله یاب سونار، فیلتر کردن و تطبیق سیگنال برد برای تخمین موقعیت و سرعت، سپس طبقه بندی وضعیت است. خروجی به شکلی مناسب برای رسم بلادرنگ با استفاده از پلاتر سریال IDE چاپ می شود.
// ClassifierDemo.ino : Arduino program to demonstrate application of a decision tree. // No copyright, 2020, Garth Zeglin. This file is explicitly placed in the public domain. // The decision tree function and is kept in a separate .ino files which will // automatically be compiled with this one by the Arduino IDE. The tree code // was generated from data using classify_gen.py. // The baud rate is the number of bits per second transmitted over the serial port. const long BAUD_RATE = 115200; //================================================================ // Hardware definitions. You will need to customize this for your specific hardware. const int sonarTriggerPin = 7; // Specify a pin for a sonar trigger output. const int sonarEchoPin = 8; // Specify a pin for a sonar echo input. //================================================================ // Standard Arduino initialization function to configure the system. void setup() { // initialize the Serial port Serial.begin( BAUD_RATE ); // Initialize the digital input/output pins. pinMode(sonarTriggerPin, OUTPUT); pinMode(sonarEchoPin, INPUT); } //================================================================ // Standard Arduino polling function. This function is called repeatedly to // handle all I/O and periodic processing. This loop should never be allowed to // stall or block so that all tasks can be constantly serviced. void loop() { // Calculate the interval in microseconds since the last polling cycle. static unsigned long last_time = 0; unsigned long now = micros(); unsigned long interval = now - last_time; last_time = now; // Poll the sonar at regular intervals. static long sonar_timer = 0; sonar_timer -= interval; if (sonar_timer < 0) { sonar_timer += 100000; // 10 Hz sampling rate // read the sonar; zeros represent a no-ping condition int raw_ping = ping_sonar(); // suppress zeros in the input, just repeating the last input int nz_ping = suppress_value(raw_ping, 0); // convert the value from microseconds to centimeters float cm = fmap(nz_ping, 0.0, 5900.0, 0.0, 100.0); // apply a low-pass filter to smooth the raw data cm = lowpass(cm); // fit a trajectory curve to recent sample history float traj[3]; trajfit(cm, traj); // quantize and classify the current estimation int posvel[2]; posvel[0] = (int) traj[0]; posvel[1] = (int) traj[1]; int cls = classify(posvel); // debounce the classification to eliminate transient changes cls = debounce(cls, 5); // emit some data to plot // Serial.print(raw_ping); Serial.print(" "); // ping time in microseconds // Serial.print(cm); Serial.print(" "); // centimeter-scaled, zero-suppressed // Serial.print(traj[0]); Serial.print(" "); // quadratic position // Serial.print(traj[1]); Serial.print(" "); // quadratic velocity Serial.print(posvel[0]); Serial.print(","); // integer position for classification Serial.print(posvel[1]); Serial.print(" "); // integer velocity for classification Serial.print(20*cls); Serial.print(" "); // integer sample classification, amplified for live plotting Serial.println(); } }
classify.ino
// Decision tree classifier generated using classify_gen.py int classify(int input[2]) { if (input[0] <= 53) { if (input[0] <= 39) { if (input[1] <= 7) { if (input[1] <= -4) { if (input[0] <= 30) { if (input[1] <= -13) { return 0; } else { return 0; } } else { return 2; } } else { if (input[0] <= 29) { if (input[0] <= 24) { return 0; } else { if (input[1] <= 2) { if (input[1] <= 0) { return 0; } else { return 0; } } else { if (input[0] <= 27) { return 0; } else { return 0; } } } } else { if (input[0] <= 37) { return 0; } else { return 0; } } } } else { if (input[0] <= 23) { return 0; } else { return 1; } } } else { if (input[1] <= -1) { return 2; } else { return 1; } } } else { if (input[1] <= 3) { if (input[1] <= -20) { return 3; } else { if (input[0] <= 78) { if (input[0] <= 64) { if (input[1] <= -7) { return 3; } else { return 3; } } else { if (input[1] <= -3) { return 3; } else { return 3; } } } else { return 3; } } } else { if (input[0] <= 78) { if (input[0] <= 65) { if (input[1] <= 11) { return 3; } else { return 1; } } else { return 1; } } else { return 3; } } } }
filters.ino
// filters.ino : filtering primitives used by the ClassifierDemo sketch. // No copyright, 2020, Garth Zeglin. This file is explicitly placed in the public domain. //================================================================ // Suppress a specific value in an input stream. One integer of state is required. int suppress_value(int input, int value) { static int previous = 0; if (input != value) previous = input; return previous; } //================================================================ // Debounce an integer stream by suppressing changes from the previous value // until a specific new value has been observed a minimum number of times. Three // integers of state are required. int debounce(int input, int samples) { static int current_value = 0; static int new_value = 0; static int count = 0; if (input == current_value) { count = 0; } else { if (count == 0) { new_value = input; count = 1; } else { if (input == new_value) { count += 1; if (count >= samples) { current_value = new_value; count = 0; } } else { new_value = input; count = 1; } } } return current_value; } //================================================================ // Floating-point version of map(). The standard Arduino map() function only // operates using integers; this extends the idea to floating point. The // Arduino function can be found in the WMath.cpp file within the Arduino IDE // distribution. Note that constrain() is defined as a preprocessor macro and // so doesn't have data type limitations. float fmap(float x, float in_min, float in_max, float out_min, float out_max) { float divisor = in_max - in_min; if (divisor == 0.0) { return out_min; } else { return (x - in_min) * (out_max - out_min) / divisor + out_min; } } //================================================================ // Low-Pass Butterworth IIR digital filter, generated using filter_gen.py. // Sampling rate: 10 Hz, frequency: 1.0 Hz. // Filter is order 4, implemented as second-order sections (biquads). // Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html float lowpass(float input) { float output = input; { static float z1, z2; // filter section state float x = output - -1.04859958*z1 - 0.29614036*z2; output = 0.00482434*x + 0.00964869*z1 + 0.00482434*z2; z2 = z1; z1 = x; } { static float z1, z2; // filter section state float x = output - -1.32091343*z1 - 0.63273879*z2; output = 1.00000000*x + 2.00000000*z1 + 1.00000000*z2; z2 = z1; z1 = x; } return output; } //================================================================ // Trajectory estimation filter generated using trajfit_gen.py. // Based on Savitzky-Golay polynomial fitting filters. // Sampling rate: 10 Hz. // The output array will contain the trajectory parameters representing the signal // at the current time: [position, velocity, acceleration], with units of [1, 1/sec, 1/sec/sec]. // Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.savgol_coeffs.html void trajfit(float input, float output[3]) { const float coeff[3][5] = {{ 0.085714, -0.142857, -0.085714, 0.257143, 0.885714}, { 3.714286, -3.857143, -5.714286, -1.857143, 7.714286}, { 28.571429, -14.285714, -28.571429, -14.285714, 28.571429}}; static float ring[5]; // buffer for recent time history static unsigned oldest = 0; // index of oldest sample // save the new sample by overwriting the oldest sample ring[oldest] = input; if (++oldest >= 5) oldest = 0; // iterate over the coefficient rows unsigned index = oldest; for (int i = 0; i < 3; i++) { output[i] = 0.0; // clear accumulator // Iterate over the samples and the coefficient rows. The index cycles // around the circular buffer once per row. for (int j = 0; j < 5; j++) { output[i] += coeff[i][j] * ring[index]; if (++index >= 5) index = 0; } } } //================================================================
sonar.ino
// sonar.ino: operate a HC04 ultrasonic range sensor // No copyright, 2020, Garth Zeglin. This file is explicitly placed in the public domain. // Run a measurement cycle on the sonar range sensor. Returns the round-trip // time in microseconds. Returns zero if no ping is detected. This code // assumes the pin constants are defined in another file. int ping_sonar(void) { // Generate a short trigger pulse. digitalWrite(sonarTriggerPin, HIGH); delayMicroseconds(10); digitalWrite(sonarTriggerPin, LOW); // Measure the echo pulse length. The ~6 ms timeout is chosen for a maximum // range of 100 cm assuming sound travels at 340 meters/sec. With a round // trip of 2 meters distance, the maximum ping time is 2/340 = 0.0059 // seconds. You may wish to customize this for your particular hardware. const unsigned long TIMEOUT = 5900; unsigned long ping_time = pulseIn(sonarEchoPin, HIGH, TIMEOUT); return ping_time; } //================================================================