レトロなデータサイエンス:YOLOの最初のバージョンのテスト

Retro Data Science Testing the First Version of YOLO

8年前に戻ってみましょう

作者によるYOLOの物体検出画像

データサイエンスの世界は常に変化しています。しばしば、これらの変化を見ることができないのは、ゆっくりと進んでいるからですが、しばらく経った後に、風景が大幅に異なっていることがわかります。10年前にまだ最先端だったツールやライブラリは、今日では完全に忘れられてしまうことがあります。

YOLO(You Only Look Once)は、人気のある物体検出ライブラリです。最初のバージョンは、かなり前の2015年にリリースされました。YOLOは高速に動作し、良好な結果を提供し、事前にトレーニングされたモデルが公開されていました。このモデルはすぐに人気を博し、プロジェクトは今でも積極的に改善されています。これにより、データサイエンスのツールやライブラリがどのように進化したかを見ることができます。この記事では、最初のV1から最新のV8まで、異なるYOLOのバージョンをテストします。

さらなるテストには、OpenCV YOLOチュートリアルの画像を使用します:

テスト画像、出典© https://opencv-tutorial.readthedocs.io

自分で結果を再現したい読者は、そのリンクを開いて元の画像をダウンロードすることができます。

それでは始めましょう。

YOLO V1..V3

YOLOに関する最初の論文「You Only Look Once: Unified, Real-Time Object Detection」は、2015年にリリースされました。そして驚くことに、YOLO v1はまだダウンロードできます。元の論文の著者の1人であるMr.Redmonは、「歴史的な目的」のためにこのバージョンを維持していると書いており、これは本当に素晴らしいことです。しかし、今日それを実行できるでしょうか?モデルは2つのファイル形式で配布されています。「yolo.cfg」という構成ファイルには、ニューラルネットワークモデルの詳細が含まれています:

[net]batch=1height=448width=448channels=3momentum=0.9decay=0.0005...[convolutional]batch_normalize=1filters=64size=7stride=2pad=1activation=leaky

そして、2番目のファイル「yolov1.weights」は、その名前が示すように、事前にトレーニングされたモデルの重みを含んでいます。

この形式はPyTorchやKerasからではないものです。このモデルは、C言語で書かれたオープンソースのニューラルネットワークフレームワークであるDarknetを使用して作成されました。このプロジェクトはまだGitHubで利用可能ですが、放棄されたように見えます。この記事を書いている時点では、164のプルリクエストと1794のオープンな問題があり、最後のコミットは2018年に行われ、後にREADME.mdだけが変更されました(まあ、これが現代のデジタル世界でプロジェクトが死んでいく様子のようです)。

オリジナルのDarknetプロジェクトは放棄されていますが、readNetFromDarknetメソッドはまだOpenCVで利用可能であり、最新のOpenCVバージョンでも存在します。したがって、現代のPython環境を使用して元のYOLO v1モデルを簡単にロードして試すことができます。

import cv2model = cv2.dnn.readNetFromDarknet("yolo.cfg", "yolov1.weights")

残念ながら、それはうまくいきませんでした。エラーしか得られませんでした:

darknet_io.cpp:902: error: (-212:Parsing error) Unknown layer type: local in function 'ReadDarknetFromCfgStream'

「yolo.cfg」には、OpenCVでサポートされていない「local」という名前のレイヤーがあることがわかりましたが、これに対する回避策があるかどうかはわかりません。とにかく、YOLO v2構成にはこのレイヤーがもう存在しないため、このモデルをOpenCVで正常に読み込むことができます:

import cv2model = cv2.dnn.readNetFromDarknet("yolov2.cfg", "yolov2.weights")

モデルの使用は、私たちが期待するほど簡単ではありません。まず、モデルの出力レイヤーを見つける必要があります:

ln = model.getLayerNames()output_layers = [ln[i - 1] for i in model.getUnconnectedOutLayers()]

次に、画像を読み込んで、モデルが理解できるバイナリ形式に変換する必要があります:

img = cv2.imread('horse.jpg')H、W = img.shape[:2]blob = cv2.dnn.blobFromImage(img、1/255.0、(608、608)、swapRB=True、crop=False)

最後に、順方向伝播を実行できます。 “forward”メソッドは計算を実行し、要求されたレイヤー出力を返します:

model.setInput(blob)outputs = model.forward(output_layers)

順方向伝播を行うことは簡単ですが、出力を解析することは少しトリッキーです。モデルは、85次元の特徴ベクトルを出力しており、最初の4桁がオブジェクト矩形を表し、5番目の桁がオブジェクトの存在確率を示し、最後の80桁がモデルがトレーニングされた80のカテゴリの確率情報を含んでいます。この情報を使用して、元の画像にラベルを描画できます:

threshold = 0.5boxes, confidences, class_ids = [], [], []# すべてのボックスとラベルを取得するfor output in outputs:    for detection in output:        scores = detection[5:]        class_id = np.argmax(scores)        confidence = scores[class_id]        if confidence > threshold:            center_x, center_y = int(detection[0] * W), int(detection[1] * H)            width, height = int(detection[2] * W), int(detection[3] * H)            left = center_x - width//2            top = center_y - height//2            boxes.append([left, top, width, height])            class_ids.append(class_id)            confidences.append(float(confidence))# OpenCV NMSBoxesメソッドを使用して埋め込み矩形を結合するindices = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)# すべてのCOCOクラスclasses = "person;bicycle;car;motorbike;aeroplane;bus;train;truck;boat;traffic light;fire hydrant;stop sign;parking meter;bench;bird;" \          "cat;dog;horse;sheep;cow;elephant;bear;zebra;giraffe;backpack;umbrella;handbag;tie;suitcase;frisbee;skis;snowboard;sports ball;kite;" \          "baseball bat;baseball glove;skateboard;surfboard;tennis racket;bottle;wine glass;cup;fork;knife;spoon;bowl;banana;apple;sandwich;" \          "orange;broccoli;carrot;hot dog;pizza;donut;cake;chair;sofa;pottedplant;bed;diningtable;toilet;tvmonitor;laptop;mouse;remote;keyboard;" \          "cell phone;microwave;oven;toaster;sink;refrigerator;book;clock;vase;scissors;teddy bear;hair dryer;toothbrush".split(";")# 画像に矩形を描画するcolors = np.random.randint(0, 255, size=(len(classes), 3), dtype='uint8')for i in indices.flatten():    x, y, w, h = boxes[i]    color = [int(c) for c in colors[class_ids[i]]]    cv2.rectangle(img, (x, y), (x + w, y + h), color, 2)    text = f"{classes[class_ids[i]]}: {confidences[i]:.2f}"    cv2.putText(img, text, (x + 2, y - 6), cv2.FONT_HERSHEY_COMPLEX, 0.5, color, 1)# Showcv2.imshow('window', img)cv2.waitKey(0)cv2.destroyAllWindows()

ここでは、np.argmaxを使用して、最大確率を持つクラスIDを見つけます。 YOLOモデルは、COCO(Common Objects in Context、Creative Commons Attribution 4.0ライセンス)データセットを使用してトレーニングされ、簡単のために、私はすべての80のラベル名を直接コードに配置しました。また、埋め込みされた矩形を結合するためにOpenCV NMSBoxesメソッドを使用しました。

最終的な結果は以下のようになります:

YOLO v2の結果、作者による画像

私たちは、2016年にリリースされたモデルを現代の環境で正常に実行しました!

次のバージョンであるYOLO v3は、2年後の2018年にリリースされ、同じコードを使用して実行することができます(重みと設定ファイルはオンラインで利用可能です)。著者が論文で書いたように、新しいモデルはより正確であり、これを簡単に検証することができます:

YOLO v3の結果、作者による画像

確かに、V3モデルは同じ画像上でより多くのオブジェクトを見つけることができました。技術的な詳細に興味のある読者は、2018年に書かれたこのTDS記事を読むことができます。

YOLO V5..V7

私たちは、readNetFromDarknetメソッドで読み込まれたモデルが動作することがわかりましたが、必要なコードはかなり「低レベル」で手間がかかります。OpenCVの開発者たちは、生活をより簡単にするために、2019年に新しいDetectionModelクラスをバージョン4.1.2に追加しました。この方法でYOLOモデルをロードすることができます。一般的なロジックは同じですが、必要なコードの量はずっと小さくなります。モデルは、クラスID、信頼度値、および矩形を1つのメソッド呼び出しで直接返します:

import cv2model = cv2.dnn_DetectionModel("yolov7.cfg", "yolov7.weights")model.setInputParams(size=(640, 640), scale=1/255, mean=(127.5, 127.5, 127.5), swapRB=True)class_ids, confidences, boxes = model.detect(img, confThreshold=0.5)# Combine boxes together using non-maximum suppressionindices = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)# All COCO classesclasses = "person;bicycle;car;motorbike;aeroplane;bus;train;truck;boat;traffic light;fire hydrant;stop sign;parking meter;bench;bird;" \          "cat;dog;horse;sheep;cow;elephant;bear;zebra;giraffe;backpack;umbrella;handbag;tie;suitcase;frisbee;skis;snowboard;sports ball;kite;" \          "baseball bat;baseball glove;skateboard;surfboard;tennis racket;bottle;wine glass;cup;fork;knife;spoon;bowl;banana;apple;sandwich;" \          "orange;broccoli;carrot;hot dog;pizza;donut;cake;chair;sofa;pottedplant;bed;diningtable;toilet;tvmonitor;laptop;mouse;remote;keyboard;" \          "cell phone;microwave;oven;toaster;sink;refrigerator;book;clock;vase;scissors;teddy bear;hair dryer;toothbrush".split(";")# Draw rectangles on imagecolors = np.random.randint(0, 255, size=(len(classes), 3), dtype='uint8')for i in indices.flatten():    x, y, w, h = boxes[i]    color = [int(c) for c in colors[class_ids[i]]]    cv2.rectangle(img, (x, y), (x + w, y + h), color, 2)    text = f"{classes[class_ids[i]]}: {confidences[i]:.2f}"    cv2.putText(img, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)# Showcv2.imshow('window', img)cv2.waitKey(0)cv2.destroyAllWindows()

如何からわかるように、モデル出力からボックスや信頼度値を抽出するために必要な低レベルのコードはもう必要ありません。

YOLO v7を実行した結果は、一般的には同じですが、馬の周りの矩形はより正確になっています:

YOLO v7の結果、作者による画像

YOLO V8

YOLOの8番目のバージョンが2023年にリリースされたため、少なくともこのテキストを書く時点では「レトロ」とは考えられません。しかし、比較のために、現在のYOLOを実行するために必要なコードを見てみましょう。

from ultralytics import YOLOimport supervision as svmodel = YOLO('yolov8m.pt')results = model.predict(source=img, save=False, save_txt=False, verbose=False)detections = sv.Detections.from_yolov8(results[0])# Create list of labelslabels = []for ind, class_id in enumerate(detections.class_id):    labels.append(f"{model.model.names[class_id]}: {detections.confidence[ind]:.2f}")# Draw rectangles on imagebox_annotator = sv.BoxAnnotator(thickness=2, text_thickness=1, text_scale=0.4)box_annotator.annotate(scene=img, detections=detections, labels=labels)# Showcv2.imshow('window', img)cv2.waitKey(0)cv2.destroyAllWindows()

コードがさらにコンパクトになりました。データセットのラベル名(モデルは「names」プロパティを提供します)や、画像に矩形とラベルを描画する方法(そのための特別なBoxAnnotatorクラスがあります)を気にする必要はありません。モデルの重みをダウンロードする必要もなくなりました。ライブラリが自動的に行ってくれます。2016年に比べて、2023年のプログラムは、約50行から約5行に「縮小」しました!明らかに素晴らしい改善です。現代の開発者は、順伝播や出力レベル形式についてはもう知らなくても、モデルが「魔法」のように動作するだけです。それが良いのか悪いのか、私にはわかりません 🙂

結果自体はほぼ同じです:

YOLO v8 results, Image by author

モデルはうまく機能し、少なくとも私のコンピュータでは、計算速度がv7に比べて改善されました。これは、GPUのより良い使用によるものかもしれません。

結論

この記事では、2016年から2023年までに作成されたほぼすべてのYOLOモデルをテストすることができました。最初に、ほぼ10年前にリリースされたモデルを実行しようとする試みは、時間の無駄に思えるかもしれません。しかし、私にとっては、これらのテストを行いながら多くを学びました:

  • 年々、人気のあるデータサイエンスツールやライブラリがどのように進化しているかを見ることが興味深かったです。低レベルのコードからすべてを実行して、実行前に事前トレーニングされたモデルをダウンロードする方法に移行する傾向は明らかです。これが良いのか悪いのか?これは興味深く開かれた問題です。
  • OpenCVがディープラーニングモデルを「ネイティブ」で実行できることは重要でした。これにより、ニューラルネットワークモデルをPyTorchやKerasのような大規模なフレームワークだけでなく、純粋なPythonやC++アプリケーションでも使用できるようになりました。すべてのアプリケーションがほぼ無制限のリソースを持つクラウドで実行されているわけではありません。IoT市場は成長しており、これはロボット、監視カメラ、スマートドアベルなどの低電力デバイスでニューラルネットワークを実行するために特に重要です。

次の記事では、Raspberry Piのような低消費電力ボードでYOLO v8を実行し、PythonおよびC++バージョンの両方をテストする方法を詳しく説明します。お楽しみに。

この記事が気に入った場合は、VoAGIに登録して、私の新しい記事の通知を受け取ったり、他の著者の数千のストーリーに完全にアクセスしたりしてください。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more