「Laravel」カテゴリーアーカイブ

Tesseract-OCRを試してみる

どうやらLinuxにはオープンソースのOCRソフト

Tesseract-OCRというものがあるようです。

これを試してみます。

こちらの記事を参考にしました。

https://kitakantech.com/tesseract-basic/

インストールは、

$ sudo apt-get install tesseract-ocr
$ sudo apt-get install tesseract-ocr-jpn

1つ目はTesseract-OCRの本体、2つ目は言語対応モジュールです。

使用方法は、こんな感じ。

$ tesseract test.jpg output -l jpn

これに対して、

読み込ませてみました。

うーん、読み込んでほしい部分が読み込まれてない。

写真だからダメなのだろうか。

たぶんスキャナで読み取った画像ならうまく読み込めるのかもしれないが。

栄養成分表をスキャナで読み取るなんて手間はありえないので、

ちょっと、この機能の実装を続けるかどうかは微妙ですな。

【ダイエット支援】アップロード処理(Vue.js側)

こちらの記事を参考にしました。

                <div id="mobile">
                    <p>
                        <input @change="onSelectedFile" type="file" name="file" />
                    </p>
                    <p>
                        <button @click="onUpload">送信</button>
                    </p>
        onSelectedFile: function(e) {
            e.preventDefault();
            let files = e.target.files;
            this.uploadFile = files[0];
        },
        onUpload: function() {
            let formData = new FormData();
            formData.append('picture', this.uploadFile);
            let config = {
                headers: {
                    'content-type': 'multipart/form-data'
                }
            };
            axios
                .post('/api/eating/upload', formData, config)
                .then(function(response) {
                })
                .catch(function(error) {
                })
        },

これの動作確認のために、Laravel側にWebAPIを作成します。

Route::post('api/eating/upload', 'Eating\ApiController@upload');
    public function upload(Request $request)
    {
        $file = $request->picture;
        logger(dump($file->getClientOriginalName()));
        return response()->json();
    }

適当な画像ファイルをアップロードしてみましたが、きちんとLaravel側でもファイルを認識しているようです。

次はOCR処理を作成していこうと思います。

【ダイエット支援】スマホから写真をアップロードできるようにする

さて、スマホのレイアウト崩れも直ったので、

これからやりたいことは、

・カメラで栄養成分表示を撮影してアップロードする

・それをOCRでテキストデータに変換し、数値を入力する

です。

まずは、スマホの画面にだけ、ファイルアップロードのコントロールを表示させます。

div#mobile {
    @include small {
    }
    @include middle {
        display: none;
    }
    @include big {
        display: none;
    }
}
                <div id="mobile">
                    <p>
                        <input type="file" name="file" />
                    </p>
                    <p>
                        <button>送信</button>
                    </p>
                </div>

メディアクエリの使い方覚えたから楽勝。

PCからはこうなってますが、

スマホからはこうなります。

で、ファイルを選択をタップすると、

こんな感じでカメラを起動して撮影したものをすぐにアップロードできるんですね。

とりあえず、今日やりたいことはできた。

次回は実際にOCRを組み込んでみたいと思います。

【ダイエット支援】スマホ画面でのダイアログ表示崩れを修正

前回の修正で

メディアクエリを使用すればレスポンシブデザインのコーディングができるよ、ということがわかりました。

では、これをsassにも適用させたいのですが、

sassに記入するには、ミックスインというものを使用するのが一般的なようです。

具体的には、こんな感じ。

@mixin small {
    @media (max-width: (479px)) {
        @content;
    }
}

@mixin middle {
    @media (min-width: (480px) and (max-width:959px)) {
        @content;
    }
}

@mixin big {
    @media (min-width: (960px)) {
        @content;
    }
}

@mixin XXでメディアクエリの定義に名前をつけ、この定義をsassの先頭あたりに記載します。

今回は大中小と記載しましたが、いいように名前を指定できます。

で、これをsassのスタイル定義に使用します。

    #content {
        z-index: 2;
        @include small {
            width: 90%;
        }
        @include middle {
            width: 50%;
        }
        @include big {
            width: 30%;
        }

こうすることでsassにもメディアクエリが適用できます。

いい感じになりました。

【ダイエット支援】スタイルの修正ってめんどくさいね・・・

TechCommitでレイアウト崩れの相談をしたところ、

            .full-height {
                height: 100vh;
            }

これがあると、高い確率でスマホのレイアウトが崩れるらしい(と言う認識)

なので、削除。

.flex-center {
    align-items: center;
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
}

display: flex;とflex-wrap: wrap;はセットで考えた方が良いらしい(という認識)

flex-wrap: wrap;を追加。

@media screen and (max-width:479px) {
    .title {
        font-size: 34px;
    }
    #whatsthis {
        margin-left: 10%;
        font-size: 10px;
    }
}

@media screen and (min-width:480px) and (max-width:959px){
    .title {
        font-size: 44px;
    }
    #whatsthis {
        font-size: 15px;
    }
}

@media screen and (min-width:960px) {
    .title {
        font-size: 84px;
    }
    #whatsthis {
        font-size: 20px;
    }
}

あとはメディアクエリを使って、ディスプレイのサイズに応じて、スタイルを切り替える、と言う手法を使うらしい。

レスポンシブデザインでは常套手段みたい。

ということで、ここまできれいになりました。

頑張った、俺。

【ダイエット支援】スマホからでも利用できるようにしたい

次のステップとして、スマホから利用することによって、スマホの情報を利用できるようにしたいなぁ、って思ってたんですが、

それ以前の問題が起きていました。

トップページ。

ひどいなこれ。

データ入力・編集・削除ダイヤログも崩れていました。

これはひどい。

まずはこれを直すところかは始めようか。

【ダイエット支援】最新版をデプロイしました。

https://taki-lab.site:8443/

ポート番号の設定も修正したので、使用できるはず。

今後の予定。

やりたいこと。

  1. 栄養成分表を撮影して、OCRで解析して自動入力。
  2. バーコードをスキャンして自動入力
  3. スマホのGPSの記録から行動履歴や運動を分析する
  4. スマホに対応させちゃう
  5. あと、見た目の問題

4.見た目の問題は後々考える。

2.はハードル高そう。それを扱うデータベースやAPIがないと無理。

個人的には3.をやりたいかな、ダイエットには必要な機能。

いろいろ解決しなきゃ行けない問題はあるけど。

GPSのデータをどうやって取り込むか、とか。

まぁ、考えます。

【ダイエット支援】【入力履歴機能】管理者アカウントを登録

残りは管理者画面を管理者以外アクセスできないようにする対処を入れます。

まずは、管理者アカウントを登録。

<?php

use Illuminate\Database\Seeder;

class ManagerAccountSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->insert(['name' => 'manager', 'email' => 'manage@gmail.com', 'password' => bcrypt('manager')]);
    }
}

実際運用するときはもちろん値書き換えるよ。

あとは、このアカウント以外は管理者画面に入れないようにする。

class MaintenanceController extends Controller
{
    public function index()
    {
        if(Auth::user()->email == 'manage@gmail.com') {
            return view('maintenance');
        }else{
            return redirect('/home');
        }
    }
}

管理者以外は/homeにリダイレクトさせています。

まぁ、管理者は一人しかいないから、権限管理とかは使用せずに決め打ちでいいかと思います。

できれば一箇所変更すれば対応できるようにしたかったけど、うまい方法が見つからなかった。

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

なかなかいい感じに仕上がっております。

    public function searchKeyword($keyword, $user)
    {
        $result = [];
        $records1 = EatingTemplateItem::where('item', 'like', "%$keyword%")->get();
        $records2 = $user->EatingHistoryItems()->where('item', 'like', "%$keyword%")->get();
        if(count($records1) + count($records2) >= 10 )
        {
            return [];
        }
        foreach($records1 as $record)
        {
            $obj = new \stdClass();
            foreach($this->templateParamNames as $paramName)
            {
                $obj->$paramName = $record->$paramName;
            }
            $result[] = $obj;
        }
        foreach($records2 as $record)
        {
            $obj = new \stdClass();
            foreach($this->templateParamNames as $paramName)
            {
                $obj->$paramName = $record->$paramName;
            }
            $result[] = $obj;
        }
        return $result;
    }
        onChangeItem: function() {
            if(this.contents.item!=""){
                var flg = this.setTemplete();
                if(flg == false) {
                    var self = this;
                    this.param.contents = this.contents;
                    axios.post('/api/eating/search', this.param).then(function(response){
                        self.keywords = [];
                        response.data.keywords.forEach(keyword => {
                            self.keywords.push(keyword);
                        });
                    }).catch(function(error){
                        self.error_flg = true;
                        self.errors = error.response.data.errors;
                    });
                }
            }else{
                this.keywords = [];
            }
        },
        setTemplete: function() {
            for (var index = 0; index < this.keywords.length; index++) {
                if(this.keywords[index].item == this.contents.item) {
                    this.contents.protein = this.keywords[index].protein;
                    this.contents.liqid = this.keywords[index].liqid;
                    this.contents.carbo = this.keywords[index].carbo;
                    this.contents.calorie = this.keywords[index].calorie;
                    return true;
                }
            }
            return false;
        }

これで入力履歴からデータの入力ができました。

ついでに同じデータが二重で履歴に登録されないように細工します。

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

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

これである程度利便性が高まりましたね。

あとは管理画面か・・・。

他のユーザーから管理画面を表示できないようにしないと。

【ダイエット支援】【入力履歴機能】履歴検索して候補の表示

うまくスクショが取れなかった・・・

    public function searchKeyword($keyword, $user)
    {
        $result = [];
        $records1 = EatingTemplateItem::where('item', 'like', "%$keyword%")->get();
        $records2 = $user->EatingHistoryItems()->where('item', 'like', "%$keyword%")->get();
        if(count($records1) + count($records2) >= 10 )
        {
            return [];
        }
        foreach($records1 as $record)
        {
            $result[] = $record->item;
        }
        foreach($records2 as $record)
        {
            $result[] = $record->item;
        }
        return $result;
    }
    public function search(Request $request)
    {
        return response()->json([
            'keywords' => $this->eatingManagement->searchKeyword($request->contents['item'], Auth::user()), 
            ]);
    }
Route::post('api/eating/search', 'Eating\ApiController@search');
                            <td><input type="search" v-model="contents.item" autocomplete="on" list="keyword" v-on:keydown="onChangeItem"/></td>
                            <datalist id="keyword">
                                <option v-for="keyword in keywords" v-bind:value="keyword" />
                            </datalist>
        onChangeItem: function() {
            if(this.contents.item!=""){
                var self = this;
                this.param.contents = this.contents;
                axios.post('/api/eating/search', this.param).then(function(response){
                    self.keywords = [];
                    response.data.keywords.forEach(keyword => {
                        self.keywords.push(keyword);
                    });
                }).catch(function(error){
                    self.error_flg = true;
                    self.errors = error.response.data.errors;
                });
            }
        }

まだコードは暫定ですが、

とりあえず、入力した文字からデータベースを検索して、入力候補表示までできました。

とりあえず、検索結果だけ。

検索結果から各パラメータを自動入力させるのはまた次回に。