1. 多感覚タスク
自動運転車やドローンなどの移動ロボットの認識システムは、さまざまなセンサーを使用して重要な情報を取得し、環境認識と物体検出を実現します。これらのセンサーには、カメラ、ライダー、レーダー、慣性測定装置 (IMU)、全地球測位衛星システム (GNSS) などが含まれます。自動運転や移動ロボットの認識システムにおけるカメラの機能は、主に周囲の物体を検出することであり、これらの認識タスクには物体の検出やセグメンテーションが含まれます。
物体検出: 物体検出とは、センサー データを処理することによって、自動車、歩行者、自転車、信号機などの環境内の物体を検出することを指します。YOLO シリーズ ネットワークは、リアルタイムの物体検出によく使用されるソリューションです。これらのネットワークは、複数のオブジェクトをリアルタイムで識別し、その位置と境界ボックスを提供できます。
インスタンスのセグメンテーション: インスタンスのセグメンテーションは、オブジェクトを検出するだけでなく、オブジェクトを異なるインスタンスに属する部分にセグメント化する、より複雑なタスクです。これは、さまざまな車両や歩行者を追跡および識別するのに役立ちます。通常、インスタンスのセグメンテーションには、Mask R-CNN などのネットワークが使用されます。
セマンティック セグメンテーション: セマンティック セグメンテーションは、道路、建物、車両などを異なる領域にセグメント化するなど、画像内の各ピクセルを特定のセマンティック カテゴリに割り当てるタスクです。U-Net や完全畳み込みネットワーク (FCN) などの CNN アーキテクチャは、セマンティック セグメンテーションに一般的に使用されます。
これらのタスクに対処するときに考慮すべき主なアプローチは 2 つあります。
単一モデル: 検出、セグメンテーションなどの複数の認識タスクを同時に処理できる単一のニューラル ネットワーク モデルを開発します。この方法は計算効率を向上させることができますが、大量のトレーニング データと複雑なネットワーク構造が必要になります。
マルチモデル アンサンブル: 複数の独立したモデルを使用して、各モデルが 1 つのタスクに焦点を当て、さまざまな知覚タスクを処理します。このアプローチでは、異なるタスク間のデータの不均衡をより簡単に処理でき、タスクの重要性に基づいてリソースを異なるモデルに割り当てることができます。
2. ハイブリッドネットのマルチタスク フレームワーク
マルチタスク用のエンドツーエンドの多感覚ネットワークである Hybridnets は、精度を向上させるためのいくつかの重要な最適化方法を提案しています。
- 加重双方向特徴ネットワークに基づく効率的なセグメンテーション ヘッドとバウンディング ボックス/カテゴリ予測ネットワークが導入されています。
- 重み付けされた双方向特徴ネットワーク内の各層に対する自動カスタマイズされたアンカリング方法が提案される。
- ネットワークパフォーマンスのバランスをとり、最適化するために、効果的なトレーニング損失関数とトレーニング戦略が提案されています。
これらの最適化に基づいて、ハイブリッドネットは、交通目標の検出、走行可能エリアのセグメント化、車線の検出などのマルチタスクを実行するためのエンドツーエンドの認識ネットワークを開発しました。これをハイブリッドネットと呼びます。ハイブリッド ネットワークは、Berkeley DeepDrive データ セット上で良好なパフォーマンスを示し、平均精度 77.3%、平均交差および和集合率 31.6%、パラメーター数 1,283 万件、浮動小数点演算数 156 億件のみでした。さらに、視覚認識タスクをリアルタイムで実行できるため、実用的で正確なマルチタスク ソリューションとなります。
アルゴリズムのソースコード: https://github.com/datvuthanh/HybridNets.git
3. モデルのデプロイメントに C++ を使用する
トレーニング済みモデルを onnx 形式のモデルに変換し、推論に OpenCV 4.6 と contrib dnn モジュールを使用します。使用する IDE は Vs 2019 です。
まず、一人用のモデル推論用のクラスを定義します。これはデモンストレーションの便宜のためにここで使用され、推論にのみ CPU を使用します。
struct Net_config
{
float confThreshold;
float nmsThreshold;
std::string modelpath;
std::string anchorpath;
};
class HybridNets
{
public:
HybridNets(Net_config config);
cv::Mat detect(cv::Mat frame);
~HybridNets();
private:
int inpWidth;
int inpHeight;
std::vector<std::string> det_class_names = {
"car" };
std::vector<std::string> seg_class_names = {
"Background", "Lane", "Line" };
int det_num_class;
int seg_numclass;
float confThreshold;
float nmsThreshold;
cv::dnn::Net net;
float* anchors = nullptr;
const float mean_[3] = {
0.485, 0.456, 0.406 };
const float std_[3] = {
0.229, 0.224, 0.225 };
const bool keep_ratio = true;
cv::Mat resize_image(cv::Mat srcimg, int* newh, int* neww, int* padh, int* padw);
cv::Mat normalize_(cv::Mat img);
std::vector<cv::Vec3b> class_colors = {
cv::Vec3b(0,0,0), cv::Vec3b(0, 255, 0), cv::Vec3b(255, 0, 0) };
};
HybridNets::HybridNets(Net_config config)
{
this->confThreshold = config.confThreshold;
this->nmsThreshold = config.nmsThreshold;
this->net = cv::dnn::readNet(config.modelpath);
this->det_num_class = det_class_names.size();
this->seg_numclass = seg_class_names.size();
size_t pos = config.modelpath.rfind("_");
size_t pos_ = config.modelpath.rfind(".");
int len = pos_ - pos - 1;
std::string hxw = config.modelpath.substr(pos + 1, len);
pos = hxw.rfind("x");
std::string h = hxw.substr(0, pos);
len = hxw.length() - pos;
std::string w = hxw.substr(pos + 1, len);
this->inpHeight = stoi(h);
this->inpWidth = stoi(w);
pos = config.anchorpath.rfind("_");
pos_ = config.anchorpath.rfind(".");
len = pos_ - pos - 1;
std::string len_ = config.anchorpath.substr(pos + 1, len);
len = stoi(len_);
this->anchors = new float[len];
FILE* fp = fopen(config.anchorpath.c_str(), "rb");
fread(this->anchors, sizeof(float), len, fp);
fclose(fp);
}
HybridNets::~HybridNets()
{
delete[] anchors;
anchors = nullptr;
}
cv::Mat HybridNets::resize_image(cv::Mat srcimg, int* newh, int* neww, int* padh, int* padw)
{
int srch = srcimg.rows, srcw = srcimg.cols;
*newh = this->inpHeight;
*neww = this->inpWidth;
cv::Mat dstimg;
if (this->keep_ratio && srch != srcw) {
float hw_scale = (float)srch / srcw;
if (hw_scale > 1) {
*newh = this->inpHeight;
*neww = int(this->inpWidth / hw_scale);
resize(srcimg, dstimg, cv::Size(*neww, *newh), cv::INTER_LINEAR);
*padw = int((this->inpWidth - *neww) * 0.5);
copyMakeBorder(dstimg, dstimg, 0, 0, *padw, this->inpWidth - *neww - *padw, cv::BORDER_CONSTANT, 114);
}
else {
*newh = (int)this->inpHeight * hw_scale;
*neww = this->inpWidth;
resize(srcimg, dstimg, cv::Size(*neww, *newh), cv::INTER_LINEAR);
*padh = (int)(this->inpHeight - *newh) * 0.5;
copyMakeBorder(dstimg, dstimg, *padh, this->inpHeight - *newh - *padh, 0, 0, cv::BORDER_CONSTANT, 114);
}
}
else {
resize(srcimg, dstimg, cv::Size(*neww, *newh), cv::INTER_LINEAR);
}
return dstimg;
}
cv::Mat HybridNets::normalize_(cv::Mat img)
{
std::vector<cv::Mat> bgrChannels(3);
split(img, bgrChannels);
for (int c = 0; c < 3; c++)
{
bgrChannels[c].convertTo(bgrChannels[c], CV_32FC1, 1.0 / (255.0 * std_[c]), (0.0 - mean_[c]) / std_[c]);
}
cv::Mat m_normalized_mat;
merge(bgrChannels, m_normalized_mat);
return m_normalized_mat;
}
cv::Mat HybridNets::detect(cv::Mat srcimg)
{
int newh = 0, neww = 0, padh = 0, padw = 0;
cv::Mat rgbimg;
cvtColor(srcimg, rgbimg, cv::COLOR_BGR2RGB);
cv::Mat dstimg = this->resize_image(rgbimg, &newh, &neww, &padh, &padw);
cv::Mat normalized_mat = this->normalize_(dstimg);
cv::Mat blob = cv::dnn::blobFromImage(normalized_mat);
this->net.setInput(blob);
std::vector<cv::Mat> outs;
this->net.forward(outs, this->net.getUnconnectedOutLayersNames());
float* classification = (float*)outs[0].data;
float* box_regression = (float*)outs[1].data;
float* seg = (float*)outs[2].data;
std::vector<cv::Rect> boxes;
std::vector<float> confidences;
std::vector<int> classIds;
float ratioh = (float)srcimg.rows / newh, ratiow = (float)srcimg.cols / neww;
const int num_proposal = outs[1].size[1];
for (int n = 0; n < num_proposal; n++)
{
float conf = classification[n];
if (conf > this->confThreshold)
{
const int row_ind = n * 4;
float x_centers = box_regression[row_ind + 1] * this->anchors[row_ind + 2] + this->anchors[row_ind];
float y_centers = box_regression[row_ind] * this->anchors[row_ind + 3] + this->anchors[row_ind + 1];
float w = exp(box_regression[row_ind + 3]) * this->anchors[row_ind + 2];
float h = exp(box_regression[row_ind + 2]) * this->anchors[row_ind + 3];
float xmin = (x_centers - w * 0.5 - padw) * ratiow;
float ymin = (y_centers - h * 0.5 - padh) * ratioh;
w *= ratiow;
h *= ratioh;
cv::Rect box = cv::Rect(int(xmin), int(ymin), int(w), int(h));
boxes.push_back(box);
confidences.push_back(conf);
classIds.push_back(0);
}
}
std::vector<int> indices;
cv::dnn::NMSBoxes(boxes, confidences, this->confThreshold, this->nmsThreshold, indices);
cv::Mat outimg = srcimg.clone();
for (size_t i = 0; i < indices.size(); ++i)
{
int idx = indices[i];
cv::Rect box = boxes[idx];
rectangle(outimg, cv::Point(box.x, box.y), cv::Point(box.x + box.width, box.y + box.height), cv::Scalar(0, 0, 255), 2);
std::string label = cv::format("%.2f", confidences[idx]);
label = this->det_class_names[classIds[idx]] + ":" + label;
putText(outimg, label, cv::Point(box.x, box.y - 5), cv::FONT_HERSHEY_SIMPLEX, 0.75, cv::Scalar(0, 0, 255), 1);
}
const int area = this->inpHeight * this->inpWidth;
int i = 0, j = 0, c = 0;
for (i = 0; i < outimg.rows; i++)
{
for (j = 0; j < outimg.cols; j++)
{
const int x = int(j / ratiow) + padw;
const int y = int(i / ratioh) + padh;
int max_id = -1;
float max_conf = -10000;
for (c = 0; c < seg_numclass; c++)
{
float seg_conf = seg[c * area + y * this->inpWidth + x];
if (seg_conf > max_conf)
{
max_id = c;
max_conf = seg_conf;
}
}
if (max_id > 0)
{
outimg.at<cv::Vec3b>(i, j)[0] = this->class_colors[max_id][0];
outimg.at<cv::Vec3b>(i, j)[1] = this->class_colors[max_id][1];
outimg.at<cv::Vec3b>(i, j)[2] = this->class_colors[max_id][2];
}
}
}
return outimg;
}
ビデオを読んでモデルを推論に使用する
int main()
{
Net_config cfg = {
0.3, 0.5, "models/hybridnets_768x1280.onnx", "models/anchors_736560.bin" };
HybridNets net(cfg);
//cv::VideoWriter outputVideo;
cv::VideoCapture cap("test2.mp4");
//cv::Size S = cv::Size((int)cap.get(cv::CAP_PROP_FRAME_WIDTH),
//(int)cap.get(cv::CAP_PROP_FRAME_HEIGHT));
//std::string out_path = "dst.ma4";
if (cap.isOpened())
{
//outputVideo.open(out_path, -1, 30.0, S, true);
cv::Mat cv_fram;
while (1)
{
cap.read(cv_fram);
if (!cv_fram.empty())
{
cv::Mat outimg = net.detect(cv_fram);
//outputVideo << outimg;
cv::imshow("视频", outimg);
}
if (cv::waitKey(100) == 27)break;
}
}
cap.release();
return 0;
}
推論の効果は次のとおりです。
4. モデルのデプロイメントに Python を使用する
import cv2
import argparse
import numpy as np
import os
print(cv2.__version__)
class HybridNets():
def __init__(self, modelpath, anchorpath, confThreshold=0.5, nmsThreshold=0.5):
self.det_classes = ["car"]
self.seg_classes = ["Background", "Lane", "Line"]
self.net = cv2.dnn.readNet(modelpath)
self.confThreshold = confThreshold
self.nmsThreshold = nmsThreshold
h, w = os.path.basename(modelpath).split('_')[-1].replace('.onnx', '').split('x')
self.inpHeight, self.inpWidth = int(h), int(w)
self.mean_ = np.array([0.485, 0.456, 0.406], dtype=np.float32).reshape((1, 1, 3))
self.std_ = np.array([0.229, 0.224, 0.225], dtype=np.float32).reshape((1, 1, 3))
self.anchors = np.load(anchorpath) ### cx_cy_w_h
def resize_image(self, srcimg, keep_ratio=True):
padh, padw, newh, neww = 0, 0, self.inpWidth, self.inpHeight
if keep_ratio and srcimg.shape[0] != srcimg.shape[1]:
hw_scale = srcimg.shape[0] / srcimg.shape[1]
if hw_scale > 1:
newh, neww = self.inpHeight, int(self.inpWidth / hw_scale)
img = cv2.resize(srcimg, (neww, newh), interpolation=cv2.INTER_LINEAR)
padw = int((self.inpWidth - neww) * 0.5)
img = cv2.copyMakeBorder(img, 0, 0, padw, self.inpWidth - neww - padw, cv2.BORDER_CONSTANT,
value=(114, 114, 114)) # add border
else:
newh, neww = int(self.inpHeight * hw_scale), self.inpWidth
img = cv2.resize(srcimg, (neww, newh), interpolation=cv2.INTER_LINEAR)
padh = int((self.inpHeight - newh) * 0.5)
img = cv2.copyMakeBorder(img, padh, self.inpHeight - newh - padh, 0, 0, cv2.BORDER_CONSTANT,
value=(114, 114, 114))
else:
img = cv2.resize(srcimg, (self.inpWidth, self.inpHeight), interpolation=cv2.INTER_LINEAR)
return img, newh, neww, padh, padw
def detect(self, srcimg):
img, newh, neww, padh, padw = self.resize_image(cv2.cvtColor(srcimg, cv2.COLOR_BGR2RGB))
scale_h, scale_w = srcimg.shape[0] / newh, srcimg.shape[1] / neww
img = (img.astype(np.float32) / 255.0 - self.mean_) / self.std_
# Sets the input to the network
blob = cv2.dnn.blobFromImage(img)
self.net.setInput(blob)
classification, box_regression, seg = self.net.forward(self.net.getUnconnectedOutLayersNames())
x_centers = box_regression[..., 1] * self.anchors[..., 2] + self.anchors[..., 0]
y_centers = box_regression[..., 0] * self.anchors[..., 3] + self.anchors[..., 1]
w = np.exp(box_regression[..., 3]) * self.anchors[..., 2]
h = np.exp(box_regression[..., 2]) * self.anchors[..., 3]
xmin = x_centers - w * 0.5
ymin = y_centers - h * 0.5
bboxes_wh = np.stack([xmin, ymin, w, h], axis=2).squeeze(axis=0)
confidences = np.max(classification.squeeze(axis=0), axis=1) ####max_class_confidence
classIds = np.argmax(classification.squeeze(axis=0), axis=1)
mask = confidences > self.confThreshold
bboxes_wh = bboxes_wh[mask]
confidences = confidences[mask]
classIds = classIds[mask]
bboxes_wh -= np.array([[padw, padh, 0, 0]])
bboxes_wh *= np.array([[scale_w, scale_h, scale_w, scale_h]])
indices = cv2.dnn.NMSBoxes(bboxes_wh.tolist(), confidences.tolist(), self.confThreshold,
self.nmsThreshold).flatten().tolist()
drive_area_mask = np.squeeze(seg, axis=0)[:, padh:(self.inpHeight - padh), padw:(self.inpWidth - padw)]
seg_id = np.argmax(drive_area_mask, axis=0).astype(np.uint8)
seg_id = cv2.resize(seg_id, (srcimg.shape[1], srcimg.shape[0]), interpolation=cv2.INTER_NEAREST)
outimg = srcimg.copy()
for ind in indices:
x, y, w, h = bboxes_wh[ind,:].astype(int)
cv2.rectangle(outimg, (x, y), (x + w, y + h), (0, 0, 255), thickness=2, lineType=cv2.LINE_AA)
cv2.putText(outimg, self.det_classes[classIds[ind]]+ ":" + str(round(confidences[ind], 2)), (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255),
thickness=1, lineType=cv2.LINE_AA)
outimg[seg_id == 1] = [0, 255, 0]
outimg[seg_id == 2] = [255, 0, 0]
return outimg
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--imgpath', type=str, default='images/test.jpg', help="image path")
parser.add_argument('--modelpath', type=str, default='models/hybridnets_768x1280.onnx')
parser.add_argument('--anchorpath', type=str, default='models/anchors_768x1280.npy')
parser.add_argument('--confThreshold', default=0.3, type=float, help='class confidence')
parser.add_argument('--nmsThreshold', default=0.5, type=float, help='nms iou thresh')
args = parser.parse_args()
yolonet = HybridNets(args.modelpath, args.anchorpath, confThreshold=args.confThreshold,
nmsThreshold=args.nmsThreshold)
srcimg = cv2.imread(args.imgpath)
srcimg = yolonet.detect(srcimg)
cv2.namedWindow('dst', 0)
cv2.imshow('dst', srcimg)
cv2.waitKey(0)
cv2.destroyAllWindows()