تشخیص اشیا با استفاده از YOLOv5 و OpenCV DNN در ++C  و Python

YOLO از اولین انتشار خود راه طولانی را پیموده است و اکنون هشت نسخه اصلی در مجموعه خانواده YOLO وجود دارد. نسخه های رسمی توسط Joseph Redmon  از YOLOv1 تا YOLOv3، و نسخه های دیگر مانند YOLOv4 و YOLOv5 و PP-YOLO و YOLOR و YOLOX .نسخه جدید YOLOv5 از اولین عرضه‌اش در سال ۲۰۲۰ توجهات و ارزیابی‌های زیادی به دست آورده است. اخیراً، YOLOv5 پشتیبانی از چارچوب OpenCV DNN را گسترش داده است، که مزیت استفاده از این مدل تشخیص اشیاء پیشرفته با ماژول OpenCV DNN را به همراه دارد.

اهداف یادگیری:

✅ استنباط Yolov5 با استفاده از Ultralytics Repo و PyTorchHub

✅ تبدیل یک مدل YOLOv5 PyTorch به ONNX

✅ پیاده سازی تشخیص شی با استفاده از ماژول YOLOv5 و OpenCV DNN

۱- چرا از OpenCV برای استنباط یادگیری عمیق استفاده کنیم؟

در دسترس بودن یک مدل DNN در OpenCV انجام استنباط را بسیار آسان می کند. تصور کنید که یک مدل قدیمی تشخیص اشیا در حال تولید دارید و می خواهید به جای آن از این مدل جدید و پیشرفته استفاده کنید. ممکن است مجبور شوید چندین کتابخانه نصب کنید تا کار کند. علاوه بر این، محیط تولید شما ممکن است به شما اجازه بروزرسانی دلخواه نرم افزار را ندهد. اینجاست که ماژول OpenCV DNN به کار می آید، زیرا دارای یک API واحد برای انجام استنباط یادگیری عمیق است و وابستگی های بسیار کمی دارد.

اگر از OpenCV DNN استفاده می کنید، ممکن است بتوانید مدل قدیمی خود را با آخرین مدل با تغییرات بسیار کمی در کد خود تعویض کنید. ثانیاً، اگر می خواهید یک مدل یادگیری عمیق را در ++C استقرار دهید، مشکل ساز می شود، اما استقرار آن در ++C با استفاده از OpenCV بسیار آسان است. در نهایت، پیاده سازی OpenCV CPU برای پردازنده های Intel بسیار بهینه شده است، بنابراین ممکن است دلیل دیگری برای در نظر گرفتن OpenCV DNN برای استنباط باشد.

۲- چرا YOLO v5؟

YOLOv5 سریع و آسان برای استفاده است. این بر اساس چارچوب PyTorch است که جامعه بزرگتری نسبت به Yolo v4 Darknet دارد. نصب ساده و سرراست است. برخلاف YOLOv4، حتی با پشتیبانی CUDA، نیازی به تلاش برای ساخت آن از منبع ندارید. شما می‌توانید از بین ده مدل چند مقیاسی موجود با تعادل بین سرعت و دقت انتخاب کنید. از ۱۱ فرمت مختلف (هم صادرات و هم زمان اجرا) پشتیبانی می کند. با توجه به مزایای هسته مبتنی بر پایتون، می توان آن را به راحتی در دستگاه های EDGE پیاده سازی کرد. iDetect یک برنامه iOS است که متعلق به Ultralytics، شرکت سازنده YOLOv5 است. این می تواند با استفاده از YOLOv5 تشخیص اشیاء بلادرنگ روی تلفن ها انجام دهد. اجازه دهید قبل از وارد شدن به کد، تاریخچه مختصری از YOLO را مرور کنیم.

۳- مروری کوتاه بر YOLOv5

با توجه به اینکه YOLOv5 دقیقاً نسخه بروز شده YOLOv4 نیست، نام YOLOv5 ممکن است جامعه بینایی کامپیوتری را گیج کند. در واقع، سه نسخه اصلی YOLO در مدت کوتاهی در سال ۲۰۲۰ منتشر شد.

اگرچه آن ها همگی مبتنی بر YOLOv3 هستند، اما هر کدام از آن ها توسعه ای مستقل به حساب می آید. معماری یک شبکه عصبی کاملاً متصل شامل موارد زیر است:

 

  • ستون فقرات (Backbone) : که ویژگی های اساسی یک تصویر را استخراج می کند.
  • رأس (Head) : شامل لایه های خروجی است که تشخیص نهایی دارند.
  • گردن (Neck) : گردن ، ستون فقرات و رأس را به هم متصل می کند و در واقع هرم ویژگی ها را تولید می کند. نقش آن جمع آوری نقشه های ویژگی مراحل مختلف است.
نمای کلی معماری YOLO
شکل: نمای کلی معماری YOLO، منبع

در حال حاضر، دو سال از انتشار اولیه، هنوز YOLOv5 مقاله ای منتشر نکرده است. بنابراین، ما هنوز اطلاعات دقیقی از معماری نداریم. اطلاعات ارائه شده در پست وبلاگ از فایل های پیکربندی GitHub readme، نسخه ها، یادداشت منتشر شده و .yaml است. با این حال، در وضعیت توسعه بسیار فعال است و می‌توانیم با گذشت زمان انتظار بهبودهای بیشتری را داشته باشیم. جدول زیر معماری نسخه ۳، ۴ و ۵ را خلاصه می کند.

مقایسه معماری نسخه های مختلف YOLO
جدول: خلاصه معماری مدل، YOLO v3، v4 و v5

YOLOv4 جانشین رسمی YOLOv3 است زیرا از مخزن اصلی جدا شده است. این محیط کاری که در ++C نوشته شده است Darknet است. از طرف دیگر، YOLOv5 با نسخه های قبلی متفاوت است و بر اساس Pytorch است. پیشرفت های مهم YOLOv5 عبارت اند از:

  • داده افزایی موزاییکی
  • لنگر های جعبه محصور کننده با آموزش خودکار

در ابتدا، YOLOv5 پیشرفت قابل توجهی نسبت به YOLOv4 نداشت. با این حال، با نسخه های اخیر، ثابت شده است که در بسیاری از زمینه ها بهتر است. مقاله YOLOX: Exceeding YOLO Series در سال ۲۰۲۱، برتری YOLOv5 را نسبت به YOLOv4 از نظر سرعت و دقت گزارش می دهد. با این حال، طبق این گزارش، همه مدل‌های YOLOv5 نتوانستند YOLOv4 را شکست دهند. مقایسه دقیق نسخه های مختلف YOLO را در پست آینده منتشر خواهیم کرد.

مقایسه سرعت و دقت نسخه های مختلف YOLO
جدول: مقایسه سرعت و دقت تشخیص دهنده های مختلف جسم در COCO 2017 test-dev . منبع

YOLOv5 ابتدا با چهار مدل عرضه شد. Small، Medium، Large و Extra Large. اخیراً YOLOv5 Nano و پشتیبانی از OpenCV DNN معرفی شده است. در حال حاضر هر مدل دارای دو نسخه P5 و P6 می باشد.

  • P5  : سه لایه خروجی P3، P4 و P5. آموزش دیده بر روی تصاویر ۶۴۰×۶۴۰٫
  • P6  : چهار لایه خروجی P3، P4، P5 و P6. آموزش دیده بر روی تصاویر ۱۲۸۰×۱۲۸۰٫
Extra-LargeLargeMediumSmallNanoModels
YOLOv5xYOLOv5lYOLOv5mYOLOv5sYOLOv5nP5
YOLOv5x6YOLOv5l6YOLOv5m6YOLOv5s6YOLOv5n6P6
جدول: لیست مدل های YOLOv5 P5 و P6

بنابراین در مجموع دارای ۱۰ مدل تشخیص شی با مقیاس ترکیبی است. ما بعداً بیشتر در مورد عملکرد آنها خواهیم دید، اما ابتدا، اجازه دهید نحوه استنباط با استفاده از آنها را ببینیم.

۴- تشخیص اشیا با استفاده از YOLOv5 و OpenCV DNN (++C  و Python)

۱-۴ دانلود کد

پوشه کد قابل دانلود شامل اسکریپت های Python و ++C و یک نوت بوک colab است. پیش نیاز ها را می توان با دستور زیر نصب کرد:

pip install -r requirements.txt

۲-۴ تبدیل مدل

از آنجایی که پلتفرم اصلی YOLOv5 PyTorch می باشد، تمامی مدل ها با فرمت pt. در دسترس هستند. با این حال، OpenCV DNN از مدل هایی با قالب onnx. هم پشتیبانی می کند. بنابراین، می توان برای تبدیل مدل به فرمت مورد نیاز مراحل زیر را دنبال کرد:

  1. شبیه سازی مخزن
  2. نصب پیش نیاز ها
  3. دانلود مدل های PyTorch
  4. ارسال به ONNX

توجه: مدل های Nano, small, and medium ONNX در پوشه کد موجود است.

امکان انجام تبدیل به صورت محلی وجود دارد، اما توصیه می کنیم از colab استفاده کنید تا برای پیش نیاز ها و دانلود داده ها به مشکل بر نخورید. دستورات زیر برای تبدیل مدل YOLOv5s هستند. این نوت بوک حاوی کد تبدیل و دانلود سایر مدل ها می باشد.

# Clone the repository. 
!git clone https://github.com/ultralytics/YOLOv5

%cd YOLOv5 # Install dependencies.
!pip install -r requirements.txt
!pip install onnx

# Download .pt model.
!wget https://github.com/ultralytics/YOLOv5/releases/download/v6.1/YOLOv5s.pt

%cd .. # Export to ONNX.
!python export.py --weights models/YOLOv5s.pt --include onnx

# Download the file.
from google.colab import files
files.download('models/YOLOv5s.onnx')

۳-۴ توضیح کد

پس از آماده کردن پیش نیاز ها، زمان آن است که کدنویسی را شروع کنیم. نمودار زیر روند کار را نشان می دهد.

دیاگرام YOLOv5 در OpenCV

۱-۳-۴- فراخوانی کتابخانه ها

++C

#include <opencv2/opencv.hpp>
#include <fstream>
// Namespaces.
using namespace cv;
using namespace std;
using namespace cv::dnn;

Python

import cv2
import numpy as np

 

۲-۳-۴ تعریف پارامترهای سراسری

پارامتر های ثابت INPUT_WIDTH و INPUT_HEIGHT برای تعیین اندازه blob هستند که BLOB مخفف Binary Large Object است که خود در واقع شامل داده های خام قابل خواندن است. تصویر باید به یک blob تبدیل شود تا شبکه بتوان آن را پردازش کرد. در این مورد ما، یک شیء آرایه ۴ بعدی با اندازه یا شکل (۱,۳,۶۴۰,۶۴۰) است.

 

  • SCORE_THRESHOLD : برای فیلتر کردن کلاس با احتمال کم امتیازات.
  • NMS_THRESHOLD : برای حذف کادرهای محدودکننده همپوشانی.
  • CONFIDENCE_THRESHOLD : برای فیلتر تشخیص های با احتمال کم.

 

در حین مرور کد، در مورد این پارامترها بیشتر بحث خواهیم کرد.

توجه: برخلاف ++C، مقادیر اندازه ورودی در پایتون نمی توانند از نوع float باشند.

 

++C

// Constants.
const float INPUT_WIDTH = 640.0;
const float INPUT_HEIGHT = 640.0;
const float SCORE_THRESHOLD = 0.5;
const float NMS_THRESHOLD = 0.45;
const float CONFIDENCE_THRESHOLD = 0.45;

// Text parameters.
const float FONT_SCALE = 0.7;
const int FONT_FACE = FONT_HERSHEY_SIMPLEX;
const int THICKNESS = 1;

// Colors.
Scalar BLACK = Scalar(0,0,0);
Scalar BLUE = Scalar(255, 178, 50);
Scalar YELLOW = Scalar(0, 255, 255);
Scalar RED = Scalar(0,0,255);

Python

# Constants.
INPUT_WIDTH = 640
INPUT_HEIGHT = 640
SCORE_THRESHOLD = 0.5
NMS_THRESHOLD = 0.45
CONFIDENCE_THRESHOLD = 0.45

# Text parameters.
FONT_FACE = cv2.FONT_HERSHEY_SIMPLEX
FONT_SCALE = 0.7
THICKNESS = 1

# Colors.
BLACK  = (0,0,0)
BLUE   = (255,178,50)
YELLOW = (0,255,255)

 

۳-۳-۴- تنظیم برچسب

تابع draw_label نام کلاس‌ها را که در گوشه سمت چپ بالای کادر محدودکننده، حاشیه‌نویسی می کند که کد نسبتاً ساده ای دارد. رشته متن ، به عنوان برچسب با تابع ()OpenCV getTextSize در آرگومان ارسال می شود تا اندازه کادر محدودکننده را برمی گرداند. این مقادیر ابعاد برای ترسیم یک مستطیل پس زمینه سیاه مورد استفاده قرار می گیرد که برچسب توسط تابع ()putText پردازش می شود.

 

++C

void draw_label(Mat& input_image, string label, int left, int top)
{
    // Display the label at the top of the bounding box.
    int baseLine;
    Size label_size = getTextSize(label, FONT_FACE, FONT_SCALE, THICKNESS, &baseLine);
    top = max(top, label_size.height);
    // Top left corner.
    Point tlc = Point(left, top);
    // Bottom right corner.
    Point brc = Point(left + label_size.width, top + label_size.height + baseLine);
    // Draw white rectangle.
    rectangle(input_image, tlc, brc, BLACK, FILLED);
    // Put the label on the black rectangle.
    putText(input_image, label, Point(left, top + label_size.height), FONT_FACE, FONT_SCALE, YELLOW, THICKNESS);
}

Python

def draw_label(im, label, x, y):
    """Draw text onto image at location."""
    # Get text size.
    text_size = cv2.getTextSize(label, FONT_FACE, FONT_SCALE, THICKNESS)
    dim, baseline = text_size[0], text_size[1]
    # Use text size to create a BLACK rectangle.
    cv2.rectangle(im, (x,y), (x + dim[0], y + dim[1] + baseline), (0,0,0), cv2.FILLED);
    # Display text inside the rectangle.
    cv2.putText(im, label, (x, y + dim[1]), FONT_FACE, FONT_SCALE, YELLOW, THICKNESS, cv2.LINE_AA)

 

۴-۳-۴- پیش پردازش

تابع pre-process شبکه و تصویر را به عنوان آرگومان ورودی می گیرد. در ابتدا تصویر به یک blob و سپس به عنوان ورودی شبکه تنظیم می شود. تابع ()getUnconnectedOutLayerNames نام لایه های خروجی را ارائه می کند که دارای ویژگی های تمام لایه ها است و از طریق آن ها تصویر از شبکه عبور کرده تا تشخیص ها حاصل گردد. درنهایت، پس از پردازش، نتایج تشخیص را برمی گرداند.

 

++C

vector<Mat> pre_process(Mat &input_image, Net &net)
{
    // Convert to blob.
    Mat blob;
    blobFromImage(input_image, blob, 1./255., Size(INPUT_WIDTH, INPUT_HEIGHT), Scalar(), true, false);

    net.setInput(blob);

    // Forward propagate.
    vector<Mat> outputs;
    net.forward(outputs, net.getUnconnectedOutLayersNames());

    return outputs;
}

Python

def pre_process(input_image, net):
      # Create a 4D blob from a frame.
      blob = cv2.dnn.blobFromImage(input_image, 1/255,  (INPUT_WIDTH, INPUT_HEIGHT), [0,0,0], 1, crop=False)

      # Sets the input to the network.
      net.setInput(blob)

      # Run the forward pass to get output of the output layers.
      outputs = net.forward(net.getUnconnectedOutLayersNames())
      return outputs

 

۵-۳-۴- پس پردازش

در تابع قبلی pre_process، نتایج تشخیص را به عنوان یک شی یا object دریافت می کنیم که برای پردازش بیشتر باید بازبینی شود. قبل از بحث بیشتر در مورد کد، اجازه دهید شکل این شی و محتوای آن را ببینیم.

شیء برگشتی یک آرایه دو بعدی است. خروجی بستگی به اندازه ورودی دارد. به عنوان مثال، با اندازه ورودی پیش فرض ۶۴۰، یک آرایه دو بعدی با اندازه ۲۵۲۰۰×۸۵ (ردیف‌ها و ستون‌ها) دریافت می کنیم. ردیف ها تعداد شناسایی ها را نشان می دهند. بنابراین هر بار که شبکه اجرا می شود، ۲۵۲۰۰ کادر محدود کننده پیش بینی می شود. هر کادر دارای یک آرایه یک بعدی از ۸۵ ورودی است که کیفیت تشخیص را نشان می دهد. این اطلاعات برای فیلتر کردن تشخیص های مورد نظر کافی است.

تشخیص با YOLOv5

دو جایگاه اول ، مختصات نرمال شده مرکز کادر شناسایی شده هستند و سپس عرض و ارتفاع نرمال شده. شاخص ۴ دارای امتیاز اطمینان است که احتمال شناسایی یک شئ را نشان می دهد. ۸۰ ورودی زیر امتیازات کلاس ۸۰ شی از مجموعه داده COCO 2017 را نشان می دهد که مدل بر روی آن ها آموزش داده شده است.

واقعیت جالب: مجموعه داده COCO 2017 در مجموع ۹۱ شی دارد. با این حال، ۱۱ شی هنوز برچسب ندارند.

الف) فیلتر تشخیص های درست

در حین باز کردن، باید مراقب شکل ها باشیم. با OpenCV-Python 4.5.5، یک tuple از یک آرایه سه بعدی به اندازه ۱x row x column است. باید row x column باشد. بنابراین، آرایه از شاخص صفر قابل دسترسی است. این مورد در ++C  رعایت نمی شود.

شبکه مختصات خروجی را بر اساس اندازه ورودی blob، یعنی ۶۴۰ تولید می کند. بنابراین، مختصات باید در فاکتورهای تغییر اندازه ضرب شود تا خروجی واقعی به دست آید. مراحل زیر برای بازکردن تشخیص ها انجام می گیرد:

 

  1. حلقه از طریق تشخیص
  2. فیلتر تشخیص های درست
  3. دریافت شاخص بهترین نمره کلاس
  4. حذف تشخیص هایی که امتیاز کلاس شان از مقدار آستانه کمتر است

++C

Mat post_process(Mat &input_image, vector<Mat> &outputs, const vector<string> &class_name)
{
    // Initialize vectors to hold respective outputs while unwrapping     detections.
    vector<int> class_ids;
    vector<float> confidences;
    vector<Rect> boxes;
    // Resizing factor.
    float x_factor = input_image.cols / INPUT_WIDTH;
    float y_factor = input_image.rows / INPUT_HEIGHT;
    float *data = (float *)outputs[0].data;
    const int dimensions = 85;
    // ۲۵۲۰۰ for default size 640.
    const int rows = 25200;
    // Iterate through 25200 detections.
    for (int i = 0; i < rows; ++i)
    {
        float confidence = data[4];
        // Discard bad detections and continue.
        if (confidence >= CONFIDENCE_THRESHOLD)
        {
            float * classes_scores = data + 5;
            // Create a 1x85 Mat and store class scores of 80 classes.
            Mat scores(1, class_name.size(), CV_32FC1, classes_scores);
            // Perform minMaxLoc and acquire the index of best class  score.
            Point class_id;
            double max_class_score;
            minMaxLoc(scores, 0, &max_class_score, 0, &class_id);
            // Continue if the class score is above the threshold.
            if (max_class_score > SCORE_THRESHOLD)
            {
                // Store class ID and confidence in the pre-defined respective vectors.
                confidences.push_back(confidence);
                class_ids.push_back(class_id.x);
                // Center.
                float cx = data[0];
                float cy = data[1];
                // Box dimension.
                float w = data[2];
                float h = data[3];
                // Bounding box coordinates.
                int left = int((cx - 0.5 * w) * x_factor);
                int top = int((cy - 0.5 * h) * y_factor);
                int width = int(w * x_factor);
                int height = int(h * y_factor);
                // Store good detections in the boxes vector.
                boxes.push_back(Rect(left, top, width, height));
            }
        }
        // Jump to the next row.
        data += 85;
    }

Python

def post_process(input_image, outputs):
      # Lists to hold respective values while unwrapping.
      class_ids = []
      confidences = []
      boxes = []
      # Rows.
      rows = outputs[0].shape[1]
      image_height, image_width = input_image.shape[:2]
      # Resizing factor.
      x_factor = image_width / INPUT_WIDTH
      y_factor =  image_height / INPUT_HEIGHT
      # Iterate through detections.
      for r in range(rows):
            row = outputs[0][0][r]
            confidence = row[4]
            # Discard bad detections and continue.
            if confidence >= CONFIDENCE_THRESHOLD:
                  classes_scores = row[5:]
                  # Get the index of max class score.
                  class_id = np.argmax(classes_scores)
                  #  Continue if the class score is above threshold.
                  if (classes_scores[class_id] > SCORE_THRESHOLD):
                        confidences.append(confidence)
                        class_ids.append(class_id)
                        cx, cy, w, h = row[0], row[1], row[2], row[3]
                        left = int((cx - w/2) * x_factor)
                        top = int((cy - h/2) * y_factor)
                        width = int(w * x_factor)
                        height = int(h * y_factor)
                        box = np.array([left, top, width, height])
                        boxes.append(box)

 

ب) حذف کادر های همپوشان

پس از فیلتر کردن تشخیص های درست،  کادر های محدودکننده مورد نظر را خواهیم داشت. با این حال، ممکن است چندین کادر محدود کننده همپوشانی به شکل زیر وجود داشته باشد.

حذف کادر های محصور کننده همپوشان

که این مشکل با انجام Non-Maximum Suppression حل می شود. تابع ()NMSBoxes لیستی از کادر ها را می گیرد، (اشتراک بر اجتماع)IOU را محاسبه می کند، و تصمیم می گیرد که کادر ها را وابسته به NMS_THRESHOLD نگه دارد. برای کسب اطلاعات بیشتر، مقاله در مورد NMS را بررسی کنید.

 

++C

    // Perform Non-Maximum Suppression and draw predictions.
    vector<int> indices;
    NMSBoxes(boxes, confidences, SCORE_THRESHOLD, NMS_THRESHOLD, indices);
    for (int i = 0; i < indices.size(); i++)
    {
        int idx = indices[i];
        Rect box = boxes[idx];
        int left = box.x;
        int top = box.y;
        int width = box.width;
        int height = box.height;
        // Draw bounding box.
        rectangle(input_image, Point(left, top), Point(left + width, top + height), BLUE, 3*THICKNESS);
        // Get the label for the class name and its confidence.
        string label = format("%.2f", confidences[idx]);
        label = class_name[class_ids[idx]] + ":" + label;
        // Draw class labels.
        draw_label(input_image, label, left, top);
    }
    return input_image;
}

Python

# Perform non maximum suppression to eliminate redundant, overlapping boxes with lower confidences.
     indices = cv2.dnn.NMSBoxes(boxes, confidences, CONFIDENCE_THRESHOLD, NMS_THRESHOLD)
     for i in indices:
           box = boxes[i]
           left = box[0]
           top = box[1]
           width = box[2]
           height = box[3]             
           # Draw bounding box.             
           cv2.rectangle(input_image, (left, top), (left + width, top + height), BLUE, 3*THICKNESS)
           # Class label.                      
           label = "{}:{:.2f}".format(classes[class_ids[i]], confidences[i])             
           # Draw label.             
           draw_label(input_image, label, left, top)
     return input_image

 

۶-۳-۴- تابع اصلی

در نهایت مدل را بارگذاری می کنیم. انجام پیش پردازش و پس پردازش و به دنبال آن نمایش اطلاعات.

 

++C

int main()
{
    // Load class list.
    vector<string> class_list;
    ifstream ifs("coco.names");
    string line;
    while (getline(ifs, line))
    {
        class_list.push_back(line);
    }
    // Load image.
    Mat frame;
    frame = imread("traffic.jpg");
    // Load model.
    Net net;
    net = readNet("YOLOv5s.onnx");
    vector<Mat> detections;     // Process the image.
    detections = pre_process(frame, net);
    Mat img = post_process(frame.clone(), detections, class_list);
    // Put efficiency information.
    // The function getPerfProfile returns the overall time for     inference(t) and the timings for each of the layers(in layersTimes).
    vector<double> layersTimes;
    double freq = getTickFrequency() / 1000;
    double t = net.getPerfProfile(layersTimes) / freq;
    string label = format("Inference time : %.2f ms", t);
    putText(img, label, Point(20, 40), FONT_FACE, FONT_SCALE, RED);
    imshow("Output", img);
    waitKey(0);
    return 0;
}

Python

if __name__ == '__main__':
      # Load class names.
      classesFile = "coco.names"
      classes = None
      with open(classesFile, 'rt') as f:
            classes = f.read().rstrip('\n').split('\n')
      # Load image.
      frame = cv2.imread(‘traffic.jpg)
      # Give the weight files to the model and load the network using       them.
      modelWeights = "YOLOv5s.onnx"
      net = cv2.dnn.readNet(modelWeights)
      # Process image.
      detections = pre_process(frame, net)
      img = post_process(frame.copy(), detections)
      """
      Put efficiency information. The function getPerfProfile returns       the overall time for inference(t) 
      and the timings for each of the layers(in layersTimes).
      """
      t, _ = net.getPerfProfile()
      label = 'Inference time: %.2f ms' % (t * 1000.0 /  cv2.getTickFrequency())
      print(label)
      cv2.putText(img, label, (20, 40), FONT_FACE, FONT_SCALE,  (0, 0, 255), THICKNESS, cv2.LINE_AA)
      cv2.imshow('Output', img)
      cv2.waitKey(0)

 

۵- استنباط با YOLOv5

اکنون که می دانید چگونه با استفاده از YOLOv5 و OpenCV شناسایی اشیا را انجام دهید، اجازه دهید نحوه انجام همین کار را با استفاده از مخزن نیز ببینیم. تشخیص شی با استفاده از YOLOv5 بسیار ساده است. دو راه برای انجام استنباط با استفاده از کد خارج از کادر وجود دارد.

  • مخزن Ultralytics
  • PyTorchHub

دستورالعمل اصلی قبلاً در GitHub readme ارائه شده است. در اینجا، به جزئیات بیشتری در مورد کارهای دیگری که می توان انجام داد، خواهیم پرداخت. اجازه دهید پیش برویم و مخزن GitHub را با استفاده از دستور زیر آغاز کنیم.

git clone https://github.com/ultralytics/yolov5.git

۱-۵ استفاده از مخزن Ultralytics

اسکریپت detect.py در فهرست اصلی مخزن YOLOv5 قرار دارد. ما می توانیم آن را به عنوان یک اسکریپت معمولی پایتون اجرا کنیم. تنها استدلال لازم مسیر منبع است. مدل ها از آخرین نسخه YOLOv5 دانلود شده اند و نتایج را در /.yolov5/runs/detect ذخیره می کند. همانطور که در Readme GitHub ذکر شد، می توان از منابع زیر استفاده کرد.

توضیحاتمنبع ورودی
بسته به تعداد وب کم های متصل، با استفاده از ۰، ۱، ۲ و غیره قابل دسترسی است.وبکم
اگرچه Readme رسمی می گوید .jpg، اما شما می توانید از فرمت های تصویر دیگری استفاده کنید. ما اکثر آن ها را تست کرده ایم و عملکرد خوبی داشته. در حال حاضر از فرمت های jpeg، png، tif، tiff، dng، webp و mpo پشتیبانی می شوند.تصویر
به طور مشابه برای ویدیوها نیز، نه تنها از .mp4  بلکه از mov، avi، mpg، mpeg، m4v، wmv و mkv نیز پشتیبانی می شود.ویدئو
ما همچنین می توانیم مسیر یک فهرست حاوی تصاویر و فیلم های مختلف را ارائه دهیم تا تمام فایل های پشتیبانی شده را یکی یکی پردازش کند. در صورت نیاز، می توانید نوع فایل، یعنی path/*.mp4 را نیز مشخص کنید.مسیر
یک ویژگی فوق العاده مفید برای پردازش مستقیم ویدیوهایYouTube  است. با این حال، برای کارایی، نیاز به نصب youtube-dl و pafy داریم که با استفاده از دستور زیر می توانید آنها را نصب کنید.
pip install youtube_dl pafy
لینک YouTube
جریان زنده YouTube با توجه به نصب youtube-dl و pafy به خوبی کار می کند. اما برای جریان ویدیویی RTSP و جریان های زنده فیس بوک خیر. به نظر می رسد کد منبع از هم اکنون از لینک های زنده YouTube پشتیبانی می کند.RTSP, RTMP and HTTP stream

استنباط YOLOv5 با تنظیمات پیش فرض، گزارشی شبیه به زیر را ایجاد می کند. اجازه دهید برخی از ویژگی های استنباط را مرور کنیم.

detect: weights=yolov5s.pt, source=C:\Users\Kukil\Desktop\image.jpg, data=data\coco128.yaml, imgsz=[640, 640], conf_thres=0.25, iou_thres=0.45, max_det=1000, device=, view_img=False, save_txt=False, save_conf=False, save_crop=False, nosave=False, classes=None, agnostic_nms=False, augment=False, visualize=False, update=False, project=runs\detect, name=exp, exist_ok=False, line_thickness=3, hide_labels=False, hide_conf=False, half=False, dnn=False
YOLOv5  v6.1-124-g8c420c4 torch 1.11.0+cpu CPU

توضیحاتپرچم ها
فایل وزن پیش فرض yolov5s.pt است که مدل کوچک PyTorch است. می توان وزن ها را با استفاده از پرچم –weights به دنبال نام مدل تغییر داد. ابتدا برنامه در root directory به دنبال مدل می گردد و در صورت موجود نبودن آن را دانلود می کند. توجه داشته باشید که ما می توانیم از هر قالبی از پلتفرم های پشتیبانی شده لیست ۱۱ استفاده کنیم.Weights
عاملی که به شدت بر سرعت و دقت یک مدل تأثیر می گذارد. پرچم –imgsz x y است که x و y اندازه ورودی blob هستند.Input size
به طور پیش فرض آستانه اطمینان ۰٫۲۵ است. برای تغییر آستانه از پرچم –conf_thresh استفاده کنید.Confidence threshold
IOU مخفف Intersection Over Union است و این آستانه برای اجرای Non-Maximum suppression است. سعی کنید با مقدار پیش فرض ۰٫۴۵ بازی کنید تا ببینید چگونه روی نتایج تأثیر می گذارد. پرچم –iou_threshIOU threshold
استفاده از پرچم –dnn به برنامه اجازه می دهد از OpenCV DNN برای استنباط ONNX استفاده کند.DNN

۲-۵ استفاده از PyTorchHub

اسکریپت زیر یک مدل از پیش آموزش دیده را از PyTorchHub دانلود می کند. به طور پیش فرض، yolov5s.pt دانلود می شود مگر اینکه نام آن تغییر کند. نتایج را می توان چاپ کرد، در ./yolov5/runs/hub ذخیره کرد، روی صفحه نمایش داده شد (محلی)، و به عنوان tensor یا pandas برگرداند. شما همچنین می توانید با ویژگی های مختلف استنباط بازی کنید. برای جزئیات این لینک را بررسی کنید.

import cv2
import torch
# Model
model = torch.hub.load('ultralytics/yolov5', 'yolov5s')
# Image
img = cv2.imread(PATH_TO_IMAGE)
# Inference
results = model(imgs, size=640)  # includes NMS
# Results
results.print()  
results.save()

اگرچه هر دو روش Ultralytics Repository و PyTorchHub مناسب هستند، اما عملکردهای محدودی دارند. ما می‌توانیم کد منبع را ویرایش کنیم، اما راه بهتر این است که آن را از ابتدا بنویسیم. به این ترتیب، با مزیت کدنویسی در ++C، کنترل بهتری بر روی کدها خواهیم داشت. اجازه دهید نگاهی به نحوه پیاده سازی YOLOv5 با استفاده از OpenCV DNN داشته باشیم.

۶- نتایج و مقایسه

۱-۶ نانو در مقایسه با متوسط و فوق بزرگ

دو نتیجه زیر با استفاده از مدل نانو، متوسط و فوق بزرگ به دست آمده است. از نظر دقت، مدل فوق العاده بزرگ بهتر است. حتی می تواند اشیایی را که چشمان ما ممکن است از دست بدهند را شناسایی کند. از طرف دیگر، نانو حدود ۱۰ برابر سریع تر است اما دقت کمتری دارد.

# Test environment configurations.
CPU: AMD RYZEN 5 4600
Input size = 640
Batch size = 1

خروجی YOLOv5n
شکل: نتایج به دست آمده با استفاده از مدل YOLOv5n
خروجی YOLOv5m
شکل: نتیجه با استفاده از مدل YOLOv5m به دست آمده است
خروجی YOLOv5x
شکل: نتایج به دست آمده با استفاده از مدل YOLOv5x

۲-۶ تست سرعت با تغییرات اندازه ورودی

در این تست سرعت، ما همان تصویر را می گیریم اما اندازه blob متفاوت است. زمان (بر حسب میلی‌ثانیه) با اجرای استنباط ۲۰ بار در هر تصویر و سپس گرفتن میانگین اندازه‌گیری می شود. همین آزمایش برای مدل های نانو، کوچک و متوسط ​​تکرار شده و نتایج زیر به دست آمده است.

توجه: برای اجرای استنباط با اندازه ورودی های مختلف، مدل ها باید بر این اساس ارسال شوند. به عنوان مثال، برای تنظیم ۴۸۰ به عنوان اندازه ورودی، با استفاده از دستور زیر مدل را صادر کنید. این کار برای بهینه‌سازی مدل‌های ONNX انجام می‌شود.

!python export.py --weights models/YOLOv5s.pt --include onnx -imsz 480 480

با این حال، ما مجبور نیستیم همه مدل ها را برای انجام تست ها تبدیل کنیم. برای بدست آوردن مدل پویا از پرچم –dynamic در حین صادرات استفاده کنید. نیازی به ذکر اندازه ورودی خاص نیست. سپس می توانیم در زمان اجرا ONNX با استفاده ازUltralytics Repository  مطابق شکل زیر استنباط کنیم که اندازه مضرب ۳۲ است.

python detect.py --source image.jpg --weights yolov5n-dynamic.onnx --imgsz size size

مقایسه سرعت با تغییر اندازه
جدول: تست سرعت با اندازه ورودی متفاوت

ما می‌توانیم به سرعت های بیشتر برسیم، اما باید به دقت هم توجه داشت. در زیر نتایج به‌دست‌آمده از اندازه ورودی متغیر به محیط YOLOv5 متوسط آمده است.

خروجی YOLOv5m با اندازه 640
شکل: استنباط با استفاده از YOLOv5m، اندازه = ۶۴۰
خروجی YOLOv5m با اندازه 480
شکل: استنباط با استفاده از YOLOv5m، اندازه = ۴۸۰
خروجی YOLOv5m با اندازه 320
شکل: استنباط با استفاده از YOLOv5m، اندازه = ۳۲۰
خروجی YOLOv5m با اندازه 160
شکل: استنباط با استفاده از YOLOv5m، اندازه = ۱۶۰

۳-۶ تجزیه و تحلیل Model wise speed

نمودار زیر مقایسه سرعت های مختلف مدل YOLOv5 را نشان می دهد. نتایج ممکن است از دستگاهی به دستگاه دیگر متفاوت باشد، اما ما یک ایده کلی از مبادله سرعت در مقابل دقت داریم. شما می توانید بسته به نیاز خود مدلی را انتخاب کنید

نمودار مقایسه زمان استنتاج YOLOv5
شکل: زمان استنباط توسط مدل‌های YOLOv5 P5 و P6.

جمع بندی

در این پست، استنباط با استفاده از out of the box را با جزئیات، و استفاده از مدل YOLOv5 در OpenCV با ++C و Python را مورد بحث قرار دادیم. شما همچنین یاد گرفتید که چگونه یک مدل PyTorch را به فرمت ONNX تبدیل کنید. امیدوارم از خواندن مقاله لذت برده باشید. اگر سوال یا پیشنهادی دارید لطفا با ما به اشتراک بگذارید.

بیشتر بخوانید :

منبع LearnOpenCV

همچنین ببینید

فناوری تشخیص چهره و کاربردهای آن، تاریخچه تکنولوژی تشخیص چهره

فناوری تشخیص چهره و کاربردهای آن + تاریخچه

فناوری تشخیص چهره یک فناوری بیومتریک است که با استفاده از تجزیه و تحلیل الگوهایی …

یک نظر

  1. سلام وقت بخیر من وقتی کدتون رو اجرا میکنم به ارور زیر میخورم و روشهای مختلفی رو برای حلش سرچ کردم ولی به نتیجه ای نرسیدم خواستم بدونم شما میدونین مشکلش از کجا میتونه باشه

    error: cannot bind non-const lvalue reference of type ‘cv::Mat&’ to an rvalue of type ‘cv::Mat’
    ۱۵۷ | img = post_process(frame.clone(), detections, class_list);
    | ~~~~~~~~~~~^~

دیدگاهتان را بنویسید

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