未分類

前回までにdockerを動かせるまでになりました。

今回はdockerで試しにプロキシサーバーを立ててみようと思います。

未分類

前回までにssh ユーザー名@elephantcat.workみたいなことができるようになりました。

次はVPSで新しいサービスを稼働させるべく、dockerを入れます。

未分類

前回はVPSでUbuntuを1台立てて、sshでユーザー名@IPアドレスに繋げられるところまで設定しました。

今回はこのVPSにドメインを設定し、sshでユーザー名@ドメインに繋げられるようにしよう、としてみます。

未分類

前回はVALUE SERVERというレンタル共有サーバーから、ConoHaのVPSに引っ越した話をしました。

今回以降はしばらくそのとき行った設定などのメモをしておきます。

未分類

VALUE SERVERのまるっとプランから、ConoHaの1GBプランに引っ越しました。

未分類

まだいろいろ適当ですが、とりあえず広告置かないとGoogle様に怒られるので取り急ぎ再開した次第です。

未分類

最初に書いたとおりVALUE-SERVER契約終了に付き、サイトは一旦休止します。

次は共有サーバーではなく月額固定のVPSにする予定です。可能であれば当サイトも再開しますが、再開時期は未定です。

未分類

はじめに

前回はなんとかvscodeを使って、djangoをUbuntu上で動かしてみました。

今回はHello,Worldでないアプリを作ろうと考え、pythonらしく物体検出でもやってみることにしました。と、言っても検出ロジックは丸パクリです。

PythonでOpenCV DNNを利用して物体検知(Object Detection)する方法 | HaneCa

DNNはおろか、OpenCVすらよく分かってません。

仕様

  1. ユーザーがサイトに来る
  2. アプリがトップページを返す
  3. ユーザーが画像を指定してアップロードする
  4. アプリが画像から物体検出する
  5. アプリが検出済み画像をファイルに出力する
  6. アプリがファイルに出力した画像を挿し込んだトップページを返して3に戻る

リクエストが複数同時に来たときの仕様がありませんが、今回は固定ファイルに上書きします。結果は神のみぞ知るスタイルです。

設計

  • 画像あり/なしで表示を分けられるテンプレートトップページ
  • モデル空
  • 物体検出は固有のモデルでもビューでもフォームでもないものに実装
  • ビューで画像を受け取り、物体検出を呼び出し、結果を受けてテンプレートを呼び出す

実装

環境調整

djangoのような大きなモジュールをpipでインストールすると、vscodeのファイル監視が許容量オーバーしてしまいます。

Running Visual Studio Code on Linux

にあるように、システムの最大監視数を増やします(8,192→524,288)。

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

これでvscodeを起動し直して、警告が出なければ大丈夫です。

でもって、新しくアプリケーション作るため、vscodeのターミナルから以下を実行します。

python manage.py startapp detect_app

フェーズ1

まずは物体検出を行わず、受け取った画像そのものを返して実装。つまり物体検出はハリボテ(モック)。

ビューはこんな感じ。コントローラがないので、全てビューで記述してます。受け取ったファイルの検証はしてません(公開は出来ない)。

from django.http import HttpResponseRedirect
from django.shortcuts import render
from .apps import DetectAppConfig
from .forms import UploadFileForm
from .upload import handle_uploaded_file
from .detect import detect


def upload_file(request):
    result = None
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            input_path = DetectAppConfig.image_filebase + DetectAppConfig.image_file_input
            output_path = DetectAppConfig.image_filebase + DetectAppConfig.image_file_output
            output_url = DetectAppConfig.image_urlbase + DetectAppConfig.image_file_output

            handle_uploaded_file(request.FILES['file'], input_path)
            detect(input_path, output_path)
            result = output_url
    else:
        form = UploadFileForm()
    return render(request, 'detect_app/upload.html', {'form': form, 'result': result})

中で使用されているフォームがこんな感じ。見事にファイルだけです。

from django import forms

class UploadFileForm(forms.Form):
    file = forms.FileField()

HTMLのテンプレートがこんな感じ。Bootstrap4を使ってますが、見た目はやはりアレです?。また画像ファイルのチェックをしていません(公開不可)。フォームデータと抽出結果のURLを渡されてビューから呼ばれます。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>物体検出</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
        integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>

<body class="bg-light">
    <div class="container">
        <div class="py-5 text-center">
            <h1>物体検出</h1>
        </div>
        <div class="row justify-content-center">
            <form method="post" enctype="multipart/form-data">
                {% csrf_token %}
                <div>
                    <label>画像ファイル</label>
                </div>
                <div>
                    {{form.file}}
                </div>
                {% if result %}
                <div>
                    <img src="{{ result }}" class="img-responsive">
                </div>
                {% endif %}
                <hr>
                {% for error in form.file.errors %}
                {{error}}
                {% endfor %}
                <button class="btn btn-primary btn-lg btn-block" type="submit">送信</button>
            </form>
        </div>
    </div>
</body>

</html>

そしてこれがハリボテ本体。

import shutil

def detect(input, output):
    shutil.copy2(input, output)

他にも設定ファイルなどはいじってますが、最後にgitを張るので省略します。フェーズ1はこんな感じです。

入力画像は下のサイトのをお借りしました(超綺麗な写真なので縮小したけど)。ありがとうございます。

Group of People Having Fun Together Under the Sun · Free Stock Photo

フェーズ2

いよいよ物体検出です。まずはpythonにパッケージの追加から。

pip install opencv-python
pip freeze > requirements.txt

このまま実装するとpylintがopencvを認めてくれないので、設定でpython.linting.pylintArgsを検索して、[項目追加]

「–extension-pkg-whitelist=cv2」を追加する。

でもdnnはこれでもダメなようです。うまく回避する方法は見つかりませんでした。ドキュメントコメントがないと怒られてるワーニングも大量にあるので、これ以上は諦めます。

では、気を取り直して実装します。中身を入れたdetect.pyです。

import cv2

def detect(input, output):
    classes = {0: 'background',
                1: 'person', 2: 'bicycle', 3: 'car', 4: 'motorcycle', 5: 'airplane', 6: 'bus',
                7: 'train', 8: 'truck', 9: 'boat', 10: 'traffic light', 11: 'fire hydrant',
                13: 'stop sign', 14: 'parking meter', 15: 'bench', 16: 'bird', 17: 'cat',
                18: 'dog', 19: 'horse', 20: 'sheep', 21: 'cow', 22: 'elephant', 23: 'bear',
                24: 'zebra', 25: 'giraffe', 27: 'backpack', 28: 'umbrella', 31: 'handbag',
                32: 'tie', 33: 'suitcase', 34: 'frisbee', 35: 'skis', 36: 'snowboard',
                37: 'sports ball', 38: 'kite', 39: 'baseball bat', 40: 'baseball glove',
                41: 'skateboard', 42: 'surfboard', 43: 'tennis racket', 44: 'bottle',
                46: 'wine glass', 47: 'cup', 48: 'fork', 49: 'knife', 50: 'spoon',
                51: 'bowl', 52: 'banana', 53: 'apple', 54: 'sandwich', 55: 'orange',
                56: 'broccoli', 57: 'carrot', 58: 'hot dog', 59: 'pizza', 60: 'donut',
                61: 'cake', 62: 'chair', 63: 'couch', 64: 'potted plant', 65: 'bed',
                67: 'dining table', 70: 'toilet', 72: 'tv', 73: 'laptop', 74: 'mouse',
                75: 'remote', 76: 'keyboard', 77: 'cell phone', 78: 'microwave', 79: 'oven',
                80: 'toaster', 81: 'sink', 82: 'refrigerator', 84: 'book', 85: 'clock',
                86: 'vase', 87: 'scissors', 88: 'teddy bear', 89: 'hair drier', 90: 'toothbrush'}

    # Load a model imported from Tensorflow
    tensorflowNet = cv2.dnn.readNetFromTensorflow('./model/frozen_inference_graph.pb', './model/graph.pbtxt')

    # Input image
    img = cv2.imread(input)
    rows, cols, channels = img.shape

    # Use the given image as input, which needs to be blob(s).
    tensorflowNet.setInput(cv2.dnn.blobFromImage(img, size=(300, 300), swapRB=True, crop=False))

    # Runs a forward pass to compute the net output
    networkOutput = tensorflowNet.forward()

    # Loop on the outputs
    for detection in networkOutput[0,0]:
        score = float(detection[2])
        if score > 0.2:
            left = detection[3] * cols
            top = detection[4] * rows
            right = detection[5] * cols
            bottom = detection[6] * rows
    
            #draw a red rectangle around detected objects
            cv2.rectangle(img, (int(left), int(top)), (int(right), int(bottom)), (0, 0, 255), thickness=2)
            #draw category name in top left of rectangle
            cv2.putText(img, classes[int(detection[1])], (int(left), int(top-4)), cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 255), 2, 8)

    cv2.imwrite(output, img)

動かすにはトレーニング済みモデルデータと設定ファイルがいるのですが、これをダウンロードするスクリプトが↓です。

#!/bin/sh
mkdir -p model
wget -O - 'http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v2_coco_2018_03_29.tar.gz' | tar xvfzO - ssd_mobilenet_v2_coco_2018_03_29/frozen_inference_graph.pb > model/frozen_inference_graph.pb
wget -O - 'https://raw.githubusercontent.com/opencv/opencv_extra/master/testdata/dnn/ssd_mobilenet_v2_coco_2018_03_29.pbtxt' > model/graph.pbtxt

サーバー起動して、POSTしてみると…

一人多いですね。

前回と同じですが、このサーバーのGitList Giteaです。

デバッグ

djangoのデバッグも特別なことはありません。デバッグメニューからデバッグの開始を選びます。

Pythonを選びます。

Djangoを選びます。

ブレークポイントを張ります。

アップロードしてみます。

こんな感じで止まりました。変数も普通に見えてます。他と変わりません。

まとめ

今回はdjangoで物体検出するアプリを作って、デバッグまでしてみました。

次回はこのアプリを進化させ、モデルも使って、PostgreSQLにデータを入れてみたいと思います。

未分類

はじめに

前回はvscodeのメニュー表示バグの回避+αを記事にしました。

今回はようやくdjangoです。

git導入

まずは開発の基本gitです。バージョン管理すると失敗してもいつでも元に戻せます。というわけでgitをインストールします。

sudo apt install git

vscode上で環境構築

フォルダ作成

まずはvscodeを起動します。[フォルダを開く]から、~/python/を開き、右上のボタンからdjango_appsフォルダを作成して開きます。

gitリポジトリの作成

フォルダが開いたら次はメニューからターミナルを開きます。

開いたターミナルでgitリポジトリをその場に作ります。

git init

Python環境の作成とgit無視ファイルの設定

出来たら次にpythonの環境をその場に作ります。

python3 -m venv env

すると何やら左側に怪しい数字が出てきます。数字の出てるボタンをクリックします。

見てみると、さっきのpython環境を作成したときに出来たファイルのようです。つまり環境作成したときに346個のファイルが変更(追加)されたよと言っています。これはvscodeがデフォルトで持っているgitの機能で、さきほど作ったリポジトリを認識していて、その変更を検出したために変更ファイル数を教えてくれてたのです。ただ、自動生成されるファイルや外部から取得するファイルはgit管理対象にしないのがお約束なので、ここでは無視ファイルに指定します。いきなり346個も指定するのは辛いので、右上の階段状のボタンを押します。

これでフォルダ階層表示になるので、変更部分が分かりやすくなります。envを右クリックして、

[.gitignoreに追加]を選択します。これは、選択したフォルダ以下全てをgit管理対象にしない=無視するという意味です。

無視にすると、変更として検出されなくなり、無視設定ファイルである.gitignoreが作成されました。このファイルを開くと

346個のファイルが全て一覧されてしまっているので、

最上位フォルダのみに修正しておきます。 (以降このフォルダに追加された場合も自動で無視したいので)

これで心置きなくpython環境を設定できます。

vscode用python環境選択

次はvscodeにpython環境を認識させます。F1キーを押して、python: Select Interpreterと打ち込んでEnterしてください。

さっき作成したpython環境が./env/bin/pythonにあるので、それを選びます。

これでvscodeがこのフォルダで使用するpython環境が設定されました。

pipを使ったインストール

後はvscodeのメニューのターミナルから新しいターミナルを開きます。env環境で立ち上がるので、ここでpipでパッケージを更新しておきます。

pip install -U pip
pip list -o
pip install -U setuptools

これで更新が必要なpythonモジュールがなくなるので、djangoをインストールします。

pip install django

djangoのプロジェクト作成と確認

まずはsample_siteプロジェクトをカレントディレクトリに作成します。

django-admin startproject sample_site .

これでサービスが稼働可能な状態になるので、djangoの開発サーバーを起動してみます。

python manage.py runserver

プロンプトが戻らずに出力が止まり、http://127.0.0.1:8000/などと出てきていたら起動成功です。firefoxを立ち上げて、http://127.0.0.1:8000/を開いてみましょう。

まだ何もプログラミングもしてないのに、画面が出ましたね。これがフレームワークの力です。骨組みはすでにあるので肉付けしてくれという状態なのです。でもこれは確認用の画面なので、この画面を編集することはありません。

アプリケーション作成

まずはCtrl+Cで開発用サーバーを止めて、sample_appを作成します。

python manage.py startapp sample_app

ここまでで骨組みが出来ています。

pipのrequirements.txt作成

今、pythonの環境は全てgitからは無視ファイルとして扱っているため、保存されません。そのため、pythonの環境はいつでも復元できるようにインストールしたパッケージのリストをバージョン付きで保存します。それがrequirements.txtです。以下のコマンドで作成します。

pip freeze > requirements.txt

sqlite3データの無視ファイル追加

データベースのデータはgitには入れないので、このファイルも無視します。db.sqlite3を右クリックして[.gitignoreに追加]してください。あとは、pythonバイトコードのキャッシュ(.pyc)も無視ファイルに追加します。最終的に.gitignoreは↓な感じになります。

env/
db.sqlite3
*.pyc

gitリポジトリにコミット

切りが良いので、リポジトリにコミットしておきます。まずは数字の出ているところアイコンを押して、変更と書いてあるバーの右側の+を押します。

すると、[変更]の上に[ステージング済みの変更]という項目が出来上がり、元々検出されていた変更ファイル群が[ステージング済みの変更]に移動しました。後はコミットするだけなのですが、初回はその前にやることがあります。確認の意味でメッセージに「初回コミット」と入れて、一度チェックボタンを押して見ると…

こんな感じで怒られます。gitは原則コミットした人の名前とメールアドレスをちゃんと記述する慣習で、その設定がされていないとエラーになります。

初回だけなので、vscode内のターミナルからサクっと登録しておきます。

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

※メールアドレスと名前は自分の物に変えてください

これでコミットできるようになったので、チェックボタンを押してコミットしてみてください。成功すれば何も出ずに16個の変更ファイルがなくなります。ターミナルからgit logとすると、履歴を見ることが出来ます。

(env) user@ubuntu1804:~/python/django_apps$ git log
commit de2a5a426e41514b9281ee3250d19560f92e632a (HEAD -> master)
Author: first_user <first_user@elephantcat.work>
Date:   Sat Feb 8 01:28:39 2020 +0900

    初回コミット
(env) user@ubuntu1804:~/python/django_apps$ 

Hello,Worldの作成

まずはViewから作成します。見たまんまなHello,Worldを返すビューです。

from django.http import HttpResponse

def index(request):
    return HttpResponse("<html><body>こんにちは!世界</body></html>")

このアプリのURLとビューの対応表です。

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

このサイトのURLとアプリの対応表です。

"""sample_site URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('sample_app/', include('sample_app.urls')),
    path('admin/', admin.site.urls),
]

では、開発サーバー起動

python manage.py runserver

firefoxで確認

OK!なので、ステージングしてコミットします。

一応GitList Giteaに置いておきました。

まとめ

djangoでHello,Worldまでをgitにコミットしながらローカルvscode環境で実行させるところまでできた。

次回(↓)はアプリケーションとしての何かの機能を実装して、デバッグまでしてみたい。

未分類

はじめに

前回は、Ubuntuにvscodeをインストールして、pythonの開発環境を立ち上げてみました。

今回は、その環境でdjangoというpython用のWebフレームワークを使って、Webアプリを作ってみたいと思っていましたが、閑話休題。一旦少しUbuntuのデスクトップ環境を少し整えます。今まで説明した設定だけではあまりに使いにくいので…

デスクトップテーマの変更

見た目なんてどうでもいいのですが、Ubuntu18.04でvscodeを使うと、メニューが表示されないバグがあるので、その回避策でテーマを変更します。

gnome-tweaksのインストール

デスクトップ版なのにデフォルトではテーマすら変更できないのが最近のUbuntuです。まずはgnome-tweaksをインストールします。

sudo apt install gnome-tweaks

インストールが終わったら、Windowsキー→tweaks[Enter]でtweaksを起動します。

この画面でテーマの変更が可能です。

Yaruのインストール

vscodeの問題回避に有効と言われているのは、18.10で配布されたテーマ(Yaru)なのですが、これは18.04でもパッケージが追加されています。ただし、追加されたパッケージはsnap用なので、snapでインストールします。(aptにもppaという私用のパッケージ配布リポジトリがありましたが、もううまく動かないとのことなので今はsnapしかありません)。

sudo snap install communitheme

インストールが終わったら再起動します(ログアウトでもいいかもしれませんが念の為) 。再起動したらログイン前に、下図のようにセッションを「Ubuntu」から「Ubuntu with communitheme snap」に変更してから、ログインします。

一度変更すれば、次回は同じものがデフォルト選択されます。ログイン完了したら、Tweaksを起動して、 テーマのうちアプリケーション、カーソル、アイコンの3つをCommunithemeに変更すれば完了です。

メニューが白背景になるので、vscodeで現象が発生しなくなります(ごめんなさい。しばらく効果あったのですが、最終的には発生しました。一度発生したら結構頻度上がりました)。アイコンなどの形もやや変わり、少し新しい感じになります。

スクリーンロックの設定

Ubuntuのスクリーンロックがとにかく時間が短いです。VirtualBox内などの環境でスクリーンロックが必要なケースは稀だと思うので、これはOFFにしてしまいます。右上のメニューから設定ボタンを押すと…

こんな画面が出るので、ココの画面ロックを押します。すると…

またダイアログが出てくるので、自動画面ロックをOFFにします。

ついでに電源管理の省電力ブランクスクリーンもOFFにしておきます。

リソースモニタの常時表示

GUIで使う場合は特に時間よりも何よりも、固まってるのか動いてるのか分からない場合があるので、システム負荷が知りたいことが多いです。そのため、上部バーなどに常駐するリソースモニタを入れておきます。

sudo apt install gnome-shell-extension-system-monitor

入れ終わったら一旦ログアウトして再ログインした後、tweaksを起動します。とりあえず曜日よりも日付が欲しい派なので、日付をONします。

拡張機能にあるシステムモニタをONにして、設定ボタンを押します。

CPU~Diskまで全てDisplayします。GPUも必要なら出来ますが、あんまり信用できないかもしれないので、私は使ってません(Windows側で見てる)。

設定完了すると、トップバーはこんな感じになります。

まとめ

これで大体Ubuntu GUIで何をするにも最低限必要な環境にはなったかと思います。GUIなので、リソース消費が激しいですけども。。。
次回(↓)こそUbuntu+vscode+djangoが出来ればいいと思います。