20201218のPythonに関する記事は30件です。

2020年に使った技術

うなすけ氏が書いてるのを見て、自分もやってみようかなと思いました
(フロントエンド、サーバーサイドエンジニアとか関係無しに個人的なものとして)

参考:https://blog.unasuke.com/2020/wrap-up-my-coding/

Application

  • frontend
    • Vue.js
  • backend
    • python, Flask
    • java, Spring Boot
  • other
    • Docker
    • Kubernetes

Vue.js, python, flaskは去年からずっと利用しているが、java, Spring Bootはつい最近触り始めた。
今のところ雰囲気でなんとかなっているが、ちゃんと勉強しとこうかなぁというところ。
Dockerも前から触っていたがkubernetesは今年になってちゃんとやった。

Cloud (GCP)

  • computing
    • GAE (Google Application Engine)
    • GKE (Google Kubernetes Engine)
    • GCE (Google Compute Engine)
    • Cloud Run
    • firebase
    • Container Registry
  • DB / storage
    • datastore
    • BigQuery
    • Cloud SQL
    • realtime database
    • Cloud Storage
  • network
    • Pub/Sub
    • Cloud Endpoint
    • Cloud Tasks
    • Cloud Load Balancing
    • Cloud DNS
  • monitoring
    • Stackdriver
    • Cloud monitoring
  • other
    • Cloud workflow
    • secret manager

GCPばかりいじっていた。やってるプロダクト、プロジェクトにも深く関わるのが大きな要因だが、couseraの学習コースとかでも触る機会が多かった。仕事だとGAEがメインだったが、cousera上ではGCEを作ったり消したりを繰り返していた。あとは新しく出たサービスが気になって動かしてみたものもある。
去年と比べるとネットワーク周りの設定を行ったり、GKEをガッツリさわった。
なお今年AWSは触らなかった模様。

エディタの話は以下に書いたので割愛。
https://qiita.com/woody-kawagoe/items/2ab0226dd325bba5681b

感想

GCPばかりやってて他に書く技術記事無えなぁと思いつつ過ごしていたけど振り返ってみると本当にGCP関連しかしてないことがわかってきた。仕事でかなり使うというのもそうだけど、新しいサービスが日々出るので気になるところであった。それとたまたま仕事でGKE触る機会ができたのも良かった。別にインフラエンジニアやってるわけではなくwebアプリ開発の方が主の業務なのだが、技術的な興味の向き先がどっちかというとインフラよりなのかもしれない。来年はkubernetes周りとかネットワークとかもっと汎用性のある記事かけたら良いなーと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SQL入門(基本的なクエリの備忘録)

SQL クエリ

備忘録です。

カラムとテーブルの選択

SELECT カラム名
FROM テーブル名;


データの取得

WHERE

SELECT *
FROM テーブル名
WHERE カラム名 = 要素  -- 「取得したい要素」と一致するものを検索
WHERE カラム名 LIKE %要素%  -- 「取得したい要素」を含む要素を検索
WHERE NOT カラム名 = 要素  -- 「取得したい要素」と一致しないものを検索
WHERE カラム名 IS NULL  -- 指定したカラムのなかでNULLのデータを検索
WHERE カラム名 IS NOT NULL  -- 指定したカラムの中でNULLでないデータを検索

比較演算子等

カラム名 =             -- 一致
カラム名 >=            -- 以上
カラム名 <=            -- 以下
カラム名 <             -- より大きい
カラム名 >             -- 未満
カラム名 LIKE          -- 含む
NOT カラム名 演算子     -- NOT文
カラム名 IS NULL       -- NULL値である
カラム名 IS NOT NULL   -- NULL値でない

AND / OR

WHERE 条件
AND 条件;  -- AND文

WHERE 条件
OR 条件;  -- OR文

並び替え

ORDER BY 並び替えたいカラム 並び方;  -- 並び替え文法
ASC  -- 昇順 ascending order
DESC  -- 降順 descending order

取得データ数の制限

LIMIT データの件数;  -- 指定したデータの件数を取得する(numpyのheadとおなじ)
/*クエリの最後に記述する*/

データベース・テーブルの作成

テーブルの作成

CREATE TABLE テーブル名(
    行名1 データ型,
    行名2 データ型
)

データ型の種類

分類 データ型名 説明
数値型 int 整数型
^ decimal(桁数) 固定長小数型
^ money 通貨
^ float 浮動小数点数値
文字列型 char(文字数), nchar(文字数) 固定長文字列型(~8000字)
^ varchar,nvarchar 可変長文字列型(~8000字)
^ text,ntext 可変長文字列型(~10億文字)
日付型 time hh:flag_mm:ss
^ date YYYY-MM-DD
^ datetime YYYY-MM-DD hh:flag_mm:ss

データの追加・更新・削除

データの追加

INSERT INTO テーブル名  -- 操作するテーブルを指定
(列名1,列名2,列名3)  -- 操作する列を指定
VALUES 
(1,2,3);  -- 追加する値を入力

データの更新(既に登録されているデータを新しいものに置き換える)

UPDATE テーブル名
SET 列名1 = 1, 列名2 = 2, 列名3 = 3  --更新する値を入力
WHERE 条件  -- 更新する場所の条件を指定

行の削除

DELETE FROM テーブル名
WHERE 条件  -- 指定した条件を満たす行を削除する

列の追加・削除

ALTER TABLE テーブル名 ADD 列名 データ型 初期値  --列の追加
ALTER TABLE テーブル名 DROP COLUMN 列名  -- 列の削除
ALTER TABLE テーブル名 ALTER COLUMN 列名 データ型  -- 指定した列のデータ型の変更

データの加工

重複する要素を省く

/*指定したカラムから重複をのぞいて計算する*/
SELECT DISTINCT(カラム名)
FROM テーブル名;

計算を実行する

SELECT カラムⅠ,カラムⅡ*5  -- カラムⅠと、カラムⅡに5を掛けた値を取得

python(SQLiteの使い方)

モジュールのインポート

import sqlite3

データベースとの接続

conn = sqlite3.connect("データベース.db")  # データベースファイルと接続
c = conn.cursor()  # カーソルオブジェクトの作成

クエリの実行

c.execute('''
クエリ
''')  # クエリの実行

データベースとの切断

conn.close()  # 切断を怠るとデータベースにロックがかかってしまう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCodeに毎日挑戦してみた 118. Pascal's Triangle(Python、Go)

Leetcodeとは

leetcode.com
ソフトウェア開発職のコーディング面接の練習といえばこれらしいです。
合計1500問以上のコーデイング問題が投稿されていて、実際の面接でも同じ問題が出されることは多いらしいとのことです。

golang入門+アルゴリズム脳の強化のためにgoとPythonで解いていこうと思います。(Pythonは弱弱だが経験あり)

28問目(問題118)

118. Pascal's Triangle

問題内容

Given a non-negative integer numRows, generate the first numRows of Pascal's triangle.

(日本語訳)

負でない整数numRowsが与えられた場合、 パスカルの三角形の最初のnumRowsを生成します。

img
In Pascal's triangle, each number is the sum of the two numbers directly above it.

Example:

Input: 5
Output:
[
     [1],
    [1,1],
   [1,2,1],
  [1,3,3,1],
 [1,4,6,4,1]
]

考え方

  1. 空の配列を作成し、数字の数だけfor分を回します

  2. ループ内、appendでその数に応じた[1]の要素を追加します

  3. 左端、右端は1なのでそれを除いた範囲で上の階層を参考に値を代入していきます

  4. 最終的に作成したlistsを戻り値とします

解答コード

class Solution:
  def generate(self, numRows):
      lists = []
      for i in range(numRows):
          lists.append([1]*(i+1))
          if i>1 :
              for j in range(1,i):
                  lists[i][j]=lists[i-1][j-1]+lists[i-1][j]
      return lists
  • Goでも書いてみます!
func generate(numRows int) [][]int {
    answer := make([][]int, numRows)
    for i := 0; i < numRows; i += 1 {
        a := make([]int, i+1)
        // add ones
        a[0], a[i] = 1, 1

        if i > 1 {
            for j := 1; j <= i/2; j += 1 {
                a[j] = answer[i-1][j-1] + answer[i-1][j]
                a[len(a)-1-j] = a[j]
            }
        }
        answer[i] = a
    }
    return answer
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ミツバサンコーワ製バイク用ドラレコEDR-21で撮影した動画ファイル(30秒毎、0.5秒の重複あり)をpythonを使ってうまく結合する

はじめに

 この記事はここのアドベントカレンダーのやつです。

 6月からバイクを始めました。免許も取ったばかりなので安全運転を心がけたいです。

 このところあおり運転に関するニュースもよく聞きますので、自衛のためにもバイク用のドラレコを取り付けました。

ミツバサンコーワ MITSUBA バイク専用ドライブレコーダー EDR-21 前後2カメラ EDR-21image.png

 前後2カメラで、162°の超広角、暗いところも強く、256GBのSDカードも対応するいい感じのやつです。購入時点で23000円くらいだったと思います。バイクショップとかで購入&取付をすると、工賃含めて4〜5万円くらいになりそうなので、Amazonで購入して自分で取り付けました。

 さて、このドラレコで撮影した動画はスマホ経由でも見ることができますし、SDカードをPCにつなげて直接見ることもできるのですが、30秒毎にファイルが細切れに保存される仕様となっており、結構扱いにくいです。
 さらに、ファイル間の前後0.5秒程度が重複して保存されており、1本に結合しようとしたらこの重複した0.5秒をカットしないとキレイにつながらず、とてもめんどくさいことになります。

 こんなイメージです。
image.png

 重複分を考慮してキレイにつなげようとすると、こんな感じにしないといけなく手動で30秒毎にこの編集するのは手間がかかります。

image.png

 動画を1本に結合して何をするわけでもないですが、現時点でいい方法も見つからなかったので、お勉強も兼ねてpythonでやってみることにしました。

概要

 ドラレコの映像には音声も乗っているので、映像と音声両方を0.5秒カットしてつなげるイメージになるでしょう。pythonでやるには、映像と音声同時に編集するのは難しそうなので、別々に編集する必要がありそうです。

 使用するソフトウェア、ライブラリはこちらにしました。

  • 映像と音声の分離・統合、動画エンコード:ffmpeg(Pythonからsubprocess.runで実行)
  • 映像の0.5秒分カット&結合:opencv
  • 音声の0.5秒分カット&結合:pydub

実装

準備

 ドラレコ動画のファイルはどのように作成されているのかみてみましょう。

image.png

 フロント/リアカメラ毎に、撮影開始日時、30秒動画の開始日時、をファイル名に保持しているようです。(N/Eは……とりあえず無視しておきます。まあ大丈夫でしょう)

実装イメージ

 プログラム的には、カメラ毎×撮影開始日時単位にグルーピングして、動画開始日時順になるように塊を作って、塊毎に以下の処理をする感じで処理していくこととします。

  1. 30秒動画を映像と音声に分離
  2. 映像と音声のお尻0.5秒をカット
  3. つなげる
  4. 全部つながったら、映像と音声をくっつけて動画として再エンコード

実装

mitsuba.py
# -*- coding: utf-8 -*-
import os
import shutil
import cv2
import glob
import subprocess
from pydub import AudioSegment
from collections import defaultdict
from tqdm import tqdm, trange
from multiprocessing import Pool,Process, Queue, TimeoutError
from queue import Empty

DUP_FRAME = 14

# multi processing
WORKERS = 4
TIMEOUT = 10

def comb_movie(movie_files, out_path, num):
    # 作成済みならスキップ
    if os.path.exists(os.path.join("out",out_path)):
        return

    # 形式はmp4
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')

    #動画情報の取得
    movie = cv2.VideoCapture(movie_files[0])
    fps = movie.get(cv2.CAP_PROP_FPS)
    height = movie.get(cv2.CAP_PROP_FRAME_HEIGHT)
    width = movie.get(cv2.CAP_PROP_FRAME_WIDTH)

    # 出力先のファイルを開く
    out = cv2.VideoWriter(f"tmp/video_{num:02}.mp4", int(fourcc), fps,
                        (int(width), int(height)))

    audio_merged = None
    for movies in movie_files:
        # 動画ファイルの読み込み,引数はビデオファイルのパス
        movie = cv2.VideoCapture(movies)
        count = movie.get(cv2.CAP_PROP_FRAME_COUNT)
        frames = []
        if movie.isOpened() == False:  # 正常に動画ファイルを読み込めたか確認
            continue

        for _ in range(int(count)):
            ret, tmp_f = movie.read()  # read():1コマ分のキャプチャ画像データを読み込む
            if ret:
                frames.append(tmp_f)

        # 読み込んだフレームを書き込み
        for frame in frames[:-DUP_FRAME]:
            out.write(frame)

        command = f"ffmpeg -y -i {movies} -vn -loglevel quiet tmp/audio_{num:02}.wav"
        subprocess.run(command, shell=True)

        audio_tmp = AudioSegment.from_file(f"tmp/audio_{num:02}.wav", format="wav")
        audio_tmp = audio_tmp[:int((count-DUP_FRAME)/fps*1000)]

        if audio_merged is None:
            audio_merged = audio_tmp
        else:
            audio_merged += audio_tmp

    # 結合した音声書き出し
    audio_merged.export(f"tmp/audio_merged_{num:02}.wav", format="wav")
    out.release()

    # 動画と音声結合
    vf = ""  #ビデオフィルタはお好みで 例)ややソフト・彩度アップ・ノイズ除去の場合 "-vf smartblur=lr=1:ls=1:lt=0:cr=-0.9:cs=-2:ct=-31,eq=brightness=-0.06:saturation=1.4,hqdn3d,pp=ac"
    # 高速なエンコーダに対応していればお好みで 例)h264_videotoolbox, libx264, h264_nvenc
    cv = f"-c:v libx264"
    # ビットレートは解像度に応じて固定にしています。
    if height == 1080: # FHD
        bv = f"-b:v 11m"
    elif height == 720: # HD
        bv = f"-b:v 6m"
    else: # VGA
        bv = f"-b:v 3m"

    loglevel = "-loglevel quiet"
    command = f"ffmpeg -y -i tmp/video_{num:02}.mp4 -i tmp/audio_merged_{num:02}.wav {cv} {bv} {vf} -c:a aac {loglevel} out/{out_path}"
    subprocess.run(command, shell=True)


def wrapper(args):
    comb_movie(*args)

if __name__ == '__main__':
    os.makedirs("./tmp", exist_ok=True)
    os.makedirs("./out", exist_ok=True)

    # ディレクトリ内の動画を:フロント・リアカメラごと、撮影開始時間ごとにまとめる
    files_dict = defaultdict(list)
    for f in glob.glob("./in/*.MP4"):
        files_dict["_".join(f.split("/")[-1].split("_")[:2])].append(f)

    data = []
    for i, (key_name, files_list) in enumerate(files_dict.items()):
        data.append((sorted(files_list), key_name+".mp4", i))

    p = Pool(WORKERS)
    with tqdm(total=len(data)) as t:
        for _ in p.imap_unordered(wrapper, data):
            t.update(1)
    # tmp 削除
    shutil.rmtree('./tmp/')

補足

ソースコードはGithubでも公開しております。

YouTube にサンプル動画上げました

  • inフォルダにドラレコの生動画を入れておきます。

image.png

  • 実行するとプログレスバーが表示されます。(本当はもう少し細かく表示したい)

image.png

  • outフォルダに結合した動画ファイルが出来上がります。

image.png

  • DUP_FRAME = 14
     重複しているフレーム数なので変更不要です。(動画が28fps:28フレーム/s × 0.5s = 14フレーム)

  • WORKERS = 4
     並列処理をして速くするオプションで、並列実行数を指定できます。環境に応じて変更してみてください。

  • ビデオフィルタはお好みで
     元動画は(ドラレコなので)シャープネスがややきつい感じなので、ソフトにしてみたり色々フィルタを設定できます。詳細はffmpegのフィルタで調べてみてください。

  • 高速なエンコーダに対応していればお好みで
     - libx264:大体どの環境でも使えます
     - h264_videotoolbox:mac用。libx264より数倍速いはず。
     - h264_nvenc:linux用(windowsもいけるかな?)libx264より数倍速いはず。

  • ビットレート
     お好みですが、元の動画がそれほどキレイじゃないのでビットレート上げてもさほど効果はないかも

おわりに

 GoPro8を買っちゃったんですが、まあキレイです。ドラレコはドラレコとして使って、車載動画はGoProで取るほうが良さそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonよりカッコいいElixirでラズパイ

本記事は「Raspberry Pi Advent Calendar 2020」の22日目です。

昨日は@shion21さんのiPhoneからラズパイにミラーリング!RPiPlayの使い方でした。

nerves_hello_lcd_20201213_185620

はじめに

2ヶ月前にラズパイを始めたとき、僕はプログラミング言語としては、Elixirを選びました。単にElixirの機能と文法が好きで、それをIoTにも使用したかったからです。一般的にはCやPythonが使用されることが多いようです。その点でElixirでラズパイをするのには不安な要素がありましたが、それが取り越し苦労だったという話です。

Elixir(えりくさ)言語とNerves(なあぶす)フレームワーク

Elixirは耐障害性、高い並列性能で長年実績のあるErlangの上に実装されたプログラミング言語で、最近ではWhatsAppなどの世界中のメッセージアプリ、チャットアプリ等でも使用されており、その性能に改めて注目が集まっていると聞きます。そのElixirをラズパイで使えるようにしてくれるNerves(なあぶす)というIoTフレームワークがあると聞き、勉強を始めたわけです。僕にとってPythonよりElixirのほうがモダンでカッコよく、Elixirを使わない理由はありません。

Nervesについて詳しくは@takasehidekiさんの「Slideshare:ElixirでIoT!?ナウでヤングでcoolなNervesフレームワーク」がわかりやすいです。また、「NervesJP」というコミュニティがあり勉強会等が実施されています。僕はNerves JPでNervesについて学んでます。

一瞬不安になったこと

世の中にあるほとんどのラズパイ情報がCかPythonを使用する前提で書かれているため、新しいことを学ぶ際には戸惑いました。例えば、先日LCDディスプレイに「Hello」と表示される練習をしようとしましたが、Elixirの資料がなかなか見つかりませんでした。ほとんどがCかPythonの既存ライブラリを使用してLCDを操作するというものでした。

大事なことは通信プロトコルと製品のデータシート

しばらく辛抱強く調査と勉強をしていると、あることに気づきました。上述のCかPythonライブラリの中身はLCDのデータシートに書かれたとおりの手順をコードにしただけで大した内容ではないと。言語がなにであろうと関係ないのですね。本当に重要なのは、パラレル通信vsシリアル通信、I2CSPI等の通信プロトコルの基礎を学ぶことと製品のデータシートを自分の目でよく読み、分かる範囲で理解する努力をすることという結論になりました。

IoTでは最終的にハードウェアとやり取りをすることが多いと思います。その部分に関してはI2C、SPI等のシリアル通信さえできればプログラミング言語は関係がないのですね。素人の僕でも概要は理解できたので、しっかり読めば誰でもある程度の操作は(ライブラリに頼らなくても)自分でできると思います。

データシートを読まずに誰かの作ったライブラリをさがすことしか考えていなかったのが間違いでした。使える既存のライブラリがあるに越したことはないですが、いずれにしても通信プロトコルとデータシートについては理解が必要だと思います。

さいごに

いい経験になりました。別にPythonでなくても、もっとカッコいい言語で自由にラズパイを楽しめるのですね。データシートさえ読めば、特にライブラリがなくてもできる場合があることがわかり、スッキリしました。結果として自作のライブラリもできました。

僕がElixirでLCDを操作した成果は一般公開しているので、みなさんにもどんどんElixirとNervesを楽しんでもらえればと思います。

明日は@sho7650さんです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

国会会議録検索システムAPIを使って国会発言をJSONで取得しCSV出力する

はじめに

政治の世界ってわかりにくいですよね。日々揚げ足取りみたいな議論が行われている様子や失言した議員さんなどがニュースに取り上げられがちですが、実際には様々な議論が交わされています(きっと)。
そんな議論の内容、議事録データを国立国会図書館がAPIで提供してくれていて、政治のオープン化やデータ解析の題材としてとても有用だと感じました。

本記事では、そのデータをJSON形式で取得してCSV出力するPythonプログラムを作成したのでそちらを紹介します。

国会議事録APIの利用例と本記事の要点について

国会会議録検索システムAPIの公式説明を抜粋すると下記のように紹介されています。

「国会会議録検索システム」は、第1回国会(昭和22(1947)年5月開会)以降の国会会議録を検索・閲覧することができるデータベースです。国立国会図書館が、衆議院・参議院と共同で提供しています。

実はこの記事執筆のちょうど1年ほど前に、別の方がQiitaで同様の記事を書いてくださっています。この中で、国会会議録検索システムについての説明や、取得したデータの活用例を紹介されていますので是非そちらもご参照ください。

一方、その記事中で当該APIの課題として「APIのレスポンスがJSON形式はなくXML形式のみ」といった点が挙げられているのですが、おそらく2020年にAPIの仕様が改善されたのかJSONでのデータ取得ができるようになっていたので、二番煎じながらJSONデータで取得するケースを紹介させていただく次第です。

国会議事録APIからのデータ取得プログラム

私の実行環境 >> OS: macOS Big Sur / Python 3.6.6

KokkaiSpeech.py
import requests
import time
import json
import sys
import csv
import re

#ベースとなるURL
base_url = "https://kokkai.ndl.go.jp/api/speech"

#URLパラメータ用の辞書を空の状態で用意し、後から順次格納する。01はヒット総数の確認用、02はデータ取得用。
params_01 = {} 
params_02 = {}

#パラメータを対話的に入力する
text_input = input('検索する文字列を入力(Enterキーでスキップ) >> ')
if text_input != "":
    params_01['any'] = str(text_input)
    params_02['any'] = str(text_input)
else:
    pass

speaker_input = input('検索する発言者名を入力(Enterキーでスキップ) >> ')
if speaker_input != "":
    params_01['speaker'] = str(speaker_input)
    params_02['speaker'] = str(speaker_input)
else:
    pass

from_input = input('開始日を入力 (e.g. 2020-09-01) >> ')
if re.match(r'[0-9]{4}-[0-1][0-9]-[0-3][0-9]', from_input): #正規表現によるパターンマッチングにて入力値が有効か判定
    params_01['from'] = str(from_input)
    params_02['from'] = str(from_input)
else:
    params_01['from'] = "2020-09-01"
    params_02['from'] = "2020-09-01"
    print("'From' date is set to 2020-09-01 due to invalid input")

until_input = input('終了日を入力 (e.g. 2020-11-30) >> ')
if re.match(r'[0-9]{4}-[0-1][0-9]-[0-3][0-9]', until_input): #正規表現によるパターンマッチングにて入力値が有効か判定
    params_01['until'] = str(until_input)
    params_02['until'] = str(until_input)
else:
    params_01['until'] = "2020-09-30"
    params_02['until'] = "2020-09-30"
    print("'Until' date is set to 2020-09-30 due to invalid input")

params_01['maximumRecords'] = 1
params_01['recordPacking'] = "json"

response_01 = requests.get(base_url, params_01) #URLのパラメータをエンコードしてAPIへリクエスト
jsonData_01 = response_01.json() #APIからのレスポンスをJSON形式で取得

#レスポンスに含まれているヒット件数を確認(レスポンスのJSONにレコード数の項目がない場合はクエリに問題ありと判断しエラー終了)
try:
    total_num = jsonData_01["numberOfRecords"]
except:
    print("クエリエラーにより取得できませんでした。")
    sys.exit()

#件数を表示し、データ取得を続行するか確認をとる
next_input = input("検索結果は " + str(total_num) + "件です。\nキャンセルする場合は 1 を、データを取得するにはEnterキーまたはその他を押してください。 >> ")
if next_input == "1":
    print('プログラムをキャンセルしました')
    sys.exit()
else:
    pass

max_return = 100 #発言内容は一回のリクエストにつき100件まで取得可能なため、その上限値を取得件数として設定
pages = (int(total_num) // int(max_return)) + 1 #ヒットした全件を取得するために何回リクエストを繰り返すか算定

#全件取得用のパラメータを設定
params_02['maximumRecords'] = max_return
params_02['recordPacking'] = "json"

Records = [] #取得データを格納するための空リストを用意

#全件取得するためのループ処理
i = 0
while i < pages:
    i_startRecord = 1 + (i * int(max_return))
    params_02['startRecord'] = i_startRecord
    response_02 = requests.get(base_url, params_02)
    jsonData_02 = response_02.json()
    #JSONデータ内の各発言データから必要項目を指定してリストに格納する
    for list in jsonData_02['speechRecord']:
        list_id = list['speechID']
        list_kind = list['imageKind']
        list_house = list['nameOfHouse']
        list_topic = list['nameOfMeeting']
        list_issue = list['issue']
        list_date = list['date']
        list_order = list['speechOrder']
        list_speaker = list['speaker']
        list_group = list['speakerGroup']
        list_position = list['speakerPosition']
        list_role = list['speakerRole']
        list_speech = list['speech'].replace('\r\n', ' ').replace('\n', ' ') #発言内容の文中には改行コードが含まれるため、これを半角スペースに置換
        list_url01 = list['speechURL']
        list_url02 = list['meetingURL']
        Records.append([list_id, list_kind, list_house, list_topic, list_issue, list_date, list_order, list_speaker, list_group, list_position, list_role, list_speech, list_url01, list_url02])

    sys.stdout.write("\r%d/%d is done." % (i+1, pages)) #進捗状況を表示する
    i += 1
    time.sleep(1) #リクエスト1回ごとに若干時間をあけてAPI側への負荷を軽減する

#CSVへの書き出し
with open("kokkai_speech_" + str(total_num) + ".csv", 'w', newline='') as f:
    csvwriter = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_NONNUMERIC) #CSVの書き出し方式を適宜指定
    csvwriter.writerow(['発言ID', '種別', '院名', '会議名', '号数', '日付', '発言番号', '発言者名', '発言者所属会派', '発言者肩書き', '発言者役割', '発言内容', '発言URL', '会議録URL'])
    for record in Records:
        csvwriter.writerow(record)

解説

上記のコード内コメントでほとんど解説を入れていますが、ポイントとしては下記となります。基本的には、API側に過剰な負荷をかけないような作法を心掛けて構成しています。

  • コンソール上で対話的に検索クエリを入力していく形にしている。
  • 検索クエリは発言内容の文字列や発言者名、期間を指定可能。必要なものを入力し不要なものはスキップできる。なお、期間のクエリをスキップした場合やそこに日付として不適な文字列が入った場合は、2020年9月の期間が設定されるようにプログラム上で条件分岐している。
  • APIの仕様上、1回のリクエストで発言データは100件までしか取得できないので、まずは1件のみのリクエストを投げてそのレスポンスから検索クエリでのヒット総件数を確認し、その後に必要回数リクエストを繰り返してヒットした全件を取得する。
  • 検索クエリでのヒット総件数を表示した時点でデータ取得をキャンセルできるようにした(検索結果が想定より多すぎる、少なすぎるといった際に無駄なデータ取得をしないように配慮)。
  • プログラムの過程でAPIに繰り返しリクエストする際、API側への負荷軽減のため若干のインターバルを入れる。

コンソール上での動作例

例として、「宇宙」を含む今年の国会発言を取得してみましょう。
下記の要領でPythonコードを実行すると、コンソール上で検索文字列や検索開始日、終了日などの入力が順次求められます。必要なものは入力し、不要であればスキップします。
検索文字列は半角スペース区切りでOR検索が出来たりしますが、細かい仕様は当該APIの公式サイトをご覧ください。
その後は検索結果の件数が表示されるので、Enterキーなどを押して処理を進めます。
今回の例では268件が該当したので、3回に分けてプログラムが自動でリクエストを投げます。この進捗状況もコンソール上に表示されます。

shell
python KokkaiSpeech.py
検索する文字列を入力(Enterキーでスキップ) >> 宇宙
検索する発言者名を入力(Enterキーでスキップ) >> 
開始日を入力 (e.g. 2020-09-01) >> 2020-01-01
終了日を入力 (e.g. 2020-11-30) >> 2020-12-15
検索結果は 268件です。
キャンセルする場合は 1 を、データを取得するにはEnterキーまたはその他を押してください。 >>  
3/3 is done.

処理が終了したら、プログラムがあるフォルダに「kokkai_speech_268.csv」という名称のCSVファイルが出力されます。

おわりに

私も技術的にまだまだ勉強中の身ですが、APIを介したJSON形式でのデータ取得は一般的によく実施されるケースだと思いますので、そうした実務においても参考になれば幸いです。

また、本記事で紹介した私のプログラムは「発言内容」単位のものですが、当該APIでは「会議」単位での取得も可能になっていますので、そのあたりは適宜補正してお使いください。その際は、APIサーバ側に極端な負荷をかけないようご留意ください。

こうした議事録を文字起こしして配信いただいている国会図書館や関係者の方々に感謝しつつ、政治のオープン化、そしてより良い社会作りに役立てていきたいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめての Intel Open VINO

はじめに

この記事は、シスコシステムズ合同会社の同士による Cisco Systems Japan Advent Calendar 2020 の 20 日目として投稿しています。
そもそも Intel OpenVINO™(以下OpenVINO)って何?Cisco とどんな関係があるの?という疑問もあると思いますのでそのあたりも含めてご紹介しようと思います。

この記事でできるようになること

  • 機械学習による画像認識の流れがわかる
  • Intel OpenVINO™とは何かがわかる
  • Intel OpenVINO™の利用方法がわかる
  • Intel OpenVINO™を利用して基本的な物体認識ができる
  • 物体認識に必要なモデルの利用方法がわかる
  • モデルを組み合わせた複合的な物体認識ができる

最終的にこんなことができます

  • カメラからリアルタイムで顔認識
    Screen Shot 2020-12-18 at 22.44.44.png

  • カメラからリアルタイムで感情認識
    Screen Shot 2020-12-18 at 22.46.37.png

  • 上記の二つを組み合わせてリアルタイムな顔と感情認識
    Screen Shot 2020-12-18 at 22.44.58.png

引用元
https://pixabay.com/ja/videos/%E3%83%90%E3%83%B3%E3%82%B3%E3%82%AF-%E3%82%BF%E3%82%A4-%E5%B8%82-%E7%BE%A4%E8%A1%86-30744/

Intel OpenVINO™ とは?

公式ページから

OpenVINO™ ツールキットは、インテル・アーキテクチャーの CPU、内蔵 GPU、インテル® FPGA、インテル® Movidius™ ビジョン・プロセッシング・ユニット (VPU) といった、インテルが 提供するさまざまなハードウェアでディープラーニング推論をより高速に実行するためのソフトウェア開発環境 / ライブラリー・スイートです。
開発者はツールキットに含まれるモデル・オプティマイザーを使用して、業界標準の DL フレームワークで作成した学習済みモデルデータを、さまざまなインテルのハードウェア上で動作するように最適化を行い、OpenVINO™ ツールキットの推論エンジンで使用する中間表現フォーマット (IR) に変換します。推論エンジンは CPU、内蔵 GPU、FPGA、VPU それぞれの性能を最大限に引き出すライブラリーで構成されています。
https://www.intel.co.jp/content/www/jp/ja/internet-of-things/solution-briefs/openvino-toolkit-essential-brief.html

初めての方にはなんのことだろうという感じだと思いますので、詳しく解説していきます。
まず、そもそも機械学習による画像認識とはどういう流れで行われるのでしょうか。
Screen Shot 2020-12-09 at 17.03.57.png
主に機械学習は上記のようにモデルの作成とモデルの運用という部分に分割されます。
そして、実際にこのモデルを使用して推論処理を行うことにより様々なアプリケーションが実装されています。

モデルを作成する際にもCPUやGPUなどのコンピューティングリソースは必要になりますが、学習するときほどではないにせよモデルを使用して推論する際にもコンピューティングリソースは必要になります。これまでは組み込み系のハードウェア、例えば監視カメラなどではコンピューティングリソースの制限などから推論処理をするためにはそれなりの場所と電力が必要でした。こういったことがハードルとなって、推論を利用したアプリケーションの活用が場所・価格・電力など様々な観点から遅れがちになっていました。

Screen Shot 2020-12-10 at 20.48.07.png
https://www.intel.co.jp/content/www/jp/ja/internet-of-things/solution-briefs/openvino-toolkit-essential-brief.html

Intel OpenVINO™はこうした課題を解決するためのプラットフォームです。
後ほど詳しくご説明いたしますが、機械学習モデル精度をある程度保持したまま単純化することにより、推論精度への影響を最小限にしながら処理負荷を劇的に削減することでCPU, GPU, FPGA, VPUなどIntelの様々なプラットフォーム上で推論処理をさせることが可能となっております。以下のような特徴があります。

特徴

  1. 非常に多くの機械学習モデルがすぐに利用可能(Open Model ZOO)
  2. CPU, GPU, FPGA, VPUなど様々なプラットフォームでの動作が可能
  3. クラウドでの開発環境も提供(Intel DevCloud for the Edge)
  4. 活用支援のためのトレーニングプログラムが充実

Ciscoとの関連性

Ciscoと画像認識のような機械学習分野はあまり結びつきを持たないイメージをお持ちの方もいらっしゃるかと思います。
あまり馴染みのない製品もあるかとは思いますが、ネットワーク機器だけではなくて以下のような製品群にて機械学習が利用されております。
Screen Shot 2020-12-11 at 14.31.30.png

2020年12月9日、10日で行われたWebexOneというイベントでもWebexの新製品発表が行われましたが、会議のその中にもリアルタイム自動翻訳やコールセンターの音声認識及び機械学習による自動音声応答など様々な分野の製品で機械学習が使用されております。
https://www.webexoneevent.com/

IntelとCiscoとは古くから様々な分野でパートナーシップを結んでいます。最近では2020年11月12日に発表されました「5Gショーケース」のエコパートナー様としてご支援いただいております。
https://www.cisco.com/c/m/ja_jp/5g-showcase.html#~customer-partner

Intel OpenVINO™を利用してみよう

それでは実際にIntel OpenVINO™を使用してみましょう。
OpenVINOを使用した開発環境は主にローカルコンピュータ上で行うものと上述のIntel DevCloud for the Edgeというクラウド上で行うものとがあります。
クラウド上につきましては最後の方で軽く触れますが、Webアプリケーションへの組み込みなど柔軟性の観点から本稿ではローカルコンピュータ上での開発環境を使用致します。
なお、OpenVINOはCPPも利用できますが、本稿では開発言語としてPythonを使用致しますので、Python環境の構築についても触れていきます。

OpenVINO利用の手順は以下の通りとなります。
1. Intel Basic Accountの作成
2. OpenVINOのダウンロード
3. OpenVINOのインストール
4. Python実行環境構築
5. 任意のエディタでCodeを記述
6. 実行及び結果の確認

Intel Basic Accountを作成しよう

Register for Basic Intel® Accountにアクセスし、必要情報を入力します。入力する箇所が多いですが、めげないで入力しましょう。
Screen Shot 2020-12-14 at 23.10.29.png
入力したらNext Stepをクリックし、Privacy NoticeとTerms of Useに同意しSubmitをクリック。
Screen Shot 2020-12-14 at 23.11.12.png

OpenVINOのダウンロードをしてみよう

OpenVINO Downloadにアクセスし、ダウンロードする対象を選択します。以下のように選択します。

  • Operating System: macOS
  • Distribution: Web and Local Install
  • Installer Type: Local Screen Shot 2020-12-14 at 22.51.05.png Register & Downloadを選択します。 Screen Shot 2020-12-14 at 22.51.35.png Choose a Versionは2021.1を選択し、Full Packageを選択します。 Screen Shot 2020-12-14 at 22.53.11.png

m_openvino_toolkit_p_2021.1.110.dmgというファイルがダウンロードされます。

OpenVINO をインストールしてみよう

インストールの前提条件として以下の要件が必要になります。
一番下のApple Xcode IDEはOptionalです。

  • CMake 3.10 or higher
  • Python 3.6 - 3.7
  • Apple Xcode* Command Line Tools (Optional) Apple Xcode* IDE (not required for OpenVINO, but useful for development)

インストール時にはCMakeが必要となりますので、homebrewでインストールしておきます。

$ brew install cmake
$ brew install cmake
Updating Homebrew...
Warning: Treating cmake as a formula. For the cask, use homebrew/cask/cmake
==> Downloading https://homebrew.bintray.com/bottles/cmake-3.19.1.catalina.bottle.tar.gz
Already downloaded: /Users/ktsutsum/Library/Caches/Homebrew/downloads/0908e631c8236f534e13e47da5070f8b651b7f59a5a97cd70899729d83baddd3--cmake-3.19.1.catalina.bottle.tar.gz
==> Pouring cmake-3.19.1.catalina.bottle.tar.gz
==> Caveats
Emacs Lisp files have been installed to:
  /usr/local/share/emacs/site-lisp/cmake
==> Summary
?  /usr/local/Cellar/cmake/3.19.1: 6,366 files, 63.9MB

先ほどダウンロードしたm_openvino_toolkit_p_2021.1.110.dmgというファイルをダブルクリックして開きます。
以下のファイルをダブルクリックします。
Screen Shot 2020-12-15 at 0.24.30.png
Screen Shot 2020-12-14 at 23.41.02.png
ここではRootとしてインストールしています。
Screen Shot 2020-12-14 at 23.41.06.png
なぜか再度ロゴが表示されます。
Screen Shot 2020-12-14 at 23.41.14.png
ライセンスに同意します。
Screen Shot 2020-12-14 at 23.41.17.png
情報提供を選択します。
Screen Shot 2020-12-14 at 23.41.21.png
インストール内容を確認します。
Screen Shot 2020-12-14 at 23.41.30.png
インストールを待ちます。
Screen Shot 2020-12-14 at 23.41.38.png
最初のパートが終わったと表示されます。残りはFinishをクリックするとWebページに飛ばされます。
Screen Shot 2020-12-14 at 23.41.48.png
こちらのWebページに飛ばされます。
Screen Shot 2020-12-14 at 23.42.26.png
ここからは次のPython環境セットアップと一緒に進めていきます。

Python実行環境を構築しよう

さて、次はPythonの実行環境を構築していきます。

1. pipenvのインストール

できるだけ環境を汚さないように仮想環境を作成します。仮想環境を作成する方法としてしてはAnacondaやpyenv,viratualenv等色々ありますが、今回は作成したCodeをWebアプリ化&コンテナ化するために必要パッケージをpackage-list.txtとして提示してくれるpipenvを利用します。
いつもどおりbrewでインストールしていきます。

brew brewってなんやねん

macOS対応のパッケージ管理ソフトウェアです。Windowsでも使用できます。以下のリンクにある説明どおりにインストールすると今後も便利かと思います。
https://brew.sh/index_ja

さて、気を取り直してpipenvをインストールしていきます。

$ brew install pipenv

出力結果は長いのでこんな感じです
$ brew install pipenv
Updating Homebrew...
==> Downloading https://homebrew.bintray.com/bottles/openssl%401.1-1.1.1i.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/066b9f114617872e77fa3d4afee2337daabc2c181d7564fe60a5b26d89d69742?response-content-disposition=attachment%3Bfilename%3D%22o
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/readline-8.1.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/fe4de019cf549376a7743dcb0c86db8a08ca2b6d0dd2f8cb796dd7cf973dc2e9?response-content-disposition=attachment%3Bfilename%3D%22r
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/sqlite-3.34.0.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/7e04c1fcd0294ec7625e43eea05714d8bb4d15d24675c99484f1403fdcb438ec?response-content-disposition=attachment%3Bfilename%3D%22s
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/python%403.9-3.9.1.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/2b9946108e230384c48fcb4fcc07febd31e3987a7b9e66ee952e2c4f153a5dee?response-content-disposition=attachment%3Bfilename%3D%22p
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/pipenv-2020.11.15.catalina.bottle.tar.gz
Already downloaded: /Users/ktsutsum/Library/Caches/Homebrew/downloads/eabcd8eb2a82ad7fbdd5f958a38256bd254a0d5fdf059e6217946c1a46fe950d--pipenv-2020.11.15.catalina.bottle.tar.gz
==> Installing dependencies for pipenv: openssl@1.1, readline, sqlite and python@3.9
==> Installing pipenv dependency: openssl@1.1
==> Pouring openssl@1.1-1.1.1i.catalina.bottle.tar.gz
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> /Users/ktsutsum/.bash_profile

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

For pkg-config to find openssl@1.1 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"

==> Summary
?  /usr/local/Cellar/openssl@1.1/1.1.1i: 8,067 files, 18.5MB
==> Installing pipenv dependency: readline
==> Pouring readline-8.1.catalina.bottle.tar.gz
==> Caveats
readline is keg-only, which means it was not symlinked into /usr/local,
because macOS provides BSD libedit.

For compilers to find readline you may need to set:
  export LDFLAGS="-L/usr/local/opt/readline/lib"
  export CPPFLAGS="-I/usr/local/opt/readline/include"

For pkg-config to find readline you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/readline/lib/pkgconfig"

==> Summary
?  /usr/local/Cellar/readline/8.1: 48 files, 1.6MB
==> Installing pipenv dependency: sqlite
==> Pouring sqlite-3.34.0.catalina.bottle.tar.gz
==> Caveats
sqlite is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have sqlite first in your PATH run:
  echo 'export PATH="/usr/local/opt/sqlite/bin:$PATH"' >> /Users/ktsutsum/.bash_profile

For compilers to find sqlite you may need to set:
  export LDFLAGS="-L/usr/local/opt/sqlite/lib"
  export CPPFLAGS="-I/usr/local/opt/sqlite/include"

For pkg-config to find sqlite you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/sqlite/lib/pkgconfig"

==> Summary
?  /usr/local/Cellar/sqlite/3.34.0: 11 files, 4.1MB
==> Installing pipenv dependency: python@3.9
==> Pouring python@3.9-3.9.1.catalina.bottle.tar.gz
==> /usr/local/Cellar/python@3.9/3.9.1/bin/python3 -s setup.py --no-user-cfg install --force --verbose --install-scripts=/usr/local/Cellar/python@3.9/3.9.1/bin --install-lib=/usr/lo
==> /usr/local/Cellar/python@3.9/3.9.1/bin/python3 -s setup.py --no-user-cfg install --force --verbose --install-scripts=/usr/local/Cellar/python@3.9/3.9.1/bin --install-lib=/usr/lo
==> /usr/local/Cellar/python@3.9/3.9.1/bin/python3 -s setup.py --no-user-cfg install --force --verbose --install-scripts=/usr/local/Cellar/python@3.9/3.9.1/bin --install-lib=/usr/lo
==> Caveats
Python has been installed as
  /usr/local/bin/python3

Unversioned symlinks `python`, `python-config`, `pip` etc. pointing to
`python3`, `python3-config`, `pip3` etc., respectively, have been installed into
  /usr/local/opt/python@3.9/libexec/bin

You can install Python packages with
  pip3 install <package>
They will install into the site-package directory
  /usr/local/lib/python3.9/site-packages

See: https://docs.brew.sh/Homebrew-and-Python
==> Summary
?  /usr/local/Cellar/python@3.9/3.9.1: 4,452 files, 70.9MB
==> Installing pipenv
==> Pouring pipenv-2020.11.15.catalina.bottle.tar.gz
==> Caveats
Bash completion has been installed to:
  /usr/local/etc/bash_completion.d
==> Summary
?  /usr/local/Cellar/pipenv/2020.11.15: 1,868 files, 25.4MB
==> Upgrading 2 dependents:
poppler 20.11.0 -> 20.12.0, diff-pdf 0.4.1_7 -> 0.4.1_8
==> Upgrading poppler 20.11.0 -> 20.12.0
==> Downloading https://homebrew.bintray.com/bottles/qt-5.15.2.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/51ab78a99ff3498a236d15d9bed92962ddd2499c4020356469f7ab1090cf6825?response-content-disposition=attachment%3Bfilename%3D%22q
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/poppler-20.12.0.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/d89f6b867a6f012f44886b157f35f33179c583af93ace39329e7b62d2482eb31?response-content-disposition=attachment%3Bfilename%3D%22p
######################################################################## 100.0%
==> Installing dependencies for poppler: qt
==> Installing poppler dependency: qt
==> Pouring qt-5.15.2.catalina.bottle.tar.gz
==> Caveats
We agreed to the Qt open source license for you.
If this is unacceptable you should uninstall.

qt is keg-only, which means it was not symlinked into /usr/local,
because Qt 5 has CMake issues when linked.

If you need to have qt first in your PATH run:
  echo 'export PATH="/usr/local/opt/qt/bin:$PATH"' >> /Users/ktsutsum/.bash_profile

For compilers to find qt you may need to set:
  export LDFLAGS="-L/usr/local/opt/qt/lib"
  export CPPFLAGS="-I/usr/local/opt/qt/include"

For pkg-config to find qt you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/qt/lib/pkgconfig"

==> Summary
?  /usr/local/Cellar/qt/5.15.2: 10,688 files, 367.9MB
==> Installing poppler
==> Pouring poppler-20.12.0.catalina.bottle.tar.gz
?  /usr/local/Cellar/poppler/20.12.0: 474 files, 26.8MB
Removing: /usr/local/Cellar/poppler/20.11.0... (476 files, 26.7MB)
Removing: /Users/ktsutsum/Library/Caches/Homebrew/poppler--20.11.0.catalina.bottle.tar.gz... (8.1MB)
==> Upgrading diff-pdf 0.4.1_7 -> 0.4.1_8
==> Downloading https://homebrew.bintray.com/bottles/diff-pdf-0.4.1_8.catalina.bottle.tar.gz
#=#=-#  #
curl: (22) The requested URL returned error: 404 Not Found
Error: Failed to download resource "diff-pdf"
Download failed: https://homebrew.bintray.com/bottles/diff-pdf-0.4.1_8.catalina.bottle.tar.gz
Warning: Bottle installation failed: building from source.
==> Downloading https://homebrew.bintray.com/bottles/automake-1.16.3.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/25fe47e5fb1af734423e1e73f0dc53637e89d825ef8d8199add239352b5b974e?response-content-disposition=attachment%3Bfilename%3D%22a
######################################################################## 100.0%
==> Downloading https://github.com/vslavik/diff-pdf/releases/download/v0.4.1/diff-pdf-0.4.1.tar.gz
==> Downloading from https://github-production-release-asset-2e65be.s3.amazonaws.com/353360/b335c080-3951-11ea-98d4-aa100bba0fcf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AK
######################################################################## 100.0%
Warning: A newer Command Line Tools release is available.
Update them from Software Update in System Preferences or run:
  softwareupdate --all --install --force

If that doesn't show you an update run:
  sudo rm -rf /Library/Developer/CommandLineTools
  sudo xcode-select --install

Alternatively, manually download them from:
  https://developer.apple.com/download/more/.

==> Installing dependencies for diff-pdf: automake
==> Installing diff-pdf dependency: automake
==> Pouring automake-1.16.3.catalina.bottle.tar.gz
?  /usr/local/Cellar/automake/1.16.3: 131 files, 3.4MB
==> Installing diff-pdf
==> ./configure --prefix=/usr/local/Cellar/diff-pdf/0.4.1_8
Last 15 lines from /Users/ktsutsum/Library/Logs/Homebrew/diff-pdf/01.configure:
checking for style of include used by make... GNU
checking dependency style of clang++... none
checking for pkg-config... /usr/local/Homebrew/Library/Homebrew/shims/mac/super/pkg-config
checking pkg-config is at least version 0.9.0... yes
checking for POPPLER... no
configure: error: Package requirements (poppler-cairo >= 0.10 poppler-glib >= 0.10 cairo-pdf) were not met:

No package 'poppler-cairo' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables POPPLER_CFLAGS
and POPPLER_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

READ THIS: https://docs.brew.sh/Troubleshooting

These open issues may also help:
diff-pdf failing to build on macOS 10.15.7 https://github.com/Homebrew/homebrew-core/issues/66385

Error: A newer Command Line Tools release is available.
Update them from Software Update in System Preferences or run:
  softwareupdate --all --install --force

If that doesn't show you an update run:
  sudo rm -rf /Library/Developer/CommandLineTools
  sudo xcode-select --install

Alternatively, manually download them from:
  https://developer.apple.com/download/more/.


==> Checking for dependents of upgraded formulae...
==> No broken dependents to reinstall!
==> Caveats
==> openssl@1.1
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> /Users/ktsutsum/.bash_profile

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

For pkg-config to find openssl@1.1 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"

==> readline
readline is keg-only, which means it was not symlinked into /usr/local,
because macOS provides BSD libedit.

For compilers to find readline you may need to set:
  export LDFLAGS="-L/usr/local/opt/readline/lib"
  export CPPFLAGS="-I/usr/local/opt/readline/include"

For pkg-config to find readline you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/readline/lib/pkgconfig"

==> sqlite
sqlite is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have sqlite first in your PATH run:
  echo 'export PATH="/usr/local/opt/sqlite/bin:$PATH"' >> /Users/ktsutsum/.bash_profile

For compilers to find sqlite you may need to set:
  export LDFLAGS="-L/usr/local/opt/sqlite/lib"
  export CPPFLAGS="-I/usr/local/opt/sqlite/include"

For pkg-config to find sqlite you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/sqlite/lib/pkgconfig"

==> python@3.9
Python has been installed as
  /usr/local/bin/python3

Unversioned symlinks `python`, `python-config`, `pip` etc. pointing to
`python3`, `python3-config`, `pip3` etc., respectively, have been installed into
  /usr/local/opt/python@3.9/libexec/bin

You can install Python packages with
  pip3 install <package>
They will install into the site-package directory
  /usr/local/lib/python3.9/site-packages

See: https://docs.brew.sh/Homebrew-and-Python
==> pipenv
Bash completion has been installed to:
  /usr/local/etc/bash_completion.d
==> qt
We agreed to the Qt open source license for you.
If this is unacceptable you should uninstall.

qt is keg-only, which means it was not symlinked into /usr/local,
because Qt 5 has CMake issues when linked.

If you need to have qt first in your PATH run:
  echo 'export PATH="/usr/local/opt/qt/bin:$PATH"' >> /Users/ktsutsum/.bash_profile

For compilers to find qt you may need to set:
  export LDFLAGS="-L/usr/local/opt/qt/lib"
  export CPPFLAGS="-I/usr/local/opt/qt/include"

For pkg-config to find qt you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/qt/lib/pkgconfig"


2. 仮想環境の準備

作業フォルダに移動したら、pipenvでpythonの仮想環境を作成していきます。
以下のような書式でpythonのバージョンを指定します。OpenVINOはpython3.7までの対応のため今回はpython3.7にて環境を作成していきます。

$ pipenv --python 3.7
$ pipenv --python 3.7
Virtualenv already exists!
Removing existing virtualenv...
Warning: the environment variable LANG is not set!
We recommend setting this in ~/.profile (or equivalent) for proper expected behavior.
Creating a virtualenv for this project...
Pipfile: /Users/ktsutsum/openvino/demos/Pipfile
Using /usr/bin/python3 (3.7.3) to create virtualenv...
⠇ Creating virtual environment...created virtual environment CPython3.7.3.final.0-64 in 458ms
  creator CPython3macOsFramework(dest=/Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME, clear=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/Users/ktsutsum/Library/Application Support/virtualenv)
    added seed packages: pip==20.2.4, setuptools==50.3.2, wheel==0.35.1
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator

✔ Successfully created virtual environment!
Virtualenv location: /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME

3. 仮想環境を使用してOpenVINOインストールの続き

仮想環境利用してOpenVINOの残りのセットアップを進めていきましょう。
まず先ほど作成した仮想環境のShellに入ります。

$ pipenv shell

https://docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_macos.html
OpenVINOインストール続きはこちらのページをご参照いただきながら進め行ければと思います。

環境変数をSourceします。

(demos) bash-3.2$ source /opt/intel/openvino_2021/bin/setupvars.sh
[setupvars.sh] OpenVINO environment initialized

Model Optimizerのインストールに必要なパッケージを事前にインストールします。

(demos) bash-3.2$ cd /opt/intel/openvino_2021/deployment_tools/model_optimizer/install_prerequisites
(demos) bash-3.2$ sudo ./install_prerequisites.sh
WARNING: The directory '/Users/ktsutsum/Library/Caches/pip' or its parent directory is not owned or is not writable by the current user. The cache has been disabled. Check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
Ignoring tensorflow: markers 'python_version >= "3.8"' don't match your environment
Requirement already satisfied: tensorflow<2.0,>=1.15.2 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from -r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (1.15.4)
Requirement already satisfied: mxnet<=1.5.1,>=1.0.0 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from -r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 3)) (1.5.1)
Requirement already satisfied: networkx>=1.11 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from -r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 4)) (2.5)
Requirement already satisfied: numpy>=1.13.0 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from -r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 5)) (1.19.4)
Requirement already satisfied: protobuf>=3.6.1 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from -r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 6)) (3.14.0)
Requirement already satisfied: onnx>=1.1.2 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from -r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 7)) (1.8.0)
Requirement already satisfied: test-generator==0.1.1 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from -r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 8)) (0.1.1)
Requirement already satisfied: defusedxml>=0.5.0 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from -r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 9)) (0.6.0)
Requirement already satisfied: six in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from test-generator==0.1.1->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 8)) (1.15.0)
Requirement already satisfied: requests<3,>=2.20.0 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from mxnet<=1.5.1,>=1.0.0->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 3)) (2.25.0)
Requirement already satisfied: graphviz<0.9.0,>=0.8.1 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from mxnet<=1.5.1,>=1.0.0->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 3)) (0.8.4)
Requirement already satisfied: decorator>=4.3.0 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from networkx>=1.11->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 4)) (4.4.2)
Requirement already satisfied: typing-extensions>=3.6.2.1 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from onnx>=1.1.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 7)) (3.7.4.3)
Requirement already satisfied: astor>=0.6.0 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (0.8.1)
Requirement already satisfied: wrapt>=1.11.1 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (1.12.1)
Requirement already satisfied: tensorflow-estimator==1.15.1 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (1.15.1)
Requirement already satisfied: termcolor>=1.1.0 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (1.1.0)
Requirement already satisfied: google-pasta>=0.1.6 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (0.2.0)
Requirement already satisfied: keras-preprocessing>=1.0.5 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (1.1.2)
Requirement already satisfied: tensorboard<1.16.0,>=1.15.0 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (1.15.0)
Requirement already satisfied: grpcio>=1.8.6 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (1.34.0)
Requirement already satisfied: absl-py>=0.7.0 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (0.11.0)
Requirement already satisfied: gast==0.2.2 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (0.2.2)
Requirement already satisfied: opt-einsum>=2.3.2 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (3.3.0)
Requirement already satisfied: wheel>=0.26 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (0.35.1)
Requirement already satisfied: keras-applications>=1.0.8 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (1.0.8)
Collecting numpy>=1.13.0
  Downloading numpy-1.18.5-cp37-cp37m-macosx_10_9_x86_64.whl (15.1 MB)
     |████████████████████████████████| 15.1 MB 2.3 MB/s
Requirement already satisfied: h5py in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from keras-applications>=1.0.8->tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (3.1.0)
Requirement already satisfied: idna<3,>=2.5 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from requests<3,>=2.20.0->mxnet<=1.5.1,>=1.0.0->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 3)) (2.10)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from requests<3,>=2.20.0->mxnet<=1.5.1,>=1.0.0->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 3)) (1.26.2)
Requirement already satisfied: certifi>=2017.4.17 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from requests<3,>=2.20.0->mxnet<=1.5.1,>=1.0.0->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 3)) (2020.12.5)
Requirement already satisfied: chardet<4,>=3.0.2 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from requests<3,>=2.20.0->mxnet<=1.5.1,>=1.0.0->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 3)) (3.0.4)
Requirement already satisfied: markdown>=2.6.8 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorboard<1.16.0,>=1.15.0->tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (3.3.3)
Requirement already satisfied: setuptools>=41.0.0 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorboard<1.16.0,>=1.15.0->tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (50.3.2)
Requirement already satisfied: werkzeug>=0.11.15 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from tensorboard<1.16.0,>=1.15.0->tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (1.0.1)
Requirement already satisfied: importlib-metadata in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from markdown>=2.6.8->tensorboard<1.16.0,>=1.15.0->tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (3.3.0)
Requirement already satisfied: cached-property in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from h5py->keras-applications>=1.0.8->tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (1.5.2)
Requirement already satisfied: zipp>=0.5 in /Users/ktsutsum/.local/share/virtualenvs/demos-UH30-bME/lib/python3.7/site-packages (from importlib-metadata->markdown>=2.6.8->tensorboard<1.16.0,>=1.15.0->tensorflow<2.0,>=1.15.2->-r /opt/intel/openvino_2021.1.110/deployment_tools/model_optimizer/install_prerequisites/../requirements.txt (line 1)) (3.4.0)
Installing collected packages: numpy
  Attempting uninstall: numpy
    Found existing installation: numpy 1.19.4
    Uninstalling numpy-1.19.4:
      Successfully uninstalled numpy-1.19.4
Successfully installed numpy-1.18.5
[WARNING] All Model Optimizer dependencies are installed globally.
[WARNING] If you want to keep Model Optimizer in separate sandbox
[WARNING] run install_prerequisites.sh venv {caffe|tf|tf2|mxnet|kaldi|onnx}

こちらで仮想環境の構築とOpenVINOインストールは完了です。

実際に Code を書いてみよう

それでは実際にCodeを書いていきましょう。
今回使用するモジュールは以下のとおりです。はじめにこれらをImportします。
エディタはお好きなものを利用して良いと思います。私は普通にvimを使っています。

名称 概要
openvino OpenVINO本体
Numpy Pythonの数値計算を行うライブラリ
Sys Python のインタプリタや実行環境に関連
# 必要なモジュール、ライブラリをインポート
import cv2
import numpy as np
import sys
sys.path.append
from openvino.inference_engine import IENetwork, IECore

なにやらIE NetworkとIE Coreというものが出てきましたね。
IEはInference Engineの略です。IENetworkというモジュールは今回の2021.1バージョンからIECoreに統合されましたので使用しないのですが、
2020バージョンを利用する場合の後方互換性としてCode上残してあります。

Screen Shot 2020-12-10 at 20.48.07.png

Inference Engineというのはモデルをモデルオプティマイザーによって最適化したものです。最適化とは何をしているかというと精度をほぼ失うことなくモデルの重みの最良歯科を行うことにより処理負荷を劇的に下げるような変換です。

ここで、今回使用するモデルのご紹介をいたします。

モデル名称 emotions_recognition_retail_0003
Input face orientation Frontal
Rotation in-plane 2±15˚
Rotation out-of-plane  Yaw: ±15˚ / Pitch: ±15˚ 
Min object width 64 pixels
GFlops 0.126
MParams 2.483
Source framework Caffe

https://docs.openvinotoolkit.org/2019_R1/_emotions_recognition_retail_0003_description_emotions_recognition_retail_0003.html

モデル名称 face-detection-retail-0004
AP (WIDER) 83.00%
GFlops 1.067
MParams  0.588 
Source framework Caffe

https://docs.openvinotoolkit.org/2019_R1/_face_detection_retail_0004_description_face_detection_retail_0004.html

上記モデルを読み込みます。

# Face Detection Model
net = ie.read_network(model='intel/face-detection-retail-0004/FP16/face-detection-retail-0004.xml', weights='intel/face-detection-retail-0004/FP16/face-detection-retail-0004.bin')
exec_net = ie.load_network(network=net, device_name="CPU", num_requests=0)

# Emotion Detection Model
net_emotion = ie.read_network(model='intel/emotions-recognition-retail-0003/FP16/emotions-recognition-retail-0003.xml', weights='intel/emotions-recognition-retail-0003/FP16/emotions-recognition-retail-0003.bin')
exec_net_emotion = ie.load_network(network=net_emotion, device_name="CPU", num_requests=0)

ここで、モデルへのパス指定をしていますが、まだモデルは手元にないと思いますのでモデルをダウンローダーを使って手に入れましょう。

(face-detection) bash-3.2$ python3 /opt/intel/openvino_2021/deployment_tools/tools/model_downloader/downloader.py --name face-detection-retail-0004
Traceback (most recent call last):
  File "/opt/intel/openvino_2021/deployment_tools/tools/model_downloader/downloader.py", line 25, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'

requests moduleをインストールします

(face-detection) bash-3.2$ pipenv install requests
Installing requests...
Adding requests to Pipfile's [packages]...
✔ Installation Succeeded
Pipfile.lock (9be4f8) out of date, updating to (444a6d)...
Locking [dev-packages] dependencies...
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
✔ Success!
Updated Pipfile.lock (444a6d)!
Installing dependencies from Pipfile.lock (444a6d)...
  ?   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00

もう一度チャレンジ

(face-detection) bash-3.2$ python3 /opt/intel/openvino_2021/deployment_tools/tools/model_downloader/downloader.py --name face-detection-retail-0004
Traceback (most recent call last):
  File "/opt/intel/openvino_2021/deployment_tools/tools/model_downloader/downloader.py", line 37, in <module>
    import common
  File "/opt/intel/openvino_2021.1.110/deployment_tools/open_model_zoo/tools/downloader/common.py", line 33, in <module>
    import yaml
ModuleNotFoundError: No module named 'yaml'

pyyaml moduleをインストールします

(face-detection) bash-3.2$ pipenv install pyyaml
Installing pyyaml...
Adding pyyaml to Pipfile's [packages]...
✔ Installation Succeeded
Pipfile.lock (444a6d) out of date, updating to (c30804)...
Locking [dev-packages] dependencies...
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
✔ Success!
Updated Pipfile.lock (c30804)!
Installing dependencies from Pipfile.lock (c30804)...
  ?   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00

今度こそ

(face-detection) bash-3.2$ python3 /opt/intel/openvino_2021/deployment_tools/tools/model_downloader/downloader.py --name face-detection-retail-0004
################|| Downloading face-detection-retail-0004 ||################

========== Downloading /Users/ktsutsum/openvino/demos/face-detection/intel/face-detection-retail-0004/FP32/face-detection-retail-0004.xml
... 100%, 98 KB, 166814 KB/s, 0 seconds passed

========== Downloading /Users/ktsutsum/openvino/demos/face-detection/intel/face-detection-retail-0004/FP32/face-detection-retail-0004.bin
... 100%, 2297 KB, 12116 KB/s, 0 seconds passed

========== Downloading /Users/ktsutsum/openvino/demos/face-detection/intel/face-detection-retail-0004/FP16/face-detection-retail-0004.xml
... 100%, 97 KB, 210379 KB/s, 0 seconds passed

========== Downloading /Users/ktsutsum/openvino/demos/face-detection/intel/face-detection-retail-0004/FP16/face-detection-retail-0004.bin
... 100%, 1148 KB, 20286 KB/s, 0 seconds passed

========== Downloading /Users/ktsutsum/openvino/demos/face-detection/intel/face-detection-retail-0004/FP16-INT8/face-detection-retail-0004.xml
... 100%, 240 KB, 257964 KB/s, 0 seconds passed

========== Downloading /Users/ktsutsum/openvino/demos/face-detection/intel/face-detection-retail-0004/FP16-INT8/face-detection-retail-0004.bin
... 100%, 586 KB, 13932 KB/s, 0 seconds passed

これでモデルをダウンローダーが完了です。
これと同じことをemotions-recognition-retail-0003についても実施します。
ここでは割愛しますが、上記のコマンドのモデル名だけを変更して実施してみてください。

続いて解析本体部分となります。非常に短いCodeです。
本稿ではOpenVINOについて取り上げておりますので、その他の説明については簡易にとどめておきます。

cap = cv2.VideoCapture(1)

while True:
    ret, frame = cap.read()

    if ret == False:
        continue

    img = cv2.resize(frame, (300, 300))
    img = img.transpose((2, 0, 1))
    img = np.expand_dims(img, axis=0)

    out = exec_net.infer(inputs={'data': img})

    out = out['detection_out']
    out = np.squeeze(out)

    for detection in out:
        confidence = float(detection[2])

        xmin = int(detection[3] * frame.shape[1])
        ymin = int(detection[4] * frame.shape[0])
        xmax = int(detection[5] * frame.shape[1])
        ymax = int(detection[6] * frame.shape[0])

        if confidence > 0.5:
            cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), color=(xmin, ymin, 0), thickness=3)

            frame_face = frame[ymin:ymax, xmin:xmax]

            img = cv2.resize(frame_face, (64, 64))
            img = img.transpose((2, 0, 1))
            img = np.expand_dims(img, axis=0)

            out = exec_net_emotion.infer(inputs={'data': img})

            out = out['prob_emotion']
            out = np.squeeze(out)

            index_max = np.argmax(out)

            list_emotion = ['neutral', 'happy', 'sad', 'surprise', 'anger']

            cv2.putText(frame, list_emotion[index_max], (xmin, ymin - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (xmin, ymin, 0), 2)

    cv2.imshow('image', frame)

    key = cv2.waitKey(1)
    if key != -1:
        break

cap.release()
cv2.destoryAllWindows()

まず以下の部分ですが、opencvを使用して内蔵カメラからの画像を取り込んでいます。
cv2.VideoCapture(1)の数字部分(1)はお手もと環境により異なります。通常は(0)の場合が多いです。
retにはtrue or false, frameには画像が配列で取り込まれます。画像がある場合ret = trueとなります。

cap = cv2.VideoCapture(1)

while True:
    ret, frame = cap.read()

    if ret == False:
        continue

次にこちら。

    img = cv2.resize(frame, (300, 300))
    img = img.transpose((2, 0, 1))
    img = np.expand_dims(img, axis=0)
  1. resizeにより入力に必要なサイズ(300, 300)へ変換しています。
  2. transposeにより(h, w, c)から(c, h, w)への変換をしています。
  • h : height(高さ)
  • w : width(幅)
  • c : channel(チャネル:色情報)

OpenCVは(h, w, c)の順番で画像を取り込みますがこのモデルでは以下のように(c, w, h)の順番で渡してやる必要があリます。
Screen Shot 2020-12-18 at 5.03.42.png

  1. expand_dimsで次元の追加 上記のように入力が期待するものは[1x3x300x300]です。ただここまでの変換だと[3x300x300]と1次元足りません。 expand_dims(axis = 0)と指定することにより次元を追加し[1x3x300x300]とすることが可能です。

次のセクションに行きましょう。

    out = exec_net.infer(inputs={'data': img})

imgに対して事前に定義したexec_netのinferメソッドを利用し推論を実施します。

    out = out['detection_out']

ではoutのデータはどのようなものでしょうか?実際にprintしてみると以下のようなarrayで返されてきます。
detection_outという名前だということがわかりますので、out = out['detection_out']としています。

{'detection_out': array([[[[0.        , 1.        , 1.        , ..., 0.1926786 ,
          0.77813023, 0.92338395],
         [0.        , 1.        , 0.02098523, ..., 0.9338631 ,
          0.99440974, 1.0033596 ],
         [0.        , 1.        , 0.01355048, ..., 0.01246494,
          0.70711267, 0.40890545],
         ...,
         [0.        , 0.        , 0.        , ..., 0.        ,
          0.        , 0.        ],
         [0.        , 0.        , 0.        , ..., 0.        ,
          0.        , 0.        ],
         [0.        , 0.        , 0.        , ..., 0.        ,
          0.        , 0.        ]]]], dtype=float32)}

ここで、outputのshapeを見てみましょう。[1, 1, N, 7]だということがわかりますが、はじめの[1, 1]は実際の処理には不要です。

Screen Shot 2020-12-18 at 5.03.47.png

そういった場合は、1次元の要素を削除できるsqueezeメソッドを利用します。

    out = np.squeeze(out)

さて、上記の[N, 7]という行列の"7"の中身を見てみましょう。以下のようになっています。

  • image_id - ID of the image in the batch : 1個
  • label - predicted class ID : 1個
  • conf - confidence for the predicted class : 1個
  • (x_min, y_min) - coordinates of the top left bounding box corner : 2個
  • (x_max, y_max) - coordinates of the bottom right bounding box corner. : 2個
for detection in out:
        confidence = float(detection[2])

        xmin = int(detection[3] * frame.shape[1])
        ymin = int(detection[4] * frame.shape[0])
        xmax = int(detection[5] * frame.shape[1])
        ymax = int(detection[6] * frame.shape[0])

Confidenceというここが顔だよという確信度をconfidenceに入れています。
下の部分では顔部分のx_min(x座標の最小値)、y_min(y座標の最小値)、x_max(x座標の最大値)、y_max(y座標の最大値)を求めています。
x_min, y_min, x_max, y_maxは0-1までの少数で与えられるため、実際のframeサイズを乗算して実際の一を割り出しています。

ちなみに、NというのはBounding Boxの個数です。この1個めの座標、2個めの座標というような感じです。

if confidence > 0.5:
            cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), color=(xmin, ymin, 0), thickness=3)

            frame_face = frame[ymin:ymax, xmin:xmax]

ここからは多少まとめて説明していきます。
Confidenceが0.5以上のものを選んで顔部分のBounding Boxの個数を減らしていきます。
cv2.rectangleメソッドを利用し四角形を書いていきます。

frame_faceには顔部分の領域だけをndarrayとして切り出します。スライスを使います。
ここから先はemotions-recognition-retail-0003モデルを使用し、上記で切り出した顔領域に対して感情認識を行っていきます。

            img = cv2.resize(frame_face, (64, 64))
            img = img.transpose((2, 0, 1))
            img = np.expand_dims(img, axis=0)

Screen Shot 2020-12-18 at 21.32.34.png

顔領域認識モデルと同じ処理を行っていきます。
感情認識モデルは上記の通りinputの画像が64 x 64を期待するモデルです。

  1. resizeにより入力に必要なサイズ(64, 64)へ変換しています。
  2. transposeにより(h, w, c)から(c, h, w)への変換をしています。
            out = exec_net_emotion.infer(inputs={'data': img})

            out = out['prob_emotion']
            out = np.squeeze(out)

outにはexec_net_emotion.inferにより感情認識した結果が入ります。
どのような形式で結果が入るかというと上記にある通り、shape[1, 5, 1, 1]となり、2番めの要素として以下の5種類の感情に分類された結果が入ります。

  1. neutral
  2. happy
  3. sad
  4. surprise
  5. anger

ここでもoutがどういう結果になるか見てみましょう。

{'prob_emotion': array([[[[8.9757685e-03]],

        [[9.8825932e-01]],

        [[1.2316966e-03]],

        [[7.2518183e-04]],

        [[8.0798665e-04]]]], dtype=float32)}

prob_emotionという名前の配列になっているということがわかります。
ということですので、outにはout['prob_emotion']を代入し、squeezeにより余分な要素を削除してきます。
上記の例ではhappyが確率が高いということがわかりますね。

            index_max = np.argmax(out)

            list_emotion = ['neutral', 'happy', 'sad', 'surprise', 'anger']

人の目で判断してもよいのですが、argmaxを使うと最大値がわかります。一番確率の高いものがわかるわけですね。
list_emotionというリストを作り、モデルの出力と同じ順番で感情の分類をリストに入れていきます。
argmaxによりリストのどの要素が最大値となるかがわかりますので、この場合ですとhappyが選択されることになります。

            cv2.putText(frame, list_emotion[index_max], (xmin, ymin - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (xmin, ymin, 0), 2)

フォントを指定し、putTextメソッドを利用しテキストを画面内に書いていきます。場所は顔領域の上側10pixelとします。1, (xmin, ymin, 0), 2はそれぞれフォントの大きさ、色、フォントの太さを表しています。ここでは複数の人物が画面内にいたときにすべて同じ色にならないように座標により色を変えています。実際にコードで試してみてください。

    cv2.imshow('image', frame)

imshowにより実際に画面に描画します。

    key = cv2.waitKey(1)
    if key != -1:
        break

cap.release()
cv2.destoryAllWindows()

waitKeyによりキー入力を待ち、何らかのキーが入力されたら画面を閉じます。

最後にコード全体を載せておきます。

import cv2
import numpy as np
import sys
sys.path.append
from openvino.inference_engine import IENetwork, IECore

ie = IECore()

# Face Detection Model
net = ie.read_network(model='intel/face-detection-retail-0004/FP16/face-detection-retail-0004.xml', weights='intel/face-detection-retail-0004/FP16/face-detection-retail-0004.bin')
exec_net = ie.load_network(network=net, device_name="CPU", num_requests=0)

# Emotion Detection Model
net_emotion = ie.read_network(model='intel/emotions-recognition-retail-0003/FP16/emotions-recognition-retail-0003.xml', weights='intel/emotions-recognition-retail-0003/FP16/emotions-recognition-retail-0003.bin')
exec_net_emotion = ie.load_network(network=net_emotion, device_name="CPU", num_requests=0)

cap = cv2.VideoCapture(3)

while True:
    ret, frame = cap.read()

    if ret == False:
        continue

    img = cv2.resize(frame, (300, 300))
    img = img.transpose((2, 0, 1))
    img = np.expand_dims(img, axis=0)

    out = exec_net.infer(inputs={'data': img})

    out = out['detection_out']
    out = np.squeeze(out)

    for detection in out:
        confidence = float(detection[2])

        xmin = int(detection[3] * frame.shape[1])
        ymin = int(detection[4] * frame.shape[0])
        xmax = int(detection[5] * frame.shape[1])
        ymax = int(detection[6] * frame.shape[0])

        if confidence > 0.5:
            cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), color=(xmin, ymin, 0), thickness=3)

            frame_face = frame[ymin:ymax, xmin:xmax]

            img = cv2.resize(frame_face, (64, 64))
            img = img.transpose((2, 0, 1))
            img = np.expand_dims(img, axis=0)

            out = exec_net_emotion.infer(inputs={'data': img})

            out = out['prob_emotion']
            out = np.squeeze(out)

            index_max = np.argmax(out)

            list_emotion = ['neutral', 'happy', 'sad', 'surprise', 'anger']

            cv2.putText(frame, list_emotion[index_max], (xmin, ymin - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (xmin, ymin, 0), 2)

    cv2.imshow('image', frame)

    key = cv2.waitKey(1)
    if key != -1:
        break

cap.release()
cv2.destoryAllWindows()

おまけ

本記事では主にローカルでの開発方法について記載していますが、クラウド上でOpenVINOのプロトタイプ開発も可能です。
Intel DevCloud for the EdgeというIntelが提供しているクラウド開発サービスがあります。
Screen Shot 2020-12-18 at 22.08.32.png
OpenVINOが予めインストールされたクラウド上の環境を利用でき、様々なIntelハードウェア(CPU/GPU/FPGA/VPU)上でアプリケーションを実行させることが可能です。
プロトタイプの実験を簡単に無料で実施することが可能です。
大きな特徴として、Jupyter-Notebookを利用した開発が可能ですが、簡単に①〜⑥について説明します。

①②:コード作成
* Jupyter-NotebookによりOpenVINOを使ったコードを記述
* OpenModelZooやModel Optimizerを利用してモデルを準備
* もし必要であればStorageServerから動画やイメージを推論させることも可能

③④:ジョブを実行
* CPU,FPGA,GPU,VPU等様々なハードウェアから実行環境を選択
* アプリケーションをジョブに登録し、単体もしくは並列処理でのEdge Inferenceを実施

⑤⑥:結果の出力
* Jupyter-Notebookで推論結果を画像やデータで表示

実際に使用するには灯籠が必要ですが、以下のReferenceから登録が可能ですので興味を持たれた方はぜひお試しください。
来年はWebアプリ開発とそのパフォーマンス分析でも書こうかな。。

Reference

  • OpenVINO Resource
    • OpenVINOを使う上でもモデルの情報など各種情報が取り揃えてあります。
  • OpenVINO Documentation
    • OpenVINOのインストール方法やpython以外の使用方法なども記載されております。
  • Intel DevCloud for the Edge
    • クラウドでのプロトタイプ開発が可能。予めIntelのリソースを組み込んだものを利用可能。

免責事項

本サイトおよび対応するコメントにおいて表明される意見は、投稿者本人の個人的意見であり、シスコの意見ではありません。本サイトの内容は、情報の提供のみを目的として掲載されており、シスコや他の関係者による推奨や表明を目的としたものではありません。各利用者は、本 Web サイトへの掲載により、投稿、リンクその他の方法でアップロードした全ての情報の内容に対して全責任を負い、本 Web サイトの利用に関するあらゆる責任からシスコを免責することに同意したものとします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python 正規表現を利用して任意の文字列群を抽出する / 名前付きグループを利用する

この記事はTakumi Akashiro ひとり Advent Calendar 2020の18日目の記事です。

始めに

みなさまは正規表現使ってますかー?!!!

私は最後に使ったのが1ヶ月前ぐらいですね。
必要なときが来るとバリバリ使います。

そんな正規表現ですが、しっかり使うと任意の文字列群を良い感じに抽出できます。

TLDL

文字列群を取り出すときは名前付きグループを使おう!

>> text = "environ/house-food/apple-pie02.fbx"
>> import re
>> reg_text = r'(?P<main>(chara|environ))/(?P<sub>[^-/]*)-?(?P<sub_sub>[^/]*)/(?P<filling>[^-]*)-pie'
>> match = re.search(reg_text, text)
>> print(match.groupdict())
{'main': 'environ', 'sub': 'house', 'sub_sub': 'food', 'filling': 'apple'}

正規表現の基本

まず正規表現の基本、適当にマッチをしていきます。

#! python3
import re

def main():
    # NOTE: どうでもいいですけど、正規表現のサンプルでアップルパイってよく見かけますよね。
    text = "environ/house-food/apple-pie02.fbx"

    match_obj= re.search(r'pie', text)
    if match_obj:
        print("ヒットしたよ!")
    else:
        print("ヒットしないよ!")

if __name__ == '__main__':
    main()

ま、こんなもんです。超簡単ですね。

速度が求められて[^先頭一致が可能な場合はre.matchを使ったり、置換したいときはre.sub使うぐらいできれば、
とりあえず正規表現を使うのには困らないですね。1


ではここで、pieの前にある文字列appleを抽出するにはどうしましょう。
色々文字列を削ったりして取り出すと思います。

でも取得対象が複数あるときは?例えばenvironhousefoodappleを一気に取りたい場合は?

こんなときに利用できるのがグループです。
ちょっと公式ドキュメントを読んでみましょう。

正規表現のシンタックス

(中略)
(...)
丸括弧で囲まれた正規表現にマッチするとともに、グループの開始と終了を表します。
グループの中身は以下で述べるように、マッチが実行された後で回収したり、その文字列中で以降 \number 特殊シーケンスでマッチしたりできます。
リテラル '(' や ')' にマッチするには、( や ) を使うか、文字クラス中に囲みます: [(]、 [)] 。

re --- 正規表現操作 — Python 3.9.1 ドキュメントより

……どう使うんだか分からん……
というわけでサンプルを出してみます。

グループを使ってみる

#! python3
import re

def main():
    text = "environ/house-food/apple-pie02.fbx"

    match_obj= re.search(r'([^/-]*)-?pie)', text)
    print(match_obj.groups())

if __name__ == '__main__':
    main()

image.png

マッチオブジェクトに対してmatch_obj.groups()することで、
グループ化した正規表現に引っかかった文字列のリストが取得できます。

なので上記のtextからenvironhousefoodappleを取り出したいと考えて,

#! python3
import re

def main():
    text = "environ/house-food/apple-pie02.fbx"

    match = re.search(r'([^/-]*)/([^/-]*)-?([^/-]*)/([^/-]*)-?pie', text)
    if match:
        print(match.groups())

if __name__ == '__main__':
    main()

とすれば……

image.png
無事、出来ましたね!

まあまあ便利ですね!


listじゃなくてdictとしてほしいんだよなーとか、
正規表現じゃ()はよく使うから、必要な部分だけほしいんだよなーとかあると思います。

そんなときにはコレ「名前付きグループ」です!

例のごとく、公式ドキュメントを読んでみます。

正規表現のシンタックス

(中略)
(?P<name>...)
通常の丸括弧に似ていますが、このグループがマッチした部分文字列はシンボリックグループ名 name でアクセスできます。
グループ名は有効な Python 識別子でなければならず、各グループ名は 1 個の正規表現内で一度だけ定義されていなければなりません。
シンボリックグループは、そのグループが名前付けされていなかったかのように番号付けされたグループでもあります。

名前付きグループ を使ってみる

ま、とりあえず使えばわかるでしょうの精神で書きます。

#! python3
import re

def main():
    text = "environ/house-food/apple-pie02.fbx"

    match = re.search(r'(?P<main>[^/-]*)/(?P<sub>[^/-]*)-?(?P<sub_sub>[^/-]*)/(?P<filling>[^/-]*)-?pie', text)
    if match:
        print(match.groupdict())

if __name__ == '__main__':
    main()

マッチオブジェクトに対してmatch_obj.groupdict()することで、
名前がついたグループ化の辞書が取得できます。

image.png

いい感じに取り出せてますね!

締め

なんも思いつかねえ……

便利ですね!


  1. 追記: 速度を気にするような話であれば、forループ内で同じ正規表現を使う場合、forの外でre.compileして再利用するとが若干早くなりますね。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

たった1行から始めるPythonのAST(抽象構文木)入門

はじめに

この記事は2020年のRevCommアドベントカレンダー20日目の記事です。 19日目は@metal-presidentさんの「モバイルチームの成長とKMM導入に向けて」でした。

11月に株式会社RevCommに入社した@rhoboroです。
前職では主にGCP x Pythonで、現職では主にAWS x Pythonで日々業務を行なっています。
RevCommでは広島県の尾道からフルリモートワークで働いているので、そういった働き方にもし興味があればこちらの記事もご覧ください。

それでは、本題に入ります。

PythonのAST(抽象構文木)とは?

この記事は、PythonのAST(抽象構文木、Astract Syntax Tree)に触れたことのない方を対象にしたASTの入門記事です。
そもそもASTとは何なのか、ASTを理解すると何ができるのかを中心に紹介していきます。

さっそくですが、タイトルにもある通りまずは1行のコマンドを打ってみましょう。
次のモジュールschema.pyを用意してから、その下にあるpython3コマンドを実行してください。

schema.py
# このクラスは下記にありました
# https://docs.python.org/ja/3/tutorial/classes.html
class MyClass:
    """A simple example class"""
    i = 12345

    def f(self):
        return 'hello world'
$ python3 -c 'import ast; print(ast.dump(ast.parse(open("schema.py").read()), indent=4))'

コマンドを実行すると、次のように出力されます。(この結果はPython3.9で実行したものです)
先ほどのschema.pyとよく見比べてみると、見た目は違いますがなんとなくソースコードと同じものを表現していることがわかると思います。また、ModuleClassDefExprなどがPythonのクラス名だとすると、この結果はPythonのオブジェクトにも見えてきます。

Module(
    body=[
        ClassDef(
            name='MyClass',
            bases=[],
            keywords=[],
            body=[
                Expr(
                    value=Constant(value='A simple example class')),
                Assign(
                    targets=[
                        Name(id='i', ctx=Store())],
                    value=Constant(value=12345)),
                FunctionDef(
                    name='f',
                    args=arguments(
                        posonlyargs=[],
                        args=[
                            arg(arg='self')],
                        kwonlyargs=[],
                        kw_defaults=[],
                        defaults=[]),
                    body=[
                        Return(
                            value=Constant(value='hello world'))],
                    decorator_list=[])],
            decorator_list=[])],
    type_ignores=[])

もうお気づきだと思いますが、これこそがPythonのASTオブジェクトです。このようにAST(抽象構文木)とは、文字列であるソースコードを解析し、それを木構造で表現したものです。
つまり、Pythonがプログラムを実行する際には、次のような処理が動いてます。

  1. ソースコードを解析してASTオブジェクトが生成される
  2. ASTオブジェクトからコードオブジェクトが生成される
  3. コードオブジェクトから実行可能なバイトコードが生成され、実行される

PythonのASTの見方

ここまででASTとはソースコードと実行可能なバイトコードの中間表現であることは何となく理解できたと思います。
それではもう少しASTオブジェクトの中を見ていきましょう。まずはそのために必要となる道具の紹介です。

標準ライブラリのastモジュール

Pythonの標準ライブラリには、ASTオブジェクトを扱うのに便利なastモジュールがあります。
先ほど実行したコマンドでも、次の2つのヘルパー関数を利用していました。
ここではどちらも一言で説明していますので、詳細は公式ドキュメントのリンクを見てください。

$ python3 -c 'import ast; print(ast.dump(ast.parse(open("schema.py").read()), indent=4))'
  • ast.parse(): 渡されたソースを解析してASTオブジェクトを返します
  • ast.dump(): 渡されたASTオブジェクトの木構造を見やすくダンプします

また、先ほどのコマンド出力結果にあったClassDefExprAssignといったキーワードはすべてast.ASTクラスのサブクラスです。定義されているサブクラスの一覧は公式ドキュメントの抽象文法を見るとわかります。抽象文法の左辺のシンボルひとつずつにクラスがあり、右辺にあるコンストラクタはそれぞれ左辺のシンボルのサブクラスです。

ASTオブジェクトを読み解く

これで必要なものが揃ったので実際にASTを見ていきましょう。
ただし、先ほどの出力結果だと大きすぎるので、ここではx=1というとてもシンプルなPythonのソースコードのASTオブジェクトを見ていきます。

$ python3 -c 'import ast; print(ast.dump(ast.parse("x=1"), indent=4))'
Module(
    body=[
        Assign(
            targets=[
                Name(id='x', ctx=Store())],
            value=Constant(value=1))],
    type_ignores=[])

Moduleは先ほどもあったのでここでは無視すると、x=1を表現しているのはAssignのところです。

Assign(
    targets=[
        Name(id="x", ctx=Store())
    ],
    value=Constant(value=1)
)

Assignは名前からわかる通り代入(assignment)を表現するノードです。
代入の左辺にあたるものがtargetsに、右辺にあたるものがvalueにそれぞれ格納されています。1

したがって、代入の左辺xを表現しているノードはName(id="x", ctx=Store())だとわかります。
Nameの引数ctxは、変数の格納、読み込み、削除と対応していて、それぞれStore()Load()Del()となっています。右辺1は定数なのでそのままConstant(value=1)ですね。

これでこのASTオブジェクトがx=1という式を表していることが理解できたと思います。この記事の最初のコマンド結果のASTオブジェクトも、同じようにastモジュールのドキュメントを片手にひとつずつ見ていくと読み解けるでしょう。

ASTオブジェクトの活用

ASTオブジェクトは先ほども述べたようにソースコードと実行可能なバイトコードの中間表現です。
それでいてPythonオブジェクトでもあるため、ソースコードやコードオブジェクトよりもPythonのプログラムから処理しやすいです。そのため、ASTオブジェクトは様々な活用方法があります。

例をあげるとmypyflake8といった静的解析ツールなどで利用されていたり、pytestではassert文のASTオブジェクトを変更しassert文をより便利なものにしています。そのほかにも、通常のPythonのソースコードではないファイルからASTオブジェクトを生成してPythonのオブジェクトとして動かすこともできます。2

また、Python3.9で追加されたast.unparse()を使うと、ASTオブジェクトからソースコードを生成できます。これを利用してJSONファイルからASTオブジェクトを構築し、pydanticのモデルクラスを生成するライブラリpydantic-generatorを作成しました。もしよかったら触ってみてください。

最後に注意

ASTオブジェクトの変更はユーザーや他の開発者の思いもしない挙動となり、混乱を生じさせる可能性が高いです。
それ以外の方法がないというとき以外は使わないようにしましょう。3

おわりに

わたし自身もそうでしたが、ASTは難しいという印象を持っている方も多いのではないでしょうか。
しかし、蓋を開けてみればドキュメントも1ページだけですし、ソースコードと1対1で対応しているためとてもシンプルなものです。
便利なこの1行でいろんなモジュールのASTオブジェクトを眺めてみてください。

$ python3 -c 'import ast; print(ast.dump(ast.parse(open("YourFile.py").read()), indent=4))'

明日はリサーチチームの@k_ishiさんです。
2020年のRevComm Advent Calendarは一日も途切れることなく続いてますので、明日もお楽しみに!


  1. 左辺がリストになっているのは、a, b = (1, 2)のようなアンパックのためです。 

  2. https://ja.wikipedia.org/wiki/Hy 

  3. 本当に必要な人は、それが必要だと理解している人です。必要か迷った時はおそらく必要ありません。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Raspberrry Pi4 でやってみた機械学習

Raspberrry Pi4 でやってみた機械学習

はじめに

今回、会社で学習用に存在した、Raspberrry Pi4を使ってインストールから解析するまでの説明をさせていただこうかなと思います。
やってみた流れは以下のようになります。

  • インストールから起動まで
  • SSHとRemote Desktop接続
  • Coral AcceleratorとRaspberry Piカメラを使って画像解析とリアルタイム映像解析

インストールから起動まで

準備したもの

  • Raspberry Pi 4 Model B(8GB基盤)
  • SDカード(Sandisk Ultra 256GB)
  • SD カードリーダー
  • Type-C電源アダプターとスイッチ
  • ケース
  • MicroHDMI-to-HDMIケーブル
  • Coral USB Accelerator
  • Raspberry Piカメラ
  • モニター、USBキーボード、USBマウス
  • Macパソコン
  • Wi-Fi(無線LAN)

ダウンロードとインストール

Mac(Pro)を使用してRaspberry Pi環境をmicroSDカードにインストールします。
OS は従来 Raspbian と呼ばれていた Raspberry Pi OS を利用します。
Raspberry Pi公式サイトに提供してるRaspberry Pi Imager を利用するため、イメージファイルを事前にダウンロードしておく必要はありません。

以下の手順の通りに実行します。

Raspberry Pi Imager のインストール

MacOSだと brew cask install raspberry-pi-imager でインストール可能ですが、今回は公式サイトから「Raspberry Pi Imager for macOS」をダウンロードしてインストールします。

Raspberry Pi OS をSDカードにインストール

SD カードリーダーに microSD カードをセットし、スタートメニューから Raspberry Pi Imager を起動します。

CHOOSE SD CARD で書き込む microSD カードを選択し、 CHOOSE OS をクリックします。
OS を選択すると、自動でイメージをダウンロードできます。

WRITE ボタンを押すと、書き込みが開始されます。
書き込みが完了すると、自動的にベリファイが走ります。

しばらく待って、ベリファイが完了するとSDカードにRaspbianのインストールが完了です。
※Raspberry Pi Imagerを使うと、他のツールを利用する必要が無いため、非常に便利です。

SSHとRemote Desktop接続

初期設定として、自分のMacからSSHとRemote Desktopが実行できるように準備します。
理由はモニター、USBキーボード、USBマウスの切替が面倒なので、モニターに繋がず、自分のMac上でRaspberry Piを利用するためです。

起動と接続

次に、以下の手順を実行していきます。
Raspbianを書き込んだmicroSDカードをRaspberry Pi4に挿入します。
初回のみ、HDMIケーブルをRaspberry Piに接続し、電源を入れて起動します。
起動シーケンスの後でログイン出来たら、以下の初期インストールを実行します。

「Welcome to the Raspberry Pi Deskop!」が表示したら「Next」を押します。

「国、言語、キーボードタイプ、タイムゾーンなど」をセットし、「Next」を押します。

初期ユーザーとパスワードは pi と raspberry になっています、ここでは、新しいパスワードを設定し、「Next」を押します。

スクリーン設定画面で「This screen shows a black border around the desktop」をオンにして「Next」を押します(再起動してから反映されます)。

Wi-Fiネットワークを設定し、「Next」を押します。

ソフトウェアの更新画面でスキップしてもいいですが、アップデートした良いので、「Next」を押します。

アップデート中にタスクマネージャーを開くと、プロセスを確認出来ます。

アップデートが完了したら、「Restart」を押して再起動を行います。

初期設定と接続設定

OS バージョン確認

pi@raspberrypi:~ $ uname -a
Linux raspberrypi 5.4.79-v7l+ #1373 SMP Mon Nov 23 13:27:40 GMT 2020 armv7l GNU/Linux

パッケージ更新

pi@raspberrypi:~ $ sudo apt update
pi@raspberrypi:~ $ sudo apt upgrade -y

vim インストール

pi@raspberrypi:~ $ sudo apt install vim -y

raspi-config による設定

pi@raspberrypi:~ $ sudo raspi-config

次のようなキャラクタベースのUIが起動します。

「5 Interfacing Options」を選択して、「P2 SSH」を選択しSSHを有効化と、「P3 VNC」を選択しVNCを有効化します。

※VNCが表示されない場合、以下のように手動でインストールしてください。

pi@raspberrypi:~ $ sudo apt update
pi@raspberrypi:~ $ sudo apt install realvnc-vnc-server realvnc-vnc-viewer

その後、以下のコマンドで再起動させます。

pi@raspberrypi:~ $ sudo reboot

VNC Viewerの設定
Raspberry Pi に割り振られた、private IPアドレスを調べます。

pi@raspberrypi:~ $ ifconfig
もしくは
pi@raspberrypi:~ $ ping raspberrypi.local

VNC Viewerを起動します。

VNC Viewerの検索ボックスに、Raspberry Piに割り振られたprivate IPアドレスを入力します。

「Connect to address」を押してRaspberry Piのユーザーとパスワードを入れます。
問題なく設定出来たらトップバーにアイコンが表示されます。

ついでに、画面解像度が良くなるように設定します。

pi@raspberrypi:~ $ sudo vim /boot/config.txt
=====
これを追加します。
hdmi_ignore_edid=0xa5000080
hdmi_group=2
hdmi_mode=85
=====

次はRaspberry Piをsleepしないように設定します。
方法は2つあります。
方法1

pi@raspberrypi:~ $ sudo vim /etc/lightdm/lightdm.conf
=====
これを追加します。
xserver-command=X -s 0 -p 0 -dpms
=====

方法2

pi@raspberrypi:~ $ sudo apt-get install xscreensaver

preferenceからscreensaverに行って、Display ModesタグのModeをDisable Screen Saverを選択します。その後、再起動させます。

自分のMacからSSHで接続する

自分のMacの「ターミナル」に以下のコマンドを入力します。
IPのところにRaspberry Piに割り振られたprivate IPアドレスを入力します。

[~]$  ssh pi@192.168.0.107

この後、ログインパスワードを入力します。

自分のMacから画面共有アプリケーションで接続する

自分のMacの「Screen Sharing」画面共有アプリを開きます。
接続先の入力部に、Raspberry Piに割り振られたprivate IPアドレスを入力し、接続を押します。

パスワードの入力部に、ログインパスワードを入力しサインインを押します。(パスワードを保存するチェックボックスをオンにすると次回入力が楽になります。)

以下のように、raspberrypiの画面が表示されます。

CoralAcceleratorとRaspberry Piカメラを使って画像解析とリアルタイム映像解析

Raspberry Piにセットアップする

初めに、CoralAcceleratorの「Get started」を行います。
Coral USB AcceleratorをRaspberry Piに繋げます。
その後、以下の手順を実行します。

pi@raspberrypi:~ $ echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list
pi@raspberrypi:~ $ curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
pi@raspberrypi:~ $ sudo apt-get update

次に、Edge TPUランタイムをインストールします。

pi@raspberrypi:~ $ sudo apt-get install libedgetpu1-std

次に、Raspberry PiカメラをRaspberry Piに繋げます。
その後、以下の手順を実行して、サンプル用に写真を撮ります。
Raspberry Pi Configurationを開きます。

Interfacesタブからcameraをenabledにします。

次のコマンドで写真を撮ります。

pi@raspberrypi:~ $ raspistill -o Desktop/image.jpg

画像解析しましょう

これらの手順に従って、Coralが提供しているコンパイル済みのモデルとサンプルスクリプトを使用して、画像分類を実行します。
まずは、GitHubからダウンロードを行います。

pi@raspberrypi:~ $ mkdir coral && cd coral
pi@raspberrypi:~ $ git clone https://github.com/google-coral/tflite.git

鳥の分類モデル、ラベルファイル、と鳥の写真をダウンロードします。

pi@raspberrypi:~ $ cd tflite/python/examples/classification
pi@raspberrypi:~ $ bash install_requirements.sh

以下の鳥の写真を使用して画像分類子を実行します。

pi@raspberrypi:~ $ python3 classify_image.py \
--model models/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite \
--labels models/inat_bird_labels.txt \
--input images/parrot.jpg


以下のような結果になります。

リアルタイム映像解析

今回は、Coralが提供しているサンプルの中から「Image recognition with video」をRaspberry Piカメラを利用して、リアルタイム映像解析を行います。
まずは、GitHubからダウンロードします。

pi@raspberrypi:~ $ mkdir google-coral && cd google-coral
pi@raspberrypi:~ $ git clone https://github.com/google-coral/examples-camera --depth 1

モデルをダウンロードします。

pi@raspberrypi:~ $ cd examples-camera
pi@raspberrypi:~ $ sh download_models.sh

gstreamerライブラリをインストールします。

pi@raspberrypi:~ $ cd gstreamer
pi@raspberrypi:~ $ bash install_requirements.sh

そして、デモを実行します。

pi@raspberrypi:~ $ python3 classify_capture.py

iPadで画像を表示して認識します。
結果として、以下の画像が得られました。

得られた名前で検索を行い、結果が正しいことが確認できました。

以上で、学習結果の説明は終了となります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

YouTubeのPython解説を覚え書き。

Learn Python - Full Course for Beginners [Tutorial]
https://www.youtube.com/watch?v=rfscVS0vtbw
(英語字幕は ところどころ間違っているので 注意が必要。)


2:14 -
python download
https://www.python.org/downloads/
Python 3 or Python 2?
Python 3 は 今後も利用されていくバージョンで、サポートもされているため、このコースではこちらを利用します。
Python 3 は 初心者が学ぶのに適しています。
(2020-12-18 現在、Python 3.9.1 のみ ダウンロードリンクがあった。)

次のインストール中のオプション 2つ:
Add Python 3.9 to PATH
Disable path length limit
これらは どちらも設定せずに インストールを完了するといいらしいです。(参考: https://gammasoft.jp/python/python-install-on-windows/)


4:53 -
Choose text editor
IDE - integrated development environment
PyCharm Community Edition を ダウンロードする。(現在 最新バージョンは 2020.3)
https://www.jetbrains.com/ja-jp/pycharm/download/#section=windows

installation Options
通常は何もチェックしなくて構いませんが、必要な場合は適宜チェックしてください。(参考: https://gammasoft.jp/python/pycharm-install-on-windows/)


6:42 -
Setup & Hello World

7:07 -
動画内の旧バージョンのPyCharmでは 右下にConfigureのメニューがあるが、最新版の2020.3では 画面左 Customizeタブの "All settings..." から 設定ができるみたい。
また、動画ではAppearanceのThemeをDarculaに変更していたが、最新版ではデフォルトからDarculaだった。

7:49 -
動画内では"Interpreter: "というオプションだけだが、最新版の初回起動時では New enviroment using: Virtualev と なっており、Base interpreterが"...Python39\python.exe"になっていれば良いようだ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Xonsh】Python製シェルが尖りまくってて神

Macのターミナル環境を色々改造しつつその一環としてshellも何個か見てきました。
bash, zsh, fish ...

中でもここ半年くらいはPython製のXonshが超絶お気に入りなので自分なりに紹介します。

ちなみに元ネタと言うか見つけたきっかけは、機械学習界隈で有名なばんくしさんのブログです。

Python製シェルxonshを半年使った所感や環境設定のまとめ

この人すごすぎ

What's Xonsh

タイトルのまんまですが、Pythonで作られたShellです。
Pythonで作られ、Pythonで動き、Pythonが実行できます。

他のシェルが何で動いてるかとかはわかりませんが、とにかくこいつが超尖っててイケてます。

Commnd and Python

Pythonで作られている事のメリットその1として、コマンドとPythonスクリプトの同居ができます。

 [ ~/Desktop/tmp ]                                                                                                                                                                
$ ls -al                                                                                                                                                                          
Permissions Size User Date Modified Name
drwxr-xr-x     - *** 18 12 19:20   .
drwx------     - *** 18 12 19:18   ..
 [ ~/Desktop/tmp ]                                                                                                                                                                
$ dirs = ['a', 'b', 'c']                                                                                                                                                          
 [ ~/Desktop/tmp ]                                                                                                                                                                
$ for dir in dirs: 
.     mkdir @(dir) 
.                                                                                                                                                                                 
 [ ~/Desktop/tmp ]                                                                                                                                                                
$ ls -al                                                                                                                                                                          
Permissions Size User Date Modified Name
drwxr-xr-x     - *** 18 12 19:21   .
drwx------     - *** 18 12 19:18   ..
drwxr-xr-x     - *** 18 12 19:21   a
drwxr-xr-x     - *** 18 12 19:21   b
drwxr-xr-x     - *** 18 12 19:21   c

こんな感じ。
わざわざ変数に入れてfor文回すよりmkdir × 3の方がはえーよ!!と思うかもしれませんが、
処理がえぐいくらい増えた時とかこれ使ったらめちゃくちゃイケてると思いませんか?

当然のごとく関数だって定義できちゃいます。

 [ ~/Desktop/tmp ]                                                                                                                                                                
$ def hello_xonsh(): 
.     print('hello xonsh !!!') 
.                                                                                                                                                                                 
 [ ~/Desktop/tmp ]                                                                                                                                                                
$ hello_xonsh()                                                                                                                                                                   
hello xonsh !!!

.xonshrc

他のシェル同様、xonshにもrcファイルがあります。(.xonshrc)
もう想像ついてるかもしれませんが、このrcファイルが全てPythonで記述できます。

個人的にはこれが神すぎて使ってる

  • Aliases
aliases['ls'] = 'exa -ahl --git'
aliases['la'] = 'exa -ahl --git'
aliases['ll'] = 'exa -ahl --git'
aliases['l'] = 'exa -ahl --git'
aliases['cat'] = 'bat'
aliases['tf'] = 'terraform'
aliases['do'] = 'docker'
aliases['dc'] = 'docker-compose'

aliasesという辞書型変数が内部で定義されていてそこに追加する。
かっこよすぎる。

  • env
import os
$DEV_ROOT = os.environ.get('HOME') + '/dev'

環境変数は$ + 変数名で定義する。
importすればPythonの標準ライブラリも使い放題。

  • Command
def _set_aws_profile(args):
    $AWS_PROFILE = args[0]
    $AWS_DEFAULT_PROFILE = args[0]
    print('aws profile is : ', p)
aliases['awsp'] = _set_aws_profile

複雑なコマンドは関数で定義してそのままaliasesに突っ込める。
args引数でコマンド引数を受け取れる。

ちなみに、ここでは引数をargsしか定義してないけど、他にもstdinとかstdoutとかも取れるみたい。
(ここはうまく使いこなせてない)

Install

ここまで読んでもし使いたくなってくれたらinstallは一瞬です。

$ brew install xonsh

もしくは

$ pip install xonsh

などなど、他にも大体の環境でインストールできる。

起動はそのまんま

$ xonsh

Use Xonsh!

Xonshは尖りまくってるゆえにデメリットもあります。
例えばコマンドによっては引数をシングルクオートでうまく括らないと動かない時とかありました。

でもそんなちっぽけなデメリットを遥かに上回るイケイケ具合なので使ってます。

Python好きはお試しを!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】YOLOv3を使ってみた

はじめに

前回記事投稿した『OpenCVを用いてネコ検出器を作る』においてうまくいった例とうまくいかなかった例がいくつか出てきました。どうにかもっと精度あげられないかと調べているときにYOLOv3という手法を見つけて試しに行なってみました(要するに思っていた以上に精度が悪く悔しかったので...)。前回の記事でも記載した私が経験したハッカソンでもYOLOという単語は出ていましたが別タスクを私は任されたためYOLOには触れずにハッカソンが終わってしまったのでいい勉強になったので記事に書かせていただきます。

YOLOv3とは

YOLOとはYou Only Look Once(一度見るだけで)の略らしいです。面白いですね!NN(ニューラルネット)を一からモデルを構築しなくても、YOLOなら大丈夫でYOLOをダウンロードすれば構築ができているので通すだけでできました。また画像だけでなく、Webカメラなどとも連動できるので、リアルタイムの検出も可能ですので画像において色々な場面で活躍ができそうだと感じました。YOLOの公式サイト

実際にYOLOを使うとこのようなことができます。
Screen_Shot_2018-03-24_at_10.48.42_PM.png
このように物体を検知して何かをラベリングすることが可能になっています。実際にネコ検出器でできなかった画像たちに試してみましょう!!

YOLOv3を使う

YOLOの導入にはこちらの記事を参考に行いました。(Macで物体検知アルゴリズムYOLO V3を動かす)

ターミナルを使います。
前提としanacondaを導入されているという状態で説明します。anacondaが導入されていないのであればまずは先に導入してください。

$ conda create -n yolo_v3 python=3.6 pip
$ source activate yolo_v3

これでPython仮想環境を作成できました。ここまで行うと

(yolo_v3) $

となると思います。
次に必要なパッケージをそのままインストールしていきます。

(yolo_v3) $ conda install pandas opencv
(yolo_v3) $ conda install pytorch torchvision -c pytorch
(yolo_v3) $ pip install matplotlib cython

上記の参考記事ではエラーがmatplotlibをインストールするときにエラーが出ると書かれていましたがエラーは私の場合はmacですが特にエラーは出なかったと思います。

ここまで終了したら'cd'で今回作業したいフォルダまで下がりgit cloneで次のファイルをインポートします。
新しいコマンドを立ち上げて下をコピペしてください。

$ git clone https://github.com/ayooshkathuria/pytorch-yolo-v3.git

また先ほどの(yolo_v3)となっている端末に戻り

(yolo_v3) $ cd ~~/pytorch-yolo-v3
(yolo_v3) $ wget https://pjreddie.com/media/files/yolov3.weights

~~/pytorch-yolo-v3は個々人該当のディレクトリで行なってください。
wgetを行うとモデルの構築が自動で行われます。少し待つとYOLOv3が使える状態になりました。

実際に試してみた

試す時も先ほどのコマンドプロンプトを用います。実行自体は下で示したコマンドでできますが、試したい画像をimagesというファイルに入れる必要があります。忘れずに入れてから実行してください。あたgithubをcloneしたときに何枚かサンプル画像はありましたのでそちらを試すでも良いと思います。

(yolo_v3) $ python detect.py --images imgs --det det

エラーが出た・・

私の場合は下のようなエラーが出てしまったのでまたまた調べて対処を見つけました。pytorch-yolo-v3のRuntimeErrorを解消できたよ

Traceback (most recent call last):
File "detect.py", line 234, in
output = torch.cat((output,prediction))
RuntimeError: invalid argument 0: Sizes of tensors must match except in dimension 0. Got 8 and 86 in dimension 1 at d:\build\pytorch\pytorch-1.0.1\aten\src\th\generic\thtensormoremath.cpp:1307

どうも"pytorch-yolo-v3/util.py"というファイルを消去し新たに下のリンクのutil.pyに差し替えることで動きました。
こちらのリンク

このutil.pyをPCに保存する方法は

1. リンクを開く
2. Rawを右クリック
3. リンク先を別名を保存をクリック(名前は変えずにutil.pyにしました)
4. "pytorch-yolo-v3/util.py"となるようにペーストする

結果

デフォルトであった画像で結果をまずは試してみました。

画像に写っているhorseをラベル化できています!スゴイですね笑

続いて

信号機(traffic light)だったりcar,truckなど識別ができています。

実際にうまくいかなかった画像で試してみた

前回は斜めを向いていて反応してくれなかったのですが今回はcatとラベリングしてくれました!!

前回はネコ3匹のうち1匹のみ検出できていましたがネコ3匹全て検出ができしかもcarまで検出ができていてとても良い精度だなと感じました。

それでもうまくいかなかった例

※検出した最終の結果のみ載せます。
det_cat5.jpg
"dog"と誤認識していました。。まぁ見えるけど。。。という感じですね。(写真が悪いかもですが・・・)
det_cat7.jpg
"bird"と誤認識していました。。遠いからですね、なるほど。

考察

写真が悪いものは誤認識されていますが、認識はとても精度がいいなと思いました。またYOLOはよく話で聞いていたのでやってみて精度がいいなと思いました。ネコ以外に渋谷ハロウィンのとても混み合っているニュースの写真で試してみてどれくらい遠い距離にいたり一部分のみしか写っていない写真でも精度は意外と良い印象を受けました。
考察ではないが、またハッカソンで吸収できなかったYOLOに関して結構調べて自分自身で結構吸収できたことがとてもよかったなぁと思った。
ハッカソンで扱った3つのモデルである顔認証・全身認証・歩容認証(骨格認証)のうち前回の記事で扱ったOpenCVでの顔認証・そして今回扱ったYOLOv3での全身認証がクリアしました。

次は骨格認証で使ったOpenPose・HumanPoseなどについて調べて実装してみて記事を書きたいなと思います!また研究室の人だったりフォローしていただいている方から機械学習で使う基本的な数学について記事欲しいと要望があったのでいつか(多分年末までに?)数学について書こうと思います!何について書こうかな・・・?まだ決めてませんがw

ここまで読んでいただきありがとうございました。
もしよければフォロー・LGTMよろしくお願いいたします。

参考

YOLOとかOpenCVとかで物体検知
Macで物体検知アルゴリズムYOLO V3を動かす
pytorch-yolo-v3のRuntimeErrorを解消できたよ
pytorch-yolo-v3

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AssumeRoleをECS&Digdag環境で利用する

やりたいこと

  • AWS アカウント A

    • S3 に日次でファイルが置かれる
  • AWS アカウント B

    • アカウント A の S3 のファイルを取得したい
    • 実行環境は ECS 上で動いている Digdag サーバー

AssumeRole は一時的なクレデンシャルを生成して、他の AWS アカウントからのアクセスを許可できるので、以下のようなセキュリティ的に避けたい設定を行わなくて済みます

  • IAM ユーザーを作成し、AWS アクセスキー発行
  • S3 の公開設定

AWS アカウント A - 参照先

ロールを作成

  1. AWS コンソールからロールを作成
  2. 信頼されたエンティティの種類を「別の AWS アカウント」にして、アカウント B のアカウント ID を設定
    • この段階では、アカウント B の root に権限が付与される(ここは後で変えます)
  3. アカウント B 用のポリシーを作成して、ロールにアタッチ
    以下は今回のやりたいことを実現する最低限のポリシーです
   {
     "Version": "2012-10-17",
     "Statement": [
       {
         "Sid": "VisualEditor0",
         "Effect": "Allow",
         "Action": "s3:GetObject",
         "Resource": ["arn:aws:s3:::[バケット名]/*"]
       }
     ]
   }

AWS アカウント B - 参照元

AssumeRole のポリシーを作成

  1. AWS コンソールからポリシーを作成
   {
     "Version": "2012-10-17",
     "Statement": [
       {
         "Sid": "VisualEditor0",
         "Effect": "Allow",
         "Action": "sts:AssumeRole",
         "Resource": "arn:aws:iam::[アカウントAのID]:role/[さっき作ったロール名]"
       }
     ]
   }
  1. ECS タスクのロールにこのポリシーをアタッチ
    ECS タスクで使われているロールは、コンソールの ECS タスク定義から、「タスク実行ロール」で確認できます

AWS アカウント A - 参照先

作成したロールの信頼関係タブから、以下のように編集

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::[アカウントBのID]:role/ECSタスクロール名"
      },
      "Action": "sts:AssumeRole",
      "Condition": {}
    }
  ]
}

さきほどは、アカウント B の root に権限を与えていましたが、ECS タスクロールに権限を与えるように変更しました

動作確認

AWS CLI

Digdag のタスクが sh で、AWS CLI を使う場合

ECS タスクで使う Docker などで以下を用意する

~/.aws/configを作成

[profile assume]
role_arn = arn:aws:iam::[アカウントAのID]:role/[ロール名]
credential_source = EcsContainer

または

echo "[profile assume]" > ~/.aws/config
echo "role_arn = arn:aws:iam::[アカウントAのID]:role/[ロール名]" >> ~/.aws/config
echo "credential_source = EcsContainer" >> ~/.aws/config
aws s3 cp [コピー元] [コピー先] --recursive --profile assume

Python

Digdag のタスクが Python の場合

from sts import STS

# role_arn: arn:aws:iam::[アカウントAのID]:role/[ロール名]
# bucket: 参照先のバケット名
# key: 参照先のオブジェクトキー
# save_file_path: 保存先(Digdagコンテナー内)
class S3(object):
    def get_by_sts(self, role_arn, bucket, key, save_file_path):
        resource = STS().get_resource(role_arn, "s3")

        resource.Bucket(bucket).download_file(
            key,
            save_file_path,
        )
import boto3


class STS(object):
    sts_client = boto3.client("sts")

    def get_resource(self, role_arn, resource_name):
        role_obj = self.sts_client.assume_role(
            RoleArn=role_arn,
            RoleSessionName="AssumeRoleSession",
        )

        resource = boto3.resource(
            resource_name,
            aws_access_key_id=role_obj["Credentials"]["AccessKeyId"],
            aws_secret_access_key=role_obj["Credentials"]["SecretAccessKey"],
            aws_session_token=role_obj["Credentials"]["SessionToken"],
        )

        return resource

boto3.client("sts").assume_roleに RoleArn と任意のセッション名を渡して、一時的なクレデンシャルを発行しています

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リスト・関数・for・while・with(open)・classと前回までの学習補足(Ruby学習後のPython初学)

リスト

test.py
list = ["0", "1", "2"]
# Rubyの配列の扱いに近い

list[0]
# 0番目の"0"が取り出される。

関数

test.py
def test(arg):
    return arg + 1

print(test(0))

#関数となるのがtest、引数となるのがargになる。
#関数の場合はreturnによる戻り値が必要である。

上記の場合、test(0)0を引数として関数testargに入って、
戻り値として+1されてreturnされて出力されている

for文

test.py
for index in range(100)
    print index

#0回から99回まで数字を出力する

for i in range(1,101)
    print index

#1回から100回の範囲指定
#forとinの間はindexやiとされるのが通例。

#リストによるfor文
num = 0
list = ["0", "1", "2"]
for item in list:
    print list[num]
    num += 1

#リストの中身に対して全て実行される。リストの場合はforとinの間はitemが通例。

while文

test.py
num = 1 #カウンタ変数を初期化
while num <= 10:
    print(num)
    num += 1 #カウンタ変数を更新

while前のnumの部分がカウンタ変数といい、while後の条件式で扱う。
なお、while文内でnumを更新し条件式に当てはまらないものを定めないとループするようになる。

with(open)

test.py
# with open('参照するfile', 'モードと呼ばれる実行処理') as file:
#     実行する処理の記述

例えばtest.pyの直上のディレクトリの中にtest.txtのfileがあった場合 ./test.txt
書くことで参照先をパス指定できる。
なお、モードと呼ばれる実行処理の中にはread(読み取り)の頭文字に当たるr
write(書き込み)の頭文字に当たるwなどがモードと飛ばれる実行処理になる。
(詳しい内容は次回以降の学習)

class

test.py
# class クラス名(頭文字は大文字):
#    def __init__(self, プロパティ1, プロパティ2, ...,):
#        self.プロパティ1 = プロパティ1
#    def メソッド名(self):
#        return 実行する処理 

#例 
class Card: #Cardというクラス
    def __init__(self, date, user_name): 
        self.date = date
        self.user_name = user_name
    def message(self): #クラス内の関数をメソッドという、今回はmessageというメソッド
        return self.user_name + self.date #メソッドの戻り値

date_a = '2020-01-01' 
user_name_a = 'Test'

card_a = Card(date_a, user_name_a) #Cardクラスにcard_aというインスタンスを生成

print(card_a.message()) #card_aに対してmessageメソッドを実行してprint出力している

前回までの補足

①VScodeでコードを書いてターミナルで実行できる環境を整えた所、実行するとエラーが出たので備忘録。#coding:utf-8の記述をしておかないと日本語を使用していることによるエラーが出る。
②予約語がPythonにもある
③print(type(調べたいもの))でstr型かint型かわかる。
④strでもintでもないブーリアン型がある。 例:T = True , N = False
⑤importできるモジュールに関してpypiで検索すると外部からのモジュールを探すことができる
有名なところでいうとNumPy、Pandas、Flask、Django等

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

__init__やselfに関する備忘録

今回は、プログラミングにおいて、__init__selfのことを深く理解してない状態だったので、備忘録として残しておきたいと思います。

開発環境

MacOS
Python3.7(anaconda)
VSCode

クラスと self と init
【Python入門】クラスの基本を1から解説する―完全版
Python の super() 関数の使い方

initとは

__init__とは、インスタンスを生成した際に、1番最初に呼び出される関数になります。

class Human:
    name = 'Jack'
    def __init__(self):
         self.name = 'Bob'

boku = Human()
print(boku.name)  ->  Bob

といった具合に、インスタンス化が行われた後に、__init__が呼び出されるため、元々はnameにはJackが入っていましたが、実行した際はBobが出力されることになります。

インスタンス化とは何か

インスタンス化とは、簡単に言えば、型作りのようなものですね。
人間という大雑把な形を作って、JackBobという個人を作る部分に分かれてくるという具合です。

class Human:
    def __init__(self)
        self.name1 = 'Jack'
        self.name2 = 'Bob'

boku = Human()  <-  ここがインスタンス化
print(boku.name1)  ->  Jack
print(boku.name2)  ->  Bob

といった具合に、boku = Human()によってインスタンス化が行われます。
インスタンス名 = クラス名()といった書き方になります。

self.変数とは何か

まずは、self.nameに実名を入れていくだけの処理を見ていきます。

class Human:
    def __init__(self):
      self.name1 = 'Jack'
      self.name2 = 'Bob'

boku = Human()
print(boku.name1)
print(boku.name2)

これだと、nameを100人分作る場合は、変数nameを100個用意しなければいけない状態になります。

次に、self.name=nameを使うバージョン

class Human:
    def __init__(self, name):
        self.name = name

boku = Human('Jack')

こちらは、Human('Steve')Human('Bob')に変更することによって、名前を変更することができます。
こちらの処理の方が、よりシンプルに書けて無駄な処理をしなくて良い状態になっております。

superとは何か

次に、__init__の後によく見かけるsuperって何ですかいっていう説明をします。

まず、複数のクラスで同じような処理をする場合は、毎度同じ処理を書くわけではありません。

1番最初に、メインのクラスを書いたら、そのクラスを継承することで、同じ処理の記述を省くことにしています。

class Human:
   def __init__(self, name, sex, weight):
       self.name = name
       self.sex = sex
       self.weight = weight

boku = Human('Jack')
print(boku.name)  ->  Jack

class Bird(Human):
   def __init__(self, name, sex, height):
   super().__init__(name, sex)
   self.height = height

このように、クラス名の後に、継承したい親クラスの名前を書きます。

そして、super().__init__(親クラスと同じ処理をする変数)という記述をすることで、self.nameself.sexを書く必要がなく、同じ処理を行うことができます。

まとめ

理解しなくても処理を行うことはできるかと思いますが、個人的にモヤモヤした状態になってしまうので、今回記事にさせてもらいました。

わかりにくい部分が多いと思いますが、皆さんのお助けになればと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python で画像フィルタパラメータを blackbox 最適化で探索してみるメモ

背景

対象の関数(問題)がブラックボックス的なものですと, そのパラメータを求めるのは

blackbox 最適化や, 機械学習の分野では hyperparameter 最適化(探索)と呼ばれているようですね.

画像処理でも blackbox 最適化をしたい要求がよくあります.

たとえば, ターゲットとなるオシャンティな画像(インスタ映え画像)に見た目を合わせて, 自分のとったちょっとイマイチな写真でもオシャンティな画像にしたいとか. この場合, 明るさとか, セピア調/フィルム調フィルタとかのフィルタパラメータを探索します.
(手動で見つけるだと無限に時間が溶けてしまいつらい)

今回はもう少し問題を単純化して, ImageMagick でブラーをかけた画像で, そのブラーのパラメータを推定してみます.

画像の誤差にはとりあえず RMSE を使ってみます
(一致するほど値が低くなる)

Python で blackbox 関数の最適化をするメモ
https://qiita.com/syoyo/items/6c33acb0fd475e651f2f

Python で外部プログラムを実行して結果の行をパースするメモ
https://qiita.com/syoyo/items/d13af423604192cee41c

を参考にして, 外部コマンドで ImageMagick を動かし, benderopt で最適化してみます.

データとコード

ぼかし前

shimokita.jpeg

ぼかし後
(-blur 27x20)

shimokita-blur.jpg

from benderopt import minimize
import numpy as np
import logging
import subprocess
import parse

logging.basicConfig(level=logging.DEBUG) # logging.INFO will print less information

blurred_filename = "shimokita-blur.jpg" # convert -blur 27x20
ref_filename = "shimokita.jpeg"

k_num_eval = 10000

def extract_result(lines):
    for line in lines:
        print(line.decode("utf-8"))
        ret = parse.parse("{:g} ({:g})", line.decode("utf-8"))
        if ret:
            return ret[0]

    raise RuntimeError("Failed to extract value from result.")

count = 0

def f(radius, sigma):
    global count

    count += 1

    print("run {} of {} ...".format(count, k_num_eval))
    tmp_filename = "shimokita-tmp.jpg"
    cmd = "convert {} -blur {}x{} {}".format(ref_filename, radius, sigma, tmp_filename)
    ret = subprocess.run(cmd, shell=True)


    # Compare two images using RMSE
    cmp_cmd = "compare -metric rmse {} {} null:".format(tmp_filename, blurred_filename)
    ret = subprocess.run(cmp_cmd, shell=True, capture_output=True)
    # `compare`(ImageMagick) outputs result into stderr, not stdout
    lines = ret.stderr.splitlines()

    val = extract_result(lines)

    return val


# We define the parameters we want to optimize:
optimization_problem_parameters = [
    {
        "name": "radius",
        "category": "uniform",
        "search_space": {
            "low": 0,
            "high": 100,
        }
    },
    {
        "name": "sigma",
        "category": "uniform",
        "search_space": {
            "low": 0,
            "high": 100,
        }
    }
]

# We launch the optimization
best_sample = minimize(f, optimization_problem_parameters, number_of_evaluation=k_num_eval)

print("radius", best_sample["radius"])
print("sigma", best_sample["sigma"])
print("err = ", f(best_sample["radius"], best_sample["sigma"]))

結果

10,000 回まわしました.

radius 27.993241302558445
sigma 19.957452459359814

err =  49.5512

Voila!

radius は 27(真値), 28(推定値)と 1 違いますが, それなりに真値に近しい結果が得られました.

shimokita-blur-estimated.jpg

idiff で差分を取ってみます.

https://openimageio.readthedocs.io/en/release-2.2.8.0/idiff.html

diff.jpg

(差分を 20 倍. 差分等倍だと視覚的にはほぼ真っ黒(= 一致))

jpg 圧縮の影響が大きそうですね...

ちなみに 100 回では全然だめで, 1000 回でまあまあそこそこ近い, という結果でした.

TODO

  • Bayesian Optimization など試したい.
  • 画像の場合は非圧縮形式(PNG, BMP, TIFF)でやりましょう.
  • ファイルに結果を書くタイプだと, ディスク I/O 消費が気になる(SSD だと寿命が縮まりそう?)ので memdisk を使えるか考えてみる
  • benderopt は並列処理に対応していないので, 非マルチスレッドな外部プログラムを実行だと処理時間がかかってしまうので並列化できるライブラリの利用を検討します.
  • 同様のやりかたで Photoshop あたりを操作してインスタ映え画像の生成を極める(コマンドライン制御できたような)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonをはじめたばかりの人に送る過去15回分のABCのA問題分析

はじめに

この記事ではABCのA問題の分析をし、「if文と演算しか使えない」などの初心者のための記事です。複数のジャンルにまたがっている回もあります(例:AtCoder Beginner Contest 182)。「典型的な問題」と書かれている問題から解くと良いでしょう。

使う文の種類

演算(+,-,*,/,//,%)

AtCoder Beginner Contest 184 A.Determinant
AtCoder Beginner Contest 182 A.twiblr
AtCoder Beginner Contest 181 A.Heavy Rotation
AtCoder Beginner Contest 180 A.box
AtCoder Beginner Contest 178 A.Not
AtCoder Beginner Contest 177 A.Don't be late
AtCoder Beginner Contest 173 A.Payment
AtCoder Beginner Contest 172 A.Calc(典型的な問題)

min,max

AtCoder Beginner Contest 185 A.ABC Preparation

if,else文

AtCoder Beginner Contest 183 A.ReLU
AtCoder Beginner Contest 182 A.twiblr
AtCoder Beginner Contest 179 A.Plural Form
AtCoder Beginner Contest 178 A.Not
AtCoder Beginner Contest 177 A.Don't be late
AtCoder Beginner Contest 174 A.Air Conditioner(典型的な問題)
AtCoder Beginner Contest 173 A.Payment

切り上げ、切り下げ

math.ceilとint(//1でも可)を用いた切り上げ、切り下げの問題
AtCoder Beginner Contest 176 A.Takoyaki

count

countを扱った問題はtryとwhile Trueの組み合わせやifとforの組み合わせでも解ける(A問題の制約ではTLEしない)。ここでは、countを使うと簡単に解ける問題はcountのところのみに書いてある。
AtCoder Beginner Contest 175 A.Rainy Season

in

リストにある要素が入っているか確認する時などに使います。(ここではfor i in rangeのinは含めないこととする。
AtCoder Beginner Contest 171 A.αlphabet

chr

アルファベットのリストなどを作ることができる組込み関数。
[python] いろいろな文字種のリストを作成を使うことができる。
AtCoder Beginner Contest 171 A.αlphabet

感想

if,else文と演算の組み合わせが多い(例:AtCoder Beginner Contest 177 A.Don't be late)。また、多くのmin, max,countを使う問題はif文を使ってもACできる。意外とfor,whileを扱う問題が少なかった。また、言われた通りに実行すればACがもらえる問題が多い。AtCoder Beginner Contest 022 A.Best Bodyなど、昔のコンテストは難易度が300を超えているものがあるが、最近のコンテストは難易度が1桁のものが多い。(AtCoder Problemsを参考にした。)

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

時間計測を with 構文でするようにしてコードを綺麗にする

普通に計算する

Python コード内で時間を計測する場合、開始時点と終了時点で time.time() を呼び出し、その差分で秒数を計算します。

import time

start_time = time.time()

# 処理
time.sleep(5)

print("{:.2f} sec".format(time.time() - start_time))

出力

5.01 sec

課題点

上記のやり方で問題があるわけではありませんが、時間計測が沢山必要な場合、計算ロジックを描くことで行数が増えますし、一時的な変数も増えます。

with 内の時間を計算する

with の内部に入ったときに開始、出るときに終了を計測し、時間を表示するクラスです。

tm.py
import time

class Timer:
    def __init__(self):
        pass

    def __enter__(self):
        self.time = time.time()

    def __exit__(self, ex_type, ex_value, trace):
        print("{:.2f} sec".format(time.time() - self.time))

使ってみた例

import time

# 読み込み
import tm

with tm.Timer():
    # 処理
    time.sleep(3)

出力結果

3.00 sec
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はてなブログのstarを自動で押しまくる

はじめに

この記事は LOCAL students Advent Calendar 2020の19日の記事です。
よろしくおねがいします。

まえおき

どうもこんにちは、今日から冬期休暇で毎週のレポートや辛い授業から開放されて喜びの舞を舞っているげんしです。
この時期は Advent Calendarが賑わっていて普段ブログを書かない人も書いたり書かなかったりする人が多くなると思います。

この人の記事、、良い、、

先程言ったように、qiitaの記事やはてなブログの記事を見る機会がこの時期は増えると思います。そして人はブログを見ていく中で面白いブログやためになったブログにはLGTMや星をつけたりすると思います。

この星ですがなんと、はてなブログでは何度でも星をつけることができます!!!!

星を無限につけていけ

すごくいい記事に出会ったとき、人は無限に星をつけたくなります。
ですが、無限に星をつけるには無限に星をクリックしないといけません。
これは少し面倒です。

作ったもの

はてなブログで星を任意の数自動でつけるものを作りました。

以下実行環境

ソフト バージョン
python 3.9.0
pip 20.2.4
selenium 3.141.0
chromedriver-binary 87.0.4280.88.0

Seleniumのインストール

$ pip3 install selenium

Google Chromeをインストールしておいてください。

お願いします。

Chrome Driverのインストール

$ pip3 install chromedriver-binary==あなたのchromeのバージョン

chromeの右上のメニュー/設定/chromeについて
から確認することができます。
僕の場合87.0.4280.88だったので
pip3 install chromedriver-binary==87.0.4280.88
でインストールしました。

実際に無限に星をつけていく

スクレイピングする際にはてなブログにログインする必要があるので事前にユーザーネーム or メールアドレスとパスワードをkey.pyに入力しておきます。

key.py
EMAIL = "ご自身のはてなブログのメールアドレスかユーザー名を入れてください"
PASSWORD = "ご自身のはてなブログのパスワードを入れてください"
main.py
# coding:utf-8
from key import EMAIL,PASSWORD
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from time import sleep
import chromedriver_binary

url = input("starをつける記事のurlを入力 : ")
starNum = int(input("starをつける個数を入力 : "))
username = EMAIL
password = PASSWORD

driver = webdriver.Chrome()

#ログインページを開く
loginUrl= "https://www.hatena.ne.jp/login"
driver.get(loginUrl)

#ログイン
loginUserName = driver.find_element_by_xpath("//*[@id='login-name']")
loginUserName.send_keys(username)

loginPassword = driver.find_element_by_xpath("//*[@id='container']/div/form/div/div[2]/div/input")
loginPassword.send_keys(password)

submitButton = driver.find_element_by_class_name("submit-button")
submitButton.click()
sleep(5)

#入力されたはてなブログのページにとぶ
driver.get(url)
starButton = driver.find_element_by_class_name("hatena-star-add-button")

#入力された回数クリック
for i in range(starNum):
  starButton.click()

sleep(5)
driver.close()

DEMO

実際にこのLOCAL学生部のAdvent Calendarの1日目の記事の北海道の技術系学生コミュニティ LOCAL学生部のご紹介を30個starをつけたいと思います。

プログラムを実行し、記事のurlとstarの数を入力すると
スクリーンショット 2020-12-16 22.21.32.png

ログイン画面が出たあと自動で入力、ログインをし、
スクリーンショット 2020-12-16 22.22.54.png

このようにちゃんと30starをつけてくれました。
スクリーンショット 2020-12-16 22.28.51.png

まとめ

星をたくさんつけたい記事があるときにぜひ使ってください。
自動でつけたような心のこもってない星をつけるのは心が痛むという人はクリックを頑張ってください。

すごく単純で簡単なコードですが、Githubで公開しているのでurlを貼っておきます。
https://github.com/Genshi0916/auto-add-hatena-blog-star

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【MicroPython】ESP32で入力フォームを作成

はじめに

ESP32 MicroPythonでWi-FiアクセスポイントとWebサーバーを立ち上げ入力フォームページを作成してみました.
↓こんな感じ
esp32.png

前提条件

  • ESP32-WROOM-32
  • MicroPython v1.13

プログラム

main.py
import socket
import network
import ssl
import re

ESSID = "ESP32"
PASSWORD = "ESPWROOM32"
IP = "192.168.5.1"

def wifi(essid, pwd, ip, mask, gw, dns):
    ap = network.WLAN(network.AP_IF)
    ap.config(essid = essid, authmode = 3, password = pwd)
    ap.ifconfig((ip,mask,gw,dns))
    # print("(ip,netmask,gw,dns)=" + str(ap.ifconfig()))
    ap.active(True)
    return ap

wifi(ESSID, PASSWORD, IP, '255.255.255.0', IP, '8.8.8.8')

def web_page():
    html = """
    <html lang = "ja">
    <head>
    <title>test</title>
    <meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="data:,">
    <style>
    </style>
    </head>
    <body>
    <h1>test page</h1>
    <h2>入力フォーム</h2>
    <form action = "" method = "GET">
    <p><input type="text" name="test" size="40"></p>
    <p><input class="button" type="submit" value="送信"></p> 
    </form>
    </body>
    </html>
   """
    return html

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)

while True:
    conn, addr = s.accept()
    response = web_page()
    conn.send('HTTP/1.1 200 OK\n')
    conn.send('Content-Type: text/html\n')
    conn.send('Connection: close\n\n')
    conn.sendall(response)
    request = str(conn.recv(1024)).lower()
    # print("request : "+  request)
    m = re.search(r'test=(\w+)', request)
    if m != None :
        word = m.group(0)
        print(word[5:])
    conn.close()

コード解説

  • def wifi(essid, pwd, ip, mask, gw, dns)
    Wi-Fiアクセスポイントを立ち上げ
  • s.bind(('', 80))
    HTTPポート80番で接続を待機
  • m = re.search(r'test=(\w+)', request)
    クライアントの入力データを取得
  • word = m.group(0)
    入力データを変数に入れる

実行

スマホなどからESP32のアクセスポイントに接続しブラウザで192.168.5.1にアクセスしてください.

おわりに

おわりです.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで学ぶアルゴリズム 第7弾:年号変換

#Pythonで学ぶアルゴリズム< 年号変換 >

はじめに

基本的なアルゴリズムをPythonで実装し,アルゴリズムの理解を深める.
その第7弾として年号を扱う.

西暦を年号へ

今回は明治~令和まで変換できるようにした.ただし,後でも記述するが,今回は汎用性のあるプログラムとしたため,年号の基準と年号名だけ追加することでいくらでも拡張することができる.ただし,同じ年の年号(例: 昭和64年と平成元年)は新たな元号とする.

明治~令和までの元年に対する西暦,つまり年号の境界を次に示す.
  年号     西暦
 明治元年    1868年
 大正元年    1912年
 昭和元年    1926年
 平成元年    1989年
 令和元年    2019年

以上を踏まえて,pythonに実装する.
以下にコードと出力を示す.

コード
era_name.py
"""
2020/12/18
@Yuya Shimizu

年号変換
"""

def trans2era_name(year):
    year_dict = {2019:"令和", 1989:"平成", 1926:"昭和", 1912:"大正", 1868:"明治"} #年号の境界
    change = True #変換できたかどうかの判定用変数

    #年号判定
    for Y in year_dict:
        if year >= Y:
            year -= Y
            if year == 0:
                print(year_dict[Y] + "元年")
            else:    
                print(year_dict[Y] + str(year + 1) + "年")
            change = False
            break

    # 年号変換対象外に対してのエラーメッセージ
    if change:
        print("明治~令和の範囲でのみ変換が可能です")

trans2era_name(1868)
出力1(2020年)
令和2年
出力2(1868年)
明治元年
出力3(1800年)
明治~令和の範囲でのみ変換が可能です

はじめにも述べたように,我ながら汎用性の高いものができたのではないかと思う.コードの説明を少し行う.year_dictの中に年号とその元年の西暦の組を格納している.定義した関数では,forでその辞書型配列を回しているため,year_dictの中身を追加するだけで,年号変換の対象を拡張することができる.ただし,年号変換できなかったときに出力されるエラーメッセージの内容は変える必要があるかもしれない.
※コメント欄の@shiracamusによる改良版ならば,その心配もなく,自動でエラーメッセージも変化してくれる.

感想

今回取り扱った年号変換は参考文献の確認問題として出題されていたもので,自分で考えてプログラムを作成した.先ほども記述したように,我ながらうまくまとめれられたのではないかと思う.アルゴリズムは学び始めたばかりであるが,アルゴリズムや考え方は大切であると感じられ,いっそうアルゴリズムへの学習意欲が高まった.

参考文献

Pythonで始めるアルゴリズム入門 伝統的なアルゴリズムで学ぶ定石と計算量
                         増井 敏克 著  翔泳社

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pyrealsense2で距離カメラのデータを扱う



株式会社 日立情報通信エンジニアリング の追立です。
「IoT」が世間一般に浸透してしばらく経ちます。現場の情報収集に必要なセンサ類も、振動、電圧など色々なものが増えました。
距離カメラも様々な機種が安価に販売されるようになっており、距離+画像のデータが利活用されるようになってきました。

本記事ではPythonから距離カメラを扱うことができるOSSという事でpyrealsense2でいろいろ試行した結果をご紹介します。
ノイズ処理や距離推定は結構使うかな?と思うのですがあまり情報を見つけられなかったのでこの記事が役立てば幸いです。


pyrealsense2とは?

pyrealsense2はIntel® RealSense™ SDK 2.0のPythonラッパーです。
Intel®社から市販されている距離カメラIntel® RealSense™シリーズを使用するためのOSSです。
License:Apache License, Version 2.0

※本AdventCalendarの趣旨はOSSであるため、特に必要が無い限りカメラの製品仕様等については説明を省略しています。
 製品情報については公式HPをご参照ください。

導入

pipコマンドで導入することができます。

install
pip3 install pyrealsense2

インストールしたモジュールがimport可能なことを確認します。

import pyrealsense2

今回インストールされたバージョンは2.39.0.2342でした。

距離データとRGB画像を保存する

早速PCにカメラを接続し、距離カメラとRGBカメラの情報を画像として保存します。
なお、今回検証で使用するカメラである「Intel® RealSense™ D435i」はステレオ方式のカメラであり、RGB映像用のカメラとは別に距離データ取得用のカメラが備わっています。よってそれぞれのカメラに対してconfigを設定していきます。

import pyrealsense2 as rs
import numpy as np

# カメラの設定
conf = rs.config()
# RGB
conf.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30)
# 距離
conf.enable_stream(rs.stream.depth, 1280, 720, rs.format.z16, 30)

# stream開始
pipe = rs.pipeline()
profile = pipe.start(conf)

cnt = 0

try:
    while True:
        frames = pipe.wait_for_frames()

        # frameデータを取得
        color_frame = frames.get_color_frame()
        depth_frame = frames.get_depth_frame()

        # 画像データに変換
        color_image = np.asanyarray(color_frame.get_data())
        # 距離情報をカラースケール画像に変換する
        depth_color_frame = rs.colorizer().colorize(depth_frame)
        depth_image = np.asanyarray(depth_color_frame.get_data())

        #お好みの画像保存処理


ここで、変換したデータ(color_image,depth_image)を画像データとして保存することができます。

距離情報を取得する

取得したデータに対してピクセルを指定することで、当該ピクセルの距離情報(m)を取得することができます。

# 距離情報の取得
depth_data = depth_frame.get_distance(x,y)

距離データとRGB映像を動画として保存する

カメラで撮影したデータを動画(.bag)として保存します。
enable_record_to_file()モジュールを先程の画像保存処理に下記のように追加することで保存することができます。

# 省略 
# カメラの設定
conf = rs.config()
conf.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30)
conf.enable_stream(rs.stream.depth, 1280, 720, rs.format.z16, 30)
conf.enable_record_to_file('export_filename.bag')  #ADD

# stream開始
pipe = rs.pipeline()
profile = pipe.start(conf)
# 省略

後処理をする

カメラで撮影したデータや保存済の.bagデータに対して後処理をかけます。
後処理をすることによってノイズを減らすことができるようです。
Pythonラッパーにもいくつかのフィルタが用意されているようですが、今回は以下の三つを試してみました。

  • Decimation Filter : 複雑さを軽減する効果
  • Spatial Filter : 平滑化する効果
  • Hole Filling Filter : 欠損データを補完する効果
後処理(Filter)
# decimarion_filterのパラメータ
decimate = rs.decimation_filter()
decimate.set_option(rs.option.filter_magnitude, 1)
# spatial_filterのパラメータ
spatial = rs.spatial_filter()
spatial.set_option(rs.option.filter_magnitude, 1)
spatial.set_option(rs.option.filter_smooth_alpha, 0.25)
spatial.set_option(rs.option.filter_smooth_delta, 50)
# hole_filling_filterのパラメータ
hole_filling = rs.hole_filling_filter()
# disparity
depth_to_disparity = rs.disparity_transform(True)
disparity_to_depth = rs.disparity_transform(False)

# 省略(フレーム取得処理)

# filterをかける
filter_frame = decimate.process(depth_frame)
filter_frame = depth_to_disparity.process(filter_frame)
filter_frame = spatial.process(filter_frame)
filter_frame = disparity_to_depth.process(filter_frame)
filter_frame = hole_filling.process(filter_frame)
result_frame = filter_frame.as_depth_frame()

フィルタをかける前後の結果を比較してみます。


フィルタをかける前:
before.png


フィルタをかけた後:
affter.png


(応用編)撮った物体のサイズを推定をしてみる

pyrealsense2には画像上のピクセル座標と深度情報を与えるとカメラからの相対的な3次元座標情報を返してくれるモジュールが有ります。
これを使ってカメラに映っている物体のサイズを推定してみます。

今回は↓の画像にある15cmの定規を対象にしてみます。
color.png

製品を見ると分かりますがRGB映像用のカメラと距離データ取得用の2つのIRカメラはついてる場所が異なります。
よって取得できるデータもRGBと距離データの間で微妙にずれています。まずはこれを補正します。

補正
# alignモジュールの定義
align_to = rs.stream.color
align = rs.align(align_to)

#     (中間処理は省略)

# frame処理で合わせる
frames = pipe.wait_for_frames()
aligned_frames = align.process(frames)
depth_frame =  aligned_frames.get_depth_frame()
color_frame =  aligned_frames.get_color_frame()

次に距離データに後処理(フィルタ)をかけます。
今回はサイズを測ろうとする座標の深度データが必要なので、欠損は補間してやる必要が有ります。
処理自体は前章でご紹介したような内容でフィルタをかけていきます。

処理前:
depth.png

処理後:
filter.png

いよいよ画像の座標から3次元座標を求めていきます。
rs2_deproject_pixel_to_point()を使いますが、これに必要なのが画像上の座標情報(x,y)と深度情報とカメラモジュールの内部パラメータです。
今回は画像をRGBカメラ側に補正していますのでRGBカメラモジュールの内部パラメータを取得します。

内部パラメータ取得
color_intr = rs.video_stream_profile(profile.get_stream(rs.stream.color)).get_intrinsics()

次に取得する座標情報を決めます。
今回は15cm定規の端から端までの長さを推定してみます。開始点を「イ」、終了点を「ロ」として画像上の座標情報を取得します。

  • イ : (685, 642)
  • ロ : (685, 290)

color_point.png

これらの情報を元に長さを推定してみます。

長さの推定
#イの3次元座標推定
I_d = result_frame.get_distance(685,642)
point_I = rs.rs2_deproject_pixel_to_point(color_intr , [685,642], I_d)
#ロの3次元座標推定
R_d = result_frame.get_distance(685,290)
point_R = rs.rs2_deproject_pixel_to_point(color_intr , [685,290], R_d)

#推定距離を算出
est_range = math.sqrt((Point_I[0]-Point_R[0])*(Point_I[0]-Point_R[0]) + (Point_I[1]-Point_R[1])*(Point_I[1]-Point_R[1]) +(Point_I[2]-Point_R[2])*(Point_I[2]-Point_R[2]))
print(est_range)
#-------------
#結果(0.1471)[m]

思ってたより実際の値に近い値が出ました。

あとがき

普段から業務で色々なデータをPythonで分析している関係上、Pythonラッパーとして提供されているので他の処理との連携がやりやすく感じました。
物体検出と組み合わせることで、カメラ1台から物体の位置などの情報を取得することもできますし、その他のセンサデータとの連携も敷居が低くなっているのではないかと思います。
データ利活用では単独のデータのみを扱うという場面は少ないと思いますので、こういったラッパーが提供されているとそれだけでとっつきやすく感じました。


参考文献

本記事の執筆にあたって以下を参考にさせて頂きました。
公式ドキュメント:https://dev.intelrealsense.com/docs

その他

Intel®、Intel® RealSense™は、アメリカ合衆国および/またはその他の国における Intel Corporation の商標です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

私の周りのPython 2020年ふりかえり

私の周りのPython 2020年ふりかえり

 Python歴4年目に入った、いつもはメーカ系列でIoT屋やっている者です。
私の周りのPython 2020年と少し語ってみます。

私の周りのPython 2020年まで

 2017年~2018,9年頃までは、研究開発やそれに付随するPoC(実証実験)で、
IoT機器でのデータ収集やカメラ制御による画像関連、
AWSなどクラウド上での分析処理ぶん回し(笑)になどなどにpythonを利用していました。
それぞれの研究題材の特化したpythonプログラムがほとんどで、
いわゆるゴリゴリのコーディング(書いた本人しかわからんのでは?)。
正常動作して目的を達成すればよいって感じ。

 あくまでPythonは、研究者や技術者が目的達成の為に一時的に利用しているに過ぎない存在でした。

私の周りのPython 2020年

 ところが今年(2020年)徐々に状況が変化してきたのです。
実用的な場面でPythonが利用され始めているのを実感した1年になりました。
 例えば、
・情報提供系システムでの、バッチ処理や Diango,Flaskを利用したWebアプリケーション
 Dashなど利用した、情報の見やすい可視化!
・IoT機器とのデータ連携の急増:非同期処理が使いやすくなってきている!(使いやすいハードの出現も要因)
・Apiサーバでの活用
などなど。

 それらは、
・Python経験者が増えてきた(特に若手)
・情報見える化のさらなる要求(現場でもデータ分析&結果からの次アクションのローテー)
のような事での利用拡大でありますが、
その他、Python的には、
・個人レベルでの開発でなく、ある程度の規模での開発&その為のルール付けや型ヒント等の開発支援のようなもの
が充実してきている。(もちろんエディタ等も)
・情報をみせるライブラリ・通信ライブラリの拡充
などなどが、後押ししているのでは
と、思っています。

そして2021年

 さて2021年のPython事情はどの様になっているでしょうか?
コロナ過でどこも予算厳しい時期でありますが、知恵絞りながらでも
上記2020年の動きのさらなる拡大は止まらんだろうなと。

それでは!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで学ぶアルゴリズム 第6弾:うるう年

#Pythonで学ぶアルゴリズム< うるう年 >

はじめに

基本的なアルゴリズムをPythonで実装し,アルゴリズムの理解を深める.
その第6弾としてうるう年を扱う.

うるう年

<条件>
4で割り切れる年をうるう年という.
※ただし,100で割り切れて,400で割り切れない年はうるう年ではない.
例)
2019年 : 4で割り切れない⇒うるう年でない
2020年 : 4で割り切れる⇒うるう年? → 100で割り切ない⇒うるう年である
1900年 : 4で割り切れる⇒うるう年? → 100で割り切れて,400で割り切れない⇒うるう年でない
2000年 : 4で割り切れる⇒うるう年? → 100で割り切れて400で割り切れる⇒うるう年である

このように条件の注意(※)さえ,満たさなければ,単純に4で割り切れるものはうるう年ということである.これをpythonで実装してみた.
以下にコードと出力を示す.

コード
leap_year.py
"""
2020/12/18
@Yuya Shimizu

うるう年
・4で割り切れる年
ただし,100で割り切れて400で割り切れない年はうるう年でない
"""

def leap_year(year):
    if year%100 == 0 and year%400 != 0:
        print(str(year) + "年はうるう年でない.")
    else:
        if year%4 == 0:
            print(str(year) + "年はうるう年である.")
        else:
            print(str(year) + "年はうるう年でない.")

year = int(input("うるう年判定\n>>"))

leap_year(year)
出力1
うるう年判定
>>2000
2000年はうるう年である.
出力2
うるう年判定
>>1900
1900年はうるう年でない.

ユーザ入力により判定するものとした.
コードはいたってシンプルで,注意したところは,より特殊な条件をはじめに持ってくることである.

改編版

@shiracamusのコメントを参考に上記のプログラムを改編した.
具体的には,関数内のprintを省きTrue, Falseを返すようにした.
以下にそのコードと出力を示す.

コード
leap_year_.py
"""
2020/12/18
@Yuya Shimizu

うるう年
・4で割り切れる年
ただし,100で割り切れて400で割り切れない年はうるう年でない

改編版
@shiracamusの提案を参考に,True, Falseを返すプログラムにした
"""

def leap_year(year):
    if year%100 == 0 and year%400 != 0:
        return False
    else:
        if year%4 == 0:
            return True
        else:
            return False

year = int(input("うるう年判定\n>>"))

print(leap_year(year))
出力
うるう年判定
>>2020
True

よりよいものができた.

感想

今までに2,3回はうるう年のコードを書いたことはあったが,アルゴリズムを通して少し頭の中を整理できたおかげか,割とシンプルにかけたのではないかと思う.

参考文献

Pythonで始めるアルゴリズム入門 伝統的なアルゴリズムで学ぶ定石と計算量
                         増井 敏克 著  翔泳社

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebRTCで常時接続のペットカメラを作ってみた(Nuxt.js + Python + Firebase + SkyWay + ラズパイ)

はじめに

catRaspberryPi
1泊2日の楽しい旅行!でも、留守番中の猫が気になってしょうがない。。
愛する猫のため「旅行中のペット見守りカメラ」を作ってみました:camera:
GitHubリポジトリはコチラ

実はネタ元としてコチラの優良記事を参考にさせていただいています。
SkyWay WebRTC Gatewayハンズオン

ネタ元様はRubyです。
今回は大変恐縮ですが、Pythonにて機能やファイルをまとめたり、何度もカメラに再接続できるようにするなど改良を重ねさせていただきました。:bow:

特徴

こんなことができます!

  • WEBアプリを通して外出先から自宅のカメラ映像がいつでもどこでも見られる
  • ログイン認証付きで安全
  • LEDを点灯・消灯と操作(将来カメラの角度を動かしたい)
  • WEBアプリからプログラムをシャットダウンできる(バルスと唱える!)

使用技術

  • Nuxt.js(Vue.js): Webアプリの作成
  • Python: 自宅カメラの制御
  • SkyWay(WebRTC): カメラ映像・メッセージの送信
  • Firebase: Webアプリのデプロイ、ログイン機能、SkyWay APIKeyの保存

diagram.png

RaspberryPi

自宅カメラ側として映像配信用に使用します。
USBカメラを接続し、SkyWayモジュールであるwebrtc-gateway、制御用Pythonプログラムを設定します。
大まかな仕組みとして、Pythonプログラムでカメラ映像を取得し、web-rtc-gatewayにアクセスして接続したWEBアプリ側にストリーム配信します。

また、LEDを接続してWebアプリ側からのメッセージで点灯・消灯できるようにしました。
将来的には、GPIOを通してサーボモーターを制御してUSBカメラの向きや角度を操作したいと考えています。

SkyWay

NTTコミュニケーションズのWebRTCを手軽に扱えるSDKです。
自宅カメラとWebアプリを接続し、映像、メッセージ送信のために使用しました。
詳しくはSkyWay公式サイト

案内に沿って無料登録して、APIKEYを取得します。
Webアプリ側は、Node.jsのモジュールとして、SkyWayを操作します。
カメラ側のRaspberryPiは、Webブラウザを立ち上げるわけでなく、ヘッドレスなのでskyway-webrtc-gatewayモジュールを使用します。

skyway-webrtc-gateway

IOT機器で、SkyWayを簡単に実行するためのコンパイルされたモジュール。
Webブラウザなしでも、WebRTCを実現して、映像や音声、メッセージを送受信することができます。
公式github

カメラ側のラズパイに、権限を付与して実行するだけでです。

Firebase

面倒な機能をサクッと実装したかったので、 今回はFirebaseを利用しました。

  • Authentification: ログイン機能
  • Firestore: SkyWayのAPI Keyを保存
  • Hosting: WEBアプリのデプロイ先

Nuxt.js(WEBアプリ側)

Vue.jsのフロントエンドフレームワークであるNuxt.jsを用いて、WEBアプリ側を構築しました。
自宅映像という超プライベートなので、ログイン機能をFirebaseのAuthenticationを利用して実装。ついでにデプロイ先もFirebaseを利用しました。
SkyWayに接続するためのAPI Keyですが、フロントエンドに持たせたく無かったです。そこでFirestoreに保存して、ログイン済みユーザーでないとAPIKeyを取得できないようにしました。

ディレクトリ構成

# 一部省略
frontend
├── components
│   ├── Header.vue
│   ├── Login.vue
│   └── ProjectTitle.vue
├── layouts
│   └── default.vue
├── middleware
│   └── authenticated.js
├── pages
│   ├── index.vue
│   └── operate.vue
├── static
│   └── favicon.ico
├── store
│   └── index.js
└── nuxt.config.js

大まかな処理の流れ

  1. pages/index.vueにユーザーはアクセス
    middleware/authenticated.jsにより、未ログインはindex以外の閲覧が不可
  2. componets/login.vueを通してログイン
  3. ログインと同時に store/index.jsでfirestoreからSkyWay APIKeyを取得
  4. pages/operate.vueにユーザーはアクセス
  5. 取得したAPI Keyを利用してSkyWayに接続
  6. すでに接続済みの自宅カメラ側のラズパイとコネクションを確立
    自宅のラズパイ側のWebRTC-gatewayからカメラ映像を視聴する

一番のポイントは面倒な部分はFirebaseに任せたことです。:thumbsup_tone3:
作りたいのはペットカメラシステムで、立派なWebサイトでは無いので、任せられる機能はFirebaseに丸投げしました。
特にAPIKeyをFirestoreに預けておくのは使い勝手がすごく良いです。

Python(自宅カメラ・ラズパイ側)

メイン&一番楽しい部分です。
WebRTC-gatewayは実行させたら、RESTAPIを通してPythonで制御します。

ディレクトリ構成

backend
├── __init__.py
├── config.py
├── data.py
├── media.py
├── peer.py
├── robot.py
├── util.py
└── webrtc_control.py

実行ファイルはwebrtc_control.pyです。
他のファイルは機能別のモジュールとなります。

webrtc_control.py

WebRTC-gateway、USBカメラを制御するメインプログラムです。
gatewayの次にこちらのプログラムを実行することで、ペットカメラはリモートで使えるようにスタンバイされます。

webrtc_control.py
# ~省略~

def main():
    queue = que.Queue()
    robot = Robot()

    peer_id = config.PEER_ID
    media_connection_id = ''
    data_connection_id = ''
    process_gst = None

    #Peerを確立します(SkyWayへの接続)
    peer_token = Peer.create_peer(config.API_KEY, peer_id)

    if peer_token is None:
        count_create = 1
        retry_peer_id = ''

        # Peerが確立できなかったら、IDを変えて5秒おきにチャレンジ
        while peer_token is None:
            time.sleep(5)
            count_create += 1
            retry_peer_id = peer_id + str(count_create)
            peer_token = Peer.create_peer(config.API_KEY, retry_peer_id)

        peer_id = retry_peer_id

    peer_id, peer_token = Peer.listen_open_event(peer_id, peer_token)

    # Peerへのeventを常時Listenするために、スレッド化します。
    th_listen = threading.Thread(target=Peer.listen_event,
                                 args=(peer_id, peer_token, queue, robot))
    th_listen.setDaemon(True)
    th_listen.start()

    # Webアプリからのメッセージを常時Listen、スレッド化します。
    th_socket = threading.Thread(target=robot.socket_loop, args=(queue,))
    th_socket.setDaemon(True)
    th_socket.start()

    try:
        while True:
           #スレッドからのキューを受け取る。 
           results = queue.get()

            #キューにList内のワード(「バルス」とか)が入っていれば、whileを抜ける
            if 'data' in results.keys():
                if results['data'] in config.SHUTDOWN_LIST:
                    break

            # 映像コネクションが確立したら、映像eventをListenする
            elif 'media_connection_id' in results.keys():
                media_connection_id = results['media_connection_id']

                th_media = threading.Thread(target=Media.listen_media_event,
                                            args=(queue, media_connection_id))
                th_media.setDaemon(True)
                th_media.start()

            #Gstreamerによる映像ストリームを開始したら、そのプロセスを取得する。
            elif 'process_gst' in results.keys():
                process_gst = results['process_gst']

            #データコネクション(メッセージのやり取り)が確立したら、そのIDを格納する。
            elif 'data_connection_id' in results.keys():
                data_connection_id = results['data_connection_id']

            #映像eventで該当する内容(close、error)したら、新たに次に接続できるように
            #使用していた映像ストリーム、MediaConnection,DataConnectionを破棄する
            #この場合、接続していたWebアプリ側が閉じたり、エラーになった場合を指す
            elif 'media_event' in results.keys():
                if results['media_event'] in ['CLOSE', 'ERROR']:
                    process_gst.kill()
                    Media.close_media_connections(media_connection_id)
                    Data.close_data_connections(data_connection_id)

    except KeyboardInterrupt:
        pass

    robot.close()
    Peer.close_peer(peer_id, peer_token)
    process_gst.kill()
    print('all shutdown!')


if __name__ == '__main__':
    main()

マルチスレッド

Peerへのイベントコールを常時Listenするために、th_listenでデーモン化してスレッド並列化しています。
th_socketもスレッドして並列してますが、こちらはDataConnection(webアプリからのmessage)のListenとなります。

while文で、各コネクションの状態が変わった時の処理分けをしています。
各スレッドはそれぞれ受け取ったeventによってqueueを飛ばすように仕込んでおり、このwhile文でそのqueue内容により処理を走らせています。
while内のth_mediaで、MediaConnectionからのイベントをListenします。

各threadからの状態によりConnectionを開放・再準備することで、Webアプリ側が何度も途中で切っても、カメラ側のラズパイと再接続可能にしています。:tada:

本来ですと、media connectionを簡単に開放・すぐに確立できるはずなのですが、仕様なのでしょうか?それとも私が未熟なのか分かりませんが、
2回目以降新たにmedia connectionを作成しようとすると、WebRTC-gatewayが400エラーになってしまいますので、新たにmediaから作る強引な実装となっています。
本来ならもっとスマートな処理のハズだけど、公式通り動かないから多少冗長な書き方にナッテルヨ。です。

util.py

WebRTC-gatewayとはRESTAPIて制御するので、頻繁にリクエストを送ることになります。
多用できるようにメソッドにまとめておきます。

util.py
import requests

import config

def request(method_name, _uri, *args):

    response = None
    uri = config.HOST + _uri
    if method_name == 'get':
        response = requests.get(uri, *args)
    elif method_name == 'post':
        response = requests.post(uri, *args)
    elif method_name == 'put':
        response = requests.put(uri, *args)
    elif method_name == 'delete':
        response = requests.delete(uri)

    else:
        print('There is no method called it')

    return response

robot.py

将来、ロボットアームでカメラ動かしたいと思って、こんな名前のクラスにしています。
このファイルは以下の機能となります。

  • GPIOの制御(Lチカやモータ)
  • socket通信の確立とデータ受け取り

Webアプリとデータコネクションを通して、GPIOへの指示を受けます。
(今はLEDのON/OFFしかできませんw)
以下は、webrtc_control.pyでスレッド化したメソッドです。

robot.py
# ~省略~

class Robot:


    # ~省略~

    # スレッド化したメソッドです。
    # Webアプリからのメッセージを待ち受けます。
    def socket_loop(self, queue):
        """ソケット通信を待ち受ける
        """

        # socket通信を作成
        self.make_socket()
        while True:
            # データ(メッセージ)を受け取る
            data = self.recv_data()
            data = data.decode(encoding="utf8", errors='ignore')
            # メッセージ内容をキューに送信する
            queue.put({'data': data})
            # メッセージ内容によりGPIOを操作(この場合、LEDの点灯・消灯)
            self.pin(data)

# ~省略~

peer.py

SkyWayとのPeer接続を確立して、SkyWayが使える状態にします。
以下は、webrtc_control.pyでスレッド化したメソッドです。

peer.py
# ~省略~

class Peer:
    """SkyWayと接続しセッション管理を行う

    """

    # ~省略~


    # スレッド化します。PeerへのeventをListenします。
    @classmethod
    def listen_event(cls, peer_id, peer_token, queue, robot):
        """Peerオブジェクトのイベントを待ち受ける
        """

        gst_cmd = "gst-launch-1.0 -e v4l2src ! video/x-raw,width=640,height=480,framerate=30/1 ! videoconvert ! vp8enc deadline=1 ! rtpvp8pay pt=96 ! udpsink port={} host={} sync=false"

        uri = "/peers/{}/events?token={}".format(peer_id, peer_token)
        while True:
            _res = request('get', uri)
            res = json.loads(_res.text)

            if 'event' not in res.keys():
                # print('No peer event')
                pass

            # Webアプリから映像接続を要求されたら
            elif res['event'] == 'CALL':
                print('CALL!')
                media_connection_id = res["call_params"]["media_connection_id"]
                queue.put({'media_connection_id': media_connection_id})

                # mediaを作成
                (video_id, video_ip, video_port) = Media.create_media()

                # GstreamerによるUSBカメラから映像ストリームを作成
                cmd = gst_cmd.format(video_port, video_ip)
                process_gst = subprocess.Popen(cmd.split())
                queue.put({'process_gst': process_gst})

                # WebアプリとmediaConnectionを接続する
                Media.answer(media_connection_id, video_id)

            # Webアプリからデータ接続(messageのやり取り)を要求されたら
            elif res['event'] == 'CONNECTION':
                print('CONNECT!')
                data_connection_id = res["data_params"]["data_connection_id"]
                queue.put({'data_connection_id': data_connection_id})

                # Dataを作成
                (data_id, data_ip, data_port) = Data.create_data()
                # データ(メッセージの飛ばし先をGPIOが処理しやすいportにリダイレクトする
                Data.set_data_redirect(data_connection_id, data_id, "127.0.0.1",
                                       robot.port)

            elif res['event'] == 'OPEN':
                print('OPEN!')

            time.sleep(1)

# ~省略~


media.py

PeerでSkyWayと接続が確立された後に、他のPeerユーザーとの映像・音声のやり取りを開始します。
以下は、webrtc_control.pyでスレッド化したメソッドです。

media.py
# ~省略~

class Media:
    """MediaStreamを利用する

    MediaConnectionオブジェクトと転送するメディアの送受信方法について指定
    """

    # ~省略~

    # スレッド化したメソッド
    # mediaのイベントを待ち受ける
    # 端的に:接続したWebアプリが、クローズ、エラーしたら知らせる
    @classmethod
    def listen_media_event(cls, queue, media_connection_id):
        """MediaConnectionオブジェクトのイベントを待ち受ける
        """

        uri = "/media/connections/{}/events".format(media_connection_id)
        while True:
            _res = request('get', uri)
            res = json.loads(_res.text)

            if 'event' in res.keys():
                # イベントをキューに投げる
                queue.put({'media_event': res['event']})

                # close,errorは別のスレッドでこのlitenの大元のmediaが開放されるので、このスレッドを終了する
                if res['event'] in ['CLOSE', 'ERROR']:
                    break

            else:
                # print('No media_connection event')
                pass

            time.sleep(1)

    # ~省略~

data.py

PeerでSkyWayと接続が確立された後に、他のPeerユーザーとのデータのやり取りを開始します。
今回は、文字のやり取りとなります。

Mediaと似ており、特筆することもないのでスキップします。

config.py

設定ファイルです。

config.py
API_KEY = "skyway API Keyを入力"
PEER_ID = "任意のpeer idを入力"
HOST = "http://localhost:8000"
SHUTDOWN_LIST = ['バルス', 'ばるす', 'balus', 'balusu', 'barusu', 'barus']

  • API_KEY: SkyWayのAPIKeyです。
  • PEER_ID: 自宅カメラ側のPEER_IDです。任意の名前に変更できます。
  • HOST: gatewayを立ち上げたときのHOST先となります。
  • SHUZTDOWN_LIST: Webアプリでこの単語を唱えると、プログラムがシャットダウンします。(バルス:skull_crossbones:)

リモートデバッグのススメ

GPIOを使ってLチカ、USBカメラから映像ストリーム取得、WebRTC-gatewayの実行と、ラズパイのハード依存が多かったので、リモートデバッグで開発しました。
今回の場合は、Mac上のIntellijでデバッグをしつつ、実際は同じネットワークのラズパイ上のコードで実行しています。
この方が、Mac上でのいつも通りのコーディング、デバッグしつつ、機種依存の機能もチェックできますのでとても開発しやすかったです。

IntelliJのリモートデバッグ方法

Jetbrains社のIDE:IntelliJでの方法となります。PyCharmでも同じ方法でリモートデバッグ可能です。
スクリーンショット 2020-12-17 22.24.27.png
実行/デバッグ構成からPython Debug Serverを選択

スクリーンショット 2020-12-17 22.29.03.png
IDE ホスト名を入力。ローカルIPアドレスでも可。(この場合は、Mac)
続いて空いている任意のポートを入力(ここもMac)
パスパッピングは、Macとラズパイでディレクトリをマッピングしたい時に指定します。

続いて説明欄の1.2を行います。
eggを追加するよりも、pipでインストールしてしまったほうが楽でした。

#実行マシーンでインストールする(この場合はラズパイ)
pip install pydevd-pycharm~=203.5981.155
#バージョンはIDEにより異なる。

コードにコマンドを追加
今回の場合だと実行ファイルであるwebrtc_control.pyの冒頭に追加します。

import pydevd_pycharm
pydevd_pycharm.settrace(<IDEホスト名>, port=<ポート番号>, stdoutToServer=True, stderrToServer=True, suspend=False)

ここまで準備完了です。
デバッグ実行は

  1. 先に、Mac側のIntelliJでPython Debug Serverをデバッグ実行
  2. ラズパイで、追加コードされたデバッグしたいコードを実行

これで、リモート環境のデバッグが可能になります。
通常のデバッグのようにブレイクポイントも、途中経過の変数の変遷も見ることができます、便利!!:smiley:

開発環境側のMacは固定IPにしておくのがおすすめです。
そうしないと毎回、追加コードのIP部分を書き直すハメになります、、

おわりに

これでも十分にペットカメラとして使えますが、
機会があればロボットアームにカメラをつけて遠隔で動かしたいです。

旅行中の自宅の猫を見るのにすごく便利&重宝しそうです:cat:
ここまで読んでいただいてありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ubuntu18.04.05 LTS におけるpython仮想環境作成

Anacondaを使用しない仮想環境作成をUbuntuにて実施。

環境

Ubuntu18.04.05 LTS
python:3.6.9


仮想環境構築

①アップデート

$sudo apt-get update
$sudo apt-get upgrade
$sudo reboot

②venvのインストール

$sudo apt install python3-venv

venv: 仮想環境構築に必要なツール。Python3.3以降が必要らしい。


③仮想環境を作る

#まずは仮想環境を作るディレクトリを作成。
$mkdir test_env
$cd test_env

mkdir test_env
Screenshot from 2020-12-18 13-31-24 (2).png
$cd test_envで作ったディレクトリ内に移動する。


$python -m venv tensorflow

『tensorflow』名で仮想環境用のディレクトリを作成する。

Screenshot from 2020-12-18 13-31-37 (2).png

中身は

Screenshot from 2020-12-18 13-45-19 (3).png

これで仮想環境が完成。


3)仮想環境内に入る

$source test_env/tensorflow/bin/activate

これで仮想環境として機能します。
私はhomeに仮想環境用のtest_envディレクトリを作成して
さらにその下にtensorflowと作成しているので長いです。

homeディレクトリに作っていればsource tensorflow/bin/activateで環境に入れます。

Screenshot from 2020-12-18 15-35-26.png
Screenshot from 2020-12-18 15-35-35.png

仮想環境内で

(tensorflow)~@~$pip3 install numpy
(tensorflow)~@~$pip3 install tensorflow-gpu==1.14
(tensorflow)~@~$pip3 install matplotlob

等々をインストールすれば大丈夫です。
ただpip3 install opencv-pythonだけはエラーを吐いてうまくいかず。
エラーをコピーし忘れたのが痛いですが
結果としてpipが古くて必要なモジュールがないって吐いていました。

pip3 install -U pip 

これを打ち込んでアップグレードしてOKとなりました。

その後に

pip3 install opencv-python==3.4.11.45

と入力してインストール出来ました。

バージョン指定したのはopencv4系の不具合が怖かったからです。

とりあえずはこれで何とか行けました。

④仮想環境をJupyter等で使用出来る様にする

仮想環境下で

(tensorflow)~@~$pip install ipykernel

#installが終わったら
(tensorflow)~@~$ipython kernel install --user --name=tensorflow

Screenshot from 2020-12-18 16-04-53 (2).png
Screenshot from 2020-12-18 16-05-53 (2).png

これでjupyternotebookでカーネルの選択が可能になります。

Screenshot from 2020-12-18 16-06-34 (2).png

まぁ色々と試してる最中です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonで外部コマンドを実行する(結果を受け取る/受け取らない場合の両方)

python3, MacOSとLinuxで動作確認済み。

import os
import subprocess


def exec_os_system(cmd: str):
    '''
    コマンドを同期実行し、
    標準出力と標準エラー出力は、実行したプロンプトに随時表示する
    戻り値はコマンドのリターンコード
    '''
    # os.systemはwaitで子プロセスの終了を待つため、リターンコードの上位8bitにリターンコードが格納される
    # それを取るため8bit右にシフトする
    # see http://d.hatena.ne.jp/perlcodesample/20090415/1240762619
    rt = os.system(cmd) >> 8
    return rt


def exec_subprocess(cmd: str, raise_error=True):
    '''
    コマンドを同期実行し、
    標準出力と標準エラー出力は最後にまとめて返却する
    raise_errorがTrueかつリターンコードが0以外の場合は例外を出す
    戻り値は3値のタプルで (標準出力(bytes型), 標準エラー出力(bytes型), リターンコード(int))
    '''
    child = subprocess.Popen(cmd, shell=True,
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = child.communicate()
    rt = child.returncode
    if rt != 0 and raise_error:
        raise Exception(f"command return code is not 0. got {rt}. stderr = {stderr}")

    return stdout, stderr, rt


if __name__ == "__main__":
    exec_os_system("date")

    stdout, stderr, rt = exec_subprocess("date")
    print(stdout)
    print(stderr)
    print(rt)


実行結果

Fri Dec 18 16:04:49 JST 2020
b'Fri Dec 18 16:04:49 JST 2020\n'
b''
0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonで緯度経度2地点間の距離計算(球面三角法利用)

import math


def get_distance_m(lat1, lon1, lat2, lon2):
    """
    2点間の距離(m)
    球面三角法を利用した簡易的な距離計算
    GoogleMapAPIのgeometory.computeDistanceBetweenのロジック
    https://www.suzu6.net/posts/167-php-spherical-trigonometry/
    """
    R = 6378137.0  # 赤道半径
    lat1 = math.radians(lat1)
    lon1 = math.radians(lon1)
    lat2 = math.radians(lat2)
    lon2 = math.radians(lon2)
    diff_lon = lon1 - lon2
    dist = math.sin(lat1) * math.sin(lat2) + math.cos(lat1) * math.cos(lat2) * math.cos(diff_lon)
    return R * math.acos(min(max(dist, -1.0), 1.0))


if __name__ == "__main__":
    # 緯度と経度方向に1秒角移動したときの距離を計算する
    distance = get_distance_m(lon1=139.0,
                              lat1=35.0,
                              lon2=139.0 + 1.0 / 3600.0,
                              lat2=35.0 + 1.0 / 3600.0)
    print(distance)

実行結果

39.972442531744505
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonリトライするデコレータ

from functools import wraps
import time


def retry_decorator(retry_num: int, sleep_sec: int):
    """
    リトライするデコレータを返す
    :param retry_num: リトライ回数
    :param sleep_sec: リトライするまでにsleepする秒数
    :return: デコレータ
    """

    def _retry(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for retry_count in range(1, retry_num + 1):
                try:
                    ret = func(*args, **kwargs)
                    return ret
                except Exception as exp:  # pylint: disable=broad-except
                    if retry_count == retry_num:
                        print(f"Retry_count over ({retry_count}/{retry_num})")
                        raise Exception from exp
                    else:
                        print(f"Retry after {sleep_sec} sec ({retry_count}/{retry_num}). Error = {exp}")
                        time.sleep(sleep_sec)

        return wrapper

    return _retry


@retry_decorator(retry_num=3, sleep_sec=1)
def hoge():
    raise Exception("fail")


if __name__ == "__main__":
    hoge()

実行結果

Retry after 1 sec (1/3). Error = fail
Retry after 1 sec (2/3). Error = fail
Retry_count over (3/3)
Traceback (most recent call last):
  File "retry.py", line 18, in wrapper
    ret = func(*args, **kwargs)
  File "retry.py", line 35, in hoge
    raise Exception("fail")
Exception: fail

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "retry.py", line 39, in <module>
    hoge()
  File "retry.py", line 23, in wrapper
    raise Exception from exp
Exception
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む