概要
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を活用するための手段として、本ページのサンプルを参考にしてください。