OpenCVのサンプルをやるだけで苦労した

将来的にOpenCVを使ってソフトを作るつもりなので、インストールしてサンプルファイルを駆動させようと思ったら、意外と苦労しました。C++でサンプルを動作させるだけなら、何年か前にもやったことがあるのですが、改めてやってみたらできませんでした。

環境

マシン macOS Mojave 10.14.

言語 C++

ライブラリ OpenCV 4.1.0

コンパイラ Clang / LLVM

実行するとどうしても、リンク異常が出てしてしまいます。参考は下記のURLです。

OpenCV/C++で画像処理入門 vol.1 〜画像を表示してみよう〜

ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

ソースからコンパイルして、cmakeを使って実行すると問題なくできました。

C++のOpenCVをCMakeで使う

いろいろ調べた結果、リンカエラーが発生するときは、ライブラリがきちんと割り当てられていなかったということ。pkg-configの設定ファイルが悪かったみたいです。余計なlibファイルも記載されていました。/usr/local/libフォルダからopencvに関連するのファイルを抽出し書き直しましたファイルはこちら。

opencv4.pc

prefix=/usr/local
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib

Name: opencv4
Description: The opencv library
Version: 4.0.0
Cflags: -I${includedir}/opencv4
Libs: -L${libdir} -lopencv_calib3d -lopencv_core -lopencv_dnn -lopencv_features2d -lopencv_flann -lopencv_gapi -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_ml -lopencv_objdetect -lopencv_photo -lopencv_stitching -lopencv_video -lopencv_videoio

pkg-configを実行すると

$ pkg-config opencv4 --cflags --libs
-I/usr/local/include/opencv4 -L/usr/local/lib -lopencv_calib3d -lopencv_core -lopencv_dnn -lopencv_features2d -lopencv_flann -lopencv_gapi -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_ml -lopencv_objdetect -lopencv_photo -lopencv_stitching -lopencv_video -lopencv_videoio

コンパイルと実行は下記のようにやる。ちなみにstd=c++11を指定しないとコンパイルができません。

$ clang++ -std=c++14 $(pkg-config --cflags --libs opencv4) main.cpp
$ ./a.out

AOJ 双方向連結リストで苦しむ

AizuOnlineJudge(AOJ)でアルゴリズムに入りましたが、だんだんと難しくなり、進むペースが遅くなってきました。それでもWeb上のヒントだけで何とか説いてきましたが、ALDS1_3_C でつまずきました。

StackやQueueと同じように配列でプログラムを組んで実行すると、10個目でタイムアウトで引っかかってしまいます。配列に挿入するときや削除するときには1個ずつずらすことをやっていたので、時間がかかりすぎたようです。そのプログラムがこれ。

https://github.com/lingmujianshi/AizuOnlineJudge/blob/master/ALDS1/ALDS1_3_C/ALDS1_3_C_timeout.cpp

他の人が解いた回答を見ましたが全く理解できませんでした。双方向連結リスト?番兵?わからない言葉ばかりです。

そこでとうとうこの本を買ってしまいました。

プログラミングコンテスト攻略のためのアルゴリズムとデータ構造 単行本(ソフトカバー)

本を見るとズバリと回答が書いてありました。双方向連結リストは自分の前の要素と次の要素へそれぞれのポインタを持つ構造体で番兵はリストの先頭を指す特別なノードで設置することで簡略化できるとのこと。

サンプルを丸写しし、投稿したら問題なく通りましたが、実を言うとコードを全然理解できませんでした。Visual Studio Codeのデバッグ機能を使ってみてみると、Node構造体の中身がぜんぜん表示されませんでした。Classを作ると確認する事ができました。サンプルはmallocを使っていたので、せっかくなのでnewを使って書き直しました。そのコードがこちら。

2つ目を追加したときの流れを書いてみたらやっと理解することができました。

    void insert(int key)
    {
        Node *x = new Node();
        x->key = key;
        // 番兵の直後に要素を追加
        x->next = nil->next;
        nil->next->prev = x;
        nil->next = x;
        x->prev = nil;
    }
insert() 図解

https://github.com/lingmujianshi/AizuOnlineJudge/blob/master/ALDS1/ALDS1_3_C/main.cpp#L32-L41

C++をWindows コマンドプロンプトでコンパイル

普段自宅ではMACBOOKで作業をしていますが、出張ででかけているときはWindowsを使っています。AOJでC++の問題を解くのに、Visual StudioのIDEをひらいてやってたこともあるのですが、プロジェクトを作って大掛かりになってしまうので、簡単にコマンドプロンプト(cmd)でやる方法をまとめます。

まず、コマンドプロンプトでコンパイルする場合はcl.exeを使います。これを直接実行すると、インクルードパスが設定されていません。と表示され実行っできません。

>cl main.cpp

Microsoft(R) C/C++ Optimizing Compiler Version 19.16.27030.1 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.
main.cpp
main.cpp(2): fatal error C1034: iostream: インクルード パスが設定されていません。

通常はスタートメニューから、「開発者コマンド プロンプト for VS 2017」を起動し実行します。 しかし、ディレクトリーに移動するのが非常に面倒です。

「開発者コマンド プロンプト for VS 2017」をプロパティで確認すると、

%comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat"

コマンドプロンプトでフォルダを指定して開く簡単な方法は、エクスプローラのアドレスバーにcmdと入力することです。そこで、上記で確認したコマンドを入力すると、コマンドプロンプトがコンパイル実行可能状態になります。

C:\Users\username\Documents\C\projects\helloworld>%comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat"
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.11
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************

cl.exeでは/EHscオプションをつけて実行します。

C:\Users\username\Documents\C\projects\helloworld>cl /EHsc main.cpp
Microsoft(R) C/C++ Optimizing Compiler Version 19.16.27030.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.
main.cpp
Microsoft (R) Incremental Linker Version 14.16.27030.1
Copyright (C) Microsoft Corporation.  All rights reserved.
/out:main.exe
main.obj

プログラム実行は、exeファイルを入力するだけ。今回はmain.exeができあがったので下記のようになります。

C:\Users\username\Documents\C\projects\helloworld>main.exe
Hello C++ World

Visual studio codeでターミナルでやるだけなら上記のやりかたで問題ありません。もしTaskで実行する場合は、実行毎に新規ターミナルが起動するのでできません。その場合は、Visual studio codeを一度閉じて、コマンドプロンプトで%comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat"を実行してからcode .と入力すると、Taskも実行できるようになります。フォルダも選択した状態で起動するので非常に便利です。

Qtではじめてのアプリをつくった。

最近はAizu Online Judgeでアルゴリズムの問題はやってましたが、 Qtの使い方の勉強で成果物が全然作ることができず、ブログの更新も全然できませんでした。C++やQtの初歩的なレベルのことは一通りやったので、このへんでなにか作ってみようと思い、Qtでアプリを作りました。

アプリといったも全く大したこともなく役に立つものではありませんが、自分の理解力を高めるためにアップします。

QLineEditで入力した数値をビットフィールドで作ったBits構造体にポインタで渡して、各ボタンのランプが点灯するようにしました。また、各ボタンを押したときには、Bits構造体を反転させるようにしています。

Bits構造体は下記の通り。

struct Bits
{
    int bit0:1;
    int bit1:1;
    int bit2:1;
    int bit3:1;
    int bit4:1;
    int bit5:1;
    int bit6:1;
    int bit7:1;
    int bit8:1;
    int bit9:1;
    int bit10:1;
    int bit11:1;
    int bit12:1;
    int bit13:1;
    int bit14:1;
    int bit15:1;
};

ポインタをキャストしてデータを参照しています。

Bits* bits; // Bits オブジェクト
int data; // 入力データ

data = ui->lineEdit_code->text().toInt();
bits = reinterpret_cast<struct Bits *>(&data);

ボタンを押したときに、 bitを反転させます。ポインタで参照すしているのでbitsを変更すればdataの値も変わります。ボタンをおしたら、QLineEditとボタンに表示します。

void MainWindow::on_pushButton_b0_clicked()
{
    bits->bit0 = ~bits->bit0;
    setLineData();
    displayButtons();
}

https://github.com/lingmujianshi/QBitDisplay

サイコロを転がす問題

Aize On Judgeの問題解きは結構続いています。もう少しでIntroduction To Programming Iが完了しそうです。今回はサイコロの問題。今まではサイコロを決まったとおりに転がしたときの目のを求める問題で迷いました。

問題は下記のリンクにあります。
Aizu Online Judge Dice I

サイコロの6面にそれぞれ数字が割り当てられて、N,S,W,E(東西南北)にサイコロを回転させたときに上に来る数値を回答するような内容です。しかも今後のためにクラスや構造体で作成しておくとのこと。

実際にサイコロを作成して、N,S,W,Eへそれぞれ回転させたときに、どこに移動するか確認し、配列を受け渡すプログラムを作成しました。

こんな感じで、S,W,Eの場合も考えると。

コードは以下のようになりました。

#include <iostream>
#include <string>
#include <array>
using namespace std;

struct Dice
{
    array<int, 6> roll;
    void toNorth()
    {
        array<int, 6> t = roll;
        roll[4] = t[0];
        roll[0] = t[1];
        roll[5] = t[4];
        roll[1] = t[5];
    }
    void toSouth()
    {
        array<int, 6> t = roll;
        roll[1] = t[0];
        roll[5] = t[1];
        roll[0] = t[4];
        roll[4] = t[5];
    }
    void toWest()
    {
        array<int, 6> t = roll;
        roll[3] = t[0];
        roll[0] = t[2];
        roll[5] = t[3];
        roll[2] = t[5];
    }
    void toEast()
    {
        array<int, 6> t = roll;
        roll[2] = t[0];
        roll[5] = t[2];
        roll[0] = t[3];
        roll[3] = t[5];
    }
};

int main()
{
    int num;
    string commands;
    Dice dice;
    for (int i=0; i<6; i++) {
        cin >> dice.roll[i];
    }
    cin >> commands;
    size_t size = commands.size();
    for (size_t i=0; i<=size; i++ ) {
        if (commands[i]=='N')
            dice.toNorth();
        else if (commands[i]=='S')
            dice.toSouth();
        else if (commands[i]=='E')
            dice.toEast();
        else if (commands[i]=='W')
            dice.toWest();
    }
    cout << dice.roll[0] << endl;
    
    return 0;
}

http://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=3407308#1

4×4行列の掛け算のプログラム

Aizu Online Judge でC++の問題で行列の掛け算の問題があり、その時はforループを3重に使って解きました。最近、OpenGL入門という本でOpenGLの勉強をしていて、サンプルに4×4行列の掛け算をするプログラムがありました。

for (int i = 0; i < 16; ++i) {
    const int j(i & 3), k(i & ~3);
    t[i] =
       a[ 0 + j] * b[k + 0] +
       a[ 4 + j] * b[k + 1] +
       a[ 8 + j] * b[k + 2] +
       a[12 + j] * b[k + 3];
}

4×4固定なので16個の配列を使います。驚いたのはiに対して3と~3のANDした結果を足して計算していること。ぱっと見てどうなっているか理解できなかったので、プログラムで一つずつ出力してみました。

まずは 3 と ~3を2進数で出力すると下記のようになります。

 3 : 00000000000000000000000000000011
~3 : 11111111111111111111111111111100

これをループで0 ~ 15を出力すると、& 3は

 0 &  3 = 0
 1 &  3 = 1
 2 &  3 = 2
 3 &  3 = 3
 4 &  3 = 0
 5 &  3 = 1
 6 &  3 = 2
 7 &  3 = 3
 8 &  3 = 0
 9 &  3 = 1
10 &  3 = 2
11 &  3 = 3
12 &  3 = 0
13 &  3 = 1
14 &  3 = 2
15 &  3 = 3

& ~3は

 0 & ~3 = 0
 1 & ~3 = 0
 2 & ~3 = 0
 3 & ~3 = 0
 4 & ~3 = 4
 5 & ~3 = 4
 6 & ~3 = 4
 7 & ~3 = 4
 8 & ~3 = 8
 9 & ~3 = 8
10 & ~3 = 8
11 & ~3 = 8
12 & ~3 = 12
13 & ~3 = 12
14 & ~3 = 12
15 & ~3 = 12

となります。0〜15は配列の下記のように割り当てます。

 0  1  2  3 
 4  5  6  7 
 8  9 10 11 
12 13 14 15

行列の掛け算は横✕縦でかけていきますので、式を出力するとこのようになります。

C[ 0]=A[ 0]*B[ 0]+A[ 4]*B[ 1]+A[ 8]*B[ 2]+A[12]*B[ 3]
C[ 1]=A[ 1]*B[ 0]+A[ 5]*B[ 1]+A[ 9]*B[ 2]+A[13]*B[ 3]
C[ 2]=A[ 2]*B[ 0]+A[ 6]*B[ 1]+A[10]*B[ 2]+A[14]*B[ 3]
C[ 3]=A[ 3]*B[ 0]+A[ 7]*B[ 1]+A[11]*B[ 2]+A[15]*B[ 3]
C[ 4]=A[ 0]*B[ 4]+A[ 4]*B[ 5]+A[ 8]*B[ 6]+A[12]*B[ 7]
C[ 5]=A[ 1]*B[ 4]+A[ 5]*B[ 5]+A[ 9]*B[ 6]+A[13]*B[ 7]
C[ 6]=A[ 2]*B[ 4]+A[ 6]*B[ 5]+A[10]*B[ 6]+A[14]*B[ 7]
C[ 7]=A[ 3]*B[ 4]+A[ 7]*B[ 5]+A[11]*B[ 6]+A[15]*B[ 7]
C[ 8]=A[ 0]*B[ 8]+A[ 4]*B[ 9]+A[ 8]*B[10]+A[12]*B[11]
C[ 9]=A[ 1]*B[ 8]+A[ 5]*B[ 9]+A[ 9]*B[10]+A[13]*B[11]
C[10]=A[ 2]*B[ 8]+A[ 6]*B[ 9]+A[10]*B[10]+A[14]*B[11]
C[11]=A[ 3]*B[ 8]+A[ 7]*B[ 9]+A[11]*B[10]+A[15]*B[11]
C[12]=A[ 0]*B[12]+A[ 4]*B[13]+A[ 8]*B[14]+A[12]*B[15]
C[13]=A[ 1]*B[12]+A[ 5]*B[13]+A[ 9]*B[14]+A[13]*B[15]
C[14]=A[ 2]*B[12]+A[ 6]*B[13]+A[10]*B[14]+A[14]*B[15]
C[15]=A[ 3]*B[12]+A[ 7]*B[13]+A[11]*B[14]+A[15]*B[15]

検証に使ったプログラムはこれです。clang++ std-+11で出力しました。

C++11で大文字小文字を区別せずに文字列を検索する

プログラムに慣れていくためにやり始めた Aizu Online Judge は始めて半月ぐらい経過したが意外と続いています。以前にも書いたかもしれませんが、普段のしごとではPLCでラダーを書く事が多いので、あまり言語系のプログラムを書きません。入門書をやっただけで終わっていまいそれ以上のレベルになかなか行けないので、アウトプットするために始めました。

問題をこなしていくたびに、いろいろと発見があるのでそれを書いていくことにします。表題にあるように今回はC++11で大文字小文字を区別せずに文字列を検索することをわかりました。C++では文字列を検索するには。stringクラスのfindを使えばよいのですが、大文字と小文字が区別されてしまいます。<algorithm>にも大文字と小文字が区別しないで検索する方法はありますが、書き方が面倒なようなので、今回は正規表現を使いました。

やり方としては正規表現オブジェクトを作成するときに、大文字小文字区別なしを示すicaseオプションをつけるだけ。あとはregex_match関数で市レベルだけです。

今回の問題は、標準入力で最初に探す元の文字列、次に探されるがわの文字列。END_OF_TEXTで終了という内容だったので、下記のようなプログラムになりました。

#include <iostream>
#include <regex>
using namespace std;
 
int main()
{
    string W,T;
    int count=0;
    cin >> W;
    regex re(W,regex_constants::icase);
    while(1){
        cin >> T;
        if(T == "END_OF_TEXT") break;
        if(regex_match(T,re)) count++;
    }
     
    cout << count << endl;
    return 0;
}

<regex>はC++11から対応なので、C++11を選択することを忘れずに。試しにC++で投稿したら当然コンパイルエラーになりました。

Aizu Online Judgeでプログラムの問題解きを始めた。

プログラムは本で勉強しているが、あまり普段使う機会がない。仕事で使う必要がないというか。プログラムができれば仕事でもいろいろと出来ることがあるのにまだレベルがそこまで行ってない。アルゴリズム題材をどんどん解いていきレベルを上げていこうと思う。今までプログラムの勉強が全然進まなかったので、昼休みの時間を利用してどんどんやっていこう。


まだ始めたばかりなので簡単な問題が多い。出力結果があっていれば正解なので、無駄なことをしていても正解になる。正解はしたが、考え方がいまいちだったなということがある。そんなときに他の人が解いたものを見るとすごく勉強になる。まさに目から鱗が落ちるというかんじだ。

Aizu Online Judgeの問題にはデータが複数用意され、標準入力にゼロが入ったら終了という内容が多い。
普通に考えると、Whileでループで回してif文で標準入力を確認して値がゼロならbreakでループから抜けるという考えだが、While分の条件式で標準入力とANDで結果がゼロであることを確認するという方法があった

int c;
while(cin >> c && c) {
// 処理
}

while は条件式が成立している間ループを続けるが、c++では条件が ゼロ以外のときにロープを続けるようだ。なので1以外の数字でもループを続ける。
もし標準入力だけを条件式にしてみる。

while(cin >> c) {
// 処理
}

するとゼロを入力してもループが終わらない。これは、標準入力のゼロはアスキーコードで入力しているからだ。最初の方法だと標準入力のゼロを変数に入力した段階で、整数のゼロに変換されたので0となり、Whileが終わるということだ。

リモコンで実際に送信してみた・・・続き

ソニーのテレビリモコンも確認

SONYテレビ点灯を確認スケッチはDEMOの例そのままでいける。”A90″を0.04秒おきにを3回送ると電源ON/OFFができるようだ。じつは、受信テストでは2回しか検出することができなかった。サンプルになかったらできなかったかもしれない。

実際の波形がどうなっているか、PicoScopeで受信の波形を測定してみた。波形を見ると本当に3回送っていることを確認することができた。

回路の電流値を確認

次に回路が計算どおりにできているかテスターを使って確かめてみた。回路の電流は計算値より低かった。抵抗を測ってみると、1つ1つの抵抗はほぼ出ているのに、合成抵抗は計算より高めになるようだ。L12170はIF値を200mAにしたいので、抵抗を6個並列にしようかと思う。IF値が130mAだがとりあえずこれで行くことにしよう。

リモコンテスト 実際に送信してみた

赤外線リモコン送信テスト

赤外線LEDが点灯したことをお確認したので、実際に送信してみた。スケッチは
スケッチ例 → IRremote → IRSendDemo
NEC照明用にプログラムを変更 、前に取得したコードを書き込む。Arduinoで確認成功。

ESP32でLチカ

将来的にWi-fiで接続し制御するようにしたいので、ここから送信はESP32を使用することにする。Arduinoのプログラムが動作し、Wi-fiやBluetoothも使用できる。Arduino IDEに拡張すれば使えるようになる。今回買ったのは開発ボードESP32-DevKitC。インストールはいろいろなサイトが親切に書いているのでここでは割愛する。
まずはLチカで点灯することを確認。持っているブレッドボードに対してちょっと大きいので、直接ソケットタイプの線を挿して接続した。スケッチはピン番号がArduinoと違うので、そこを変更するだけ。今回は26番ピンに接続した。

ESP32でリモコン送信

これもピン番号を変えるだけ。IRsendを定義する時にピン番号を記入する。
このスケッチでは、照明の明るさが1つ上がる

Arduinoで受信確認

Arduinoが空いたので送信したデータを受信して確認した。
スケッチ例 → IRremote → IRrecvDemo
変更はいらなず、そのまま書き込むだけ。

リモコンと同じデータを受信することを確認した。照明に向けると5秒毎に1段階ずつ照明が明るくなることを確認した。

まとめ

赤外線LEDから実際にデータを送って切り替わることを確認した。ライブラリがあるのですごく簡単だった。また、ESP32を初めて使用したがArduinoからの移行があまりにも簡単で驚いた。開発ボードの値段が1500円ぐらいと価格も安いので、この先もいろいろなことに使えそうだと思った。