AR.Droneを動かそう (PID制御)

CV Droneがターゲットにしているのは、趣味の人(私とか)はもちろんですが、
AR.Droneを用いて研究したい人というのもあります。

「なんか画像処理させたいよー」
とか
「目的地まで移動させたいよー」
とか、思い当たる人は少なくないでしょう。

最新のCV Droneのサンプル(sample_tracking.cpp)には前カメラを使って色のついたマーカをゆっくり追いかけるといったものを入れていますが...

コレジャナイ!ようです。まぁ薄々そんな気はしてた

下カメラを使ってマーカをPID制御するには?というのは割と多く寄せられるコメントなので、ここでサンプルをお見せしましょう。


#include "ardrone/ardrone.h"

// --------------------------------------------------------------------------
// main(引数の数, 引数リスト)
// メイン関数
// 戻り値: 正常終了:0 エラー: -1
// --------------------------------------------------------------------------
int main(int argc, char **argv)
{
// AR.Droneクラス
ARDrone ardrone;

// 初期化
if (!ardrone.open()) {
std::cout << "初期化に失敗しました" << std::endl;
return -1;
}

// 下カメラに切り替え
ardrone.setCamera(1);

// 閾値
int minH = 0, maxH = 179;
int minS = 0, maxS = 255;
int minV = 0, maxV = 255;

// XMLファイルを開く
std::string filename("thresholds.xml");
cv::FileStorage fs(filename, cv::FileStorage::READ);

//ファイルが有れば閾値読み込み
if (fs.isOpened()) {
maxH = fs["H_MAX"];
minH = fs["H_MIN"];
maxS = fs["S_MAX"];
minS = fs["S_MIN"];
maxV = fs["V_MAX"];
minV = fs["V_MIN"];
fs.release();
}

// ウィンドウを生成
cv::namedWindow("binalized");
cv::createTrackbar("H max", "binalized", &maxH, 179);
cv::createTrackbar("H min", "binalized", &minH, 179);
cv::createTrackbar("S max", "binalized", &maxS, 255);
cv::createTrackbar("S min", "binalized", &minS, 255);
cv::createTrackbar("V max", "binalized", &maxV, 255);
cv::createTrackbar("V min", "binalized", &minV, 255);
cv::resizeWindow("binalized", 0, 0);

// メインループ
while (1) {
// キー入力
int key = cv::waitKey(30);
if (key == 0x1b) break;

// 画像を取得
cv::Mat image = ardrone.getImage();

// HSVに変換
cv::Mat hsv;
cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);

// 2値化
cv::Mat binalized;
cv::Scalar lower(minH, minS, minV);
cv::Scalar upper(maxH, maxS, maxV);
cv::inRange(hsv, lower, upper, binalized);

// 表示
cv::imshow("binalized", binalized);

// ノイズ除去
cv::Mat kernel = getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
cv::morphologyEx(binalized, binalized, cv::MORPH_CLOSE, kernel);
//cv::imshow("morphologyEx", binalized);

// 輪郭を検出
std::vector<std::vector<cv::Point>> contours;
cv::findContours(binalized.clone(), contours, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);

// 一番大きい輪郭を抽出
int contour_index = -1;
double max_area = 0.0;
for (int i = 0; i < (int)contours.size(); i++) {
double area = fabs(cv::contourArea(contours[i]));
if (area > max_area) {
contour_index = i;
max_area = area;
}
}

// マーカ
static cv::Point marker(binalized.cols / 2, binalized.rows / 2);

// マーカが見つかった
if (contour_index >= 0) {
// 重心
cv::Moments moments = cv::moments(contours[contour_index], true);
marker.y = (int)(moments.m01 / moments.m00);
marker.x = (int)(moments.m10 / moments.m00);

// 表示
cv::Rect rect = cv::boundingRect(contours[contour_index]);
cv::rectangle(image, rect, cv::Scalar(0, 255, 0));
//cv::drawContours(image, contours, contour_index, cv::Scalar(0,255,0));
}

// 離陸・着陸
if (key == ' ') {
if (ardrone.onGround()) ardrone.takeoff();
else ardrone.landing();
}

// キー入力で移動
double vx = 0.0, vy = 0.0, vz = 0.0, vr = 0.0;
if (key == CV_VK_UP) vx = 1.0;
if (key == CV_VK_DOWN) vx = -1.0;
if (key == CV_VK_LEFT) vr = 1.0;
if (key == CV_VK_RIGHT) vr = -1.0;
if (key == 'q') vz = 1.0;
if (key == 'a') vz = -1.0;

// マーカ追跡のON/OFF
static int track = 0;
if (key == 't') track = !track;
cv::putText(image, (track) ? "track on" : "track off", cv::Point(10, 20), cv::FONT_HERSHEY_SIMPLEX, 0.5, (track) ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0), 1, CV_AA);

// マーカ追跡
if (track) {
// PIDゲイン
const double kp = 0.001;
const double ki = 0.000;
const double kd = 0.000;

// 誤差 (画像座標系-機体座標系の変換のため符号とXYが逆)
double error_x = (binalized.rows / 2 - marker.y);
double error_y = (binalized.cols / 2 - marker.x);

// 時間 [s]
static int64 last_t = 0.0;
double dt = (cv::getTickCount() - last_t) / cv::getTickFrequency();
last_t = cv::getTickCount();

// 積分項
static double integral_x = 0.0, integral_y = 0.0;
if (dt > 0.1) {
// リセット
integral_x = 0.0;
integral_y = 0.0;
}
integral_x += error_x * dt;
integral_y += error_y * dt;

// 微分項
static double previous_error_x = 0.0, previous_error_y = 0.0;
if (dt > 0.1) {
// リセット
previous_error_x = 0.0;
previous_error_y = 0.0;
}
double derivative_x = (error_x - previous_error_x) / dt;
double derivative_y = (error_y - previous_error_y) / dt;
previous_error_x = error_x;
previous_error_y = error_y;

// 操作量
vx = kp * error_x + ki * integral_x + kd * derivative_x;
vy = kp * error_y + ki * integral_y + kd * derivative_y;
vz = 0.0;
vr = 0.0;
std::cout << "(vx, vy)" << "(" << vx << "," << vy << ")" << std::endl;
}

// 移動
ardrone.move3D(vx, vy, vz, vr);

// 表示
cv::imshow("camera", image);
}

// さようなら
ardrone.close();

// 閾値を保存
fs.open(filename, cv::FileStorage::WRITE);
if (fs.isOpened()) {
cv::write(fs, "H_MAX", maxH);
cv::write(fs, "H_MIN", minH);
cv::write(fs, "S_MAX", maxS);
cv::write(fs, "S_MIN", minS);
cv::write(fs, "V_MAX", maxV);
cv::write(fs, "V_MIN", minV);
fs.release();
}

return 0;
}
※注意 サンプルはP制御です(ki=kd=0.0のため)。IゲインとDゲインは自分で調整してください。

時間(Δt)で微分とか積分をコード化できるようになれば、PID制御器の実装なんてチョロいもんです。

ゲインの調整法はというと...
みんな大好きZiegler-Nicholsの限界感度法が有名でしょうか。

とりあえずコードはゲイン調整しやすいように

// マーカ追跡
if (track) {
// PIDゲイン
const double kp = 0.001;
const double ti = DBL_MAX;
const double td = 0.000;
:
:
// 操作量
vx = kp * (error_x + integral_x/ti + td * derivative_x);
vy = kp * (error_y + integral_y/ti + td * derivative_y);
vz = 0.0;
vr = 0.0;
std::cout << "(vx, vy)" << "(" << vx << "," << vy << ")" << std::endl;
}
こんな感じに変えときましょう。

限界感度法というのは、最初はIゲインとDゲインをゼロにして、Pゲインを徐々に上げていき、周期的な振動をするようになったらその時の限界ゲイン(Kc)と変動周期(Pc)を元にPIDゲインを決定するものです。
KpTiTd
P制御0.5Kc0.0
PI制御0.45Kc0.83Pc0.0
PID制御0.6Kc0.5Pc0.125Pc

あくまで参考程度に使う感じになるかと思います。最終的にはカンで決めます。
関連記事

コメントの投稿

非公開コメント

はじめまして

こんにちは。CV Droneを大学の研究に使用させて頂いています。
素晴らしいコードを公開して頂き、ありがとうございます。

正面のカメラを用いてARを認識し、トラッキングしたいのですが、サンプルに含まれているマーカーやチェスボードを印刷し、ホバリングしているドローンに見せても特に動きがありません。
お手数ですが、[sample_tracking.cpp]を実行した後に、どのような流れで何を認識し、トラッキングするのかご教授いただけませんでしょうか。

よろしくお願いします。

Re: はじめめまして

コメントありがとうございます。

説明が足りてませんでしたね。
sample_trackinngは、色の付いた何らかの物体を追跡する、というものです。

サンプルの動かし方は以下の通りです。
1. 色の付いた物体(赤色のボールや画用紙など)を用意
2. AR.DroneをWiFi接続後サンプルを起動
3. binalizedウィンドウ内のスライダを動かして物体の色の付いた部分が白で表示されるようにする
4. cameraウィンドウで色の付いた物体を囲む四角形が表示されることを確認
5. スペースキーを押してAR.Droneを離陸させる
6. 色の付いた物体をAR.Droneの正面に配置した状態で「t」キーを押すと追跡開始

サンプル内でマーカと呼んでたのでまぎらわしかったですね。

ARマーカやチェスボードを追跡するには、自分でマーカ検出用のコードを追加しましょう(画面上のx,y座標が分かれば大丈夫です)。

No title

こんにちは。
ご質問させて頂きます。
PID制御のサンプルを実行すると以下のようなエラーが吐かれます。
お忙しい中恐縮ですが、解決法をご教授いただけましたら幸いです。

-----
error C2065: 'binalized' : 定義されていない識別子です。 C:\Users\sou\Desktop\研究用\cvdrone_jp\src\main.cpp
error C2228: '.cols' の左側はクラス、構造体、共用体でなければなりません C:\Users\sou\Desktop\研究用\cvdrone_jp\src\main.cpp
error C2228: '.rows' の左側はクラス、構造体、共用体でなければなりません C:\Users\sou\Desktop\研究用\cvdrone_jp\src\main.cpp
IntelliSense: 識別子 "binalized" が定義されていません c:\Users\sou\Desktop\研究用\cvdrone_jp\src\main.cpp

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます

Re: はじめまして

コメントありがとうございます。

sample_tracking.cppは起動しただけでは追跡を行いません。
1. binalizedウィンドウの閾値バーを動かし対象の色領域を抽出
2. Spaceを押して離陸
3. Tを押して追跡開始
が正しい動作手順ですので、もう一度試してみてください。

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます

Re: No title

ardrone.move3D(vx, vy, vz, vr);
が抜けてました。すみません。
↓GitHubに修正版を上げております。
https://github.com/puku0x/cvdrone/blob/master/samples/sample_tracking.cpp

PIDのソースは先ほど修正しました。
ご不便をおかけして申し訳ありませんでした。

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます

Re: No title

uvaasdさん

限界感度法は、おおよその目安を求めるだけですので「この通りやれは完璧に制御できる」というものではありません。
しかしながら、制御対象のモデルが不明な場合に、制御ゲインを決定するアプローチとしては参考になる部分が多いため、当記事で紹介しました。

経験則に基づくものですが、これはどうでしょうか?
1. PIDゲインを全て0にする
2. 周期的に振動するまでPゲインを上げる
3. 1.のゲインを半分にして定常偏差が無くなるまでIゲインを上げる
4. 目標付近で暴れない程度にDゲインを上げる(0のままでも良い)

限界感度法について

はじめまして.PIDゲインについて質問させていただきます.

私はAR.Drone2.0を用いたPID制御を研究の一環として取り組んでおり,床に置いたマーカーをドローン底面のカメラで読み取り,マーカーが画角の中心に来るようにドローンの位置を制御しようとしています.
しかし,限界感度法を用いてゲインを決定するためP制御を行っていますが,そもそもP制御による位置制御では,マーカーが画角から外れてしまい,Pゲインの調整を行っても発散するまでの時間が長くなるだけで上手くいきません.

サンプルにはKp=0.001とありますが,少なくともこのゲインのP制御でドローンの位置制御が行えているということでしょうか?それとも例としての適当な値なのでしょうか?できればPID制御に成功した際のパラメータを一例として教えて頂きたいです.

よろしくお願いします.

Re: 限界感度法について

大好きからあげさん

コメントありがとうございます。

> Kp=0.001
サンプル内の数値は実験当時に求めたPIDゲインの内、Pゲインだけを載せています。
ドローン自体の個体差や、飛行高度(サンプルは5m程度を想定)によって変化しますが、動作テストとして適当な値として0.001を採用しています。

限界感度法でゲインを調整するときは、マーカが画角から外れないように出来るだけ高度を上げてください。

PID制御に成功した際の具体的な値は、残念ながら失念してしまいました。

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます
プロフィール

puku

Author:puku
暇な時はゲームかプログラミングしてる人だよ。
だいたい月1更新。
CV Drone はこちら(GitHub)

最近はQiitaでOnsenUI2で遊んでいる。

最新記事
最新コメント
最新トラックバック
検索フォーム
カレンダー
04 | 2023/05 | 06
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 - - -
月別アーカイブ
カテゴリ
スポンサードリンク
RSSリンクの表示
FC2カウンター
リンク
ブロとも申請フォーム

この人とブロともになる

アクセスランキング
[ジャンルランキング]
コンピュータ
822位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
152位
アクセスランキングを見る>>
FC2ブログランキング

FC2Blog Ranking

QRコード
QR