VS codeのターミナルでbyobuを使う
VS codeの設定
VS codeのターミナル(統合/内蔵ターミナル)でbyoubuを使おうとすると、F3などのキーがVS code側に取られてしまい、byobuの操作ができません。次の設定を行うことで解決できます。
VS codeの、設定 > ターミナル >
Integrated: Allow Chordsのチェックを外す。
Integrated: Commands To Skip Shell
「項目の追加」をクリックし、次のコマンドを入力。
-workbench.action.quickOpen -workbench.action.terminal.findNext -workbench.action.terminal.findPrevious -workbench.action.debug.continue -workbench.action.debug.restart -workbench.action.debug.run -workbench.action.debug.start -workbench.action.debug.stop -workbench.action.focusNextPart -workbench.action.focusPreviousPart -workbench.action.debug.pause -workbench.action.quickOpen -workbench.action.terminal.focusFind
これらは、おおよそ次のキー入力を、VS codeのキーバインドから外す、という設定です。コマンドの頭に"-"をつけることで外す意味になっています。 (shift/ctrl+)F2/F3/F4/F5/F6/F12 ctrl+p/n/f/b/a/e/d/h
統合ターミナルで横取りされるキーバインドと調べ方
どのキーバインドがVS code側に取られてしまうのかは、上記Integrated: Commands To Skip Shellの「既定の設定(JSON)」をクリックするとdefaultSettings.json内の「既定でスキップされるコマンド」にすべて記載されています。
残念ながら、これらのコマンドにはバインドされているキーが記載されていません。キーバインドの詳細は、shift+ctrl+pなどで「基本設定:既定のキーボードショートカットを開く(JSON)」で「既定のキーバインド」を開くとわかります。
統合ターミナルで入力したキーがVS code側に取られてしまうな、と思ったら、「既定のキーバインド」でそのキーを探し、該当するコマンドが「既定の設定(JSON)」にあればそれを上記の「項目の追加」で頭に"-"をつけて追加する、という感じです。
USBカメラの映像の背景差し替え
USBカメラの映像の背景を、指定した画像ファイルに置き換えてみました。
googleによるセマンティックセグメンテーションDeepLabV3+を使用しました。次のところの、Keras版を使用しました。
GitHub - bonlime/keras-deeplab-v3-plus: Keras implementation of Deeplab v3+ with pretrained weights
使用したPCのスペックは次の通り。
使用したライブラリなど。
このQiitaの記事を参考にさせていただきました。
keras-deeplab-v3-plusで人だけとってみる(ソース有り) - Qiita
プログラムは次の通り。
# -*- coding: utf-8 -*- """ 背景差し替えプログラム USBカメラの映像で、人物以外の背景を指定した画像ファイルに差し替えます。 引数に差し替えたい背景画像ファイル名をしてしてください。 $ python vwall.py 背景画像ファイル名 必要なライブラリ python(3.6以上) opencv(4.x) tensorflow(1.13.1)またはtensorflor-gpu(1.13.1) keras(2.2.4) Keras版DeepLabV3+(2020年5月のリポジトリで、tensorflow 1.x用ブランチ) https://github.com/bonlime/keras-deeplab-v3-plus """ import cv2 import numpy as np import model from keras.applications.mobilenetv2 import preprocess_input import sys if len(sys.argv) < 2: print("壁紙ファイル名を指定してください。") sys.exit(1) wallfile = sys.argv[1] def inference(model_dlv3, img): """ 推論実施 :param model_dlv3: DeepLabV3のモデル :param img: カメラからの入力画像 :return: 人物部分のマスク画像 """ img = np.array(img, dtype='float32') img = preprocess_input(img) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (512, 512)) predicted = model_dlv3.predict(img[np.newaxis, ...]) person_score = predicted[0, :, :, 15] back_score = predicted[0, :, :, 0] mask = (person_score > back_score).astype("uint8") * 255 mask = cv2.resize(mask, (640, 480)) return mask def main(): wall = cv2.imread(wallfile) wall = cv2.resize(wall, (640, 480)) cap = cv2.VideoCapture(0) model_dlv3 = model.Deeplabv3() while(cap.isOpened()): img_camera = cap.read()[1] img_camera = cv2.resize(img_camera, (640, 480)) mask = inference(model_dlv3, img_camera) mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) n_mask = cv2.bitwise_not(mask) wall_mask = cv2.bitwise_and(wall, n_mask) person = cv2.bitwise_and(img_camera, mask) output = cv2.bitwise_or(person, wall_mask) cv2.imshow("camera input", img_camera) cv2.imshow("output", output) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() if __name__ == '__main__': main()
Linux Kernel 4.19のリアルタイム性能
cyclictestによるリアルタイム性能測定
rt-testsパッケージに含まれるcyclictestとhackbenchを使ってDebian 10(Linux Kernel 4.19)のリアルタイム性能を見てみました。
結論から言うと、latencyが1ms程度の精度で良いのであれば、PREEMPT RT Kernelではない、標準Kernel(Voluntary PREEMPT)でも十分と思います。2000年代のKernel 2.4の頃は、PREEMPT RT Kernelでないとリアルタイム性能はダメダメだったと思うのですが、今のKernelはあまり精度が必要のないかなり緩いソフトリアルタイムなら十分な性能です。
cyclictest/hackbenchコマンド
cyclictestは、nanosleepまたはclock_nanosleepを使用して、指定した時間sleepしたあとどれぐらいの時間ずれがあるのかを繰り返し測定するコマンドです。
使用例
$ sudo cyclictest --smp -m -l 30000 # /dev/cpu_dma_latency set to 0us policy: other/other: loadavg: 0.12 0.08 1.00 1/245 1587 T: 0 ( 1585) P: 0 I:1000 C: 2152 Min: 27 Act: 64 Avg: 63 Max: 85 T: 1 ( 1586) P: 0 I:1500 C: 1421 Min: 32 Act: 63 Avg: 65 Max: 125
Ctrl-cで停止します。"-p 49"のようにオプションをつけると、SCHED_FIFOでプライオリティ49で動作します。このオプションなしの場合、通常のSCHED_OTHERで動作します。
使用例:SCHED_FIFOの場合
$ sudo cyclictest --smp -m -l 30000 -p 49 # /dev/cpu_dma_latency set to 0us policy: fifo: loadavg: 0.00 0.04 0.81 1/245 1596 T: 0 ( 1595) P:49 I:1000 C: 1676 Min: 5 Act: 6 Avg: 5 Max: 19 T: 1 ( 1596) P:49 I:1500 C: 1103 Min: 5 Act: 5 Avg: 5 Max: 43
また、hackbenchはCPUに負荷をかけるコマンドです。 使用例
$ hackbench -l 100000 Running in process mode with 10 groups using 40 file descriptors each (== 400 tasks) Each sender will pass 100000 messages of 100 bytes ここでCtrl-cにより停止すると、次の表示。 Signal 2 caught, longjmp'ing out! longjmp'ed out, reaping children sending SIGTERM to all child processes signaling 400 worker threads to terminate Time: 1.985
オプション"-l"で実行回数を指定します。
測定条件
次の条件でリアルタイム性能を測定しました。
- ハードウェア:ECS Liva bat-mini:Celeron N2807 1.58GHz / RAM 2GB / eMMC 32GB
- Debian GNU/Linux 10 (buster):Kernel 4.19
- デスクトップが表示されている状態でsshによりログインし、コマンド実行。
- 標準のKernelと、PREEMPT_RT Kernelで実行。
- 何もアプリを起動していない状態と、hackbenchによりCPU負荷100%の状態でcyclictestを実行。
- SCHED_OTHERと、SCHED_FIFO・プライオリティ49の2種類でcyclictestを実行。
- ループ回数30000回。
標準Kernel
まずは標準Kernelの結果から。 uname -aの結果は次の通り。
$ uname -a Linux XXX 4.19.0-8-amd64 #1 SMP Debian 4.19.98-1+deb10u1 (2020-04-27) x86_64 GNU/Linux
標準Kernelのconfigは次の通り。
CONFIG_HIGH_RES_TIMERS=y # CONFIG_PREEMPT_NONE is not set CONFIG_PREEMPT_VOLUNTARY=y # CONFIG_PREEMPT is not set
標準Kernel:負荷なし:通常プロセス(SCHED_OTHER)
T: 0 ( 1795) P: 0 I:1000 C: 30000 Min: 10 Act: 55 Avg: 63 Max: 9281 T: 1 ( 1796) P: 0 I:1500 C: 19955 Min: 26 Act: 56 Avg: 72 Max: 9602
Celeron N2807は2コア2スレッドのため、Tは0と1の2つ。IはIntervalでCはCount。Min/Avg/Maxはそれぞれ最小/平均/最大のlatency(μs)で、Actはループごとのlatencyで、実行中はこの部分が更新され続けます。
latencyは平均63と72μsということですが、最大は9281と9602と9msを超えています。
標準Kernel:負荷100%:通常プロセス(SCHED_OTHER)
T: 0 ( 2454) P: 0 I:1000 C: 30000 Min: 48 Act: 67 Avg: 1525 Max: 101095 T: 1 ( 2455) P: 0 I:1500 C: 27390 Min: 56 Act: 1822 Avg: 1272 Max: 59227
CPU負荷100%では、平均1525と1272と1msを超えており、最大は101095と59227と100msを超える場合が出ています。
標準Kernel:負荷なし:リアルタイムプロセス(SCHED_FIFOプライオリティ49)
T: 0 ( 1928) P:49 I:1000 C: 30000 Min: 3 Act: 5 Avg: 5 Max: 289 T: 1 ( 1929) P:49 I:1500 C: 20010 Min: 4 Act: 5 Avg: 5 Max: 369
SCHED_FIFOにすると負荷なしの状態では平均5μs、最大でも289と369μsとなります。
標準Kernel:負荷100%:リアルタイムプロセス(SCHED_FIFOプライオリティ49)
T: 0 ( 2463) P:49 I:1000 C: 30000 Min: 6 Act: 18 Avg: 14 Max: 446 T: 1 ( 2464) P:49 I:1500 C: 19266 Min: 7 Act: 16 Avg: 14 Max: 32
SCHED_FIFOなら負荷100%でも平均14μs、最大446と32μsとなり、latency 1ms以下で良いようなシステムでは十分な性能となっています。
以上は標準Kernelの結果です。リアルタイムプロセスSCHED_FIFOにすればそこそこいい性能と思います。
PREEMPT RT Kernel
次にPREEMPT_RTパッチ適用版のKernelです。Debianの場合、PREEMPT_RTパッチ適用版Kernelがaptコマンドで次のように簡単にインストールできます。
$ sudo apt install linux-image-rt-amd64
uname -a の結果は次の通り。
$ uname -a Linux XXX 4.19.0-8-rt-amd64 #1 SMP PREEMPT RT Debian 4.19.98-1+deb10u1 (2020-04-27) x86_64 GNU/Linux
Kernelのconfig
CONFIG_HIGH_RES_TIMERS=y CONFIG_PREEMPT=y CONFIG_PREEMPT_RT_BASE=y CONFIG_HAVE_PREEMPT_LAZY=y CONFIG_PREEMPT_LAZY=y # CONFIG_PREEMPT_NONE is not set # CONFIG_PREEMPT_VOLUNTARY is not set # CONFIG_PREEMPT__LL is not set # CONFIG_PREEMPT_RTB is not set CONFIG_PREEMPT_RT_FULL=y CONFIG_PREEMPT_COUNT=y
PREEMPT RT Kernel:負荷なし:通常プロセス(SCHED_OTHER)
T: 0 ( 837) P: 0 I:1000 C: 30000 Min: 10 Act: 64 Avg: 64 Max: 5289 T: 1 ( 838) P: 0 I:1500 C: 19987 Min: 12 Act: 65 Avg: 65 Max: 7399
標準Kernelと大差ありません。
PREEMPT RT Kernel:負荷100%:通常プロセス(SCHED_OTHER)
T: 0 ( 1248) P: 0 I:1000 C: 30000 Min: 36 Act: 72 Avg: 1032 Max: 150414 T: 1 ( 1249) P: 0 I:1500 C: 26185 Min: 65 Act: 71 Avg: 656 Max: 68300
平均latencyが、標準Kernelより若干良いぐらい。
PREEMPT RT Kernel:負荷なし:リアルタイムプロセス(SCHED_FIFOプライオリティ49)
T: 0 ( 843) P:49 I:1000 C: 30000 Min: 4 Act: 6 Avg: 5 Max: 75 T: 1 ( 844) P:49 I:1500 C: 19997 Min: 4 Act: 6 Avg: 5 Max: 22
標準Kernelの同条件と比べると、最大latencyが75と22μsと、一桁少なくなっています。
PREEMPT RT Kernel:負荷100%:リアルタイムプロセス(SCHED_FIFOプライオリティ49)
T: 0 ( 1253) P:49 I:1000 C: 30000 Min: 7 Act: 15 Avg: 12 Max: 41 T: 1 ( 1254) P:49 I:1500 C: 16310 Min: 7 Act: 15 Avg: 12 Max: 29
標準Kernelの同条件と比べると、平均latencyが14μsに対して12μs、最大latencyが41と29μsと100μs以下になっています。
以上から、標準KernelよりもPREEMPT RT Kernelの方が、リアルタイムプロセスの最大latencyが少し良いという結果です。
PythonとOpenCVで車線検出
PythonとOpenCVで、車線を検出するプログラムを作成しました。チョー単純なロジックなので、条件が想定外になると検出できないんですが、ある程度検出できています。
映像から1フレームの画像を取り出し、グレースケールに変換してからヒストグラムを平坦化します。空の部分は不要なので消去します。それとは別に、フレームの画像をHSVに変換してから一定の範囲の黄色だけを抽出します。これは、白線の検出用にグレースケールに変換してしまうと、黄色がわからなくなるため、別途黄色だけ抽出する、ということです。
抽出した黄色部分もグレースケールに変換してから、Cannyを使用して輪郭を抽出します。その輪郭画像に対してHoughLinesを使用して直線を検出しています。
使用している道路の映像は、PS4のゲーム、The Crew2のものです。画像処理用としては、実写の映像よりもだいぶ条件がいい状態です。実写の映像を自分で撮影してくるのはメンドイので横着しました。
# -*- coding: utf-8 -*- """ 車線検出 Cannyによるエッジ検出とHoughLinesによる直線検出 python 3.6 opencv 4.1 """ import cv2 import numpy as np import sys # コマンドライン引数 1番目=入力映像ファイル名 2番目=出力映像ファイル名 3番目=出力輪郭線ファイル名 args = sys.argv if len(args) == 1: sys.exit(1) input_file = args[1] output_file = None contour_file = None if len(args) >= 3: output_file = args[2] if len(args) == 4: contour_file = args[3] def equalize(img_org): """ 平坦化 """ img_tmp = cv2.cvtColor(img_org, cv2.COLOR_BGR2GRAY) # ヒストグラム平坦化 img_tmp = cv2.equalizeHist(img_tmp) # 画面の上半分消去 count = np.array([[0, 0], [1279, 0], [1279, 340], [0, 340]]) img_tmp = cv2.fillPoly(img_tmp, pts=[count], color=(0)) return img_tmp def yellow(img_org): """ 黄色線抽出 HSVに変換してから黄色だけを抽出する """ img_hsv = cv2.cvtColor(img_org, cv2.COLOR_BGR2HSV) # 日本の道路の黄色線 # lower_color = np.array([0, 40, 50], np.uint8) # upper_color = np.array([15, 200, 200], np.uint8) # The Crew2の黄色線 lower_color = np.array([15, 100, 50], np.uint8) upper_color = np.array([40, 200, 200], np.uint8) # マスク画像の生成 img_mask = cv2.inRange(img_hsv, lower_color, upper_color) # フレーム画像とマスク画像の共通の領域を抽出 img_tmp = cv2.bitwise_and(img_hsv, img_hsv, mask=img_mask) img_tmp = cv2.cvtColor(img_tmp, cv2.COLOR_BGR2GRAY) ret, img_tmp = cv2.threshold(img_tmp, 10, # 閾値 256, # 画素値の最大値 cv2.THRESH_BINARY) # 2値化type # 画面の上半分消去 count = np.array([[0, 0], [1279, 0], [1279, 340], [0, 340]]) img_tmp = cv2.fillPoly(img_tmp, pts=[count], color=(0)) return img_tmp def road(img): """ 車線検出・描画 HoughLinesを使用して一定の長さの直線を検出・描画 """ road = np.zeros((720, 1280, 3), np.uint8) lines = cv2.HoughLines(img, 1, # lines np.pi/180, # rho 150) # theta if lines is not None: for line in lines: for rho, theta in line: a = np.cos(theta) b = np.sin(theta) x0 = a*rho y0 = b*rho x1 = int(x0 + 2000*(-b)) y1 = int(y0 + 2000*(a)) x2 = int(x0 - 2000*(-b)) y2 = int(y0 - 2000*(a)) # 水平に近い線は描画しない if abs(a) < 0.15: continue # print("rho,theta,a,b,x0,y0", rho, theta, a, b, x0, y0) cv2.line(road, (x1, y1), (x2, y2), (0 ,0, 255), 2) # 画面の上半分消去 count = np.array([[0, 0], [1279, 0], [1279, 340], [0, 340]]) road = cv2.fillPoly(road, pts=[count], color=(0, 0, 0)) return road def main(): cap = cv2.VideoCapture(input_file) # 描画した映像の録画用 fmt = cv2.VideoWriter_fourcc("m", "p", "4", "v") size = (1280, 720) if output_file is not None: output_writer = cv2.VideoWriter(output_file, fmt, 30, size) if contour_file is not None: contour_writer = cv2.VideoWriter(contour_file, fmt, 30, size) while(cap.isOpened()): # 1フレームの画像読み込み img_org = cap.read()[1] if img_org is None: break # グレースケールに変換してから平坦化 img_tmp = equalize(img_org) # 黄色線は別に抽出してからグレースケールに変換 img_yellow = yellow(img_org) # 2つの画像をorする img_tmp = cv2.bitwise_or(img_tmp, img_yellow) # blurをかけて細かな部分をつぶす img_tmp = cv2.blur(img_tmp, ksize=(5, 5)) # 輪郭抽出 img_for_draw = cv2.Canny(img_tmp, 200, # threshold1 255, # threshold2 apertureSize = 3) # 車線検出・描画 road_lines = road(img_for_draw) # グレースケールからRGBに変換 img_for_draw = cv2.cvtColor(img_for_draw, cv2.COLOR_GRAY2BGR) # 元の画像と描画した車線を合成 img = cv2.bitwise_or(img_org, road_lines) cv2.imshow("img", img) # 映像ファイルに保存 if output_file is not None: output_writer.write(img) if contour_file is not None: contour_writer.write(img_for_draw) # qキーが押されたら終了 if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() if __name__ == '__main__': main()