0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenCVのDNNモジュールでセマンティックセグメンテーションをする(C++編)

Posted at

はじめに

セマンティックセグメンテーション (Semantic Segmentation)は、デジタル画像を複数の画像セグメント (画像領域または画像オブジェクトとも呼ばれる) に分割するプロセスです。
セグメンテーションの目的は、画像の表現を簡略化して、より意味があり分析しやすいものにすることです。
たとえば、自動運転車は車両、歩行者、交通標識、歩道、その他の道路の特徴を識別する必要があります。

今回はこのセマンティックセグメンテーションをOpenCVのDNNモジュールを使いやってみたいと思います。

準備

OpenCVを使うには事前にcmakeやC++コンパイラ、OpenCVがインストールされていないといけません。

Linux

Linuxではapt-getでインストールできます。

apt-get install cmake make g++ gcc libopencv-dev

Windows

Windowsでビルドするには以下のサイトをみてcmakeやC++コンパイラやOpenCVを準備をしてください。

WindowsにCmakeを導入
https://qiita.com/matskeng/items/c466c4751e1352f97ce6

MinGW-w64のインストール
https://qiita.com/yomei_o/items/2e706a5fa3ac5ffc3a50

WindowsでOpenCVをMinGW-w64でビルド
https://qiita.com/yomei_o/items/4d794536e24ffc12e29d

Mac

macでも以下のような感じでインストールできます。
https://qiita.com/kai_kou/items/df335eb7ee78229ee46f

セグメンテーションの学習済みONNXモデルの取得

今回はPytorchのセマンティックセグメンテーションのサイトからセマンティックセグメンテーションのモデルデータを取得します。

https://pytorch.org/hub/pytorch_vision_deeplabv3_resnet101/

Pytorchのモデルデータは、簡単にONNX形式に変換することができます。

import torch.onnx 
import torch

model = torch.hub.load('pytorch/vision:v0.10.0', 'deeplabv3_resnet50', pretrained=True)
# or any of these variants
# model = torch.hub.load('pytorch/vision:v0.10.0', 'deeplabv3_resnet101', pretrained=True)
# model = torch.hub.load('pytorch/vision:v0.10.0', 'deeplabv3_mobilenet_v3_large', pretrained=True)

model.eval() 

onnx_file = "deeplabv3_resnet50.onnx"

dummy_input = torch.randn(1, 3, 512, 512)
output = model(dummy_input)

torch.onnx.export(
    model=model,  # モデルの指定
    args=(torch.randn((1, 3, 512, 512))),
    f=onnx_file,  # 出力ファイル名
    export_params=True,
    opset_version=17,  # opsetのバージョン指定
    input_names=["input"],  # 入力ノード名
    output_names=["output"],  # 出力ノード名
)

プログラムの作成

適当なディレクトリに「CMakeLists.txt」と「opencv_deeplabv3.cpp」のファイルを置きます。

opencv_deeplabv3.cpp

#include<opencv2/opencv.hpp>

static int inputWidth = 512;
static int inputHeight = 512;

constexpr int colorList[8][3] = {
    {255, 0, 0},
    {0, 255, 0},
    {0, 0, 255},
    {0, 255, 255},
    {255, 0, 255},
    {255, 255, 0},
    {255, 255, 255},
    {0, 0, 0} };


void tensor_to_mat(float* p, cv::Mat& map)
{
    unsigned int count = 0;
    map = cv::Mat::zeros(inputHeight, inputWidth, CV_8U);

    for (int i = 0; i < inputHeight * inputWidth; i++)
    {
        const unsigned char val = p[i];
        const int y = count / inputWidth;
        const int x = count % inputWidth;
        map.at<unsigned char>(y, x) = val;
        count++;
    }
}


void normalize(const cv::Mat& inputImage, cv::Mat& outputImage)
{
    const cv::Scalar imgMean = cv::Scalar(123.675, 116.28, 103.53);
    const cv::Scalar imgStd = cv::Scalar(58.395, 57.12, 57.375);
    inputImage.convertTo(outputImage, CV_32F);
    float* d = (float*)outputImage.data;
    float c;
    for (int i = 0; i < outputImage.rows; i++)
    {
        for (int j = 0; j < outputImage.cols; j++)
        {
            for (int k = 0; k < outputImage.channels(); k++)
            {
                c = inputImage.at<cv::Vec3b>(i, j)[k];
                c -= (float)imgMean(k);
                c /= (float)imgStd(k);
                d[k * 512 * 512 + i * 512 + j] = c;
            }

        }
    }
}

constexpr int numClasses = 4;
constexpr int color_list[numClasses][3] = {
    {255, 0, 0},
    {0, 255, 0},
    {0, 0, 255},
    {255, 255, 0}
};

void drawSegmentationMap(const cv::Mat& input, const cv::Mat& segmentation, cv::Mat& output)
{
    cv::Mat resizedSegmentation;
    cv::resize(segmentation, resizedSegmentation, cv::Size(input.cols, input.rows), 0, 0, cv::INTER_NEAREST);

    cv::Mat overlay = cv::Mat::zeros(input.rows, input.cols, CV_8UC3);
    for (int classId = 0; classId < numClasses; classId++)
    {
        cv::Mat mask;
        cv::inRange(resizedSegmentation, classId, classId, mask);

        overlay.setTo(
            cv::Scalar(color_list[classId][0], color_list[classId][1], color_list[classId][2]), mask);
    }

    const float alpha = 0.5;
    cv::addWeighted(input, alpha, overlay, 1 - alpha, 0, output);
}


int main()
{

    
    cv::Mat image_bgr = cv::imread("deeplab1.png");
    cv::Mat image_rgb;
    cv::Mat image_n;
    cv::cvtColor(image_bgr,image_rgb, cv::COLOR_BGR2RGB);
    cv::resize(image_rgb, image_rgb, cv::Size(inputWidth, inputHeight));
    normalize(image_rgb,image_n);
    int size_2[4] = { 1,3,inputHeight,inputWidth };
    cv::Mat inpBlob(4, size_2, CV_32FC1, image_n.data);
    
    cv::dnn::Net net = cv::dnn::readNetFromONNX("deeplabv3_resnet50.onnx");
    net.setInput(inpBlob);


    cv::Mat out;
    out=net.forward();

    float* v = (float*)out.data;
    float* mm = new float[512 * 512];
    float m, aa;
    for (int y = 0; y < 512; y++)for (int x = 0; x < 512; x++) {
        m = 0;
        aa = -10000;
        for (int i = 0; i < 21; i++) {
            //printf("%d  %f\n", i, v[512 * 512 * i + 512 * y + x]);
            if (v[512 * 512 * i + 512 * y + x] > aa) {
                aa = v[512 * 512 * i + 512 * y + x];
                m = i;
            }
        }
        mm[y * 512 + x] = m ? 1 : 0;
        //if (y % 4 == 0 && x % 4 == 0)printf("%d,", (int)m);
    }

    cv::Mat output;
    tensor_to_mat(mm, output);
    delete[] mm;

    cv::Mat outputImage;
    drawSegmentationMap(image_bgr, output, outputImage);

    cv::imwrite("output.bmp", outputImage);
    return true;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.13)
project(test_cmake CXX C)

find_package(OpenCV REQUIRED)

add_executable(test opencv_deeplabv3.cpp)

target_include_directories(test PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries (test PRIVATE gdi32 comdlg32 ${OpenCV_LIBS})

画像は次のものを使用しました。

deeplab1.png
deeplab1.png

OpenCVのDNNモジュールの使い方は解説するまでもなくとてもシンプルです。
まず、以下のようにネットワークモデルを読み込みます。

net=cv::dnn::readNetFromONNX("deeplabv3_resnet50.onnx");

次に、イメージをfloatの配列に変換します。

blob=cv::dnn::blobFromImage(img);

データをネットにセットします。

net.setInput(blob);

そして、推論の実行をします。

cv::Mat output = net.forward();

ビルド

LinuxやMacでは先ほど使ったディレクトリでシェルから以下のコマンドを実行すればプログラムがビルドされます。

mkdir build
cd build
cmake ..
make

Windowsでビルドする場合はこんな感じでビルドします。

mkdir build
cd build
set OpenCV_DIR=C:\prog\opencv-4.10.0\build\install
cmake -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -G "MinGW Makefiles" ..
make

結果

今回は、何が物体を検出したエリアを緑色で塗りつぶすようにしてみました。

deeplab1_output_opencv.jpg

大体あっているようですが、よく見ると羊の脚の部分や犬など醜い部分が見分けられてないですね。
OpenCVを使ったセマンティックセグメンテーションのやり方でした。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?