「技術」カテゴリーアーカイブ

【ラズパイ】プログラムからカメラで動画撮影する。

今回はさらにxキーで動画撮影開始・停止を行います。

プログラムはこのようになりました。

from datetime import datetime
import cv2, os

cap = cv2.VideoCapture(0)
fourcc = cv2.VideoWriter_fourcc(*'XVID')


def main():

  if cap == None:
    return False

  out = None
  capture = False

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

    cv2.imshow("preview", img)

    if capture:
      if out != None:
        out.write(img)

    key = cv2.waitKey(1)
    if key == ord('z'):
      cv2.imwrite("test.png", img)
    if key == ord('x'):
      if not capture:
        capture = True
        out = cv2.VideoWriter('test.avi',fourcc, 20.0, (640,480))
      else:
        capture = False
        out.release()
        out = None
    elif key < 255:
      break

  # 事後処理
  cap.release()
  if out != None:
    out.release()
  cv2.destroyAllWindows()

if __name__ == '__main__':
  main()
  

今回は新しくVideoWriter_fourcc()というのが出てきました。

引数の文字列は動画コーデックを示しているみたいです。

よく分からんけど。

まぁ、結局はこのコードをコピペして使うんだー

※ちなみに、プログラムをブログに書いたり、gitHubにあげているのは、就職して職場内に行ったとしてもコピペして使えるようにするためです。

VideoWriter()をつかって動画に保存するためのオブジェクトを作成します。

引数は、動画ファイル名、上のfourCC、フレームレート(fps)、解像度です。

オブジェクトを作成したら、フレームデータをこのオブジェクトにwrite()で書き込むだけです。

今回はxキーで録画開始・停止を行うので、xキーオンオフでオブジェクトを作成・解放を操作しています。

次回はいよいよリモートやってみようかな

【ラズパイ】プログラムからカメラの画像を保存する

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

前回はカメラの画像を表示させていただけですが、

今回はこの画像をファイルに保存します。

例えば、zキーを押すことで画像をファイルに保存することにします。

プログラムはこうなります。

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)

    key = cv2.waitKey(1)
    if key == 122:
      cv2.imwrite("test.png", img)
    elif key < 255:
      break

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

if __name__ == '__main__':
  main()

cv2.waitKey(1)が122というのは、押したキーがzキーだったと言うことを示しています。

画像ファイルに保存する場合はimwrite()を使ってフレームデータとファイル名を渡すとカレントディレクトリに指定ファイル名で保存されます。

【ダイエット支援】今後の機能追加について

一通り食事管理機能は完成したのですが、

正直、いまいち使いづらいです。

数値を毎回入力しなければならないので。

過去に入力したデータを再利用できないかな、と思っているのですが、

調べてみると、HTMLのinputタグにはtype=searchというものがありまして、

                                <input type="search" v-model="contents.item" autocomplete="on" list="keyword"/>
                                <datalist id="keyword">
                                    <option value="札幌" />
                                    <option value="札駅" />
                                    <option value="新さっぽろ" />
                                    <option value="東札幌" />
                                    <option value="札束" />
                                </datalist>

こんな感じでautocomplete=”on” list=”キーワード”と記入すると、

こんな感じでdatalistタグのid=”キーワード”の内容が入力候補として表示されます。

これをうまく使えないかと。

品名の一部を入力→入力履歴を検索→候補を表示→履歴からデータを入力

という感じで、うまく処理できないかと思っています。

データベース

本来なら栄養素情報を別テーブルにしてそれを参照するという、正規化が必要なのだと思いますが、

いまからデータベースに変更を入れるのは、既存機能の大規模改修が発生(めんどくさい)ので、

それはそのままに、履歴検索用のテーブルを用意することにします。

それがあれば、入力履歴検索用のAPIを作成すれば行けるような気がします。

UI

datalistの中のoptionタグがv-forでリストを反映させることができると思います。

ただ、大量のoptionが画面に表示されても鬱陶しいので、例えば、検索結果が10件以上だったらあえて表示させない、というのも、一つの手かもしれません。

とりあえず、こんな方針でやってみますか。

次回から着手します。

【Cocos2d-x】画像を使用する

今回は画像を使ってみます。

画像はスプライトと呼ばれる単位で扱われます。

例えば、ただ画面に画像を表示する場合は、

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("pipo-charachip001b.png");
    if (sprite == nullptr)
    {
        problemLoading("'pipo-charachip001b.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;
}

画像をResources/res配下に設置し、

Sprite::create(“[画像ファイル名]”)スプライトの作成。

setPosition()で表示位置を決めてaddChild()でスプライトを設置すれば表示されます。

でも大抵は一枚の絵の中にいくつものパーツがまとめられているのが普通です。

なので、画像をクリッピングして、一部だけを表示させます。

    // add "HelloWorld" splash screen"
    auto sprite = Sprite::create("pipo-charachip001b.png", Rect(0,0,32,32));
    if (sprite == nullptr)
    {
        problemLoading("'pipo-charachip001b.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);
    }

    auto sprite2 = Sprite::create("pipo-charachip001b.png", Rect(0,32,32,32));
    if (sprite2 == nullptr)
    {
        problemLoading("'pipo-charachip001b.png'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite2->setPosition(Vec2(visibleSize.width/2 + origin.x + sprite->getContentSize().width, visibleSize.height/2 + origin.y));

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

じゃあ、これを使って、アニメーションさせます。


    auto sprite = Sprite::create("pipo-charachip001b.png");
    if (sprite == nullptr)
    {
        problemLoading("'pipo-charachip001b.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);
    }

    Vector<SpriteFrame*> animFrames;
    animFrames.reserve(12);
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(0,0,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(32,0,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(64,0,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(0,32,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(32,32,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(64,32,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(0,64,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(32,64,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(64,64,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(0,96,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(32,96,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(64,96,32,32)));

    Animation* animation = Animation::createWithSpriteFrames(animFrames, 0.1f);
    Animate* animate = Animate::create(animation);

    sprite->runAction(RepeatForever::create(animate));

    auto moveBy = MoveBy::create(2, Vec2(50, 0));
    sprite->runAction(moveBy);

    return true;

SpriteFrameを組み合わせてAnimationを作成し、それからAnimateを作成します。

これからアクションを作成して、実行させます。

なかなか良い感じじゃないですか?

【北海道大戦】メインシーンの作り直し

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

はい、大幅に書き換えました。

詳細はgitHubのソースを見て欲しいのですが、

https://github.com/takishita2nd/HokkaidoWar

まず、メインシーンで使用していたデータは全てGameDataクラスに移動しました。

    class GameData
    {
        public enum GameStatus
        {
            None,
            SelectCity,
            ActionEnemy,
            ActionPlayer,
            ShowResult,
            GameEnd,
            GameOver
        }

        public GameStatus gameStatus = GameStatus.None;
        public List<City> Cities = null;
        public List<City> AliveCities = null;
        public Battle Battle = null;
        public Player Player = null;

このクラスはシングルトンで管理します。

    class Singleton
    {
        private static GameData _gameData = null;

        public static GameData GetGameData()
        {
            if (_gameData == null)
            {
                _gameData = new GameData();
            }
            return _gameData;
        }

このシングルトンクラス、ゲッターのでいいんじゃないかと思い始めた。

やっていることは同じですが。

メソッドでやるか、プロパティでやるかの違いです。

気が向いたら直します。

シーン遷移は全てシーンクラスのインスタンスを作り直します。

            if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
            {
                var scene = new MainScene();
                asd.Engine.ChangeScene(scene);
            }

そのときに、ゲームデータを元に画面を作り直す、という感じです。

これで、バトルシーンからメインシーンに切り替わっても、ゲームが止まることは無くなりました。

これでやっと次に進める・・・。

【北海道大戦】バトルシーンを状態遷移に組み込んでみる。

前回作ったバトルシーンを、既存の状態遷移に組み込んでみました。

変更前はこんな感じでした。

結構複雑になってきましたね。

でも修正はそんなに難しくない・・・と思いきや。

どうやらシーンは切り替えるごとに作り直さなくちゃいけないらしくて

バトルシーンからメインシーンに切り替わるときに、OnUpdated()処理が走らないという。

おそらくシーンのプロパティにIsAliveというのがあって、

これはReadOnlyのプロパティなので、やっぱりシーンを作り直さなくちゃいけないんだろうなぁ、と思います。

ということは、ゲームデータはシーンとは別に保持しなくちゃいけなくて、

大幅に設計変更が発生しそう、

と分かった時に心が折れました。

まぁ、完成はさせたいので、時間があるときに修正しておきます。

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

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

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

やり方は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は調べてみると、いろいろな事ができるみたいなので、もうちょっと調べてみます。

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

前回までの状況はこちら

最新ソースはこちら(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座標が少し上になっていますね。

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

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