その他
デプスカメラを「雄弁なスチルカメラ」として利用する
デプスカメラ Intel RealSense D415 を使ってあれこれを試しているうちにふとこんなことを考えました。
RealSense D400 シリーズの出力からは、(1)一般的な RGB カラー画像 (2)自然光照度の影響を受けにくい赤外線画像、そして、(3)被写体の位置関係が色彩的に表現された深度情報画像を得ることができます。この三種類の情報を照合するとスコープ内の一瞬の状況を多角的にとらえることが可能です。例を示します。
このユニークな特性を以下の想定のもとで「ゆるい見守り」に活かせるのではないかと考えました。
前に人感センサを利用した安否確認のしくみを手がけたことがあります。これは現在もプライベートで利用を続けているのですが、そこにこれらの画像情報が加われば安心感がずっと大きくなりそうです。
Raspberry Pi 3 B+ のセットアップ
上のような形で一式を設置・運用するとすれば圧迫感の小さい小振りなホスト機と組み合わせることが好ましいでしょう。幸い RealSense D400 シリーズは Raspberry PI 3 にも対応しています。
さっそく手持ちの Raspberry Pi 3 B+ の環境設定に着手しました。完了までに当初の想定よりも手がかかった事情もありメモを残しておきます。
「realsense raspberry pi 3」のキーワードで情報を検索すると、上の記事と同様にラズパイで RealSense D400 シリーズを利用するための OS 環境として Ubuntu Mate 16.x を使う記事が多くヒットする。まずはこれらの記事を参考に準備を進めたが、結論として Raspberry Pi 3 B+ で Ubuntu Mate 16.x を起動することはできなかった。不審に思いながら情報を探して目にした「まろにー」様による次の記事の内容は正確だった。
さらに情報をさぐり以下の記事に行き着いた。Raspbian の使用を前提とする内容で、「These steps are for D435 with Raspberry Pi3 and 3+」とある。
- Raspberry 3 B+ 現段階ではUbuntu Mate 16.04.2(Xenial)は起動しない (2018-09-09) - shittaca.seesaa.net
この記事の内容は最終的には正確だった。ただしこうした話題にありがちな落とし穴もいくつかあった。備忘をかねその内容を以下に控える。
- Raspbian(RaspberryPi3) Installation - github.com/IntelRealSense
- 現在 www.raspberrypi.org で配布されている「Raspbian Buster with desktop」には適用できない。記事中の「4.14.34-v7+」に近い旧バージョンを探し「2018-11-13-raspbian-stretch-full (4.14.79-v7+) 」を試したところ成功した
- 一連の手順でのビルドには時間がかかることに注意。手元では試行錯誤分のロスを含めトータルで 20時間程度を要した
- 記事末尾の指示どおりに raspi-config コマンドで OpenGL Driver を有効にすると OpenCV を利用したプログラムでの表示に不具合が生じる。@PINTO 様による以下の記事にこれに関連する記述がみられる
手元では OpenGL よりも OpenCV に馴染んでいるため逆に OpenGL ドライバを無効化 ([raspi-config] - [7.Advanced Options] - [A7 GL Driver] - [G3 Legacy])し、プログラミングには OpenCV を利用している。一方でこの設定では公式の RealSense Viewer 等が激重になるため適宜使い分けが必要。
- デプスカメラRealSenseD435で・・ (記事名を一部省略させて頂きました) - qiita.com/PINTO
【注意点】 OpenGL Driver を有効にすると、OpenCVの 「imshow」 メソッドが正常に動作しなくなる点を受け入れる必要がある。 こちらの記事の実装 のように、OpenCVの 「imshow」 メソッドに頼らないでOpenGLの描画系メソッドだけで描画処理を実装しなくてはならない点に注意。
こういった過程を経て手元の Raspberry Pi 3 B+ で D415 を利用できる状態になりました。手頃なサイズでいい感じです。
下の動画にはこの環境で公式の Intel RealSense Viewer を実行した様子を収めています。なお、ここでは前掲の事情によりラズパイ側で OpenGL Driver を有効にしています。
D400 からの赤外線投光と深度情報精度との関係
RealSense D400 シリーズは深度計測にふたつの赤外線センサ(カメラ)を用いたステレオアクティブ方式を採用しており、計測精度向上の補助を目的とするドットパターンの赤外線投光を行うことが可能となっています。この投光はデフォルトで有効です。
前述の想定のように今回は RGB, 赤外線, 深度のみっつの画像情報をあわせて捕捉することがポイントですが、この赤外線投光機能は赤外線カメラ経由で得られる画像と深度情報の両方に影響を及ぼします。手元での観察結果からこのことを整理してみます。
まず、赤外線投光を有効化 / 無効化した状態で D415 から採取した各フレームの静止画像例を以下に示します。赤外線画像はふたつの赤外線カメラの左側のものを使用。なお、この製品の赤外線カメラはあくまでも深度計測を目的とするものであり生のままの画像は視覚的に暗いため、OpenCV で明度とコントラストを補正した版を並べて掲げています。(画像はいずれもクリックで大きく表示)
RGB
|
深度 | 赤外線 L | 赤外線 L(補正ずみ) |
RGB
|
深度 | 赤外線 L | 赤外線 L (補正ずみ) |
上のサンプルから次のように判断しました。
ちなみに、赤外線投光の有効・無効をストリーミングの途中で切り替えることはできません。サンプルを採取するために用意した Python コードを以下に引用します。
#!/usr/bin/env python # -*- coding: utf-8 -*- # # D415_IRemitterTest.py # # for Intel RealSense D415 # # 2020-03 # import pyrealsense2 as rs import numpy as np import cv2 # 赤外線投光の有無 USE_IR_EMITTER = True #USE_IR_EMITTER = False WIDTH = 640 HEIGHT = 480 FPS = 15 # イメージの明度・コントラストを補正 # https://www.pynote.info/entry/opencv-change-contrast-and-brightness def adjust(img, alpha=1.0, beta=0.0): dst = alpha * img + beta return np.clip(dst, 0, 255).astype(np.uint8) def main(): # RealSense ストリーミング開始 pipeline = rs.pipeline() config = rs.config() config.enable_stream(rs.stream.depth, WIDTH, HEIGHT, rs.format.z16, FPS) config.enable_stream(rs.stream.color, WIDTH, HEIGHT, rs.format.bgr8, FPS) config.enable_stream(rs.stream.infrared, 1, WIDTH, HEIGHT, rs.format.y8, FPS) profile = pipeline.start(config) device = profile.get_device() depth_sensor = device.first_depth_sensor() if USE_IR_EMITTER: # IR 投光を最大に depth_sensor.set_option(rs.option.emitter_enabled, 1) laser_range = depth_sensor.get_option_range(rs.option.laser_power) depth_sensor.set_option(rs.option.laser_power, laser_range.max) else: # IR 投光を無効に depth_sensor.set_option(rs.option.emitter_enabled, 0) try: while True: # RealSense フレームデータ取得 frames = pipeline.wait_for_frames() depth_frame = frames.get_depth_frame() color_frame = frames.get_color_frame() ir_frame = frames.get_infrared_frame() depth_img_raw = np.asanyarray(depth_frame.get_data()) color_img = np.asanyarray(color_frame.get_data()) ir_img_raw = np.asanyarray(ir_frame.get_data()) depth_img = cv2.applyColorMap(cv2.convertScaleAbs(depth_img_raw, alpha=0.08), cv2.COLORMAP_JET) # IR フレームイメージの明度を調整 ir_img = adjust(ir_img_raw, alpha=2.0, beta=40.0) # 各フレームイメージを表示 cv2.imshow('RGB', color_img) cv2.imshow('IRRaw', ir_img_raw) cv2.imshow('IR', ir_img) cv2.imshow('Depth', depth_img) # キー入力判定 key = cv2.waitKey(1) if key & 0xFF == ord('q') or key == 27: # ESC cv2.destroyAllWindows() break elif key & 0xFF == ord('s'): # フレームイメージを画像出力 cv2.imwrite('ImgRGB.jpg', color_img) cv2.imwrite('ImgIRRaw.jpg', ir_img_raw) cv2.imwrite('ImgIR.jpg', ir_img) cv2.imwrite('ImgDepth.jpg', depth_img) finally: pipeline.stop() if __name__ == '__main__': main()
動体検知について
見守りに際しもっともシリアスな状況と考えられるのは「対象者がまったく動かない状態」でしょう。そのため、室内の明るさにかかわらずスコープ内の動きの有無を適切に把握できるようにしておきたいと考えました。その手段として赤外線カメラからの出力をもとに動体検知を行うことを思い立ちました。
こういった処理を手元で扱った経験はありませんでしたが、ネット上の情報を参考に考え方を整理してまず以下の内容での簡易的な実装を試みました。あくまでもフレーム間の情報の変化量を根拠とする内容で人体検出等の要素は含みません。
※各パラメータは実地で適切な値を割り出す
上の要領を画像と動画の例で示してみます。
1. 前回分のフレーム画像
|
2. 今回分の画像 (体と手を少し動かしてみた)
|
3. 上記 1, 2 画像の差分
|
4. ピクセル値を所定の閾値で 0 と 255 に分ける
|
試作
内容
ここまでの話題を元に以下の想定に基づく内容のアプリケーションを試作しました。MQTT ブローカーには定番の Beebotte を利用します。JavaScript 用の API セットも提供されているため Web ブラウザ上のコンテンツでメッセージを取り回すことが可能です。
メモ
関連する話題を控えます。
以下のように書き換えると表示されます。
http://drive.google.com/uc?export=view&id={ID}
ソースコード
以上を踏まえざっくり用意したプログラムです。
#!/usr/bin/env python # -*- coding: utf-8 -*- # # AnpiRealSense.py # # for Intel RealSense D415 # # 2020-03 # import threading import datetime import time import os import requests import pyrealsense2 as rs import numpy as np import cv2 import paho.mqtt.client as mqtt import json from pydrive.auth import GoogleAuth from pydrive.drive import GoogleDrive # for RealSense WIDTH = 640 HEIGHT = 480 FPS = 15 # for MQTT MQTT_TOKEN = 'token_w5xxxxxxxxxx' MQTT_HOSTNAME = 'mqtt.beebotte.com' MQTT_PORT = 8883 MQTT_TOPIC = 'Anpi/q01' MQTT_CACERT = 'mqtt.beebotte.com.pem' MQTT_URL_PUB = 'https://script.google.com/xxxx/exec' # for Google Drive ID_RGB = '1YTEcpNyVv5axxxxxxxxxx' ID_IR = '1qt0tzv5-DfJxxxxxxxxxx' ID_DEPTH = '17A8Xybfz6qoxxxxxxxxxx' ID_RGB_PREV = '1sxb67Zspim2xxxxxxxxxx' ID_IR_PREV = '1A9C-9vgo97Kxxxxxxxxxx' ID_DEPTH_PREV = '1oJSOeebzL0exxxxxxxxxx' ID_RGB_MOVE = '1cMLgJB3_zcDxxxxxxxxxx' ID_IR_MOVE = '1rbaVHzpD4m-xxxxxxxxxx' ID_DEPTH_MOVE = '1dSdfAOy4oHqxxxxxxxxxx' ID_IR_DIFF = '1JuZLW1g8eaOxxxxxxxxxx' FN_RGB = 'RGB' FN_IR = 'IR' FN_DEPTH = 'Depth' FN_SUFFIX = '.jpg' FN_SUFFIX_PREV = '_prev.jpg' FN_SUFFIX_MOVE = '_move.jpg' FN_SUFFIX_DIFF = '_diff.jpg' QUEUE = 0 Q_UPDATE = 100 Q_DETECT = 200 # MQTT コネクション確立時コールバック def on_mqtt_connect(client, userdata, flags, respons_code): client.subscribe(MQTT_TOPIC) print('mqtt on_connect') # MQTT メッセージ受信時コールバック def on_mqtt_message(client, userdata, msg): global QUEUE QUEUE = Q_UPDATE print('mqtt on_message') print(msg.topic + ' ' + str(msg.payload)) # イメージに日付時分表示を挿入 def putDateTime(img): dt_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M') cv2.putText(img, dt_str, (20, 36), cv2.FONT_HERSHEY_DUPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA ) cv2.putText(img, dt_str, (18, 34), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA ) # jpg ファイル群の世代を更新 def rotateFile(fname, img): fn = fname + FN_SUFFIX fn_prev = fname + FN_SUFFIX_PREV if os.path.exists(fn): if os.path.exists(fn_prev): os.remove(fn_prev) os.rename(fn, fn_prev) cv2.imwrite(fn, img) if os.path.exists(fn_prev): return True return False # Google Drive へ各フレームの jpg データをアップロード def upload_data(id, fname, gdrive): f = gdrive.CreateFile({'id': id}) f.SetContentFile(fname) f.Upload() print('upload_data [' + fname +']') def do_upload(mode, rgb_img, ir_img, depth_img, ir_img_diff, gdrive): print('do_upload start') putDateTime(rgb_img) putDateTime(ir_img) putDateTime(depth_img) if mode == Q_UPDATE: # 任意の時点でのスナップショット if rotateFile(FN_RGB, rgb_img): upload_data(ID_RGB_PREV, FN_RGB + FN_SUFFIX_PREV, gdrive) upload_data(ID_RGB, FN_RGB + FN_SUFFIX, gdrive) if rotateFile(FN_DEPTH, depth_img): upload_data(ID_DEPTH_PREV, FN_DEPTH + FN_SUFFIX_PREV, gdrive) upload_data(ID_DEPTH, FN_DEPTH + FN_SUFFIX, gdrive) if rotateFile(FN_IR, ir_img): upload_data(ID_IR_PREV, FN_IR + FN_SUFFIX_PREV, gdrive) upload_data(ID_IR, FN_IR + FN_SUFFIX, gdrive) elif mode == Q_DETECT: # 動体検知時 cv2.imwrite(FN_RGB + FN_SUFFIX_MOVE, rgb_img) cv2.imwrite(FN_DEPTH + FN_SUFFIX_MOVE, depth_img) cv2.imwrite(FN_IR + FN_SUFFIX_MOVE, ir_img) cv2.imwrite(FN_IR + FN_SUFFIX_DIFF, ir_img_diff) upload_data(ID_RGB_MOVE, FN_RGB + FN_SUFFIX_MOVE, gdrive) upload_data(ID_DEPTH_MOVE, FN_DEPTH + FN_SUFFIX_MOVE, gdrive) upload_data(ID_IR_MOVE, FN_IR + FN_SUFFIX_MOVE, gdrive) upload_data(ID_IR_DIFF, FN_IR + FN_SUFFIX_DIFF, gdrive) requests.post(MQTT_URL_PUB, data={'hoge':'1'}) print('do_upload done') # イメージの明度・コントラストを補正 # https://www.pynote.info/entry/opencv-change-contrast-and-brightness def adjust(img, alpha=1.0, beta=0.0): dst = alpha * img + beta return np.clip(dst, 0, 255).astype(np.uint8) def main(): global QUEUE # init MQTT print('init mqtt start') mqtt_client = mqtt.Client() mqtt_client.username_pw_set('token:%s'%MQTT_TOKEN) mqtt_client.on_connect = on_mqtt_connect mqtt_client.on_message = on_mqtt_message mqtt_client.tls_set(MQTT_CACERT) mqtt_client.connect(MQTT_HOSTNAME, port=MQTT_PORT, keepalive=60) print('init mqtt done') # init Google Drive print('init gdrive start') gauth = GoogleAuth() gauth.CommandLineAuth() gdrive = GoogleDrive(gauth) print('init gdrive done') # RealSense ストリーミング開始 print('rs streaming starting') pipeline = rs.pipeline() config = rs.config() config.enable_stream(rs.stream.depth, WIDTH, HEIGHT, rs.format.z16, FPS) config.enable_stream(rs.stream.color, WIDTH, HEIGHT, rs.format.bgr8, FPS) config.enable_stream(rs.stream.infrared, 1, WIDTH, HEIGHT, rs.format.y8, FPS) profile = pipeline.start(config) print('rs streaming started') # IR 投光を最大に device = profile.get_device() depth_sensor = device.first_depth_sensor() depth_sensor.set_option(rs.option.emitter_enabled, 1) laser_range = depth_sensor.get_option_range(rs.option.laser_power) depth_sensor.set_option(rs.option.laser_power, laser_range.max) ir_img_prev = np.empty(0) ir_img_diff = np.empty(0) time_start = time.time() time_deetected = 0 try: while True: mqtt_client.loop(0.1) # RealSense フレームデータ取得 frames = pipeline.wait_for_frames() depth_frame = frames.get_depth_frame() color_frame = frames.get_color_frame() ir_frame = frames.get_infrared_frame() if not depth_frame or not color_frame or not ir_frame: continue depth_img_raw = np.asanyarray(depth_frame.get_data()) color_img = np.asanyarray(color_frame.get_data()) ir_img_raw = np.asanyarray(ir_frame.get_data()) depth_img = cv2.applyColorMap(cv2.convertScaleAbs(depth_img_raw, alpha=0.08), cv2.COLORMAP_JET) # IR フレームイメージの明度を調整 ir_img = adjust(ir_img_raw, alpha=2.0, beta=40.0) # 動体検知 # ノイズよけにストリーミング開始から 10秒程度は看過 if ir_img_prev.size != 0 and time.time() - time_start > 10: # 直近の IR フレームイメージとの差分イメージを取得 ir_diff = cv2.absdiff(ir_img, ir_img_prev) # 明度 80 を閾値に 0, 255 に二値化 ret, ir_img_diff = cv2.threshold(ir_diff, 80, 255, cv2.THRESH_BINARY) cv2.imshow('diff', ir_img_diff) # 255 値ポイントのみの配列を生成 ar = ir_img_diff[ir_img_diff == 255] # 所定の件数以上なら動きありと判定 if ar.size > 200: #print(ar.size) # 前回アップロードから 30分未経過ならスキップ if time.time() - time_deetected >= 1800: print('motion detected') time_deetected = time.time() QUEUE = Q_DETECT # 今回分 IR フレームイメージを次回の比較用にコピー ir_img_prev = ir_img.copy() # 各フレームイメージを表示 cv2.imshow('RGB', color_img) cv2.imshow('IR', ir_img) cv2.imshow('Depth', depth_img) # キー入力判定 key = cv2.waitKey(1) if key & 0xFF == ord('q') or key == 27: # ESC cv2.destroyAllWindows() break elif key & 0xFF == ord('s'): QUEUE = Q_UPDATE # アップロード指示あり if QUEUE != 0: # Google Drive へ各フレームの jpg データをアップロード do_upload(QUEUE, color_img, ir_img, depth_img, ir_img_diff, gdrive) QUEUE = 0 time.sleep(0.2) # 200ms finally: pipeline.stop() print('exit') if __name__ == '__main__': main()
HTML リソース(アーカイブ)
下の動画で使用している HTML ページと実際に撮影した各フレーム画像を静的に再構成したアーカイブを下記へ収めています。
- デモページ ( target="_blank" )
上のデモページの末尾に今回の試作で使用した実際の HTML 記述をコメントとして引用しています(各種 ID はダミーです)。
動作の様子
(tanabe) |
オプティカルフローを簡易ジェスチャ認識に利用する
少し前に Intel 製デプスカメラ RealSense D415 と 3DiVi 社製 Nuitrack SDK の組み合わせでジェスチャ認識を試していました。下の動画には三脚に据えた D415 ごしに所定のジェスチャでスマート照明の操作をテストした様子を収めています。
このように期待どおりの反応は得られるのですが、次のことが気になっていました。
このあたりの事情は Kinect も同様のようです。もちろん骨格の検出は腕や手を適切に識別するために必要ですが、一方でもっとラフなものもあればと考えました。日常感覚ではコタツや布団にもぐり込んだまま姿勢を正すことなくスッと指示を出すといったこともできれば便利そうです。そんなわけで試しに簡単なしくみを作ってみることにしました。
考え方として、スコープ内の被写体に所定の水準以上の移動をざっくり検知した場合にその向きを判定することを想定しました。実現方法を考えながら社内でそういう話をしたところ、「OpenCV でオプティカルフローを利用してはどうか?」というレスポンスがありました。手元ではトラッキング API (リンク: @nonbiri15 様によるドキュメントの和訳) を試していたところでオプティカルフローのことは知らずにおり、@icoxfog417 様の次の記事中の比較がちょうどわかりやすくとても参考になりました。
画像間の動きの解析については、様々な目的とそれを実現する手法があります。ここでは、まずOptical Flowがその中でどのような位置づけになるのか説明しておきます。
:
以下は GitHub 上の OpenCV 公式のサンプルプログラムです。これは Dense(密)型で Gunnar Farneback 法が用いられています。
一般的なウェブカメラを PC へ接続してこのプログラムを実行した様子を以下の動画に収めています。スコープ内の被写体の動きが格子点の座標群を起点に捕捉されている様子が視覚的に表現されます。
このサンプルプログラムに上下左右四方向への移動を大きく判定する処理を加えてみました。次の動画の要領で動作します。
加筆したプログラムのソースコードです。まだまだ改良の余地はあるものの今回オプティカルフローの利便性に触れたことは大きな収穫でした。いろいろな使い方ができそうです。
#!/usr/bin/env python # -*- coding: utf-8 -*- # # 高密度オプティカルフローを利用した簡易ジェスチャ認識の試み # # Web カメラ視野内の被写体全般について # Left, Right, Up, Down 四方向への移動を検知する内容 # # OpenCV 公式の下記サンプルに処理を追加したもの # https://github.com/opencv/opencv/blob/master/samples/python/opt_flow.py # # 2020-04 # import sys import time import numpy as np import cv2 # 有効移動量の閾値 THRESHOLD_DETECT = 300 # 切り捨ての閾値 THRESHOLD_IGNORE = 2 # 所定アクション区間内の x, y 方向への総移動量 MOVE = [0, 0] # 移動発生カウンタ COUNT_MOVE = 0 # 直近のジェスチャ検出システム時間 msec TIME_DETECT = 0 def millis(): return int(round(time.time() * 1000)) def put_header(img, str): cv2.putText(img, str, (26, 50), cv2.FONT_HERSHEY_SIMPLEX, 2, (255,0,0), 3) cv2.putText(img, str, (24, 48), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0,255), 3) # 指定イメージに高密度オプティカルフローのベクトル情報を重ねて線描(原作) # あわせてスコープ内の左右上下 四方向への移動状況を判定 def draw_flow(img, flow, step=16): global MOVE, COUNT_MOVE, TIME_DETECT # 当該イメージの Hight, Width h, w = img.shape[:2] # イメージの縦横サイズと step 間隔指定に基づき縦横格子点座標の配列を生成 y, x = np.mgrid[step/2:h:step, step/2:w:step].reshape(2,-1).astype(int) # 格子点座標配列要素群に対応する移動量配列を得る fx, fy = flow[y,x].T # 各格子点を始点として描画する線分情報(ベクトル)の配列を生成 lines = np.vstack([x, y, x+fx, y+fy]).T.reshape(-1, 2, 2) lines = np.int32(lines + 0.5) vis = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) # 移動ベクトル線を描画 cv2.polylines(vis, lines, 0, (0, 255, 0)) #for (x1, y1), (_x2, _y2) in lines: # cv2.circle(vis, (x1, y1), 2, (0, 0, 255), -1) # 線分情報配列の全要素について # 始点から終点までの x, y 各方向への移動量の累計を得る vx = vy = 0 for i in range(len(lines)): val = lines[i][1][0]-lines[i][0][0] if abs(val) >= THRESHOLD_IGNORE: vx += val val = lines[i][1][1]-lines[i][0][1] if abs(val) >= THRESHOLD_IGNORE: vy += val # 総移動量に加算 MOVE[0] += vx MOVE[1] += vy # 移動量の累計が所定の閾値以上なら移動中と判断 if abs(vx) >= THRESHOLD_DETECT or abs(vy) >= THRESHOLD_DETECT: # 移動発生カウンタを加算 COUNT_MOVE += 1 # 所定の閾値未満なら移動終了状態と仮定 else: mx = my = 0 if COUNT_MOVE > 0 and \ millis() - TIME_DETECT > 1000: # ノイズ除け # x, y 各方向への移動量の平均を求める mx = int(MOVE[0]/COUNT_MOVE) my = int(MOVE[1]/COUNT_MOVE) # x, y 方向いずれかの移動量平均が所定の閾値以上なら # 左右上下のどの方向への移動かを判定して表示 if abs(mx) >= THRESHOLD_DETECT or abs(my) >= THRESHOLD_DETECT: TIME_DETECT = millis() if abs(mx) >= abs(my): if mx >= 0: put_header(vis, 'LEFT') else: put_header(vis, 'RIGHT') else: if my >= 0: put_header(vis, 'DOWN') else: put_header(vis, 'UP') # 総移動量と移動発生カウンタをクリア MOVE = [0, 0] COUNT_MOVE = 0 return vis def main(): cam = cv2.VideoCapture(0) # 最初のフレームを読み込む _ret, prev = cam.read() # グレイスケール化して保持 prevgray = cv2.cvtColor(prev, cv2.COLOR_BGR2GRAY) while True: # フレーム読み込み _ret, img = cam.read() # グレイスケール化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 前回分と今回分のイメージから高密度オプティカルフローを求める flow = cv2.calcOpticalFlowFarneback(prevgray, gray, None, 0.5, 3, 15, 3, 5, 1.2, 0) # 今回分を保持しておく prevgray = gray # flow を可視化 # https://docs.opencv.org/3.1.0/d7/d8b/tutorial_py_lucas_kanade.html """ hsv = np.zeros_like(img) hsv[...,1] = 255 mag, ang = cv2.cartToPolar(flow[...,0], flow[...,1]) hsv[...,0] = ang*180/np.pi/2 hsv[...,2] = cv2.normalize(mag,None,0,255,cv2.NORM_MINMAX) rgb = cv2.cvtColor(hsv,cv2.COLOR_HSV2BGR) cv2.imshow('hsv', rgb) """ # 画像にフローのベクトル情報を重ねて表示 cv2.imshow('flow', draw_flow(gray, flow)) ch = cv2.waitKey(1) if ch & 0xFF == ord('q') or ch == 27: # ESC break if __name__ == '__main__': main() cv2.destroyAllWindows()
(tanabe)
デプスカメラを「バーチャル背景」用 Web カメラとして使う
先日ちょっとしたきっかけから Intel 製 RealSense D415 を買いました。デプスカメラに触れるのは初めてでしたが、Microsoft Azure Kinect の国内販売がまもなく開始される旨もアナウンスされておりこの分野の今後の動向が楽しみです。
試作の内容
さっそくこの D415 をあれこれ試しています。公式の RealSense SDK 2.0 の勉強をかね習作のひとつとして次の内容のプログラムを作ってみました。
静止状態でも常に微妙な揺れを伴う深度情報を RGB 画像切り抜き領域の判定に用いているため静的なクロマキー合成とは異なり境界にノイズが発生しがちですが、デプスカメラ利用の一例として紹介します。
動作の様子
デモ動画:1分4秒 無音
ソースコード
プログラムでは画像処理と表示に OpenCV を併用しています。
// // Intel RealSense D400 シリーズ // // カメラから所定の距離内の RGB 画像を抽出して表示. // スペースキー押下で背景画像を差し替え上の画像と合成する. // // 2020-02 // #include <librealsense2/rs.hpp> #include <opencv2/dnn.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> #include <iostream> #include <cmath> #define Width 640 #define Height 480 #define Fps 15 // RGB 画像抽出対象圏内の距離 #define Depth_Clipping_Distance 1.0f // 1メートル // 背景画像ファイル数 #define NumBgImages 8 // Depth スケール情報を取得 float get_depth_scale(rs2::device dev) { // センサ情報を走査 for (rs2::sensor& sensor : dev.query_sensors()) { // 深度センサなら Depth スケール情報を返す if (rs2::depth_sensor dpt = sensor.as<rs2::depth_sensor>()) { return dpt.get_depth_scale(); } } throw std::runtime_error("Device does not have a depth sensor"); } // RGB フレーム中の Depth_Clipping_Distance 圏外を塗りつぶし // 圏内 = 255, 圏外 = 0 のマスクイメージを得る cv::Mat remove_background(rs2::video_frame& video_frame, const rs2::depth_frame& depth_frame, float depth_scale) { const uint16_t* p_depth_frame = (const uint16_t*)(depth_frame.get_data()); uint8_t* p_video_frame = (uint8_t*)((void*)(video_frame.get_data())); // マスク用の Matrix をモノクロで用意 cv::Mat m = cv::Mat(cv::Size(Width, Height), CV_8UC1); // 当該 video_frame の幅, 高さ, 1ピクセルのバイト長 int width = video_frame.get_width(); int height = video_frame.get_height(); int other_bpp = video_frame.get_bytes_per_pixel(); // RGB につき 3 // Depth フレームを走査して RGB フレームの画像情報を加工 // OpenMP で二重 for ループを並列に処理 // VC++ ではコンパイルオプション /openmp が必要 // https://docs.microsoft.com/ja-jp/cpp/parallel/openmp/openmp-in-visual-cpp?view=vs-2019 // https://docs.microsoft.com/ja-jp/cpp/parallel/openmp/d-using-the-schedule-clause?view=vs-2019 #pragma omp parallel for schedule(dynamic) for (int y = 0; y < height; y++) { auto depth_pixel_index = y * width; for (int x = 0; x < width; x++, ++depth_pixel_index) { // 現座標箇所のセンサからのメートル距離を得る auto pixels_distance = depth_scale * p_depth_frame[depth_pixel_index]; // Depth の死角領域 (<=0) および 対象圏外に該当の場合 if (pixels_distance <= 0.f || pixels_distance > Depth_Clipping_Distance) { // RGB フレーム内の対象オフセット auto offset = depth_pixel_index * other_bpp; // 0x999999 で塗りつぶす std::memset(&p_video_frame[offset], 0x99, other_bpp); // 背景部分としてマーク m.at<uchar>(y, x) = 0; } else { // 前景部分としてマーク m.at<uchar>(y, x) = 255; } } } // 作成したマスクイメージを CV_8UC1 から CV_8UC3 に変換して返す cv::Mat mask; cv::cvtColor(m, mask, CV_GRAY2BGR); return mask; } // 所定の画像データをロード cv::Mat loadImage(int n) { char fn[16]; sprintf(fn, "%02d.jpg", n); return cv::imread(fn); } int main(int argc, char * argv[]) try { rs2::log_to_console(RS2_LOG_SEVERITY_ERROR); int photoNumber = 0; // ストリーミング用のパイプライン rs2::pipeline pipeline; rs2::config cfg; cfg.enable_stream(RS2_STREAM_COLOR); // RGB ストリーム cfg.enable_stream(RS2_STREAM_DEPTH); // 深度ストリーム rs2::pipeline_profile profile = pipeline.start(cfg); // このカメラの Depth スケール情報を取得 // "Depth スケール * Depth フレーム内の各ピクセルの値" がセンサからのメートル距離 float depth_scale = get_depth_scale(profile.get_device()); // info rs2::device rs_dev = profile.get_device(); std::cout << "Device Name" << ": " << rs_dev.get_info(RS2_CAMERA_INFO_NAME) << std::endl; std::cout << "Firmware Version" << ": " << rs_dev.get_info(RS2_CAMERA_INFO_FIRMWARE_VERSION) << std::endl; std::cout << "Recomended Firmware Version" << ": " << rs_dev.get_info(RS2_CAMERA_INFO_RECOMMENDED_FIRMWARE_VERSION) << std::endl; std::cout << "Serial Number" << ": " << rs_dev.get_info(RS2_CAMERA_INFO_SERIAL_NUMBER) << std::endl; std::cout << "Product Id" << ": " << rs_dev.get_info(RS2_CAMERA_INFO_PRODUCT_ID) << std::endl; std::cout << "USB Type" << ": " << rs_dev.get_info(RS2_CAMERA_INFO_USB_TYPE_DESCRIPTOR) << std::endl; std::cout << "Depth Scale" << ": " << depth_scale << std::endl; // RGB ストリーム分の align オブジェクトを用意 rs2::align align(RS2_STREAM_COLOR); // 最初の背景画像をロード cv::Mat photo = loadImage(photoNumber); while (1) { // カメラからのフレームセット受信を待つ rs2::frameset frameset = pipeline.wait_for_frames(); // アライメントを RGB ストリーム分のビューポートに揃える frameset = align.process(frameset); // RGB フレームを取得 (video_frame クラスに注意) rs2::video_frame video_frame = frameset.get_color_frame(); // Depth フレームを取得 rs2::depth_frame depth_frame = frameset.get_depth_frame(); // RGB フレーム中の Depth_Clipping_Distance 圏外を塗りつぶし // 圏内 = 255, 圏外 = 0 のマスクイメージを得る cv::Mat mask = remove_background(video_frame, depth_frame, depth_scale); cv::Mat rgbCvMatsrc, rgbCvMatDst; // RGB フレームからピクセルデータを取得し OpenCV のマトリックスに変換 rgbCvMatsrc = cv::Mat(cv::Size(Width, Height), CV_8UC3, (void*)video_frame.get_data(), cv::Mat::AUTO_STEP); // チャネル並びを RGB から BGR に cv::cvtColor(rgbCvMatsrc, rgbCvMatDst, cv::COLOR_RGB2BGR, 0); // 抽出した RGB 画像 //cv::imshow("Src", rgbCvMatDst); // 背景画像 //cv::imshow("pic", photo); // マスクと反転マスク cv::Mat mask_inv, fg, bg, mix; cv::bitwise_not(mask, mask_inv); //cv::imshow("mask", mask); //cv::imshow("mask_inv", mask_inv); // 前景となる抽出画像にマスクをかけて背景色を 0 に cv::bitwise_and(rgbCvMatDst, mask, fg); //cv::imshow("fg", fg); // 背景画像に反転マスクをかけて前景部分を 0 に cv::bitwise_and(photo, mask_inv, bg); //cv::imshow("bg", bg); // 加工した前景と背景をマージして表示 //cv::bitwise_or(fg, bg, mix); cv::add(fg, bg, mix); cv::imshow("Enter", mix); // キー押下チェック int key = cv::waitKey(1); if (key == 32) { // SPACE // 背景画像を変更 if (++photoNumber > NumBgImages) { photoNumber = 0; } photo = loadImage(photoNumber); } else if (key == 'q' || key == 27) { // 'q' or ESC cv::destroyAllWindows(); break; } } return EXIT_SUCCESS; } catch (const rs2::error & e) { std::cerr << "RealSense error calling " << e.get_failed_function() << "(" << e.get_failed_args() << "):\n " << e.what() << std::endl; return EXIT_FAILURE; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; }
メモ:マスク合成の手順
A: 圏内の RGB 画像
|
B: 0, 255 のマスク
|
C: 元画像 & マスク | C: + c: |
a: 背景画像
|
b: 255, 0 のマスク
|
c: 背景 & マスク |
仮想 Web カメラと組み合わせてみる
上のプログラムはあくまでも習作ですが、ふと、以下のような仮想 Web カメラソフトウェアと組み合わせれば、たとえばテレワーク環境からのビデオミーティング参加時に背後のプライベート空間の露出を避ける目的などに利用できるのではないかと考えました。
ちなみに所属部署ではリモート会議に Google Hangouts Meet を利用しています。上のデモを部内で紹介した折に Zoom にはこれと同様のことをスマートに実現できる「バーチャル背景」機能が用意されていることを知り、この手の需要が少なくないことをあらためて実感しました。
上記の SplitCam を使えば PC 上の所定のウィンドウの任意の領域をローカルのカメラ映像として流すことができます。最新版では領域選択方法が不明でしたが、「SplitCam links to download OLD VERSIONS!」ページから「SPLITCAM 8.1.4.1」をダウンロードして試すと期待する結果が得られました。他のプログラムからは普通の Web カメラとして認識されるため汎用的に扱えそうです。
以下にざっくりと組み合わせの手順を示します。(クリックで大きく表示)
2. ミーティング用クライアントからカメラとして SplitCam を選択
(tanabe)
生活を「不自由」にするためのソリューション
自分の自由を拘束してみることの価値
生活をより便利にするための製品やサービスの開発と普及が加速しています。新しいものは大いに活用したいところですが、一方で利便性への過剰な没入はしばしば怠惰・依存・不健康といった負の要素と背中合わせであることにも注意しておきたいものです。
近年、スマートホーム等のキーワードに象徴される光景とは逆向きにあえて利用者に不便を強いるための機器が出始めていることを知り関心を持ちました。
不自由さのあとの自由からはその恩恵をあらためて新鮮な思いで学ぶことができるでしょう。あるいは、今まで自分にとって重要で欠かせないと思っていたものとの関係を見直すきっかけとなるかもしれません。そのように、普段あまりに見なれた自由との間に距離をおいてみる試みは、自分の日常への向き合い方を考えるための材料のひとつになりえるのではないでしょうか。
中島 らも (著) 「とほほのほ」 1991/1 - www.amazon.co.jp
「楽園はどこにあるのか (4)」 より
そのマヤの「現世-楽園置換装置」というのは、かなり大きなドームの形をしている。内部はガランとした空洞で、この遺跡からは多量の炭化した「トウガラシ」が発見された。神官たちは支配下の善男善女たちをこのドームに入れ、大量のトウガラシをいぶした煙をドーム内にあおぎ入れたのだろう。人々は恐怖と苦痛で、発狂と死の直前まで追い込まれる。そのときドームの戸が開かれる。冷たくて香りのよい空気がなだれ込んでくる。人々は自分が立っている「いま」「ここ」がすなわち楽園にほかならないことを確信するのだ。製品の例
1. kSafe
概要
kSafe は Kitchen Safe Inc (米 カリフォルニア)による製品。 タイマーつきの電子ロック式小物入れ。あらかじめ設定した時間が経過しなければ中身を取り出せないしくみで、日常生活において依存対象となりがちなスマートフォンやもろもろの嗜好品へ接触する自由を自分自身で制限することができる。
以下、公式サイトの記事より。
- kSafe by Kitchen Safe | The Time Lock Safe - www.thekitchensafe.com
A powerful tool
to build good habits
Once the timer is set, and the button is pressed, the safe will remain locked until the timer reaches zero.
No overrides!
www.thekitchensafe.com
- frequently asked questions | kSafe by Kitchen Safe - www.thekitchensafe.com
Why do I need the kSafe?Google 訳
Short Answer - Because it’s really cool! And, it’s been scientifically proven to increase your chances of reaching your goals.
Long Answer - The kSafe was developed based on research published by scientists at MIT, Harvard, Stanford, Princeton, and Yale. They discovered that pre-commitment can significantly increase our chances of achieving our goals. :なぜkSafeが必要なのですか?
簡単な回答 - 本当にクールだから! そして、あなたの目標を達成する可能性を高めることが科学的に証明されています。
長い答え - kSafeは、MIT、ハーバード、スタンフォード、プリンストン、エールの科学者によって発表された研究に基づいて開発されました。 彼らは、事前コミットメントが私達の目標を達成する私達の可能性をかなり高めることができることを発見した。 :権威めいたものを引き合いに出しながら肝心の「いつ・どこで・誰が・どのように」が省略されていることが残念だが、製品そのものは興味ぶかい。
価格
ただし、この kSafe は結構値が張る。下記のように公式サイトからの直販でノーマルサイズの「Medium」が送料別 49米ドルという価格。
- Purchase | kSafe by Kitchen Safe - www.thekitchensafe.com (2019年2月時点)
www.thekitchensafe.com 逡巡とその後
この製品のコンセプトに関心がからまりしばらく直販サイトを徘徊した。 総額で $49 なら買ってもいいと思ったが、日本への最安送料 $29.34 が加わると Medium で計 $78.34。手元での費用対効果を想定するとこれはかなり微妙で悩ましい金額だった。
上のスクリーンショットのようにカートに入れたまましばらく好奇心と理性の間を行き来していると何日か後に販売元から以下のメールが届いた。 個人的にはこの内容に微妙な印象が残り結局買うのをやめた。
From: kSafe by Kitchen Safe
Date: 2019年2月15日(金) 4:40
Subject: Are you OK? Kitchen Safe is worried
To: xxxxxxxxx@gmail.com
Hey,
We noticed that you didn't complete your Kitchen Safe order! The only reasonable explanation we can think of is that your computer exploded right before you could click "Complete my Order". Don't worry, we saved your shopping cart so you can complete the order from a friend's computer or phone (see bottom of email).
If your computer didn't explode and you're just on the fence, be sure to read some of our customer reviews.
Also, you can apply this coupon: CommitToChange to save 10% on checkout. It expires in the next 24 hours.
Thank you,
The Kitchen Safe Team
www.thekitchensafe.com今となっては高額出費を抑えられたことにむしろ感謝している。
2. Timer Lock
概要
Timer Lock はノーブランドの中国製品。 名前のとおり上の kSafe と同様のタイマーロック製品であり、こちらは箱型ではなく錠前のスタイル。
価格
価格は kSafe に比べれば安い。アマゾンジャパンでは 2000円ほどの価格から販売されている。複数のセラーが存在。
- タイマー式南京錠 USB充電 安心 防犯グッズ - www.amazon.co.jp ¥ 2,099 (2019年2月時点)
ちなみに eBay ではその半額程度で出回っている。
- Timer Lock | eBay - www.ebay.co.uk
購入
買ってみることにした。当初は eBay の利用を考えたが、アマゾンのレビュー等を参照すると製品の大元の品質に対する一抹の懸念が残った。結局、価格差を保険料と割り切り、実際に問題に直面した場合に返品のしやすいアマゾンで購入した。
観察
以下、現物を手にした状態でのメモ。
- サイズ感は写真のとおり
- ワイヤを右側のホールへ装着して使う。差し込んだワイヤは本体右脇のボタン押下でリリース
- 左右ボタンでキッチンタイマー風に時分を設定(最長99時間99秒)し、中央ボタンで開始。5秒間の猶予後はタイマー満了まで解除不可となる
- 錠前の形状ではあるがこういう華奢なつくりなので非常時(?)には簡単に壊すことができる。言いかえれば防犯用途にはまったく適さない
- 内蔵バッテリーを付属の USB ケーブルで充電して使う。本体装着側が MicroB ではなくレアな外径 2.35mm / 内径0.7mm プラグなのがちょっと残念
- 手元では一度のフルチャージを経てこの 2か月ほど週に 3, 4 回、それぞれ 12時間程度のタイマー設定で使っているがまだ充電切れの予兆なし
使用例
自宅の観音開きの押入れをロック対象とすることにした。これなら小物入れ式の kSafe とは異なり楽器など大きなものでも最長 99時間99分後の未来へ預けることができる。 本体のワイヤは押入れの取手を直接ホールドできる長さではないため以前ホームセンターで買った硬質プラスティック素材のチェーンを併用した。写真のようにちょっと物々しい感じに。
動作の様子: 1分17秒 音量注意
所感
実際に使ってみるとこれは望外に良い製品だった。普通に丁寧に扱えば何の問題もない。予備をかねて eBay でもう一台購入。 説明書をみたところではこちらがより新しいバージョンらしい。少しデザインが異なるが機能は同じ。
この価格で買える不自由・非日常の価値は大きい。
付記
手元の一連のメモを上の記事にまとめていたところ、前掲の kSafe のアマゾンジャパンでの販売価格が高騰していることに気がつきました。 アマゾンでは以前からメーカー直販よりもかなり割高な 9,700円という価格で販売されていましたが、現在は 1万円を優に超えておりちょっと驚きました。
- Kitchen Safe タイムロッキングコンテナ (ホワイトクリア) - www.amazon.co.jp
¥15,792 (2019年4月29日時点)元々あまり目立つ製品ではないためかこれまでの観察の範囲ではアマゾンでの価格に変動はありませんでした。不思議に思って情報を探してみると @ryogomatsumaru さんによる 4月22日の次のツイートがこのブームの発端らしいことを知りました。
最近スマホをついつい触りすぎて時間を浪費してることに危機感を覚えて、「設定した時間の間は絶対にあけられない箱」を買ったんだけど、これ思ったよりもずっと仕事がはかどるようになるのでオススメです。
— 松丸 亮吾@東大ナゾトレ9巻発売中! (@ryogomatsumaru) 2019年4月22日
勉強中に携帯いじっちゃう人とか、仕事中に誘惑に負けやすい人。ぜひ。 pic.twitter.com/YBXW6ovpD8
すごい影響力ですね。まさに情報の時代であることをあらためて実感しました。話題が重なるのでこの記事はキャンセルしようかとも思いましたが、せっかくなので史上初の10連休と平成最後の日の記念(?)をかねて。
(tanabe)
40年前の「子供の科学」誌との再会を通じて
「子供の科学」は株式会社誠文堂新光社様の発行する小中学生向けの月刊科学雑誌です。創刊は関東大震災の翌年 1924年(大正13年)とふるく、世代をまたいで読み継がれています。この記事をご覧になっている方の中にもかつての読者(現役の読者も?)が少なくないことでしょう。
手元では 1977年前後にこの「子供の科学」(以下 "同誌")を購読していました。それからずっと長い間忘れていたのですが、先日実家に残っていた当時の何冊かを見つけました。
懐かしい気持ちで久しぶりに 40年前の誌面を読み返したところ、子供と同じ目の高さで科学や技術、未来に向き合っていた当時の大人たちの懐の深さと真剣さを現在の年齢なりに強く感じました。商業誌である以上、誌面には小さな読者たちからの要望のいくばくかも反映されていたであろうことを考え合わせるとさらに興味ぶかく感じられます。
この機会に印象に残ったいくつかの記事を電子化しておくことにしました。その内容を同誌編集部様のご承諾のもとに紹介します。あわせて、40年を経た 2017年現在の誌面と 93年前の創刊号にも目を向けてみたいと思います。
1977年 第12号より
- できごと - 1977年 - wikipedia
表紙
遺物の写真のまわりに 4つの記事のタイトルのみが淡々と並ぶシンプルな装丁。「マリモはどうして丸くなる?」という取り立てて派手さのないタイトルが堂々と表紙に据えられていることも印象的。
目次
同誌の内容は多岐に渡る。「原子力製鉄」という記事と「実用工作 ちりとり」という記事が何の違和感もなく共存していることに守備範囲の広さが感じられる。
「針のいらないレコードができた」
:
もし, プレイヤーにレコードをのせ, 回転させるだけで, 針の付いたピックアップがないのに, 実際に演奏を聞くのと少しも変わらない, すばらしい音質(これを超ハイ・ファイと言います)で再生できたら, おそらくみなさんはびっくりさせられることでしょう.
:
このレーザー光線による微小ビットの記録再生技術は, さらにいろいろな応用が考えられています.
テレビに接続すると映像の出るレコードとか, 超長時間録音とか, 例えば100曲ほど入ったジュークボックスが, たった1枚のディスクでできるとか, あるいは楽器別に同時記録ができるマルチ・チャネル方式への応用など、研究者達の夢は限りなく広がっています.
「PCM レーザサウンドディスク」は、コンパクトディスク(CD)が製品化されるよりも前に三菱電機・ティアック・東京電化の三社によって開発が進められていたディジタルオーディオシステム。音楽愛好者の重要な音源だった FM 放送の周辺で「PCM 録音」のキーワードが話題になり始めていた時分であり現在が過去の未来であることをあらためて感じさせられる。時代を経てアナログオーディオの価値が再認識された現代ではむしろ「針を使うレコード」のほうが新鮮で贅沢であることもまた興味深い。
関連記事
「自由に曲線が切れるカッター」
ナイフなどで紙や布を曲線に切る事は, たいへんむずかしい事です. この道具は, ゴムのローラーを利用して自由に曲線が切れるようにしたものです.
:
ローラーが付いているので, 紙や布の上を自由に動かす事ができ, ジグザグにでも, 曲線状にでも切れるというわけです.
:
世界初のロータリーカッターである オルファ株式会社の「マルカッター」の解説。短くさりげない記事だが曲線の切れるカッターのしくみが丁寧にわかりやすく説明されている。 この記事を書いた青木国夫氏(故人)は当時国立科学博物館工学研究部長の要職にあった著名な研究者。
関連記事
:
これらを通覧して明らかなように,青木は博物館資料や科学館に関する分野と,科学史・科学技術に関する分野について長く関心を持ち続けていたことが窺える。特に後者については,「著作物」目録で明らかなように,少年向けの雑誌に毎号のように,身近な道具でできる実験,科学史上の発見・発明と偉人,読まれるべき好著の紹介の文章を寄せている。これは,少年時代に科学のおもしろさや不思議さを体験的に知ることがいかに大切であるかを自らが実感していたからである。
:
「自動焦点カメラのしくみ」
ひと昔前にはまったくの夢だった自動焦点カメラの出現です.
:
月の探索用に積み込まれた無人操作カメラや, 8ミリシネマカメラには前から使われていましたが, この種類の普及カメラに組み込まれたのはこれが最初で, 文字どおり「シャッターを押すだけ」のカメラがここで実現したわけです.
:
その仕組みは, これまでの距離計とレンズの焦点調節用の伸縮を自動連動させ, 二つの距離計映像の合致を2個の電子の目が見くらべて, 同じになるとそこでストップ, 実際には, シャッターを押すだけでピントも露出もピッタリ, その間は一瞬の手早さで, 撮影完了ということになります.
その仕組みを, もう少しくわしく説明しますと, ざっとこんなところです(図1)
:
この方式は, 短焦点レンズ用(もともとピント幅が広い)には好適ですが,(中略)一眼レフなどの高級機用としては, 今後の研究をまたねばなりません.
コニカ株式会社(現コニカミノルタ株式会社)の C35AF は 1977年に発売された世界初の普及型オートフォーカスカメラ。この記事は発売されたばかりの同製品に用いられている当時最新の AF 技術を子供に理解することの可能な表現と内容で正面から解説している。ちなみに、記事を執筆した故・松田二三男氏は同誌で人気のあった読者投稿写真コーナー (目次) の選者を長らく務めた。
関連記事
「触媒について調べよう」
触媒とは, そのもの自体, 反応の始めと終わりで少しも変化しないで, ほかの物質の反応の速さを変えるものをいうのです.
:
塩素酸カリウムという薬は, 熱すると酸素を発生するのですが, ガスバーナーで熱するぐらいではなかなか酸素はでてきません. :
あらかじめ, 二酸化マンガン(触媒)を少し加えておくと, 200℃くらいの温度で完全に分解します.
:
過酸化水素水(市販品のオキシフルとかオキシドールという消毒薬)を使って触媒の研究をしてみましょう.
:
特にこれといった脈絡もなく「触媒」を扱う化学実験の話題が急にさらりと出てくるのがまた面白いところ。前述のように同誌は広く自然科学分野全般をカバーしており(時には社会科学方面の話題も)、毎回こうした単発の記事が何本も掲載されていた。当時市立中学校の理科教諭だった執筆者の岩崎幸敏氏は 70年代から 90年代にかけて中学生向けの多くの著書を上梓している。
「ぼくの発明 きみのくふう」(読者投稿コーナー)
● カッターナイフの改良 ●
今までカッターの刃を折る時, 手がしびれることがあったので, つめ切りと同じしくみのものをとりつけ, 手がしびれることなく簡単に刃が折れるようにしました. (B)のねじを回しカッターの刃に, (1)を近づけ, あとはつめを切るのと同じ方法で折る. (川上○○ / 北海道沙流郡)
評
この案は不安解消策として有効です. ただし, カッターにこのようなしくみを付けると, 価格が高くなったり, 使用しにくくなるなどの問題点も出てきそうです. 小学生にしてはいい着眼です.
ネットも PC もなく一個人が自由に情報発信を行う手段がほぼ皆無だった時代には新聞・雑誌等の投稿欄の存在感が現代よりもずっと大きく、同誌のこのコーナーにも人気があった。
子供が真剣に何かを考え工夫をこらして自分なりの結論を出し、それを第三者が理解できるように説明する努力を経て意見を求めるという一連の構図の素晴らしさをあらためて考えさせられる。
さらに、子供から寄せられたアイディアに決して安易に迎合することなく、むしろ大人である自分の視点での率直な意見を時に助言を添えつつストレートに伝えようとする選者の一貫した姿勢がそのことをさらに際立たせている。この号に掲載された応募作品は以下のとおり。彼らは今どんな大人になっているのだろうか。
余談:エポック社「システム10」の広告ページ
この号には発売から間もないエポック社製の家庭用ビデオゲーム機「システム10」の広告が掲載されている。スペースインベーダーが大ヒットしたのが翌 1978年。ちなみに「機動戦士ガンダム」の初放映は翌々年の 1979年。のち 1983年のファミコンの登場によりビデオゲーム機市場の様相は一変するが、そこに至るにはまだしばらくの時間を要した。
2017年 6月号より
- できごと - 2017年 - wikipedia
1977年当時の誌面との印象的な再会からほどなくして、本年 2017年の6月号を購入しました。創刊からすでに 90余年、現在は紙媒体とは別に Kindle 電子版も発行されていることを知って驚きました。
40年ぶりに買った同誌はこの時代相応に表現や文体がマイルドになってはいるものの、ガンとして変わらないロゴマークと同様に、誌面から伝わってくるテイストがあの頃のそれとあまり変わっていないことに安心しました。支障のない範囲で一部を抜粋してみます。
表紙と目次
記事より
- 「水中の食虫植物 タヌキモ」
定番の水棲動植物の特集記事。前掲の 1977年12号では「マリモ」でしたね :−)
- 「ジブン専用パソコン 第3回ラズビアン(OS)を設定しよう!」
食虫植物の解説とラズパイの連載記事がやっぱり普通に共存
- 「ぼくの発明 きみの工夫」
ボリュームは減ったもののこのコーナーが今も健在であることが嬉しい
創刊号(1924年 10月 第1巻 第1號)より
- できごと - 1924年 - wikipedia
この記事の冒頭にもリンクを掲載した「子供の科学」公式サイトの次のページから 1924年(大正13年)の創刊号を閲覧することができます。
非常に貴重な誌面がこのような形で公開されていることはとても興味深く読み入ってしまいます。とりわけ、最初のページに掲載されている「この雑誌の役目」という文章に感銘を受けました。93年前に書かれたその全文を以下に引用します。
巻頭の辞「この雑誌の役目」
この雑誌の役目 主幹
愛らしき少年少女諸君!!!子供科学画報は、皆さんのために、次のような役目をもって生まれました。
およそ天地の間は、びっくりするような不思議なことや、面白いことで、満ちているのでありますが、これを知っているのは学者だけで、その学者のかたは、研究がいそがしいものですから、皆さんにお知らせするひまがありません。したがって、多くのかたは、それを知らずに居ります。そのなかで特に少年少女諸君の喜びそうなことを学者のかたにうかがって、のせて行くのも、この雑誌の役目の一つです。
皆さんが学校で学んでいる理科を、一そうわかりやすく、面白くするために、その月々に教わることがらについての写真や絵を皆さんのためにそろえるのも、この雑誌の一つの役目でもあります。理科の本にかいてある事がらに限りません。読本のなかにある理科の事がらに関するものも、のせておきます。
毎日のように見たり使ったりしているもの事について、皆さんは、くわしく知りたいと思われることがありましょう。皆さんの御望みを満たすため、絵を入れてできるだけわかりやすく、そういうもの事を説明するのも、また、一つの役目であります。
簡単な器械の造りかたをお伝えして皆さんの発明の才をあらわし、面白い理科の遊びのできるようにするのも、役目の一つであります。
しかし、この雑誌の一ばん大切な目的は、ほんとうの科学というものが、どういうものであるかを、皆さんに知っていただくことであります。ちかごろは、「科学科学」とやかましくいいますが、ほんとうに科学というものを知っている人は、沢山ないようです。人は生まれながら、美しいものを好む心を持っておりますが、それと同じように、自然のもの事についてくわしく知り、深くきわめようとする欲があります。昔から、その欲の強い人々がしらべた結果、自然のもの事のあいだには、沢山の定まった規則のあることがわかりました。科学ということは、この規則を明らかにすることであります。多くの人が科学といっているのは、大ていは、その応用に過ぎません。この規則を知ることによって、人間は、自然にしたがって、無理のないように生き、楽しく暮らすことができ、これを応用して世が文明におもむくのです。
個人的な雑感
久しぶりに「子供の科学」誌に接し、私自身がもっとも強く印象に残ったのは、それぞれの分野の専門家の大人たちがそれぞれに子供たちに本気で向き合い、本気で何事かを伝えようとしている姿勢です。過剰に機嫌をとりながら話を聞かせようとするのではなく、興味をもった子供たちへ度合いに応じた「努力」の余地を残しながら適度な粒度まで知識や情報を砕いた上でそれを示し、あとは読み手側の好奇心と探求心にゆだねつつ同時にそれらを育んでいこうとする共通の意思のようなものを感じました。
難しいことをわかりやすく説明できることが最良とよく言われますが、そこに相手を受け身にすることなく相手の向上心を呼び誘うための配慮を加える余裕があればさらに素晴らしいことでしょう。そして、そういった配慮こそがその道に精通していなければなかなか果たすことの難しい命題ではないかと思います。同誌の記事の執筆者はすべて第一線の専門家であり、「子供だまし」ではなく真剣に世代のバトンを引き渡すためにはまさに適役でしょう。この誌面に限らずこういったあまり表には出てこない場所にもまたこれまで静かにこの国を支えてきた多くの大切な要素が脈々と息づいているのかもしれません。
どの世代にもそれぞれの役割があります。現役の大人の世代は年齢とともにいずれ順番に次の世代と交代していくことになりますが、科学や技術、学問の分野の話題に限らず、自分を含め今の大人たちが新しい世代に対する役割を適切に果たせているのか? 彼ら彼女らからの問いかけや疑問に対して都合よく言いくるめたりはぐらかしたりせずまっすぐに答えることができているのか? そういったことは大人から子供への一方的なプレゼントではなく、やがて次の世代との交代を終えた未来の自分の生きる世の中を支える礎にもなるものでしょう。誰もが先人たちから受け継いだこの大きなループの中で生きています。その片隅で自分が少年時代に読んだこの雑誌をすっかり大人になった今の年齢でふたたび読み返し、ふとそんなことを考えました。あの頃の大人たちに、あらためて、謹んで、御礼申し上げます。
(tanabe)