【ラズパイ】プログラムからカメラを操作する

前回はコマンドでカメラを操作しましたが、

今回はプログラムからカメラを使用します。

やり方は2種類あるみたいで、

python-picameraを使用する方法と、

openCVを使用する方法です。

今回はopenCVを使用してみます。

なぜなら、openCVは画像解析なんかもできるので、今後も応用が利くかも、と思いましたので。

openCVのインストール

$ sudo apt-get install python3-opencv

pip3を使用するインストール方法もネットにありましたが、

こちらはインストールの過程でビルド処理がありますので、

実行すると、一日コースです。

apt-getを使用したほうが短時間で完了します。

(それでも数分かかります。)

早速実行してみる

from datetime import datetime
import cv2, os

def main():

  cam = cv2.VideoCapture(0)
  if cam == None:
    return False

  while True:
    # カメラから映像を読み込む
    _, img = cam.read()

    cv2.imshow("preview", img)

    # キーを入力した際に撮影を終了する
    if cv2.waitKey(1) < 255: break

  # 事後処理
  cam.release()
  cv2.destroyAllWindows()

if __name__ == '__main__':
  main()
  

cam.read()を実行すると、処理結果と、frameデータが取得できます。

ここでは処理結果は使用しないので、_ と書いています。

imshow()を実行するとframeのデータを画面に表示されます。

それをwhileループしているので、プレビューのように映像が表示されます。

※ラズパイの画面をVNCのリモートデスクトップで表示させています。

最終的には、このframeデータをリモートに飛ばして表示させたい。

openCVは調べてみると、いろいろな事ができるみたいなので、もうちょっと調べてみます。

古いディスプレイを撤去してから、体調が良くなりました

あれから二日経ちましたか。

体調は大分良くなりました。

まだかすかに耳鳴りがありますが、ほとんど気にならないレベルです。

やっぱりノングレア非対応ディスプレイが原因だったのですね。

あとは一日の内、定期的に目を休ませることにする。

アイマッサージャーとか使っていましたが、

なんだかんだ言って、一番良いのは蒸しタオルじゃないかと思いまして。

一番手軽。

蒸しタオルのやり方。

手ぬぐいを水で濡らして固く絞る。

それをレンジで1分間チンする。

以上。(火傷に注意)

引き続き体調回復に努めます。

【ダイエット支援】【食事管理】目標カロリーを保持、グラフに反映させる。

前回までの状況はこちら

最新ソースはこちら(gitHub)。

https://github.com/takishita2nd/diet-mng

前回計算した内容をデータベースに保存・読み出しを行う機能を実装します。

まずはデータベースのマイグレーションを作成。

class CreateEatingTarget extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('eating_targets', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->double('protein');
            $table->double('liqid');
            $table->double('carbo');
            $table->double('calorie');
            $table->timestamps();
            $table->engine = 'InnoDB';
            $table->charset = 'utf8mb4';
            $table->collation = 'utf8mb4_unicode_ci';
        });

        Schema::create('eating_target_user', function (Blueprint $table) {
            $table->integer('user_id')
                  ->foreign('user_id')
                  ->references('id')->on('users')
                  ->onDelete('cascade');
            $table->integer('eating_target_id')
                  ->foreign('eating_target_id')
                  ->references('id')->on('eating_targets')
                  ->onDelete('cascade');
            $table->engine = 'InnoDB';
            $table->charset = 'utf8mb4';
            $table->collation = 'utf8mb4_unicode_ci';
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('eating_target_user');
        Schema::dropIfExists('eating_targets');
    }
}
$ php artisan migrate

このテーブルのモデルを作成。

class EatingTarget extends Model
{
    protected $table = 'eating_targets';
    
    public function users()
    {
        return $this->belongsToMany('App\User');
    }
}

Userテーブルからのリレーションも作成します。

class User extends Authenticatable
{
    public function EatingTargets()
    {
        return $this->belongsToMany('App\Model\EatingTarget');
    }

このデータベースにアクセスするためのAPIを作成します。

セット処理。

Route::post('api/eating/settarget', 'Eating\ApiController@setTarget');
class ApiController extends Controller
{
    /**
     * 目標栄養素を設定する
     */
    public function setTarget(Request $request)
    {
        $paramNames = $this->eatingManagement->getTargetParam();
        $param = [];
        foreach($paramNames as $name) {
            $param[$name] = $request->contents[$name];
        }
        $this->eatingManagement->setTarget($param, Auth::user());
        return response()->json();
    }
use App\Model\EatingTarget;

class EatingManagementRepository
{
    private $targetParamNames = ['protein', 'liqid', 'carbo', 'calorie'];

    public function setTarget($param, $user)
    {
        $model = $user->EatingTargets()->first();
        if(is_null($model)) {
            $model = new EatingTarget();
        }

        foreach($this->targetParamNames as $name)
        {
            $model->$name = $param[$name];
        }
        $model->save();

        $this->attachToUser($model, $user);
    }

    public function getTargetParam()
    {
        return $this->targetParamNames;
    }

これをVue.jsの処理に組み込みます。

    methods: {
        clickAdd: function() {
            var self = this;
            this.param.contents = this.contents;
            axios.post('/api/eating/settarget', this.param).then(function(response){
                self.closeModal();
                self.$emit('update');
            }).catch(function(error){
                self.error_flg = true;
                self.errors = error.response.data.errors;
            });
        },

次は、このデータを読み出す処理を実装します。

グラフデータを読み出すAPIがすでにありますので、これにデータを追加します。

class ApiController extends Controller
{
    /**
     * グラフ用データを取得する
     */
    public function graph(Request $request)
    {
        return response()->json([
            'data' => $this->eatingManagement->getDaily(Auth::user(), $request->contents['date']), 
            'target' => $this->eatingManagement->getTarget(Auth::user())
            ]);
    }
class EatingManagementRepository
{
    public function getTarget($user)
    {
        return $user->EatingTargets()->first();
    }
        graphUpdate: function() {
            var ctx = document.getElementById("eating");
            var self = this;
            this.contents.date = this.todayDate;
            this.param.contents = this.contents;
            this.datasets = [];
            axios.post('api/eating/graph', this.param).then(function(response){
                if(response.data.data != null) {
                    self.datasets.push(Math.ceil(response.data.data.protein / response.data.target.protein * 100));
                    self.datasets.push(Math.ceil(response.data.data.liqid / response.data.target.liqid * 100));
                    self.datasets.push(Math.ceil(response.data.data.carbo / response.data.target.carbo * 100));
                    self.datasets.push(Math.ceil(response.data.data.calorie / response.data.target.calorie * 100));
                    var myChart = new Chart(ctx, {

なかなかいい感じなので、これで行きましょう。

【cocos2d-x】シーンを追加する。

今日はもう一個やったので、まとめておく。

シーンを追加する場合。

Cocos2d-xでは1シーンにつき1クラスと考えまして、

今回はSampleSceneを追加してみます。

C++の場合はクラスを記入したヘッダーファイルと、処理本体を記述したソースファイル本体を作成します。

中身は、とりあえずHelloWorldとほぼ同じにして、最初にSampleSceneを表示するようにします。

class SampleScene : public cocos2d::Scene
{
public:
    static cocos2d::Scene* createScene();

    virtual bool init();
    
    // a selector callback
    void menuCloseCallback(cocos2d::Ref* pSender);
    
    // implement the "static create()" method manually
    CREATE_FUNC(SampleScene);
};
#include "SampleScene.h"
#include "SimpleAudioEngine.h"

USING_NS_CC;

Scene* SampleScene::createScene()
{
    return SampleScene::create();
}

// Print useful error message instead of segfaulting when files are not there.
static void problemLoading(const char* filename)
{
    printf("Error while loading: %s\n", filename);
    printf("Depending on how you compiled you might have to add 'Resources/' in front of filenames in HelloWorldScene.cpp\n");
}

// on "init" you need to initialize your instance
bool SampleScene::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Scene::init() )
    {
        return false;
    }

    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();

    // add "HelloWorld" splash screen"
    auto sprite = Sprite::create("HelloWorld.png");
    if (sprite == nullptr)
    {
        problemLoading("'HelloWorld.png'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

        // add the sprite as a child to this layer
        this->addChild(sprite, 0);
    }
    return true;
}


void SampleScene::menuCloseCallback(Ref* pSender)
{
    //Close the cocos2d-x game scene and quit the application
    Director::getInstance()->end();

    /*To navigate back to native iOS screen(if present) without quitting the application  ,do not use Director::getInstance()->end() as given above,instead trigger a custom event created in RootViewController.mm as below*/

    //EventCustom customEndEvent("game_scene_close_event");
    //_eventDispatcher->dispatchEvent(&customEndEvent);


}
    // create a scene. it's an autorelease object
    auto scene = SampleScene::createScene();

    // run
    director->runWithScene(scene);

    return true;
}

ただ、C言語やっている人なら分かると思いますが、これだけじゃあビルドしてくれません。

通常はmakefileを弄るのですが、WindowsはVisualStudio、AndroidはAndroid Studioでビルドするので、

VisualStudioの場合

プロジェクト名\proj.win32\プロジェクト名.vcxproj

のここに追加するファイルを記載する。

  <ItemGroup>
    <ClCompile Include="..\Classes\AppDelegate.cpp" />
    <ClCompile Include="..\Classes\HelloWorldScene.cpp" />
    <ClCompile Include="..\Classes\SampleScene.cpp" />
    <ClCompile Include="main.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="..\Classes\AppDelegate.h" />
    <ClInclude Include="..\Classes\HelloWorldScene.h" />
    <ClInclude Include="..\Classes\SampleScene.h" />
    <ClInclude Include="main.h" />
  </ItemGroup>

Androidの場合

プロジェクト名\CMakeLists.txt

のここに追加するファイルを記載する。

# add cross-platforms source files and header files 
list(APPEND GAME_SOURCE
     Classes/AppDelegate.cpp
     Classes/HelloWorldScene.cpp
     Classes/SampleScene.cpp
     )
list(APPEND GAME_HEADER
     Classes/AppDelegate.h
     Classes/HelloWorldScene.h
     Classes/SampleScene.h
     )

Mac/iOSはXcodeのメニューから追加すれば良いと思う。(知らんけど)

こういうシーンの追加って、cocosコマンドでなんとかできないんですかね?

シーンに限らず、クラス追加するときも同じか。

めんどくさい。

【Cocos2d-x】ラベルでディスプレイ情報などを表示する。

前回の記事でシーンの内容は大体分かった。

ちょっと気になるのはvisibleSizeとoriginの値。

じゃあ、visibleSizeとoriginの値をラベルに表示させちゃおう。

    auto str = String();
    str.appendWithFormat("visible (%f %f)", visibleSize.width, visibleSize.height);
    auto label = Label::createWithTTF(str.getCString(), "fonts/msgothic.ttc", 24);
    if (label == nullptr)
    {
        problemLoading("'fonts/msgothic.ttc'");
    }
    else
    {
        // position the label on the center of the screen
        label->setPosition(Vec2(origin.x + visibleSize.width/2,
                                origin.y + visibleSize.height - label->getContentSize().height));

        // add the label as a child to this layer
        this->addChild(label, 1);
    }

    auto str2 = String();
    str2.appendWithFormat("origin (%f %f)", origin.x, origin.y);
    auto label2 = Label::createWithTTF(str2.getCString(), "fonts/msgothic.ttc", 24);
    if (label2 == nullptr)
    {
        problemLoading("'fonts/msgothic.ttc'");
    }
    else
    {
        // position the label on the center of the screen
        label2->setPosition(Vec2(origin.x + visibleSize.width/2,
                                origin.y + visibleSize.height - label->getContentSize().height * 2));

        // add the label as a child to this layer
        this->addChild(label2, 2);
    }

フォントについて

フォントはResource/fontsフォルダに拡張子ttfファイルが置いてあると思いますが、

Label::createWithTTF()をコールするときにフォントファイルを指定します。

Windowsだったらフォントはc:\windows\fontsがあるので、ここにあるttf/ttcファイルをここに置けば使用することができます。

文字列について

文字列はcocos2dx::Stringというクラスが存在するらしい。

これを使った方がいろいろと便利なので、これを使用する。

addChild()の第二パラメータ

これはzIndexとあったので、重ねて表示する場合、上に表示する順番を示すパラメータですね。

数字が大きい方が上に表示されるみたいです。

Windowsでの表示結果。

visibleは画面のサイズ、originは原点の座標のようです。

Windowsはこれでいいのですが、Android(pixel4a)の場合はこうなりました。

Pixel4aはちょっと横長なので、heightが少し小さいようです。

アスペクト比が異なり、アスペクト比は長辺が基準なので、その分heightが小さいのですね。

あと、左下にピンホールカメラがあるので、その分だけ、originのy座標が少し上になっていますね。

特殊ディスプレイ、嫌い。

まぁ、この点はどうするか後で考えよう。

古いディスプレイには御引退いただこう。

以前話していたVDT症候群のような症状。

もしかしたらと思いまして、

古いディスプレイの電源を切って使用しないようにしていたところ、

だいぶ症状は改善してきました。

まだ耳鳴りするけど、そんなに気になるほどじゃないです。

というのも、この古いディスプレイ、

ノングレアディスプレイじゃないのです。

グレアというのは、室内の光りがディスプレイに反射して映る現象で、

これが目にあまり良くない。

最近のディスプレイはこういった現象を起きないようにするノングレア対応されています。

今使用している二枚のディスプレイは、いずれもノングレアディスプレイです。

まぁ、今回のは飛び抜けて一番古いディスプレイだったからねぇ。

仕方が無いけど、引退していただこう。

新しいディスプレイ買わなきゃ。

【クラフトピア】2つ目のダンジョン攻略。

さらに強い敵を求めて、新たな大陸のダンジョンを攻略していきます。

1回攻略したダンジョンはアイテムは復活しないのですね。

ダンジョンはタルが沢山あるので、それを破壊すると調味料が手に入るし、

ダンジョン内の敵モブを倒すと作物の種が手に入ったりします。

ボスは光っているコアが弱点なので、ジャンプアタックを繰り返すと倒せます。

この種とは別のボスはいるのですか?

腰の所に立ててしまえます。

こうなればコア攻撃し放題。

最後のここを空けると、アヌビスに渡せるアイテムが手に入ります。

と言うことは、たくさんダンジョンを攻略すれば良いのですね。

拠点もかなり充実してきました。

まだ自動化は難しいですけどね。

【モラタメ】スチーミー 豚チャーシュー用

なんか大量に届いた(6枚)

これを使用すると豚チャーシューが電子レンジで作れてしまうらしい。

ということで。

豚バラブロック500グラム。

スチーミー1枚当たり、250グラムらしいので。

4等分。

フォークで表面に穴を空けて、

1枚当たり2ブロックをスチーミーに入れます。

ああ、チャックを空けると、かなりニンニクの匂いが効いてるね。

これを、向きに注意して電子レンジでチンする。

うちの電子レンジは500Wなので、1枚ずつ9分30秒チンし、そのあと5分放置。

出来上がった物はこちら。

見た目はかなり良い感じです。

ちなみに、パックの中はかなり脂でギットギトです。

この残ったタレ、後で使おうと思ったけど遠慮しておきます。

切り分けるとこんな感じ。

はしっこの方を食べてみると、表面は少しカリッとしていて、良い感じに脂が抜けております。

これは丼にして食べよう。

さすがに全部一気に食べると確実に太るので、タッパーに取ってあります。

これらは後でラーメンを食べるときに焼いてトッピングにしましょう。

しかし、電子レンジで15分足らずでこんなに美味しいチャーシューが食べられるとは、

予想外でした。

スチーミーの構造的に蒸気で圧力がかかっている状態なので、圧力鍋で調理している感じらしい。

短時間で調理できるので、ガス代もかからないのでかなり便利です。

これ、チャーシュー以外に使えないかな?

【クラフトピア】アヌビス神に会いに行く。

最初の島の上の方に、浮いている島があるんですよ。

かまどの上昇気流で行けるかなと思ったんですが、

どうも無理っぽい。

よくよく右上のクエストの内容を見たら、建築で登るって会ったので、

建築で昇りました。

ダンジョン攻略時にもらえるアイテムで能力をアップさせることが出来るみたいです。

ダンジョン周回すれば良いのかな?

とにもかくにも、素材を取り尽くしてしまったので、違う島に行ってみたいと思います。

この門からアイテムを消費することで、隣の島が開放されます。

より強い敵と戦うことが出来ます。

【北海道大戦】バトルシーンの画面を作成する

前回までの状況はこちら。

最新ソースはこちら(gitHub)。

https://github.com/takishita2nd/HokkaidoWar

バトルシーンの画面を作成していきます。

とは言っても部品を配置しているだけですが。

    class BattleScene : asd.Scene
    {
        private asd.TextObject2D _label = null;
        private asd.TextObject2D _attackCity = null;
        private asd.TextObject2D _deffenceCity = null;
        private asd.TextureObject2D _image_gu_attack = null;
        private asd.TextureObject2D _image_choki_attack = null;
        private asd.TextureObject2D _image_par_attack = null;
        private asd.TextureObject2D _image_gu_deffence = null;
        private asd.TextureObject2D _image_choki_deffence = null;
        private asd.TextureObject2D _image_par_deffence = null;
        private asd.TextObject2D _attackParam = null;
        private asd.TextObject2D _deffenceParam = null;

        public BattleScene()
        {

        }

        protected override void OnRegistered()
        {
            var layer = new asd.Layer2D();
            AddLayer(layer);

            // 下地
            var background = new asd.GeometryObject2D();
            layer.AddObject(background);
            var bgRect = new asd.RectangleShape();
            bgRect.DrawingArea = new asd.RectF(0, 0, 1800, 1000);
            background.Shape = bgRect;

            _label = new asd.TextObject2D();
            _label.Font = Singleton.GetLargeFont();
            _label.Text = "VS";
            _label.Position = new asd.Vector2DF(470, 400);
            layer.AddObject(_label);

            _attackCity = new asd.TextObject2D();
            _attackCity.Font = Singleton.GetLargeFont();
            _attackCity.Text = "札幌";
            _attackCity.Position = new asd.Vector2DF(450, 150);
            layer.AddObject(_attackCity);

            _deffenceCity = new asd.TextObject2D();
            _deffenceCity.Font = Singleton.GetLargeFont();
            _deffenceCity.Text = "小樽";
            _deffenceCity.Position = new asd.Vector2DF(450, 650);
            layer.AddObject(_deffenceCity);

            _attackParam = new asd.TextObject2D();
            _attackParam.Font = Singleton.GetLargeFont();
            _attackParam.Text = "戦闘力:10000";
            _attackParam.Position = new asd.Vector2DF(700, 650);
            layer.AddObject(_attackParam);

            _deffenceParam = new asd.TextObject2D();
            _deffenceParam.Font = Singleton.GetLargeFont();
            _deffenceParam.Text = "戦闘力:8000";
            _deffenceParam.Position = new asd.Vector2DF(700, 150);
            layer.AddObject(_deffenceParam);

            _image_gu_attack = new asd.TextureObject2D();
            _image_gu_attack.Texture = Singleton.GetImageGu();
            _image_gu_attack.Position = new asd.Vector2DF(300, 500);
            layer.AddObject(_image_gu_attack);

            _image_choki_attack = new asd.TextureObject2D();
            _image_choki_attack.Texture = Singleton.GetImageChoki();
            _image_choki_attack.Position = new asd.Vector2DF(450, 500);
            layer.AddObject(_image_choki_attack);

            _image_par_attack = new asd.TextureObject2D();
            _image_par_attack.Texture = Singleton.GetImagePar();
            _image_par_attack.Position = new asd.Vector2DF(600, 500);
            layer.AddObject(_image_par_attack);

            _image_gu_deffence = new asd.TextureObject2D();
            _image_gu_deffence.Texture = Singleton.GetImageGu();
            _image_gu_deffence.Position = new asd.Vector2DF(300, 250);
            layer.AddObject(_image_gu_deffence);

            _image_choki_deffence = new asd.TextureObject2D();
            _image_choki_deffence.Texture = Singleton.GetImageChoki();
            _image_choki_deffence.Position = new asd.Vector2DF(450, 250);
            layer.AddObject(_image_choki_deffence);

            _image_par_deffence = new asd.TextureObject2D();
            _image_par_deffence.Texture = Singleton.GetImagePar();
            _image_par_deffence.Position = new asd.Vector2DF(600, 250);
            layer.AddObject(_image_par_deffence);
        }

        protected override void OnUpdated()
        {

        }
    }

シンプルだけどまぁいいでしょう。

グー・チョキ・パーは絵文字が使えないっぽいので、絵文字を拡大してスクリーンショットで画像を作成しています。

次回は実際にシーンの切り替えをやってみます。