2021年10月15日金曜日

VC++ デバイス情報の取得

 現在、Yolo(darknet)による画像(物体)認識にトライしています。

ようやく、USBカメラ映像に対する処理プログラムの切り出しに目途が立ったところです。

そこで、プログラム内部で、PCに接続したカメラを特定し、そのカメラ映像を処理するようにしたいと思ったのですが、私にとって結構な難題であったので、以下にその手法について述べます。


デバイスリスト表示プログラムの作成

◎プロジェクトプロパティの設定

              C/C++/プリプロセッサ        INITGUID
              C/C++/詳細設定/「指定の警告を無効にする。」 4996
              〇リンカー/入力              setupapi.lib        

 ◎コーディング

           〇インクルードファイル指定 setupapi.h、devguid.h 、devpkey.h

           〇使用関数  SetupDiGetClassDevs、SetupDiEnumDeviceInfo、SetupDiGetDeviceProperty

◎デバイス情報の取得 

    〇クラスGUID は devguid.h で定義されています。

    〇要求する情報の種別は devpkey.h で定義で定義されています。


    〇プログラムソース

   以下はデバイス情報取得用のテストコードです。

/******************************************/

#include <iostream>

#include <windows.h>

#include <setupapi.h>

#include <devguid.h>

#include <devpkey.h>


void TransBuff(BYTE* buff);


int main()

{

    std::cout << "Hello World!\n";

    HDEVINFO hDevInfo;


    if (false) {

        //全デバイス

        hDevInfo = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT);

    }

    else {

        //カメラ限定

        hDevInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_CAMERA, NULL, NULL, DIGCF_PRESENT);

    }

    DWORD index = 0;

    SP_DEVINFO_DATA DeviceInfoData = { 0 };

    DeviceInfoData.cbSize = sizeof(DeviceInfoData);


    BYTE Buffer[4096];

    // Get device info data

    while (SetupDiEnumDeviceInfo(hDevInfo, index, &DeviceInfoData)) {

        DEVPROPTYPE PropType;

        if (true) {

            SetupDiGetDeviceProperty(

                hDevInfo,

                &DeviceInfoData,

                &DEVPKEY_Device_FriendlyName,       // プリプロセッサ INITGUID が必要

                &PropType,

                Buffer,

                sizeof(Buffer),

                NULL,

                0);

        }

        else {

            // DEVPKEY_Device_HardwareIds


            SetupDiGetDeviceProperty(

                hDevInfo,

                &DeviceInfoData,

                &DEVPKEY_Device_HardwareIds,       // プリプロセッサ INITGUID が必要

                &PropType,

                Buffer,

                sizeof(Buffer),

                NULL,

                0);

        }

         if (DeviceInfoData.ClassGuid.Data1 == (GUID_DEVCLASS_CAMERA).Data1) {

            std::cout << "Found Cammera ";


            if (PropType == DEVPROP_TYPE_STRING || PropType ==(MAX_DEVPROP_TYPEMOD| DEVPROP_TYPE_STRING)) {

                TransBuff(Buffer);

                std::cout << Buffer;

            }

            std::cout << "\n";

        }

        else if (DeviceInfoData.ClassGuid.Data1 == (GUID_DEVCLASS_DISPLAY).Data1) {

            std::cout << "Found Display ";


            if (PropType == DEVPROP_TYPE_STRING || PropType == (MAX_DEVPROP_TYPEMOD | DEVPROP_TYPE_STRING)) {

                TransBuff(Buffer);

                std::cout << Buffer;

            }

            std::cout << "\n";

        }

        else if (DeviceInfoData.ClassGuid.Data1 == (GUID_DEVCLASS_MOUSE).Data1) {

            std::cout << "Found Mouse ";


            if (PropType == DEVPROP_TYPE_STRING || PropType == (MAX_DEVPROP_TYPEMOD | DEVPROP_TYPE_STRING)) {

                TransBuff(Buffer);

                std::cout << Buffer;

                std::cout << "\n";

            }

        }

        index++;

   }

    SetupDiDestroyDeviceInfoList(hDevInfo);

}


void TransBuff(BYTE* buff) {

    int ip = 0;

    int ip2 = 0;

    for (int i = 0;; i++) {

        ip = i;

        ip2 = i * 2;

        buff[ip] = buff[ip2];

        if (buff[ip2] == 0x00) {

            break;

        }

    }

    return;

}

/*****************end code**********************/

◎実行結果

        〇カメラのデバイス情報 

ノートPC内臓カメラ
















USBカメラ










        〇テストプログラム実行結果
デバイス名表示











デバイスID表示














2021年10月1日金曜日

Yolo(darknet)画像検出をUSBカメラに特化したプログラムに改造

darknet本体プログラムは、画像(物体)検出、機械学習、、多機能な巨大プログラムで プログラムソースは難解そのものです。(私にとっては) そこで、USBカメラによる画像入力/画像検出のみに機能を限定したプログラムに改造することで、このプログラムの部分的な解析を行いました。
  
USBカメラでTV画面を撮影
取込み画像から物体検出


















1.プログラムの改造
  1)VisualStudio darknetソリューションのdarknetプロジェクトでの作業
    ・OpenCvのクラス、関数を直接 使用する
メインのプログラムソース darknet.c のファイル名を  AiDetect_Main.cpp (適宜)に変更 (darknet.hもdarknet.hppに変更  各ソースのヘッダー指定(#include)も変更)

            ・動画の画像検出を 画像(静止画)の画像検出のループ処理 に置き換える。
 関数 test_detector() [detector.c] の内容を AiDetect_Main.cpp に展開

プログラムソース AiDetect_Main.cpp
************************************************
#include <iostream>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>

#include <opencv2/opencv.hpp>
#include <opencv2/core/types.hpp>
#include <opencv2/videoio/videoio.hpp>
#include <opencv2/imgcodecs/imgcodecs.hpp>
#include <opencv2/highgui.hpp>

#include "darknet.h"

#include "list.h"
#include "parser.h"
#include "option_list.h"

#define CAM_FRAME_WIDTH 1920
#define CAM_FRAME_HEIGHT 1080

cv::VideoCapture* cap = NULL;
image mat_to_image(cv::Mat mat);
int main(int argc, char **argv)
{
#ifdef _DEBUG
    printf(" _DEBUG is used \n");
#endif

#ifdef DEBUG
    printf(" DEBUG=1 \n");
#endif

int i;
for (i = 0; i < argc; ++i) {
if (!argv[i]) continue;
strip_args(argv[i]);
}

    if(argc < 2){
        fprintf(stderr, "usage: %s <function>\n", argv[0]);
        return 0;
    }
    gpu_index = find_int_arg(argc, argv, "-i", 0);
    if(find_arg(argc, argv, "-nogpu")) {
        gpu_index = -1;
        printf("\n Currently Darknet doesn't support -nogpu flag. If you want to use CPU - please compile Darknet with GPU=0 in the Makefile, or compile darknet_no_gpu.sln on Windows.\n");
        exit(-1);
    }
    if (gpu_index >= 0) {
        cuda_set_device(gpu_index);
    }

    show_cuda_cudnn_info();

    show_opencv_info();


    int letter_box = 0;
    float thresh =0.25;    // 0.24
    float hier_thresh = 0.5;
  
    int* gpus = 0;
    int gpu = 0;
    int ngpus = 0;

    gpu = gpu_index;
    gpus = &gpu;
    ngpus = 1;


    char* datacfg = "cfg/coco.data";
    char* cfg = "cfg/yolov3.cfg";
    char* weights = "yolov3.weights";
  
    char* filename = 0;

    list* options = read_data_cfg(datacfg);
    char* name_list = option_find_str(options, "names", "data/names.list");
    int names_size = 0;
    char** names = get_labels_custom(name_list, &names_size); //get_labels(name_list);

    image** alphabet = load_alphabet();
    network net = parse_network_cfg_custom(cfg, 1, 1); // set batch=1
    if (weights) {
        load_weights(&net, weights);
    }
    if (net.letter_box) letter_box = 1;
    net.benchmark_layers = 0;
    fuse_conv_batchnorm(net);
    calculate_binary_weights(net);
    if (net.layers[net.n - 1].classes != names_size) {
        printf("\n Error: in the file %s number of names %d that isn't equal to classes=%d in the file %s \n",
            name_list, names_size, net.layers[net.n - 1].classes, cfg);
        if (net.layers[net.n - 1].classes > names_size) getchar();
    }
    srand(2222222);
    char buff[256];
    char* json_buf = NULL;
    int json_image_id = 0;
    FILE* json_file = NULL;

    int j;
    float nms = .45;    // 0.4F

    int index = 1;

    try {
        cap = new cv::VideoCapture(index);
        cap->set(cv::CAP_PROP_FRAME_WIDTH, CAM_FRAME_WIDTH);
        cap->set(cv::CAP_PROP_FRAME_HEIGHT, CAM_FRAME_HEIGHT);

        
    }
    catch (...) {
        std::cerr << " OpenCV exception: Web-camera " << index << " can't be opened! \n";
    }

    cv::Mat frame;


    const std::string windowNamePrediction = "predictions";

    cv::namedWindow(windowNamePrediction, cv::WINDOW_GUI_NORMAL | cv::WINDOW_GUI_NORMAL);
    cv::resizeWindow(windowNamePrediction, 960, 540);
    cv::moveWindow(windowNamePrediction,20,20);

    while (1) {

        //画像取得

        cap->read(frame);

        image im = mat_to_image_cv((mat_cv*)&frame);


        image sized;

        sized = resize_image(im, net.w, net.h);

        layer l = net.layers[net.n - 1];
        int k;


        for (k = 0; k < net.n; ++k) {
            layer lk = net.layers[k];
            if (lk.type == YOLO || lk.type == GAUSSIAN_YOLO || lk.type == REGION) {
                l = lk;
                printf(" Detection layer: %d - type = %d \n", k, l.type);
            }
        }


        float* X = sized.data;

        double time = get_time_point();
        // 画像認識
        network_predict(net, X);

        printf("Predicted in %lf milli-seconds.\n",  ((double)get_time_point() - time) / 1000);

        int nboxes = 0;
        detection* dets = get_network_boxes(&net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes, letter_box);
        if (nms) {
            if (l.nms_kind == DEFAULT_NMS) do_nms_sort(dets, nboxes, l.classes, nms);
            else diounms_sort(dets, nboxes, l.classes, nms, l.nms_kind, l.beta_nms);
        }


        //draw_detections_v3(im, dets, nboxes, thresh, names, alphabet, l.classes, ext_output);

        //show_image(im, "predictions2");

        //draw_detection_v3 および show_imageを以下に展開

        int ditectNo = 0;

        std::list<detection> lstDetct =  std::list<detection>();


        lstDetct.clear();


        // 最も物体認識の確度が高いものを選択
        // 確度が閾値未満のものを除外
        for (i = 0; i < nboxes; ++i) {
            int best_class = -1;
            float best_class_prob = thresh;
            int j;
            for (j = 0; j < dets[i].classes; ++j) {
                int show = strncmp(names[j], "dont_show", 9);
                if (dets[i].prob[j] > best_class_prob ) {
                    best_class = j;
                    best_class_prob = dets[i].prob[j];
                }
            }


            if (best_class >= 0) {
                dets[i].best_class_idx = best_class;
                lstDetct.push_back(dets[i]);
               ++ditectNo;
            }
        }


        auto itrBox = lstDetct.begin();


        ///*  */
        for(int ib = 0 ; ib < ditectNo; ib++)
        {
            int idxBox = (&itrBox._Ptr->_Myval)->best_class_idx;
            int clsBox = (&itrBox._Ptr->_Myval)->classes;

            // 描画 BOX
            box b;
            b.x = (&itrBox._Ptr->_Myval)->bbox.x;
            b.y = (&itrBox._Ptr->_Myval)->bbox.y;
            b.h = (&itrBox._Ptr->_Myval)->bbox.h;
            b.w = (&itrBox._Ptr->_Myval)->bbox.w;
            std::string strlbl = names[idxBox];

            // 物体認識 確度
            float prob = (&itrBox._Ptr->_Myval)->prob[idxBox];


            // 描画 色 設定
            int offset = idxBox * 123457 % clsBox;
            float red = get_color(2, offset, clsBox);
            float green = get_color(1, offset, clsBox);
            float blue = get_color(0, offset, clsBox);
            

            cv::Scalar boxColor = cv::Scalar((int)(red*255),(int)(green*255),(int)(blue*255));

            if(ib < ditectNo -1)itrBox = itrBox.operator++();


            int left = (b.x - b.w / 2.) * im.w;
            int right = (b.x + b.w / 2.) * im.w;
            int top = (b.y - b.h / 2.) * im.h;
            int bot = (b.y + b.h / 2.) * im.h;

            if (left < 0) left = 0;
            if (right > im.w - 1) right = im.w - 1;
            if (top < 0) top = 0;
            if (bot > im.h - 1) bot = im.h - 1;




            cv::rectangle(frame, cv::Rect(left, top, right - left, bot - top), boxColor);

            cv::putText(frame, strlbl, cv::Point(left, top), 1, 1.5, boxColor,2);

            printf("%s left:%d right:%d top:%d bot:%d prob:%g \n",strlbl.c_str(),left,right,top,bot,prob);




        }

       

        

        cv::imshow(windowNamePrediction, frame);


        free_detections(dets, nboxes);
        free_image(im);
        free_image(sized);

        int key = cv::waitKey(1);
        if (key == 27/*ESC*/) break;
    }

    if (json_file) {
        char* tmp = "\n]";
        fwrite(tmp, sizeof(char), strlen(tmp), json_file);
        fclose(json_file);
    }

    cap->release();

    // free memory
    free_ptrs((void**)names, net.layers[net.n - 1].classes);
    free_list_contents_kvp(options);
    free_list(options);


    const int nsize = 8;
    for (j = 0; j < nsize; ++j) {
        for (i = 32; i < 127; ++i) {
            free_image(alphabet[j][i]);
        }
        free(alphabet[j]);
    }
    free(alphabet);

    free_network(net);





    if (gpus && ngpus > 1) free(gpus);


    return 0;
}

************************************************
コマンド引数 detect teset cfg/coco.data cfg/yolov3.cfg yolov3.weights
このプログラムでは detect,testは無意味

cfg/yolov3.cfg yolov3.weightsはYolo4でも使えそう。
************************************************

2.プログラムdarknetの解析
    1)画像認識(物体認識)の結果について。
   認識結果は、get_network_boxes()で物体の囲むBOXと cfg/coco.data data/coco.names で指定された認識可能な種別(class)ごとの認識確度が出力される。
従って、認識確度が最も高い種別が認識された画像(物体)の種別と判定される。

             
  

2021年9月26日日曜日

迷いに迷って DELL G15 5511を購入(Yolo darknetで)

  AIによる画像認識用にDELL G15を購入しました。

Windows10のセットアップからYoLo/darknetのインストール、画像認識の実行までを記します。

0.電源ON

DELL G15キーボード
  ・一瞬 「電源スイッチが 無い!」 と 思いきや、 キーボードの右奥端のキーに電源マーク 、1度そのキーを押してみる。 起動せず、 もう1度押してみる まだ起動せず。 しばらく置いて 数度 押す。するとようやく 画面にDELLのマークの表示。 ようやくセットアップ開始。

==> ノートPCを開くことがSW ON せっかちに電源キーを押したため 電源ON/OFF が意図せずに繰り返しため電源ONにてこずったようです。


1.Window10のセットアップ

  ・ローカルアカウントでセットアップできない! 昨年末 職場でLENOVOの3台のPCをローカルアカウントでセットアップしたのですが、今それが出来ない。一旦マイクロソフトアカウントでセットアップしてからローカルアカウントを作成。 最近のWindows10のアップデートでそうなったのか? DELL固有の話なのか分からない。


2.YoLo/darknetのインストール

    1)ツールのインストール

        ・VisualStudio Communication 2019 のインストール。

        ・CMake 3.21.2 のインストール

            VisualStudioのソリューション/プロジェクトの生成を行います。

            (数多くのパス設定、プリプロセッサ、etc.を行ってくれます。)

        ・Nvidia CUDA11.4.2ToolKit及びCUDNN

     ToolKitのインストール後 CUDNNのダウンロードファイル(cudnn-11.4-windows-x64**.zip)を解凍しbin等のフォルダをCUDA ToolKitのフォルダと統合する。

 2)OpenCVのインストール

   これが一番面倒 

        ・OpenCVをダウロード後 opencv-4.5.3-vc14-vc15.exe を実行(解凍)                 ・CMakeで CUDAを組み込むためのオプションを設定し、Generateを行う。

        ・CMakeで生成されたOpenCVのプロジェクトのビルドを行う。

    3darknetのインストール

   ・GitHubからdarknet をダウンロードする。取得したdarknet-master.zipを解凍する。

        ・CMakeでOpenCVを組み込むためのオプションを設定しGenerateを行う。

        ・CMakeで生成されたdarknetのプロジェクトビルドを行う。

3.YoLo/darknetの実行

darknetをDynabook及びDell G15で稼働
・次のコマンドラインで実行
darknet detector demo cfg/coco.data cfg/yolov3.cfg yolov3.weights data/Car.mp4

 Car.mp4はフリー動画をダウンロード






        ・実行結果

        GPU非搭載のDynabookに比べ G15は 画像認識において70~80倍のパフォーマンスを示しました。

Dynabook AVG_FPS は 0.4fps
DELL G15 AGV_FPS は 31.6fps

(上の動画のAVG_FPSは OBS Studio を動画作成に使用したため3割程度低めの値を表示しています。)




2021年7月4日日曜日

妄想鉄道の夜 第1夜 Nゲージ+Arduino

 Arduino(ATMEGA328P)をNゲージに載せてみました。


 電子工作を始めたころから、鉄道模型にマイコン制御を組み込みたいと思っていました。
とりあえず、20年前に購入したNゲージを引っ張り出してSLの走行をためしました。
 最初、レールからの給電がうまくいかず。レールみがきから始めました。

それから3か月ようやくマイコン搭載の車両の走行が実現出来ました。
 

 概要

 Nゲージ車両にはマイコンATMEGA328Pを搭載、コントローラからIRによる指令を受信し、モータードライバを介して、モータを制御し、前進、停止、後退、速度調整を行います。











車両搭載/コントローラ マイコンの選定

 搭載マイコンは Texas InstrumentsのMSP430、Microchip Technology ATMEGA 考えています。 搭載スペースでは MSP430 開発の手軽さでは ATMEGA。とりあえず ATMEAG328Pを選択しました。

 コントローラ用のマイコンも開発の手軽さでArduino Nanoを選択。

Nゲージ車両






※モータドライバ、ATMEAG328P、2200uFコンデンサーの配置がポイント、配置を間違えると、発車、停止、前進後進切替え時にマイコンが暴走、制御不能に、









Nゲージコントローラ

 ※意外と、電動ポイントの駆動に電流が必要なことが判明、1000uF~4700uFのコンデンサに電荷をため込み、一気に放電させることでなんとか 電動ポイントの駆動に成功。
 極性(+/―)の反転用にモータドライバを使用。
 (コンデンサー/モータードライバ間に抵抗を配置して、過電流の防止を試みたが失敗、いろいろやって 2200uF のコンデンサを採用しました。)











マイコンのプログラミング 開発環境

 開発環境はArduinoIDEを使用します。




Arduino IDE は setup()関数で初期処理、Loop()関数で処理の本体を C言語で記述します。








ボードマネージャで 車両搭載マイコン ATMEAG328Pの場合Arduino Unoを選択、コントローラ用のマイコンの場合 Arduino Nano を選択して プログラミングを行います。 
(ターゲット毎に切替えるのは結構面倒)

ATMEAG328Pのプログラミングについて




ATMEAG328Pのプログラミングは、Aruduino UNO R3 自体に 取り外し可能な ATMEAG328P が搭載されているので Arduino Unoとしてプログラミングして、プログラムの書き込み後、UNOから取り外し、Nゲージ車両に搭載します。



 
※ Arduino IDEでのプログラミングで ピン「D9」 を使った場合 ATMEAG328Pでは  PB1 15ピンを使うことになる。





マイコンのプログラミング 実装

 今回のプログラミングの中心は 赤外線通信です。参考のプログラムコードはArduino IDEのスケッチ例から取得しました。

 IR通信 送信側(Nゲージコントローラ側)



Nゲージコントローラ側のプログラミングはスケッチ例/IRemote/IRsendRawDemoを参考にしました。

(※IrSender.sendRaw_P()を使用)

 IR通信 受信側(Nゲージ車両側)

【きむ茶工房ガレージハウス】 ◆ 赤外線リモコンを送信器にして何か動かす記事さんのコードを拝借
  赤外線リモコン受信モジュールの信号の Hi/Low の 間隔を測り 受信データをビットパターンで取得する。