概要

OpenCVの応用例として、今回はAhmet Yaylaliogluさんの「指を数える」という例をGR-LYCHEEで実行してみます。輪郭検出や凹凸の検出、HSV色空間を利用した肌色検出などを応用した例です。

留意:GR-LYCHEEに搭載されているRZ/A1LU MPUはRAMが3MBですので、処理できることは限定されてきます。また、ROMに書き込むメモリも大きくなり、書込み時間が30秒ぐらいに長くなりますので、その点はご留意ください。

Basic Hand Detection Finger Counting with GR-LYCHEE


準備

ハードウェア

GR-LYCHEE、USBケーブル(マイクロBタイプ)を準備します。カメラの取り付け方は「カメラや付属品、LCDの取り付け方」を参照してください。

ソフトウェア

DisplayAppをダウンロードして解凍してください。PCでカメラの画像を確認するためのアプリケーションです。


指を数える

それでは試してみましょう。以下のプログラムを実行してみてください。

プログラム書き込みには30秒ほどかかりますので、書込みが終わってMbedドライブが再認識されるまで、USBケーブルを抜いたりしないでください。


// GR-LYCHEE OpenCV finger count example.
// Referred :
// https://github.com/redbeardanil/Estimation-of-the-number-of-real-time-hand-fingers-with-image-processing/blob/master/ElAlgilaV2.cpp

#include <Arduino.h>
#include <Camera.h>
#include <DisplayApp.h>
#include <opencv.hpp>
 
using namespace cv;
#define IMAGE_HW 320
#define IMAGE_VW 240
// 1MB of NC_BSS is not used for malloc function.
// It's better to secure buffer as a static memory in this NC_BSS
uint8_t bgr_buf[3 * IMAGE_HW * IMAGE_VW]__attribute((section("NC_BSS"),aligned(32)));
uint8_t hsv_buf[3 * IMAGE_HW * IMAGE_VW]__attribute((section("NC_BSS"),aligned(32)));
uint8_t syn_buf[3 * IMAGE_HW * IMAGE_VW]__attribute((section("NC_BSS"),aligned(32)));
 
Camera camera(IMAGE_HW, IMAGE_VW);
DisplayApp display_app;
bool g_mode = false;
 
void setup() {
    camera.begin();
    delay(100);
}
 
void loop() {
    static unsigned long last_time = 0;
    if((millis() - last_time) > 2000){
        g_mode = !g_mode;
        last_time = millis();
    }
 
    Mat img_raw(IMAGE_VW, IMAGE_HW, CV_8UC2, camera.getImageAdr());
    Mat img_bgr(IMAGE_VW, IMAGE_HW, CV_8UC3, bgr_buf);
    cvtColor(img_raw, img_bgr, COLOR_YUV2BGR_YUYV); // copy camera image to img_bgr
 
    Mat img_hsv(IMAGE_VW, IMAGE_HW, CV_8UC3, hsv_buf);
    cvtColor(img_bgr, img_hsv, COLOR_BGR2HSV);
 
    Mat img_temp, img_mask;
    inRange(img_hsv, Scalar(0, 50, 50), Scalar(20, 255, 255), img_temp);
    inRange(img_hsv, Scalar(160, 50, 50), Scalar(180, 255, 255), img_mask);
    img_mask = img_mask + img_temp;
 
//    medianBlur(img_mask, img_mask, 3); // cut noise
    GaussianBlur(img_mask, img_mask, Size(27, 27), 3.5, 3.5);
    threshold(img_mask, img_mask, 140, 255, 0);
 
    // mask processing for drawing a picture
    Mat img_synthesis(IMAGE_VW, IMAGE_HW, CV_8UC3, syn_buf);
    img_synthesis = Scalar(0, 0, 0);
 
    // find contours
    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;
    findContours(img_mask, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
 
 
    int fingerCount = 0;
    if (contours.size() > 0){
        size_t indexOfBiggestContour = -1;
        size_t sizeOfBiggestContour = 0;
        for (size_t i = 0; i < contours.size(); i++){
            if (contours[i].size() > sizeOfBiggestContour){
                sizeOfBiggestContour = contours[i].size();
                indexOfBiggestContour = i;
            }
        }
        vector<vector<int> >hull(contours.size());
        vector<vector<Point> >hullPoint(contours.size()); //elin hareketine göre eli çevreleyen çokgen
        vector<vector<Vec4i> > defects(contours.size()); //parmak uclarindaki yesil noktalar..multi dimensional matrix
        vector<vector<Point> >defectPoint(contours.size()); //point olarak parmak ucu noktalarýný x,y olarak tutuyor
        vector<vector<Point> >contours_poly(contours.size()); //eli çevreleyen hareketli dikdörtgen
        Point2f rect_point[4];
        vector<RotatedRect>minRect(contours.size());
        vector<Rect> boundRect(contours.size());
        for (size_t i = 0; i<contours.size(); i++){
            if (contourArea(contours[i])>5000){
                convexHull(contours[i], hull[i], true);
                convexityDefects(contours[i], hull[i], defects[i]);
                if (indexOfBiggestContour == i){
                    minRect[i] = minAreaRect(contours[i]);
                    for (size_t k = 0; k<hull[i].size(); k++){
                        int ind = hull[i][k];
                        hullPoint[i].push_back(contours[i][ind]);
                    }
                    for (size_t k = 0; k<defects[i].size(); k++){
                        if (defects[i][k][3]>13 * 256){
                            int p_start = defects[i][k][0];
                            int p_end = defects[i][k][1];
                            int p_far = defects[i][k][2];
                            defectPoint[i].push_back(contours[i][p_far]);
                            circle(img_synthesis, contours[i][p_end], 3, Scalar(0, 255, 0), 2); //i ydi
                            fingerCount++;
                        }
 
                    }
 
                    drawContours(img_synthesis, contours, i, Scalar(255, 255, 0), 2, 8, vector<Vec4i>(), 0, Point());
                    drawContours(img_synthesis, hullPoint, i, Scalar(255, 255, 0), 1, 8, vector<Vec4i>(), 0, Point());
                    drawContours(img_mask, hullPoint, i, Scalar(0, 0, 255), 2, 8, vector<Vec4i>(), 0, Point());
                    approxPolyDP(contours[i], contours_poly[i], 3, false);
                    boundRect[i] = boundingRect(contours_poly[i]);
                    rectangle(img_mask, boundRect[i].tl(), boundRect[i].br(), Scalar(255, 0, 0), 2, 8, 0);
                    minRect[i].points(rect_point);
                    for (size_t k = 0; k<4; k++){
                        line(img_mask, rect_point[k], rect_point[(k + 1) % 4], Scalar(0, 255, 0), 2, 8);
                    }
                }
            }
        }
    }
 
    if(g_mode){ img_mask = Scalar(255); }// for showing original image
    img_bgr.copyTo(img_synthesis, img_mask);
 
    // draw text
    stringstream ss;
    ss << "Finger Count: " << fingerCount;
    putText(img_synthesis, ss.str(), Point(5, img_synthesis.rows - 8),
            FONT_HERSHEY_DUPLEX, 0.5, Scalar(255, 255, 255), 1);
 
    // display image
    size_t jpegSize = camera.createJpeg(IMAGE_HW, IMAGE_VW, img_synthesis.data, Camera::FORMAT_RGB888);
    display_app.SendJpeg(camera.getJpegAdr(), jpegSize);
 
}

USBケーブルをGR-LYCHEEのUSB0につなぎ、DisplayAppで画像を確認します。2秒ごとにオリジナルイメージと画像処理をしているイメージが切り替わります。HSV色空間を使った肌色検出、輪郭検出、そしてconvexHull、convexityDefectsによる指のカウントがどのように行われているか分かると思います。


非キャッシュメモリ 1MBの使用について

GR-LYCHEEのRAM 3MBは以下のようにセクションが区切られています。MMU用テーブルが割り当てられたL_TTB領域 16KB、キャッシュのあるRAM領域 1.875MB、キャッシュのないRAM_NC領域 1MBです。

RAM_NC領域は変数や配列を定義するとき、明示的に__attribute((section("NC_BSS"),aligned(32)))を指定する必要があります。

セクション 開始アドレス サイズ 説明
L_TTB 0x20000000 0x4000(16KB) MMU用テーブル
RAM 0x20020000 0x1E0000(1.875MB) キャッシュのあるRAM領域
RAM_NC 0x20200000 0x100000(1MB) キャッシュのないRAM領域

GR-LYCHEEのライブラリを使用した場合、変数や配列の定義で確保されるメモリ以外は、HEAPとしてMalloc等の動的メモリ確保に使用されます。RAM_NC領域1MBを活用するための手段として、本ページのサンプルを参考にしてください。