
交通標識検索アルゴリズムの独自の実装を共有したいと思います。
なぜ輪郭解析なのか?
輪郭解析の干渉に対する耐性はやや弱いですが、単純さと速度により、このアプローチを非常にうまく適用できました。
ただし、実際には、Androidプラットフォームで必要な係数の検索を実装することは非常に困難であることが判明しました(OpenCVウィジェットを使用しようとはせず、代わりに画面をフレームに分割しました。設定は左側にあり、背面カメラからのビデオストリームは右側にあります)。 UIおよびプロジェクトロジックの特定の実装は、以下のリンクから入手できます。
交通標識を検索する一連の操作は次のとおりです。
- ノイズリダクション
たとえば、非線形フィルターを通して
cv::medianBlur(original, original, 3);
- 画像をRGBAからHSVに変換する
HSVカラーモデルを使用する場合、道路標識のベースの赤色は、色調、彩度、明るさに基づいてより正確に区別されます。
同時に、色調について話すと、通常は正確に色を意味します。 彩度は、記述されている色がどれだけ白で希釈されているかを示します(たとえば、ピンクは赤と白の混合です)。 明るさの概念を説明するのは最も難しく、いくつかの仮定では、明るさは光の強さとして理解できます。
cv::Mat hsv; cv::cvtColor(original, hsv, cv::COLOR_RGB2HSV); if (layerType == LAYER_HSV) { *(cv::Mat*)matAddress = hsv; }
- 道路標識を色で強調表示する
処理中、画像は個別のチャンネルH、S、Vに分割され、特定のしきい値でさらに2値化され、論理的な加算によって最終画像マトリックスに結合されます。
std::vector<cv::Mat> channels; cv::split(hsv, channels); cv::Mat minHueThreshold = channels[0] < lowerHue; if (layerType == LAYER_HUE_LOWER) { *(cv::Mat*)matAddress = minHueThreshold; } cv::Mat maxHueThreshold = channels[0] > upperHue; if (layerType == LAYER_HUE_UPPER) { *(cv::Mat*)matAddress = maxHueThreshold; } if (layerType == LAYER_HUE) { *(cv::Mat*)matAddress = minHueThreshold | maxHueThreshold; } cv::Mat saturationThreshold = channels[1] > minSaturation; if (layerType == LAYER_SATURATION) { *(cv::Mat*)matAddress = saturationThreshold; } cv::Mat valueThreshold = channels[2] > minValue; if (layerType == LAYER_VALUE) { *(cv::Mat*)matAddress = valueThreshold; } cv::Mat colorFiltered = (minHueThreshold | maxHueThreshold) & saturationThreshold & valueThreshold; if (layerType == LAYER_RED_FILTERED) { *(cv::Mat*)matAddress = colorFiltered; }
- 外部交通標識の検出
まず、ストレッチングの操作が実行されます。これにより、ノイズが除去され、オブジェクト、シャドウによって分離された画像領域が統一されます。
cv::Mat colorDilated; cv::dilate(colorFiltered, colorDilated, cv::Mat()); if (layerType == LAYER_DILATED) { *(cv::Mat*)matAddress = colorDilated; }
輪郭を検索するには、SimpleBlobDetectorクラスを使用します。これは、丸いオブジェクトに対するfindContours()関数の特殊な場合にすぎません。 外部およびネストされた輪郭を見つけて、それらのアタッチメント階層を決定できます。
cv::SimpleBlobDetector::Params params; params.filterByColor = false; params.filterByConvexity = false; params.filterByInertia = false; params.filterByArea = true; // A = 254.46900494077 px^2 9 px params.minArea = 255; // A = 723822.94738709 px^2 480 px params.maxArea = 723823; params.filterByCircularity = true; params.minCircularity = 0.85f; cv::Ptr<cv::SimpleBlobDetector> detector = cv::SimpleBlobDetector::create(params); std::vector<cv::KeyPoint> keyPoints; detector->detect(colorDilated, keyPoints);
keyPointsベクトルは、道路標識の輪郭の座標と半径を記録し、画面上で強調表示されます。
実際の状況ではうまく機能するとは言えません(おそらく、検出アルゴリズムは最適ではありませんが、最も使いやすいと判明しました)。 また、最適な係数を見つけるのは困難です(私の都市では道路標識の「狩り」がたくさんありました。日焼けした標識に出会ったこともあり、それらにはまったく赤がありませんでした)。
しかし、単純さは目を楽しませてくれるので、たとえばロボット工学などで輪郭解析の用途を見つけることは非常に可能です。
AndroidでOpenCVを使用した経験に関する追加の利点:
カメラの画像が上下逆になっている場合は回転させます
extern "C" JNIEXPORT void JNICALL Java_ru_dksta_prohibitingsigndetector_ActivityMain_rotation(JNIEnv /* *env */, jclass /* activity */, jlong matAddress, jint angle) { CV_Assert(angle % 90 == 0 && angle <= 360 && angle >= -360); cv::Mat* mat = (cv::Mat*) matAddress; if (angle == 180 || angle == -180) { cv::flip(*mat, *mat, -1); } }
ソルト&ペッパーノイズの実装
extern "C" JNIEXPORT void JNICALL Java_ru_dksta_prohibitingsigndetector_ActivityMain_saltPepperNoise(JNIEnv /* *env */, jclass /* activity */, jlong matAddress) { cv::Mat* mat = (cv::Mat*) matAddress; cv::Mat noise = cv::Mat::zeros((*mat).rows, (*mat).cols, CV_8U); cv::randu(noise, 0, 255); cv::Mat black = noise < 30; cv::Mat white = noise > 225; (*mat).setTo(255, white); (*mat).setTo(0, black); }
「ピクチャーインピクチャー」モードで処理するカメラのビデオを表示する
if (secondView) { cv::Mat miniView = colorDilated.clone(); cv::cvtColor(miniView, miniView, cv::COLOR_GRAY2RGB); cv::resize(miniView, miniView, cv::Size(), 0.6, 0.6, cv::INTER_LINEAR); cv::Size miniSize = miniView.size(); cv::Size maxSize = original.size(); int startY = maxSize.height - miniSize.height; for (int y = startY; y < maxSize.height; y++) { for (int x = 0; x < miniSize.width; x++) { (*(cv::Mat*)matAddress).at<cv::Vec3b>(cv::Point(x, y)) = miniView.at<cv::Vec3b>(cv::Point(x, y - startY)); } } }
OpenCVでテキストを書く
cv::Mat* mat = (cv::Mat*)matAddress; int textStartY = TEXT_LINE_HEIGHT; std::ostringstream output; output << std::setw(2) << std::setfill('0') << fpsCount << " FPS"; cv::putText(*mat, output.str(), cv::Point(TEXT_START_X, textStartY), FONT_FACE, FONT_SCALE, GREEN, TEXT_THICKNESS); output.seekp(0); textStartY += TEXT_LINE_HEIGHT; cv::putText(*mat, getLayerTypeDesc(layerType), cv::Point(TEXT_START_X, textStartY), FONT_FACE, FONT_SCALE, GREEN, TEXT_THICKNESS);
→ プロジェクトへのリンク