search
LoginSignup
5
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

OpenCV Advent Calendar 2020 Day 25

posted at

updated at

ラズパイ3のGPUでOpenCVを動かす

はじめに

ラズパイのGPUで高速化ができるという話を聞いて、これはぜひ試したいと思いラズパイを購入してOpenCV動かしてみました、という記事です。

https://qiita.com/9_ties/items/15ab7fa198991a61a3a9
https://asukiaaa.blogspot.com/2018/11/raspberry-pigpuopencl.html
https://p-0.me/b/p/593/

ラズパイのGPUを使用する方法としては、ラズパイ3であればVideoCore4というGPUが使用されており、ISA(命令セットアーキテクチャ)も公開されていてプログラムから使用するためのライブラリがいくつか公開されています。

https://github.com/doe300/VC4CL
https://github.com/nineties/py-videocore

ラズパイ4の場合はVideoCore6というGPUを使用しているのですが、IPAが公開されておらず、使用するハードルは高そうです。(しかし、ハックしてpythonから使用できるようにした強者がおられます Link)

TensorflowだとOpenGLESのGPU Delegateというのを使用してGPUでの計算ができるようですね。
https://qiita.com/terryky/items/fa18bd10cfead076b39f

今回はOpenCL経由でOpenCVを動かすのが最も簡単そうなので、ラズパイ3とVC4CLを使ってOpenCLでGPUを使用するという方法を取りたいと思います。
先人がいろいろとリソースを用意してくださっているため、この記事はそれのまとめと、とりあえずやってみましたという感じの記事になっています。

セットアップ

VC4CLの取得

ラズパイのGPUをOpenCLのインターフェースで使用するためのソフトです。
リポジトリのWikiに沿って、debパッケージが用意されているのでそれらをダウンロードしてインストールします。

clinfoでインストールできたかの確認を行います。(VideoCore4にアクセスするにはsudoが必要みたいです。)

sudo clinfo

OpenCVのビルド

以下のリポジトリがVC4CLを使ったOpenCVのコンパイルについてシェルスクリプトなどがまとめられています。
今回、OpenCVのバージョンは4.0.1を使っています。

aptで以下を取ってきます。


sudo apt-get install -y \
     build-essential \
     gettext \
     ccache \
     cmake \
     pkg-config \
     libpng-dev \
     libpng++-dev \
     libjpeg-dev \
     libtiff5-dev \
     libjasper-dev \
     libavcodec-dev \
     libavformat-dev \
     libavresample-dev \
     libswresample-dev \
     libavutil-dev \
     libswscale-dev \
     libv4l-dev \
     libxvidcore-dev \
     libx264-dev \
     libgtk-3-dev \
     libgdk-pixbuf2.0-dev \
     libpango1.0-dev \
     libcairo2-dev \
     libfontconfig1-dev \
     libatlas-base-dev \
     liblapack-dev \
     liblapacke-dev \
     libblas-dev \
     libopenblas-dev \
     gfortran \
     python-pip \
     python3-pip \
     python-numpy \
     python-dev \
     python3-dev \
     libeigen2-dev \
     libeigen3-dev \
     libopenexr-dev \
     libgstreamer1.0-dev \
     libgstreamermm-1.0-dev \
     libgoogle-glog-dev \
     libgflags-dev \
     libprotobuf-c-dev \
     libprotobuf-dev \
     protobuf-c-compiler \
     protobuf-compiler \
     libgphoto2-dev \
     qt5-default \
     libvtk6-dev \
     libvtk6-qt-dev \
     libhdf5-dev \
     freeglut3-dev \
     libgtkglext1-dev \
     libgtkglextmm-x11-1.2-dev \
     libwebp-dev \
     libtbb-dev \
     libdc1394-22-dev \
     libunicap2-dev \
     ffmpeg 

opencvとopencv-contribをgithubからクローンしてきて以下のようなフォルダ構成を作りました。

.
├── opencv
│   ├── 3rdparty
│   ├── apps
│   ├── build
│   ├── cmake
│   ├── data
│   ├── doc
│   ├── include
│   ├── modules
│   ├── platforms
│   └── samples
└── opencv_contrib
    ├── doc
    ├── modules
    └── samples

opencvのbuildフォルダに移動し、ビルドを行います。
cmakeオプションは上のリポジトリを参考に以下のようにしました。

cd opencv
mkdir build
cd build
cmake -D CMAKE_BUILD_TYPE=RELEASE \
       -D CMAKE_INSTALL_PREFIX=/usr/local \
       -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \
       -D BUILD_SHARED_LIBS=ON \
       -D BUILD_CUDA_STUBS=ON \
       -D BUILD_DOCS=OFF \
       -D BUILD_ZLIB=OFF \
       -D BUILD_TIFF=OFF \
       -D BUILD_JPEG=OFF \
       -D BUILD_JASPER=OFF \
       -D BUILD_PNG=OFF \
       -D BUILD_OPENEXR=OFF \
       -D BUILD_PERF_TESTS=OFF \
       -D BUILD_TBB=OFF \
       -D BUILD_WEBP=OFF \
       -D BUILD_TESTS=OFF \
       -D BUILD_EXAMPLES=OFF \
       -D BUILD_JAVA=OFF \
       -D WITH_EIGEN=ON \
       -D WITH_GSTREAMER=ON \
       -D WITH_GTK=ON \
       -D WITH_JASPER=ON \
       -D WITH_JPEG=ON \
       -D WITH_OPENEXR=ON \
       -D WITH_PNG=ON \
       -D WITH_TIFF=ON \
       -D WITH_V4L=ON \
       -D WITH_LIBV4L=ON \
       -D WITH_VTK=ON \
       -D WITH_LAPACK=ON \
       -D WITH_LAPACKE=ON \
       -D WITH_PROTOBUF=ON \
       -D WITH_1394=ON \
       -D WITH_EIGEN=ON \
       -D WITH_FFMPEG=ON \
       -D WITH_GPHOTO2=ON \
       -D WITH_OPENGL=OFF \
       -D WITH_QT=ON \
       -D WITH_TBB=ON \
       -D WITH_WEBP=ON \
       -D WITH_UNICAP=ON \
       -D WITH_OPENNI=OFF \
       -D WITH_GDAL=OFF \
       -D WITH_CUBLAS=OFF \
       -D WITH_NVCUVID=OFF \
       -D WITH_CUDA=OFF \
       -D WITH_CUFFT=OFF \
       -D WITH_IPP=OFF \
       -D WITH_IPP_A=OFF \
       -D WITH_OPENMP=ON \
       -D WITH_PTHREADS_PF=OFF \
       -D WITH_PVAPI=OFF \
       -D WITH_MATLAB=OFF \
       -D WITH_XIMEA=OFF \
       -D WITH_XINE=OFF \
       -D WITH_OPENCL=ON \
       -D WITH_OPENCLAMDBLAS=OFF \
       -D WITH_OPENCLAMDFFT=OFF \
       -D WITH_OPENCL_SVM=OFF \
       -D INSTALL_PYTHON_EXAMPLES=ON \
       -D ENABLE_CXX11=ON \
       -D ENABLE_CCACHE=ON \
       -D ENABLE_FAST_MATH=ON \
       -D ENABLE_NEON=ON \
       -D ENABLE_VFPV3=ON \
       -D ENABLE_OMIT_FRAME_POINTER=ON \
       -D BUILD_opencv_apps=ON \
       -D BUILD_opencv_aruco=ON \
       -D BUILD_opencv_bgsegm=ON \
       -D BUILD_opencv_calib3d=ON \
       -D BUILD_opencv_bioinspired=ON \
       -D BUILD_opencv_dnn=ON \
       -D BUILD_opencv_dpm=ON \
       -D BUILD_opencv_core=ON \
       -D BUILD_opencv_face=ON \
       -D BUILD_opencv_features2d=ON \
       -D BUILD_opencv_flann=ON \
       -D BUILD_opencv_freetype=ON \
       -D BUILD_opencv_fuzzy=ON \
       -D BUILD_opencv_hfs=ON \
       -D BUILD_opencv_highgui=ON \
       -D BUILD_opencv_imgcodecs=ON \
       -D BUILD_opencv_imgproc=ON \
       -D BUILD_opencv_ml=ON \
       -D BUILD_opencv_objdetect=ON \
       -D BUILD_opencv_optflow=ON \
       -D BUILD_opencv_phase_unwrapping=ON \
       -D BUILD_opencv_photo=ON \
       -D BUILD_opencv_plot=ON \
       -D BUILD_opencv_python2=ON \
       -D BUILD_opencv_python3=ON \
       -D BUILD_opencv_reg=ON \
       -D BUILD_opencv_rgbd=ON \
       -D BUILD_opencv_saliency=ON \
       -D BUILD_opencv_shape=ON \
       -D BUILD_opencv_stereo=ON \
       -D BUILD_opencv_stitching=ON \
       -D BUILD_opencv_superres=ON \
       -D BUILD_opencv_surface_matching=ON \
       -D BUILD_opencv_text=ON \
       -D BUILD_opencv_tracking=ON \
       -D BUILD_opencv_ts=ON \
       -D BUILD_opencv_video=ON \
       -D BUILD_opencv_videoio=ON \
       -D BUILD_opencv_videostab=ON \
       -D BUILD_opencv_viz=OFF \
       -D BUILD_opencv_world=OFF \
       -D BUILD_opencv_xfeature2d=ON \
       -D BUILD_opencv_ximgproc=ON \
       -D BUILD_opencv_xobjdetect=ON \
       -D BUILD_opencv_xphoto=ON \
       -D BUILD_opencv_java=OFF \
       -D BUILD_opencv_cudaarithm=OFF \
       -D BUILD_opencv_cudabgsegm=OFF \
       -D BUILD_opencv_cudacodec=OFF \
       -D BUILD_opencv_cudafeatures2d=OFF \
       -D BUILD_opencv_cudafilters=OFF \
       -D BUILD_opencv_cudaimgproc=OFF \
       -D BUILD_opencv_cudalegacy=OFF \
       -D BUILD_opencv_cudaobjdetect=OFF \
       -D BUILD_opencv_cudaoptflow=OFF \
       -D BUILD_opencv_cudastereo=OFF \
       -D BUILD_opencv_cudawarping=OFF \
       -D BUILD_opencv_cudev=OFF \
       -D Tesseract_INCLUDE_DIR=$TESS_INC_DIR \
       -D Tesseract_LIBRARY=$TESS_LIBRARY \
       -D OPENCL_INCLUDE_DIR=/usr/include \
       -D OPENCL_LIBRARY=/usr/lib/arm-linux-gnueabihf/libOpenCL.so ..

opencvをラズパイでビルドする際はスワップ領域を増やしておくことも忘れずに行っておきましょう。

swapon
sudo sed -i -e 's/SWAPSIZE=.*/SWAPSIZE=2048/g' /etc/dphys-swapfile
sudo systemctl restart dphys-swapfile.service
swapon

cmakeが通ったら、ビルドしてインストールします。

make
sudo make install

簡単なプログラムを動かしてみる

とりあえず、以下のようなカラー画像を白黒にするだけのプログラムを作り、動くかどうか試してみます。
OpenCLを使うために、UMatを使用します。

opencv_test.cpp
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>

using namespace cv;

int main(int argc, const char* argv[]) {
    Mat src = imread("lena.jpg", IMREAD_UNCHANGED);
    UMat usrc;
    src.copyTo(usrc);
    UMat dst;
    cvtColor(usrc, dst, COLOR_BGR2GRAY);
    imwrite("lena_gray.jpg", dst);
    return 0;
}

こちらも実行の際はsudoが必要です。

# ./opencv_test 
# [VC4CL] can't open /dev/mem
# [VC4CL] This program should be run as root. Try prefixing command with: sudo
sudo ./opencv_test

プロファイルをとって実際にGPUが使用されているか調べたかったのですが、VC4CLのプロファイラの使い方が分からなかったので、次のいろんな画像サイズでのフィルタするプログラムをCPUとGPUで速度比較して確認したいと思います。

いろんな画像サイズでフィルタしてみる

いろんな画像サイズでbilateralFilterを使って、CPU使用時とGPU使用時の速度比較を行っていみました。

filter.cpp
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace cv;

int main(int argc, const char * argv[])
{
    /* input image */
    Mat src = imread("lena.jpg", IMREAD_UNCHANGED);
    resize(src, src, Size(), 1, 1);
    UMat usrc;
    src.copyTo(usrc);
    UMat udst, ufilt;
    cvtColor(usrc, udst, COLOR_BGR2GRAY);
    auto start = getTickCount();
    bilateralFilter(udst, ufilt, 8, 15, 15);
    auto end = getTickCount();
    double elapsedmsec = (end - start) * 1000 / getTickFrequency();
    std::cout << elapsedmsec << std::endl;
    imwrite("lena_filt.jpg", ufilt);

    return 0;
}

やり方としては入力画像をresizeでサイズ変更してサイズを大きくしていった際にbilateralFilterにかかった時間を計測します。
CPUで実行する場合は、上のプログラムのUMatを普通のMatにすることでCPUで実行可能です。
サイズとかかった時間の表を以下に示します。

画像サイズ CPU[ms] GPU[ms]
512x512 48.2466 336.359
2560x2560 557.283 7510.3
5120x5120 2022.19 2541.41
7680x7680 4699.34 5896.73
10240x10240 22274.4 18024.3
12800x12800 35434.4 32194.6

これ以上はメモリ不足で実行できませんでした。10240x10240から少しGPUのほうが速くなってきているという感じでした。
(なぜかGPUでは線形に計算量が増えず途中で一旦計算時間が減るという現象が起きています。)

おまけ(Yolov3を動かしてみる→動かない)

DNNモジュールが動くと嬉しいなと思い、Yolov3の学習済みモデルを以下から取ってきて、OpenCV+VC4Lで動くかどうか試してみました。
https://pjreddie.com/darknet/yolo/

プログラムは以下のようなものを用意しました。

yolo.cpp
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utils/trace.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace cv::dnn;
using namespace std;

string 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 drier",
                    "toothbrush"};


vector<String> getOutputsNames(const Net& net) {
    static vector<String> names;
    if (names.empty()) {
        vector<int> outLayers = net.getUnconnectedOutLayers();
        vector<String> layersNames = net.getLayerNames();
        names.resize(outLayers.size());
        for (size_t i = 0; i < outLayers.size(); ++i)
            names[i] = layersNames[outLayers[i] - 1];
    }
    return names;
}

void draw_box(int classId, float conf, int left, int top, int right, int bottom, Mat& frame) {
    rectangle(frame, Point(left, top), Point(right, bottom), Scalar(255, 178, 50), 3);
    string label = format("%.2f", conf);
    label = CLASSES[classId] + ":" + label;
    int baseLine;
    Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
    top = max(top, labelSize.height);
    rectangle(frame, Point(left, top - round(1.5*labelSize.height)), Point(left + round(1.5*labelSize.width), top + baseLine), Scalar(255, 255, 255), FILLED);
    putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0,0,0), 1);
}

void postprocess(Mat& frame, const vector<Mat>& outs) {
    const float confThreshold = 0.5;
    const float nms = 0.4;
    vector<int> classIds;
    vector<float> confidences;
    vector<Rect> boxes;
    for (size_t i = 0; i < outs.size(); ++i) {
        float* data = (float*)outs[i].data;
        for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols) {
            Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
            Point classIdPoint;
            double confidence;
            minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
            if (confidence > confThreshold) {
                int centerX = (int)(data[0] * frame.cols);
                int centerY = (int)(data[1] * frame.rows);
                int width = (int)(data[2] * frame.cols);
                int height = (int)(data[3] * frame.rows);
                int left = centerX - width / 2;
                int top = centerY - height / 2;
                classIds.push_back(classIdPoint.x);
                confidences.push_back((float)confidence);
                boxes.push_back(Rect(left, top, width, height));
            }
        }
    }
    vector<int> indices;
    NMSBoxes(boxes, confidences, confThreshold, nms, indices);
    for (size_t i = 0; i < indices.size(); ++i)
    {
        int idx = indices[i];
        Rect box = boxes[idx];
        draw_box(classIds[idx], confidences[idx], box.x, box.y,
                 box.x + box.width, box.y + box.height, frame);
    }
}

int main(int argc, char **argv)
{
    CV_TRACE_FUNCTION();
    String modelConfiguration = "yolov3.cfg";
    String modelWeights = "yolov3.weights";

    String imageFile = "2.jpeg";
    Net net = dnn::readNetFromDarknet(modelConfiguration, modelWeights);
    net.setPreferableBackend(DNN_BACKEND_OPENCV);
    net.setPreferableTarget(DNN_TARGET_OPENCL);
    Mat frame = imread(imageFile);

    Mat inputBlob = blobFromImage(frame, 1 / 255.0, Size(416, 416), Scalar(0, 0, 0), true);
    net.setInput(inputBlob);
    vector<Mat> outs;
    net.forward(outs, getOutputsNames(net));
    postprocess(frame, outs);

    vector<double> layersTimes;
    double freq = getTickFrequency() / 1000;
    double t = net.getPerfProfile(layersTimes) / freq;
    string label = format("Inference time for a frame : %.2f ms", t);
    putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255));

    imwrite("detection.jpg", frame);
    return 0;
}

いくつか関連するイシューが挙がっており、VC4CLをオプション変えてコンパイルしたり、OpenCVのDNNモジュールがOpenCLではIntel向けのオプションを渡しているっぽいのでそれを無視するようにしてみたりしてみましたが、結局動きませんでした。

https://github.com/opencv/opencv/issues/13852
https://github.com/doe300/VC4CL/issues/59
https://github.com/doe300/VC4CL/issues/29

イシューでは動いているみたいなコメントもあったのですが、やり方の詳細があまり書いておらず実際どうなのか分かりません。

感想

GPUの効果を得るにはかなり大きな画像じゃないと厳しい感じでしたが、ラズパイのリソースを使いこなしている感があって楽しいですね。
bilateralFilter以外の関数でどういう結果になるかも気になります。
慣れてきたら、Realsenseとか使ってOpenCLを使った点群処理とかも行わせてみたいなと思います。
来年も良いOpenCVライフを送りましょう!

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
What you can do with signing up
5
Help us understand the problem. What are the problem?