差分
このページの2つのバージョン間の差分を表示します。
| 両方とも前のリビジョン 前のリビジョン 次のリビジョン | 前のリビジョン | ||
| tutorial_3 [2022/01/04 13:40] – satoshi | tutorial_3 [2022/01/05 10:49] (現在) – [第四項 Reactについて] satoshi | ||
|---|---|---|---|
| 行 1: | 行 1: | ||
| - | ====== 第3章 スターターコード【中級編 ライン判定(通過判定)】 ====== | + | ====== 第3章 ライン判定(WEBUIを使った通過判定)【中級編】 ====== |
| この章では、\\ | この章では、\\ | ||
| * エリア検知からラインをまたいだ検知への応用 | * エリア検知からラインをまたいだ検知への応用 | ||
| 行 245: | 行 245: | ||
| - SCORER People Trackerで保存した見取り図を取得して、その上に複数の線分を設定できるページ | - SCORER People Trackerで保存した見取り図を取得して、その上に複数の線分を設定できるページ | ||
| が用意されています。\\ | が用意されています。\\ | ||
| - | 2と4、3と5は同じ設定値を参照していますので、どちらかで後進すればもう片方に反映します。\\ | + | エリアや線分を追加してsaveボタンをおすことでJSONファイルに結果が保存され、次回表示時にもその結果が表示されます\\ |
| + | 2と4、3と5は同じ設定値を参照していますので、どちらかで更新すればもう片方に反映します。\\ | ||
| データの出力先はdrawuiフォルダ内の\\ | データの出力先はdrawuiフォルダ内の\\ | ||
| < | < | ||
| 行 254: | 行 255: | ||
| </ | </ | ||
| となっており、JSON形式は前章までで利用していたものと同じになります。\\ | となっており、JSON形式は前章までで利用していたものと同じになります。\\ | ||
| + | 利用を終了する場合はTerminal上でCtrl+Cでサーバーが止まります。\\ | ||
| + | スターターコードの完成の項でデータのつなぎ込みをしてみましょう\\ | ||
| ===== 第四項 Reactについて ===== | ===== 第四項 Reactについて ===== | ||
| + | drawuiで利用しているのはReactという最近のWEB開発ではデファクトスタンダード(事実上の業界標準)となりつつある技術を利用しています。\\ | ||
| + | 昔のイメージのHTMLファイルをページごとに作成して表示というものではなく、1つのindex.htmlに複雑なjavascriptを追加することで、スマホのアプリのような動きのある自由な表現が可能になる仕組みです。 | ||
| + | そのため、解説しているサイトや本は日本でも多数でてきています。\\ | ||
| + | 公式ページも日本語で公開されており、技術に興味がある人は\\ | ||
| + | [[https:// | ||
| + | 等から学び始めることがよいでしょう。\\ | ||
| + | |||
| + | ただし今回はReact自体が分からなくても、どのプログラムファイルをどういじれば何が変わるかという点だけでピンポイントで上級編の一環として解説していこうと思いますので、本チュートリアルを進めるうえではReactの学習は必須ではありません。\\ | ||
| + | |||
| ===== 第五項 WEBサーバーの解説===== | ===== 第五項 WEBサーバーの解説===== | ||
| + | WEBサーバーについては今回FlaskというPythonで簡単にWEBサーバーを構築できる仕組みを採用します。\\ | ||
| + | Flaskでは\\ | ||
| + | <sxh Python; | ||
| + | from flask import Flask | ||
| + | app = Flask(__name__) | ||
| + | |||
| + | @app.route('/' | ||
| + | def hello_world(): | ||
| + | return "Hello World!" | ||
| + | |||
| + | if __name__ == ' | ||
| + | app.run(host=' | ||
| + | </ | ||
| + | だけで\\ | ||
| + | < | ||
| + | python app.py | ||
| + | </ | ||
| + | を実行すると[[http:// | ||
| + | 今回はHello World!ではなく、Reactで作ったコードにアクセスできるように設定します。スターターコードの\\ | ||
| + | < | ||
| + | app = Flask(__name__, | ||
| + | (中略) | ||
| + | @app.route("/" | ||
| + | def index(): | ||
| + | return render_template(" | ||
| + | @app.errorhandler(404) | ||
| + | def not_found(e): | ||
| + | return render_template(" | ||
| + | </ | ||
| + | がその設定になり、トップページと、何もないページにアクセスした場合強制的にトップページを表示するという設定となります。Reactを使う上ではこういうものだと思ってもらえれば大丈夫です。\\ | ||
| + | |||
| + | なお、このFlaskでのWEBページ公開方法は開発用となっており、インターネットで大量にアクセスしてくるような大規模な本番用WEBページには非推奨です。\\ | ||
| + | 昔から有名なApacheや最近はNginxなどのWEBサーバーがよく使われています。\\ | ||
| + | ですが、SCORER Edge上で少数の人間がアクセスしてくる分には全く問題ありません。\\ | ||
| + | |||
| ===== 第六項 APIサーバーの解説 ===== | ===== 第六項 APIサーバーの解説 ===== | ||
| + | 描画画面でエリアや線分を設定したあと、そのデータを保存したり、ページ表示時に読み込んだりする部分のプログラム(API)についての解説ですが、今回は特殊な方法を使っているため、WEBサーバー公開と同じserver.py内で処理が行われています。 | ||
| + | <sxh Python; | ||
| + | import os | ||
| + | import sys | ||
| + | |||
| + | from flask import Flask, json, jsonify, request, render_template | ||
| + | |||
| + | |||
| + | |||
| + | app = Flask(__name__, | ||
| + | |||
| + | def get_file(filename): | ||
| + | print(f' | ||
| + | try: | ||
| + | with open( f' | ||
| + | file = json.load(infile) | ||
| + | except Exception: | ||
| + | return jsonify(reason=' | ||
| + | else: | ||
| + | return file | ||
| + | |||
| + | def save_file(filename, | ||
| + | try: | ||
| + | with open(f' | ||
| + | json.dump(data, | ||
| + | except Exception: | ||
| + | return ( | ||
| + | jsonify(reason=' | ||
| + | 500 | ||
| + | ) | ||
| + | |||
| + | return data, 200 | ||
| + | |||
| + | @app.route("/" | ||
| + | def index(): | ||
| + | return render_template(" | ||
| + | @app.errorhandler(404) | ||
| + | def not_found(e): | ||
| + | return render_template(" | ||
| + | |||
| + | @app.route('/ | ||
| + | def camareas(): | ||
| + | if request.method == ' | ||
| + | return get_file(' | ||
| + | elif request.method == ' | ||
| + | return save_file(' | ||
| + | else: | ||
| + | return {' | ||
| + | |||
| + | @app.route('/ | ||
| + | def sketchareas(): | ||
| + | if request.method == ' | ||
| + | return get_file(' | ||
| + | elif request.method == ' | ||
| + | return save_file(' | ||
| + | else: | ||
| + | return {' | ||
| + | |||
| + | @app.route('/ | ||
| + | def camlines(): | ||
| + | if request.method == ' | ||
| + | return get_file(' | ||
| + | elif request.method == ' | ||
| + | return save_file(' | ||
| + | else: | ||
| + | return {' | ||
| + | |||
| + | @app.route('/ | ||
| + | def sketchlines(): | ||
| + | if request.method == ' | ||
| + | return get_file(' | ||
| + | elif request.method == ' | ||
| + | return save_file(' | ||
| + | else: | ||
| + | return {' | ||
| + | | ||
| + | if __name__ == ' | ||
| + | app.run(host=' | ||
| + | |||
| + | </ | ||
| + | 10-30行目が読み出しや保存に関するコード\\ | ||
| + | 39-73行目はどのURLにアクセスして来たらどういう動き(どのファイルを保存・読み出しするか)かを定義しています。\\ | ||
| + | |||
| ===== 第七項 スターターコードの完成 ===== | ===== 第七項 スターターコードの完成 ===== | ||
| + | |||
| + | 第四項のコードからの変更として、\\ | ||
| + | * 保存ファイルの読み出し先を変更する | ||
| + | ということを行います。\\ | ||
| + | <sxh Python; | ||
| + | import sys | ||
| + | import os | ||
| + | import json | ||
| + | import math | ||
| + | from datetime import datetime, timedelta, timezone | ||
| + | import csv | ||
| + | import glob | ||
| + | import numpy as np | ||
| + | |||
| + | def intersect(p1, | ||
| + | tc1 = (p1[0] - p2[0]) * (p3[1] - p1[1]) + (p1[1] - p2[1]) * (p1[0] - p3[0]) | ||
| + | tc2 = (p1[0] - p2[0]) * (p4[1] - p1[1]) + (p1[1] - p2[1]) * (p1[0] - p4[0]) | ||
| + | td1 = (p3[0] - p4[0]) * (p1[1] - p3[1]) + (p3[1] - p4[1]) * (p3[0] - p1[0]) | ||
| + | td2 = (p3[0] - p4[0]) * (p2[1] - p3[1]) + (p3[1] - p4[1]) * (p3[0] - p2[0]) | ||
| + | return tc1*tc2< | ||
| + | |||
| + | if __name__ == ' | ||
| + | | ||
| + | dirpath = ' | ||
| + | line_def_file = os.environ[' | ||
| + | | ||
| + | JST = timezone(timedelta(hours=+9), | ||
| + | print(sys.argv[1]+" | ||
| + | file_list = sorted(glob.glob(' | ||
| + | print(" | ||
| + | for filename in file_list: | ||
| + | print(filename) | ||
| + | | ||
| + | json_open = open(line_def_file, | ||
| + | result_json_file = open(' | ||
| + | linedef = json.load(json_open) | ||
| + | resultarr = {} | ||
| + | for lineidx, line in enumerate(linedef[' | ||
| + | linename=' | ||
| + | print(linename) | ||
| + | resultarr[linename]={} | ||
| + | | ||
| + | print(" | ||
| + | for lineidx, line in enumerate(linedef[' | ||
| + | linename=' | ||
| + | print(linename) | ||
| + | polygon = [] | ||
| + | print(line) | ||
| + | for point in line[' | ||
| + | polygon.append([point[' | ||
| + | |||
| + | for filename in file_list: | ||
| + | print(linename+" | ||
| + | if os.path.getsize(filename) == 0: | ||
| + | continue | ||
| + | | ||
| + | raw_json_open = open(filename, | ||
| + | raw_json = json.load(raw_json_open) | ||
| + | | ||
| + | for frame in raw_json: | ||
| + | jptime = datetime.fromtimestamp(frame[' | ||
| + | people = frame[' | ||
| + | for person in people: | ||
| + | | ||
| + | # | ||
| + | personpos = [(person[" | ||
| + | if person[' | ||
| + | if intersect(personpos, | ||
| + | resultarr[linename][person[' | ||
| + | resultarr[linename][person[' | ||
| + | resultarr[linename][person[' | ||
| + | resultarr[linename][person[' | ||
| + | else: | ||
| + | resultarr[linename][person[' | ||
| + | |||
| + | json.dump(resultarr, | ||
| + | | ||
| + | for linename, records in resultarr.items(): | ||
| + | print(linename) | ||
| + | f = open(" | ||
| + | write = csv.writer(f) | ||
| + | write.writerow([" | ||
| + | for tracking_id, | ||
| + | write.writerow([record[' | ||
| + | | ||
| + | f.close() | ||
| + | </ | ||
| + | 実際変更するところは20行目のみで、ファイルの参照先をtutorial内ではなく隣のdrawuiフォルダのcamlines.jsonに切り替えています。\\ | ||
| + | |||
| + | さて、これでカメラ座標系の設定としては洗練されたものになりましたが、そのまま見取り図座標での設定ができるようにコードを変更していきましょう。\\ | ||
| + | <sxh Python; | ||
| + | import sys | ||
| + | import os | ||
| + | import json | ||
| + | import math | ||
| + | from datetime import datetime, timedelta, timezone | ||
| + | import csv | ||
| + | import glob | ||
| + | import numpy as np | ||
| + | |||
| + | def intersect(p1, | ||
| + | tc1 = (p1[0] - p2[0]) * (p3[1] - p1[1]) + (p1[1] - p2[1]) * (p1[0] - p3[0]) | ||
| + | tc2 = (p1[0] - p2[0]) * (p4[1] - p1[1]) + (p1[1] - p2[1]) * (p1[0] - p4[0]) | ||
| + | td1 = (p3[0] - p4[0]) * (p1[1] - p3[1]) + (p3[1] - p4[1]) * (p3[0] - p1[0]) | ||
| + | td2 = (p3[0] - p4[0]) * (p2[1] - p3[1]) + (p3[1] - p4[1]) * (p3[0] - p2[0]) | ||
| + | return tc1*tc2< | ||
| + | |||
| + | if __name__ == ' | ||
| + | | ||
| + | dirpath = ' | ||
| + | line_def_file = os.environ[' | ||
| + | | ||
| + | JST = timezone(timedelta(hours=+9), | ||
| + | print(sys.argv[1]+" | ||
| + | file_list = sorted(glob.glob(' | ||
| + | print(" | ||
| + | for filename in file_list: | ||
| + | print(filename) | ||
| + | | ||
| + | json_open = open(line_def_file, | ||
| + | result_json_file = open(' | ||
| + | linedef = json.load(json_open) | ||
| + | resultarr = {} | ||
| + | for lineidx, line in enumerate(linedef[' | ||
| + | linename=' | ||
| + | print(linename) | ||
| + | resultarr[linename]={} | ||
| + | | ||
| + | print(" | ||
| + | for lineidx, line in enumerate(linedef[' | ||
| + | linename=' | ||
| + | print(linename) | ||
| + | polygon = [] | ||
| + | print(line) | ||
| + | for point in line[' | ||
| + | polygon.append([point[' | ||
| + | |||
| + | for filename in file_list: | ||
| + | print(linename+" | ||
| + | if os.path.getsize(filename) == 0: | ||
| + | continue | ||
| + | | ||
| + | raw_json_open = open(filename, | ||
| + | raw_json = json.load(raw_json_open) | ||
| + | | ||
| + | for frame in raw_json: | ||
| + | jptime = datetime.fromtimestamp(frame[' | ||
| + | people = frame[' | ||
| + | for person in people: | ||
| + | | ||
| + | # | ||
| + | personpos = [person[" | ||
| + | if person[' | ||
| + | if intersect(personpos, | ||
| + | resultarr[linename][person[' | ||
| + | resultarr[linename][person[' | ||
| + | resultarr[linename][person[' | ||
| + | resultarr[linename][person[' | ||
| + | else: | ||
| + | resultarr[linename][person[' | ||
| + | |||
| + | json.dump(resultarr, | ||
| + | | ||
| + | for linename, records in resultarr.items(): | ||
| + | print(linename) | ||
| + | f = open(" | ||
| + | write = csv.writer(f) | ||
| + | write.writerow([" | ||
| + | for tracking_id, | ||
| + | write.writerow([record[' | ||
| + | | ||
| + | f.close() | ||
| + | </ | ||
| + | 20行目参照JSONファイルの変更と61行目人物の座標を座標を見取り図上のものを利用するだけの変更するだけにとどまりました。\\ | ||
| + | これでスターターコードは完成です。\\ | ||
| + | |||
| + | |||
| ===== 第八項 エリア検知のUI連携 ===== | ===== 第八項 エリア検知のUI連携 ===== | ||
| - | ===== 第九項 カメラ座標から見取り図座標への切り替え | + | ついでにエリア検知もUIで設定できるようにしましょう。変更箇所の解説はあまりいらないかと思いますので、結果プログラムを見てください。 |
| + | <sxh Python; | ||
| + | import sys | ||
| + | import os | ||
| + | import json | ||
| + | import cv2 | ||
| + | import math | ||
| + | from datetime import datetime, timedelta, timezone | ||
| + | import csv | ||
| + | import glob | ||
| + | import numpy as np | ||
| + | |||
| + | if __name__ | ||
| + | |||
| + | dirpath | ||
| + | duration | ||
| + | area_def_file | ||
| + | |||
| + | JST = timezone(timedelta(hours=+9), | ||
| + | print(sys.argv[1]+" | ||
| + | file_list = sorted(glob.glob(' | ||
| + | print(" | ||
| + | for filename in file_list: | ||
| + | print(filename) | ||
| + | |||
| + | json_open = open(area_def_file, | ||
| + | result_json_file = open(' | ||
| + | areadef = json.load(json_open) | ||
| + | resultarr = {} | ||
| + | for areaidx, area in enumerate(areadef[' | ||
| + | areaname=' | ||
| + | print(areaname) | ||
| + | resultarr[areaname]={} | ||
| + | print(" | ||
| + | for areaidx, area in enumerate(areadef[' | ||
| + | areaname=' | ||
| + | print(areaname) | ||
| + | polygon = [] | ||
| + | for point in area[' | ||
| + | polygon.append([point[' | ||
| + | |||
| + | contour = np.array(polygon) | ||
| + | print(contour) | ||
| + | |||
| + | for filename in file_list: | ||
| + | print(areaname+" | ||
| + | if os.path.getsize(filename) == 0: | ||
| + | continue | ||
| + | |||
| + | raw_json_open = open(filename, | ||
| + | raw_json = json.load(raw_json_open) | ||
| + | |||
| + | for frame in raw_json: | ||
| + | jptime = datetime.fromtimestamp(frame[' | ||
| + | people = frame[' | ||
| + | for person in people: | ||
| + | |||
| + | # | ||
| + | personpos = [person[" | ||
| + | test = cv2.pointPolygonTest(contour, | ||
| + | if test> | ||
| + | if person[' | ||
| + | resultarr[areaname][person[' | ||
| + | resultarr[areaname][person[' | ||
| + | resultarr[areaname][person[' | ||
| + | if resultarr[areaname][person[' | ||
| + | resultarr[areaname][person[' | ||
| + | else: | ||
| + | resultarr[areaname][person[' | ||
| + | |||
| + | json.dump(resultarr, | ||
| + | |||
| + | for areaname, records in resultarr.items(): | ||
| + | print(areaname) | ||
| + | f = open(" | ||
| + | write = csv.writer(f) | ||
| + | write.writerow([" | ||
| + | for tracking_id, | ||
| + | write.writerow([record[' | ||
| + | |||
| + | f.close() | ||
| + | </ | ||