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

ホストPC外部から仮想PCに接続できるようにする

これの続き。

仮想PCにUbuntu20.04をセットアップしたものの、

今の状態ではホストPCからでしか仮想PCに接続できないので、

これをホストPCの外からでも接続できるようにします。

仮想PCのネットワーク設定はデフォルトこうなっています。

内部ネットワークが選択されているから、外からアクセス出来ないんでしょう。

なので、新しい仮想ネットワークスイッチを追加。

接続の種類を外部ネットワークにして、物理ネットワークデバイスを設定。

これを仮想PCのネットワークに設定します。

これで仮想PCを起動。

ルータのDHCPでIPアドレスが割り当てられていれば成功。

あとは、MACアドレスを静的な値に設定、ルータのDHCP設定で固定IPアドレスを登録すればOKでしょう。

マインクラフトサーバを自宅に設置したい。

今使用しているモデムですが、

無線ルータ・ひかり電話内蔵のモデルなんですが、

今までは業者が全部セットアップしてくれたおかげで、設定をいじくることができませんでした。

しかし、ビッグローブにプロバイダを乗り換えたことで、設定が初期化・再設定を行ったことにより、設定をいじくることができるようになったのです。

マインクラフトサーバはメインPC内の仮想PCで動作させたいと思います。

今持っているラズパイ4では、メモリは十分でもCPUパワーが足りない。

そして、外部からマイクラサーバに接続するには、ルータの設定で仮想PCに接続できるよう設定を変える必要があるのですが。

通常のルータはポートフォワーディングといって、特定ポート番号でのアクセスのみを内部PCに転送する機能があるのですが、

このモデム一体型ルータはポート番号に関係なく、外からのアクセスを内部PCに転送する設定にしかできないみたいです。

ということは、その設定を有効にした場合、転送先のPCは常に外からのアクセスに晒されるので、ファイヤーウォールの設定は必須になりますな。

さて、実際に仮想PCを立ち上げようか。

【ラズパイ】【カメラ】WEBから動画を撮影する。

今回はWebのプレビュー画面から動画の撮影を行いたいと思います。

赤丸のボタンを設置し、押すと録画開始、もう一度押すと録画停止という感じです。

まずはサーバ側。

すでに動画を撮影する方法は知っているので、これを使用します。

POSTリクエストを受け付ける処理を書いていきます。

    def do_POST(self):
        global thread
        global aviFilename
        global capture
        global out

        content_len = int(self.headers.get('content-length'))
        requestBody = json.loads(self.rfile.read(content_len).decode('utf-8'))

        if requestBody['contents']['command'] == 1:
            _, img = cap.read()
            dt_now = datetime.datetime.now()
            filename = dt_now.strftime('%Y%m%d_%H%M%S')+".jpg"
            cv2.imwrite(filename, img)

            response = {
                'status' : 200,
                'path': "http://pi4.local:8000/" + filename
            }
        if requestBody['contents']['command'] == 2:
            dt_now = datetime.datetime.now()
            aviFilename = dt_now.strftime('%Y%m%d_%H%M%S')+".avi"
            out = cv2.VideoWriter(aviFilename, fourcc, 20.0, (640,480))
            
            capture = True
            thread = threading.Thread(target=videoCapture)
            thread.start()

            response = {
                'status' : 200,
            }
        if requestBody['contents']['command'] == 3:
            capture = False

            thread.join()

            out.release()
            out = None

            response = {
                'status' : 200,
                'path': "http://pi4.local:8000/" + aviFilename
            }
        else:
            response = {
                'status' : 200
            }

        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        responseBody = json.dumps(response)

        self.wfile.write(responseBody.encode('utf-8'))

録画開始(command=2)を受信した場合、VideoWriterを作成し、動画を保存するスレッドを起動します。

録画停止(command=3)を受信した場合は、このスレッドを停止するように動作します。

実際のスレッド処理はこんな感じです。

def videoCapture():
    while capture:
        _, img = cap.read()
        out.write(img)

captureフラグがTrueのときは延々とカメラの映像を動画ファイルに保存します。

録画停止時にcaptureフラグをFalseに変更し、このスレッドが終了するのをjoinで待ってから、作成された動画ファイルのパスを返信します。

動画のダウンロードですが、たぶん、content-typeを用意しないと行けないので、Dictionaryに追加しています。

CONTENT_TYPE = {'.html': 'text/html; charset=utf-8', '.txt': 'text/plain; charset=utf-8', '.js': 'text/javascript', '.json': 'application/json',
        '.jpeg': 'image/jpeg', '.jpg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif',
        '.css':'text/css', '.avi': 'video/x-msvideo'}

さて、このままだと録画中にブラウザを閉じてしまって、録画を止める手段が無くなってしまいます。

これの対処を次回やります。

(そろそろカメラネタも無くなってきた。)

cocos2d-xのコーディングしてて思ったこと。(ガベコレの話)

C#やJavaにはガベージコレクションと言って、使用しなくなったオブジェクトを自動的に解放してくれる仕組みがありまして、

だからnewしたオブジェクトの使用後を意識しなくても使用できるんですが、

C++にはそんな機能は無く、

newしたオブジェクトは明示的にdeleteしないと、そのオブジェクトは消えません。

で、cocos2d-xはspriteなどを作成するときにnewを使用せずにcreate()メソッドを使用しているんです。

そして、サンプルを見る限りではdeleteは行っていない。

これはcocos2d-xのプラットフォームの中で独自にガベージコレクション的な動きをしています。

ただ、cocos2d-xのガベコレとC#やJavaのガベコレとは少しロジックが異なるようです。

詳しい内容はこの記事を見て欲しく

http://furicotech.blogspot.com/2015/03/memory-management-in-cocos2d-x.html

まぁ、この内容をまとめると、

cocos2d-xのspriteなどのオブジェクトは1回使い切り。(その都度createしなければならない。)

これ、自分もコーディングしててハマりました。

あると思っていたspriteのポインタが無効なポインタになっていた、と言うことが多々ありました。

C#と同じ感覚で使用することができないんですね。

なので、クラス設計をどうするか、少し悩んでいます。

spriteなどの部品はシーンクラスの中で全部処理させた方が良いかもしれない。

【cocos2d-x】タッチした場所にエフェクトを入れる

よくスマホゲームって、タッチした場所にアニメーションを入れてエフェクトかけてるじゃないですか。

あれを実装して見たいと思います。

今回もフリー素材のぴぽや倉庫さんから素材を拝借しました。

すでに、タッチ処理のやり方もアニメーションのやり方も知っているので、これを組み合わせます。

コードはこうなりました。

bool HelloWorld::init()
{

  中略

    auto listener1 = EventListenerTouchOneByOne::create();
    listener1->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

    return true;
}

bool HelloWorld::onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event)
{
    auto anime = Sprite::create("pipo-btleffect007.png");
    if (anime == nullptr)
    {
        problemLoading("'pipo-btleffect007.png'");
    }
    else
    {
        this->addChild(anime, 0);
    }
    float frameHeight = anime->getContentSize().height;
    float frameWidth = anime->getContentSize().width / 14.0;
    Vector<SpriteFrame*> animFrames;
    animFrames.reserve(14);
    for(int i = 0; i < 14; i++) {
        animFrames.pushBack(SpriteFrame::create("pipo-btleffect007.png", Rect(frameWidth * i,0,frameHeight,frameWidth)));
    }
    Animation* animation = Animation::createWithSpriteFrames(animFrames, 0.02f);
    Animate* animate = Animate::create(animation);

    anime->setPosition(touch->getLocation().x, touch->getLocation().y);
    anime->runAction(animate);

    return true;
}

前回のタッチ処理でラムダ式を使いましたが、

C++のラムダ式は思った以上に使いづらいので

普通にコールバックとして関数を定義し、マクロで登録する形にしました。

アニメーション画像は14枚横に並んでいるので、この画像を横に14等分割にクリッピングして使用しています。

あとはアニメーション画像の位置をタップ位置に設定してアニメーションを実行します。

前回はanimateをforever(無限ループ)していましたが、今回は1回きりのアニメーションなので、animateをそのまま使用しています。

どちらも親クラスがActionクラスなので、問題ありません。

【北海道大戦】戦闘結果をメインシーンへ反映

たぶん、これが最後の仕上げ。

まずはじゃんけんの結果を判定します。

    class BattleScene : asd.Scene
    {
        private void onClickMouseShowActionResult(asd.Vector2DF pos)
        {
            var result = janken(selectedAttack, selectedDeffece);
            if(result == BattleResult.win)
            {
                _deffencePower -= _attackPower;
                if(_deffencePower <= 0)
                {
                    Singleton.GameData.BattleResultUpdate(BattleResult.win);
                    var scene = new MainScene();
                    asd.Engine.ChangeScene(scene);
                }
                _deffenceParam.Text = "戦闘力:" + _deffencePower;
            }
            else if(result == BattleResult.lose)
            {
                _attackPower -= _deffencePower;
                if (_attackPower <= 0)
                {
                    Singleton.GameData.BattleResultUpdate(BattleResult.lose);
                    var scene = new MainScene();
                    asd.Engine.ChangeScene(scene);
                }
                _attackParam.Text = "戦闘力:" + _attackPower;
            }
            _attackResult.Hide();
            _deffenceResult.Hide();
            _status = GameStatus.SelectDeffenceAction;
        }

        private BattleResult janken(BattleIcon.Icon attack, BattleIcon.Icon deffence)
        {
            switch (attack)
            {
                case BattleIcon.Icon.Gu:
                    switch (deffence)
                    {
                        case BattleIcon.Icon.Gu:
                            return BattleResult.draw;
                        case BattleIcon.Icon.Choki:
                            return BattleResult.win;
                        case BattleIcon.Icon.Par:
                            return BattleResult.lose;
                        default:
                            return BattleResult.draw;
                    }
                case BattleIcon.Icon.Choki:
                    switch (deffence)
                    {
                        case BattleIcon.Icon.Gu:
                            return BattleResult.lose;
                        case BattleIcon.Icon.Choki:
                            return BattleResult.draw;
                        case BattleIcon.Icon.Par:
                            return BattleResult.win;
                        default:
                            return BattleResult.draw;
                    }
                case BattleIcon.Icon.Par:
                    switch (deffence)
                    {
                        case BattleIcon.Icon.Gu:
                            return BattleResult.win;
                        case BattleIcon.Icon.Choki:
                            return BattleResult.lose;
                        case BattleIcon.Icon.Par:
                            return BattleResult.draw;
                        default:
                            return BattleResult.draw;
                    }
                default:
                    return BattleResult.draw;
            }
        }

今回のじゃんけん判定はベタな方法で実装しました。

上手い方法が思い浮かばなかった。

janken()でじゃんけんの結果を判定し、その結果を反映、勝負が決まったら、その情報をGameData.BattleResultUpdate()に反映し、MainSceneに遷移します。

    class GameData
    {
        public void BattleResultUpdate(BattleResult result)
        {
            if (gameStatus == GameStatus.ActionEnemy)
            {
                Battle.EnemyTurnEnd(result);
            }
            else if (gameStatus == GameStatus.ActionPlayer)
            {
                Battle.MyTurnEnd(result);
                gameStatus = GameStatus.ActionEnemy;
            }
        }

状態が敵のターンか、自分のターンかで分岐させます。

    class Battle
    {
        public void EnemyTurnEnd(BattleResult result)
        {
            if(result == BattleResult.win)
            {
                lastAttack.CombinationCity(lastDeffece);
                lastDeffece.Lose();
                aliveCities.Remove(lastDeffece);
                lastDeffece = null;
            }
        }

        public void MyTurnEnd(BattleResult result)
        {
            if (result == BattleResult.win)
            {
                lastAttack.CombinationCity(lastDeffece);
                lastDeffece.Lose();
                aliveCities.Remove(lastDeffece);
                lastDeffece.ClearPaint();
                lastDeffece = null;
            }

            lastAttack.ClearPaint();
            lastAttack = null;

            cityCnt++;
            if (cityCnt >= _cities.Count)
            {
                _cities = cityRandomReplace(aliveCities);
                aliveCities = copyCity(_cities);
                cityCnt = 0;
                turn++;
            }
        }

これでデータの反映もできたはず。

あとはもう少しブラッシュアップさせて行きます。

【ラズパイ】【カメラ】Webからカメラの画像を保存する。

前回はカメラのプレビュー画面をWebに表示させましたが、

今回はWebからシャッターボタンを設置して、カメラの画像を保存させます。

すでにPOSTリクエストを処理する方法も知っていますし、カメラの画像を保存する方法も知っているので、これらを組み合わせればできるはずです。

まずはサーバ(ラズパイ)の処理。


    def do_POST(self):
        content_len = int(self.headers.get('content-length'))
        requestBody = json.loads(self.rfile.read(content_len).decode('utf-8'))

        if requestBody['contents']['command'] == 1:
            _, img = cap.read()
            dt_now = datetime.datetime.now()
            filename = dt_now.strftime('%Y%m%d_%H%M%S')+".jpg"
            cv2.imwrite(filename, img)

            response = {
                'status' : 200,
                'path': "http://pi4.local:8000/" + filename
            }
        else:
            response = {
                'status' : 200
            }

        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        responseBody = json.dumps(response)

        self.wfile.write(responseBody.encode('utf-8'))

POST処理を追加しました。

カメラの画像をファイルに保存し、そのファイルパスをレスポンスで返すようなイメージです。

次はWeb側。

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="vue.min.js"></script>
  <script src="jquery-3.5.1.slim.min.js"></script>
</head>
<body>
  <div id="app">
      <image id="camera" src="" /><br />
      <button @click="onShutter">Shutter</button><br />
      <a id="picture" href="" target="_blank">{{ path }}</a>
  </div>

  <script>
    var app = new Vue({
      el: '#app',
      data: {
        timer: null,
        param: {},
        contents: {
          command: 1,
        },
        path: "",
      },
      created: function() {
        self = this;
        this.timer = setInterval(function() {self.onLoad()}, 50)
      },
      methods: {
        onLoad: function() {
            axios.get('http://pi4.local:8000/Streaming').then(function(response){
                $("#camera").attr('src', response.data.image);
            }).catch(function(error){
            });
        },
        onShutter: function() {
          self = this;
          this.param.contents = this.contents;
          axios.post('http://pi4.local:8000/', this.param).then(function(response){
                $("#picture").attr('href', response.data.path);
                self.path = response.data.path;
            }).catch(function(error){
            });
        }
      }
    })
  </script>
</body>
</html>

ボタンとリンクを追加しました。

ボタンをクリックすると、onShutter処理が実行され、ラズパイ側にPOSTリクエストを送信します。

そのレスポンスから画像のファイルパスを取得し、リンクに反映させます。

同じ仕組みで動画の撮影もできそう。

次回やります。

【ダイエット支援】【入力履歴機能】入力履歴にデータを記入

ここもサクッと作成できると思う。

namespace App\Repository;

use App\Model\EatingHistoryItem;

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

    /**
     * ヒストリにデータを1件追加する
     */
    public function addHistory($param, $user)
    {
        $model = new EatingHistoryItem();
        foreach($this->templateParamNames as $name)
        {
            $model->$name = $param[$name];
        }
        $model->save();

        $this->attachToUser($model, $user);
    }
class ApiController extends Controller
{
    /**
     * データを1件登録する
     */
    public function add(Request $request)
    {
        $paramNames = $this->eatingManagement->getParam();

        $param = [];
        foreach($paramNames as $name) {
            $param[$name] = $request->contents[$name];
        }

        $this->eatingManagement->add($param, Auth::user(), $request->contents['timezone']);
        $this->eatingManagement->addHistory($param, Auth::user());
        
        return response()->json();
    }

入力したデータをそのまま履歴にも記入する、という処理ですな。

ここまではサクッとできたけど、次回からはかなりヘビーになると思う。

【COCOS2D-X】背景画像を設置。

画像はWeb検索して、見つけたフリー素材を使用しました。

で、これ多分、普通に表示するだけだと、画面からはみ出してしまうので。

これを上手く画面に収まるようにしたい。

見た感じ、画像の高さがはみ出ているので、高さの表示を画像に合うように拡大率を計算して、画像を縮小させたいと思います。

    auto sprite = Sprite::create("ID003_Western-Castle_noon.jpg");
    if (sprite == nullptr)
    {
        problemLoading("'ID003_Western-Castle_noon.jpg'");
    }
    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 scaleRate = visibleSize.height / sprite->getContentSize().height;
    sprite->setScale(scaleRate);

    auto str = String();
    str.appendWithFormat("width %f height %f", sprite->getContentSize().width, sprite->getContentSize().height);
    label->setString(str.getCString());

この画像の範囲をベースにして、画面を作っていきましょうか。

あ、画面をタップしたときにエフェクトかかるようにしたいなぁ。

次回やります。

【ラズパイ】【カメラ】プレビュー画面をラズパイから取得・表示(修正)

この記事のソースが余りにも冗長すぎるので、書き換えました。

こんな感じです。

CONTENT_TYPE = {'.html': 'text/html; charset=utf-8', '.txt': 'text/plain; charset=utf-8', '.js': 'text/javascript', '.json': 'application/json',
        '.jpeg': 'image/jpeg', '.jpg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif',
        '.css':'text/css'}



    def do_GET(self):
        parsed = urlparse(self.path)
        if parsed.path == '/Streaming':
            enc = sys.getfilesystemencoding()

            _, img = cap.read()
            resized_img = cv2.resize(img, (480, 320))
            _, encoded_img = cv2.imencode('.jpg', resized_img, [int(cv2.IMWRITE_JPEG_QUALITY), 30])
            dst_base64 = base64.b64encode(encoded_img).decode('utf-8')

            data = {
                'image': 'data:image/jpg;base64,' + dst_base64
            }

            encoded = json.dumps(data).encode()

            self.send_response(HTTPStatus.OK)
            self.send_header("Content-type", "text/html; charset=%s" % enc)
            self.send_header("Access-Control-Allow-Origin", "null")
            self.send_header("Content-Length", str(len(encoded)))
            self.end_headers()

            self.wfile.write(encoded)
        elif parsed.path.endswith('/'):
            self.send_response(HTTPStatus.OK)
            with open('index.html',mode='br') as f:
                data = f.read()
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            self.wfile.write(bytes(data))
        else:
            self.send_response(HTTPStatus.OK)
            filepath = '.' + parsed.path
            with open(filepath, mode='br') as f:
                data = f.read()
            self.send_header("Content-type", CONTENT_TYPE[pathlib.Path(filepath).suffix])
            self.end_headers()
            self.wfile.write(bytes(data))

まず、URLが”/”の場合はindex.htmlを返します。

そして、URLが”/Streaming”の場合はカメラの映像をbase64で返します。

それ以外の場合(今回で言うと、jsファイル達)、URLで指定されたファイル名と同じファイルを返します。

ただ、その場合、content-typeを正しいものにしないとブラウザが正しく動いてくれないので、ファイル拡張子に対応する適切なcontent-typeを設定するようにしています。

それを対応づけているのがCONTENT_TYPEです。

こうすることで、今後jsファイル以外のものにも対応出来ます。

一般的なHTTPサーバに比べれば不十分ですが、今回目指しているのはそれではないので、こんなもんで十分でしょう。

ソースはgitHubに公開しました。

https://github.com/takishita2nd/RemoteCamera